Start rebuilding with a cleanly-separated UI framework

Nathan Sobo created

Change summary

.gitignore                     |   2 
Cargo.lock                     | 938 ++++++++++++++++++++++++++++++++++++
Cargo.toml                     |   2 
gpui/Cargo.toml                |  21 
gpui/src/keymap.rs             | 502 +++++++++++++++++++
gpui/src/lib.rs                |   5 
gpui/src/platform/event.rs     |  24 
gpui/src/platform/mac/app.rs   | 176 ++++++
gpui/src/platform/mac/event.rs | 115 ++++
gpui/src/platform/mac/mod.rs   |   8 
gpui/src/platform/mod.rs       |  20 
zed/Cargo.toml                 |  12 
zed/src/main.rs                |  38 +
13 files changed, 1,863 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -0,0 +1,938 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1"
+
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "async-channel"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+ "vec-arena",
+]
+
+[[package]]
+name = "async-fs"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2"
+dependencies = [
+ "async-lock",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd"
+dependencies = [
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "libc",
+ "log",
+ "nb-connect",
+ "once_cell",
+ "parking",
+ "polling",
+ "vec-arena",
+ "waker-fn",
+ "winapi",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1996609732bde4a9988bc42125f55f2af5f3c36370e27c778d5191a4a1b63bfb"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-net"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06de475c85affe184648202401d7622afb32f0f74e02192857d0201a16defbe5"
+dependencies = [
+ "async-io",
+ "blocking",
+ "fastrand",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef37b86e2fa961bae5a4d212708ea0154f904ce31d1a4a7f47e1bbc33a0c040b"
+dependencies = [
+ "async-io",
+ "blocking",
+ "cfg-if 1.0.0",
+ "event-listener",
+ "futures-lite",
+ "once_cell",
+ "signal-hook",
+ "winapi",
+]
+
+[[package]]
+name = "async-task"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
+
+[[package]]
+name = "atomic-waker"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "blake2b_simd"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "blocking"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+]
+
+[[package]]
+name = "cache-padded"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
+
+[[package]]
+name = "cc"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832"
+dependencies = [
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics",
+ "foreign-types 0.3.2",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318"
+dependencies = [
+ "bitflags",
+ "block",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types 0.3.2",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
+dependencies = [
+ "cache-padded",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
+
+[[package]]
+name = "core-graphics"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types 0.3.2",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "foreign-types 0.3.2",
+ "libc",
+]
+
+[[package]]
+name = "core-text"
+version = "19.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25"
+dependencies = [
+ "core-foundation",
+ "core-graphics",
+ "foreign-types 0.3.2",
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3"
+dependencies = [
+ "autocfg",
+ "cfg-if 1.0.0",
+ "lazy_static",
+ "loom",
+]
+
+[[package]]
+name = "dirs"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+
+[[package]]
+name = "fastrand"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared 0.1.1",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared 0.3.0",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855"
+
+[[package]]
+name = "futures-core"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
+
+[[package]]
+name = "futures-io"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500"
+
+[[package]]
+name = "futures-lite"
+version = "1.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "generator"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cdc09201b2e8ca1b19290cf7e65de2246b8e91fb6874279722189c4de7b94dc"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustc_version",
+ "winapi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "gpui"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "cocoa",
+ "core-foundation",
+ "core-text",
+ "foreign-types 0.5.0",
+ "log",
+ "metal",
+ "objc",
+ "pathfinder_color",
+ "pathfinder_geometry",
+ "smol",
+ "tree-sitter",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "loom"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "generator",
+ "scoped-tls",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "metal"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c"
+dependencies = [
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "foreign-types 0.3.2",
+ "log",
+ "objc",
+]
+
+[[package]]
+name = "nb-connect"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670361df1bc2399ee1ff50406a0d422587dd3bb0da596e1978fe8e05dabddf4f"
+dependencies = [
+ "libc",
+ "socket2",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+ "objc_exception",
+]
+
+[[package]]
+name = "objc_exception"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
+
+[[package]]
+name = "parking"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
+
+[[package]]
+name = "pathfinder_color"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69bdc0d277d559e35e1b374de56df9262a6b71e091ca04a8831a239f8c7f0c62"
+dependencies = [
+ "pathfinder_simd",
+]
+
+[[package]]
+name = "pathfinder_geometry"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
+dependencies = [
+ "log",
+ "pathfinder_simd",
+]
+
+[[package]]
+name = "pathfinder_simd"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b451513912d6b3440e443aa75a73ab22203afedc4a90df8526d008c0f86f7cb3"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
+
+[[package]]
+name = "polling"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "log",
+ "wepoll-sys",
+ "winapi",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "redox_users"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+ "rust-argon2",
+]
+
+[[package]]
+name = "regex"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+
+[[package]]
+name = "rust-argon2"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
+dependencies = [
+ "base64",
+ "blake2b_simd",
+ "constant_time_eq",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7f3f92a1da3d6b1d32245d0cbcbbab0cfc45996d8df619c42bccfa6d2bbb5f"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "simplelog"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720"
+dependencies = [
+ "chrono",
+ "log",
+ "termcolor",
+]
+
+[[package]]
+name = "smol"
+version = "1.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "async-net",
+ "async-process",
+ "blocking",
+ "futures-lite",
+ "once_cell",
+]
+
+[[package]]
+name = "socket2"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "tree-sitter"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d18dcb776d3affaba6db04d11d645946d34a69b3172e588af96ce9fecd20faac"
+dependencies = [
+ "cc",
+ "regex",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "vec-arena"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d"
+
+[[package]]
+name = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wepoll-sys"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zed"
+version = "0.1.0"
+dependencies = [
+ "dirs",
+ "gpui",
+ "libc",
+ "log",
+ "simplelog",
+]

Cargo.toml 🔗

@@ -0,0 +1,2 @@
+[workspace]
+members = ["zed", "gpui"]

gpui/Cargo.toml 🔗

@@ -0,0 +1,21 @@
+[package]
+authors = ["Nathan Sobo <nathansobo@gmail.com>"]
+edition = "2018"
+name = "gpui"
+version = "0.1.0"
+
+[dependencies]
+pathfinder_color = "0.5"
+pathfinder_geometry = "0.5"
+smol = "1.2"
+tree-sitter = "0.17"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+anyhow = "1"
+cocoa = "0.24"
+core-foundation = "0.9"
+core-text = "19.2"
+foreign-types = "0.5"
+log = "0.4"
+metal = "0.21"
+objc = "0.2"

gpui/src/keymap.rs 🔗

@@ -0,0 +1,502 @@
+use anyhow::anyhow;
+use std::{
+    any::Any,
+    collections::{HashMap, HashSet},
+};
+use tree_sitter::{Language, Node, Parser};
+
+extern "C" {
+    fn tree_sitter_zed_context_predicate() -> Language;
+}
+
+pub struct Matcher {
+    pending: HashMap<usize, Pending>,
+    keymap: Keymap,
+}
+
+#[derive(Default)]
+struct Pending {
+    keystrokes: Vec<Keystroke>,
+    context: Option<Context>,
+}
+
+pub struct Keymap(Vec<Binding>);
+
+pub struct Binding {
+    keystrokes: Vec<Keystroke>,
+    action: String,
+    action_arg: Option<Box<dyn ActionArg>>,
+    context: Option<ContextPredicate>,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Keystroke {
+    pub ctrl: bool,
+    pub alt: bool,
+    pub shift: bool,
+    pub cmd: bool,
+    pub key: String,
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct Context {
+    pub set: HashSet<String>,
+    pub map: HashMap<String, String>,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum ContextPredicate {
+    Identifier(String),
+    Equal(String, String),
+    NotEqual(String, String),
+    Not(Box<ContextPredicate>),
+    And(Box<ContextPredicate>, Box<ContextPredicate>),
+    Or(Box<ContextPredicate>, Box<ContextPredicate>),
+}
+
+trait ActionArg {
+    fn boxed_clone(&self) -> Box<dyn Any>;
+}
+
+impl<T> ActionArg for T
+where
+    T: 'static + Any + Clone,
+{
+    fn boxed_clone(&self) -> Box<dyn Any> {
+        Box::new(self.clone())
+    }
+}
+
+pub enum MatchResult {
+    None,
+    Pending,
+    Action {
+        name: String,
+        arg: Option<Box<dyn Any>>,
+    },
+}
+
+impl Matcher {
+    pub fn new(keymap: Keymap) -> Self {
+        Self {
+            pending: HashMap::new(),
+            keymap,
+        }
+    }
+
+    pub fn set_keymap(&mut self, keymap: Keymap) {
+        self.pending.clear();
+        self.keymap = keymap;
+    }
+
+    pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
+        self.pending.clear();
+        self.keymap.add_bindings(bindings);
+    }
+
+    pub fn push_keystroke(
+        &mut self,
+        keystroke: Keystroke,
+        view_id: usize,
+        ctx: &Context,
+    ) -> MatchResult {
+        let pending = self.pending.entry(view_id).or_default();
+
+        if let Some(pending_ctx) = pending.context.as_ref() {
+            if pending_ctx != ctx {
+                pending.keystrokes.clear();
+            }
+        }
+
+        pending.keystrokes.push(keystroke);
+
+        let mut retain_pending = false;
+        for binding in self.keymap.0.iter().rev() {
+            if binding.keystrokes.starts_with(&pending.keystrokes)
+                && binding
+                    .context
+                    .as_ref()
+                    .map(|c| c.eval(ctx))
+                    .unwrap_or(true)
+            {
+                if binding.keystrokes.len() == pending.keystrokes.len() {
+                    self.pending.remove(&view_id);
+                    return MatchResult::Action {
+                        name: binding.action.clone(),
+                        arg: binding.action_arg.as_ref().map(|arg| (*arg).boxed_clone()),
+                    };
+                } else {
+                    retain_pending = true;
+                    pending.context = Some(ctx.clone());
+                }
+            }
+        }
+
+        if retain_pending {
+            MatchResult::Pending
+        } else {
+            self.pending.remove(&view_id);
+            MatchResult::None
+        }
+    }
+}
+
+impl Default for Matcher {
+    fn default() -> Self {
+        Self::new(Keymap::default())
+    }
+}
+
+impl Keymap {
+    pub fn new(bindings: Vec<Binding>) -> Self {
+        Self(bindings)
+    }
+
+    fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
+        self.0.extend(bindings.into_iter());
+    }
+}
+
+impl Default for Keymap {
+    fn default() -> Self {
+        Self(vec![
+            Binding::new("up", "menu:select_prev", Some("menu")),
+            Binding::new("ctrl-p", "menu:select_prev", Some("menu")),
+            Binding::new("down", "menu:select_next", Some("menu")),
+            Binding::new("ctrl-n", "menu:select_next", Some("menu")),
+        ])
+    }
+}
+
+impl Binding {
+    pub fn new<S: Into<String>>(keystrokes: &str, action: S, context: Option<&str>) -> Self {
+        let context = if let Some(context) = context {
+            Some(ContextPredicate::parse(context).unwrap())
+        } else {
+            None
+        };
+
+        Self {
+            keystrokes: keystrokes
+                .split_whitespace()
+                .map(|key| Keystroke::parse(key).unwrap())
+                .collect(),
+            action: action.into(),
+            action_arg: None,
+            context,
+        }
+    }
+
+    pub fn with_arg<T: 'static + Any + Clone>(mut self, arg: T) -> Self {
+        self.action_arg = Some(Box::new(arg));
+        self
+    }
+}
+
+impl Keystroke {
+    pub fn parse(source: &str) -> anyhow::Result<Self> {
+        let mut ctrl = false;
+        let mut alt = false;
+        let mut shift = false;
+        let mut cmd = false;
+        let mut key = None;
+
+        let mut components = source.split("-").peekable();
+        while let Some(component) = components.next() {
+            match component {
+                "ctrl" => ctrl = true,
+                "alt" => alt = true,
+                "shift" => shift = true,
+                "cmd" => cmd = true,
+                _ => {
+                    if let Some(component) = components.peek() {
+                        if component.is_empty() && source.ends_with('-') {
+                            key = Some(String::from("-"));
+                            break;
+                        } else {
+                            return Err(anyhow!("Invalid keystroke `{}`", source));
+                        }
+                    } else {
+                        key = Some(String::from(component));
+                    }
+                }
+            }
+        }
+
+        Ok(Keystroke {
+            ctrl,
+            alt,
+            shift,
+            cmd,
+            key: key.unwrap(),
+        })
+    }
+}
+
+impl Context {
+    pub fn extend(&mut self, other: Context) {
+        for v in other.set {
+            self.set.insert(v);
+        }
+        for (k, v) in other.map {
+            self.map.insert(k, v);
+        }
+    }
+}
+
+impl ContextPredicate {
+    fn parse(source: &str) -> anyhow::Result<Self> {
+        let mut parser = Parser::new();
+        let language = unsafe { tree_sitter_zed_context_predicate() };
+        parser.set_language(language).unwrap();
+        let source = source.as_bytes();
+        let tree = parser.parse(source, None).unwrap();
+        Self::from_node(tree.root_node(), source)
+    }
+
+    fn from_node(node: Node, source: &[u8]) -> anyhow::Result<Self> {
+        let parse_error = "error parsing context predicate";
+        let kind = node.kind();
+
+        match kind {
+            "source" => Self::from_node(node.child(0).ok_or(anyhow!(parse_error))?, source),
+            "identifier" => Ok(Self::Identifier(node.utf8_text(source)?.into())),
+            "not" => {
+                let child = Self::from_node(
+                    node.child_by_field_name("expression")
+                        .ok_or(anyhow!(parse_error))?,
+                    source,
+                )?;
+                Ok(Self::Not(Box::new(child)))
+            }
+            "and" | "or" => {
+                let left = Box::new(Self::from_node(
+                    node.child_by_field_name("left")
+                        .ok_or(anyhow!(parse_error))?,
+                    source,
+                )?);
+                let right = Box::new(Self::from_node(
+                    node.child_by_field_name("right")
+                        .ok_or(anyhow!(parse_error))?,
+                    source,
+                )?);
+                if kind == "and" {
+                    Ok(Self::And(left, right))
+                } else {
+                    Ok(Self::Or(left, right))
+                }
+            }
+            "equal" | "not_equal" => {
+                let left = node
+                    .child_by_field_name("left")
+                    .ok_or(anyhow!(parse_error))?
+                    .utf8_text(source)?
+                    .into();
+                let right = node
+                    .child_by_field_name("right")
+                    .ok_or(anyhow!(parse_error))?
+                    .utf8_text(source)?
+                    .into();
+                if kind == "equal" {
+                    Ok(Self::Equal(left, right))
+                } else {
+                    Ok(Self::NotEqual(left, right))
+                }
+            }
+            "parenthesized" => Self::from_node(
+                node.child_by_field_name("expression")
+                    .ok_or(anyhow!(parse_error))?,
+                source,
+            ),
+            _ => Err(anyhow!(parse_error)),
+        }
+    }
+
+    fn eval(&self, ctx: &Context) -> bool {
+        match self {
+            Self::Identifier(name) => ctx.set.contains(name.as_str()),
+            Self::Equal(left, right) => ctx
+                .map
+                .get(left)
+                .map(|value| value == right)
+                .unwrap_or(false),
+            Self::NotEqual(left, right) => ctx
+                .map
+                .get(left)
+                .map(|value| value != right)
+                .unwrap_or(true),
+            Self::Not(pred) => !pred.eval(ctx),
+            Self::And(left, right) => left.eval(ctx) && right.eval(ctx),
+            Self::Or(left, right) => left.eval(ctx) || right.eval(ctx),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_keystroke_parsing() -> anyhow::Result<()> {
+        assert_eq!(
+            Keystroke::parse("ctrl-p")?,
+            Keystroke {
+                key: "p".into(),
+                ctrl: true,
+                alt: false,
+                shift: false,
+                cmd: false,
+            }
+        );
+
+        assert_eq!(
+            Keystroke::parse("alt-shift-down")?,
+            Keystroke {
+                key: "down".into(),
+                ctrl: false,
+                alt: true,
+                shift: true,
+                cmd: false,
+            }
+        );
+
+        assert_eq!(
+            Keystroke::parse("shift-cmd--")?,
+            Keystroke {
+                key: "-".into(),
+                ctrl: false,
+                alt: false,
+                shift: true,
+                cmd: true,
+            }
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_context_predicate_parsing() -> anyhow::Result<()> {
+        use ContextPredicate::*;
+
+        assert_eq!(
+            ContextPredicate::parse("a && (b == c || d != e)")?,
+            And(
+                Box::new(Identifier("a".into())),
+                Box::new(Or(
+                    Box::new(Equal("b".into(), "c".into())),
+                    Box::new(NotEqual("d".into(), "e".into())),
+                ))
+            )
+        );
+
+        assert_eq!(
+            ContextPredicate::parse("!a")?,
+            Not(Box::new(Identifier("a".into())),)
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_context_predicate_eval() -> anyhow::Result<()> {
+        let predicate = ContextPredicate::parse("a && b || c == d")?;
+
+        let mut context = Context::default();
+        context.set.insert("a".into());
+        assert!(!predicate.eval(&context));
+
+        context.set.insert("b".into());
+        assert!(predicate.eval(&context));
+
+        context.set.remove("b");
+        context.map.insert("c".into(), "x".into());
+        assert!(!predicate.eval(&context));
+
+        context.map.insert("c".into(), "d".into());
+        assert!(predicate.eval(&context));
+
+        let predicate = ContextPredicate::parse("!a")?;
+        assert!(predicate.eval(&Context::default()));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_matcher() -> anyhow::Result<()> {
+        #[derive(Clone, Debug, Eq, PartialEq)]
+        struct ActionArg {
+            a: &'static str,
+        }
+
+        let keymap = Keymap(vec![
+            Binding::new("a", "a", Some("a")).with_arg(ActionArg { a: "b" }),
+            Binding::new("b", "b", Some("a")),
+            Binding::new("a b", "a_b", Some("a || b")),
+        ]);
+
+        let mut ctx_a = Context::default();
+        ctx_a.set.insert("a".into());
+
+        let mut ctx_b = Context::default();
+        ctx_b.set.insert("b".into());
+
+        let mut matcher = Matcher::new(keymap);
+
+        // Basic match
+        assert_eq!(
+            matcher.test_keystroke("a", 1, &ctx_a),
+            Some(("a".to_string(), Some(ActionArg { a: "b" })))
+        );
+
+        // Multi-keystroke match
+        assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
+        assert_eq!(
+            matcher.test_keystroke::<()>("b", 1, &ctx_b),
+            Some(("a_b".to_string(), None))
+        );
+
+        // Failed matches don't interfere with matching subsequent keys
+        assert_eq!(matcher.test_keystroke::<()>("x", 1, &ctx_a), None);
+        assert_eq!(
+            matcher.test_keystroke("a", 1, &ctx_a),
+            Some(("a".to_string(), Some(ActionArg { a: "b" })))
+        );
+
+        // Pending keystrokes are cleared when the context changes
+        assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
+        assert_eq!(
+            matcher.test_keystroke::<()>("b", 1, &ctx_a),
+            Some(("b".to_string(), None))
+        );
+
+        let mut ctx_c = Context::default();
+        ctx_c.set.insert("c".into());
+
+        // Pending keystrokes are maintained per-view
+        assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
+        assert_eq!(matcher.test_keystroke::<()>("a", 2, &ctx_c), None);
+        assert_eq!(
+            matcher.test_keystroke::<()>("b", 1, &ctx_b),
+            Some(("a_b".to_string(), None))
+        );
+
+        Ok(())
+    }
+
+    impl Matcher {
+        fn test_keystroke<A: Any + Clone>(
+            &mut self,
+            keystroke: &str,
+            view_id: usize,
+            ctx: &Context,
+        ) -> Option<(String, Option<A>)> {
+            if let MatchResult::Action { name, arg } =
+                self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, ctx)
+            {
+                Some((name, arg.and_then(|arg| arg.downcast_ref::<A>().cloned())))
+            } else {
+                None
+            }
+        }
+    }
+}

gpui/src/lib.rs 🔗

@@ -0,0 +1,5 @@
+pub mod keymap;
+pub mod platform;
+
+pub use pathfinder_color as color;
+pub use pathfinder_geometry as geometry;

gpui/src/platform/event.rs 🔗

@@ -0,0 +1,24 @@
+use crate::{geometry::vector::Vector2F, keymap::Keystroke};
+
+#[derive(Debug)]
+pub enum Event {
+    KeyDown {
+        keystroke: Keystroke,
+        chars: String,
+    },
+    ScrollWheel {
+        position: Vector2F,
+        delta: Vector2F,
+        precise: bool,
+    },
+    LeftMouseDown {
+        position: Vector2F,
+        cmd: bool,
+    },
+    LeftMouseUp {
+        position: Vector2F,
+    },
+    LeftMouseDragged {
+        position: Vector2F,
+    },
+}

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

@@ -0,0 +1,176 @@
+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,
+};
+
+#[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>)>>,
+}
+
+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);
+        }
+    }
+}
+
+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;
+            }
+        }
+    }
+
+    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);
+    }
+}

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

@@ -0,0 +1,115 @@
+use super::Event;
+use crate::{geometry::vector::vec2f, keymap::Keystroke};
+use cocoa::appkit::{
+    NSDeleteFunctionKey as DELETE_KEY, NSDownArrowFunctionKey as ARROW_DOWN_KEY,
+    NSLeftArrowFunctionKey as ARROW_LEFT_KEY, NSPageDownFunctionKey as PAGE_DOWN_KEY,
+    NSPageUpFunctionKey as PAGE_UP_KEY, NSRightArrowFunctionKey as ARROW_RIGHT_KEY,
+    NSUpArrowFunctionKey as ARROW_UP_KEY,
+};
+use cocoa::{
+    appkit::{NSEvent as _, NSEventModifierFlags, NSEventType},
+    base::{id, YES},
+    foundation::NSString as _,
+};
+use std::{ffi::CStr, os::raw::c_char};
+
+const BACKSPACE_KEY: u16 = 0x7f;
+const ENTER_KEY: u16 = 0x0d;
+const ESCAPE_KEY: u16 = 0x1b;
+
+impl Event {
+    pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
+        let event_type = native_event.eventType();
+
+        // Filter out event types that aren't in the NSEventType enum.
+        // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
+        match event_type as u64 {
+            0 | 21 | 32 | 33 | 35 | 36 | 37 => {
+                return None;
+            }
+            _ => {}
+        }
+
+        match event_type {
+            NSEventType::NSKeyDown => {
+                let modifiers = native_event.modifierFlags();
+                let unmodified_chars = native_event.charactersIgnoringModifiers();
+                let unmodified_chars = CStr::from_ptr(unmodified_chars.UTF8String() as *mut c_char)
+                    .to_str()
+                    .unwrap();
+
+                let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() {
+                    match first_char as u16 {
+                        ARROW_UP_KEY => "up",
+                        ARROW_DOWN_KEY => "down",
+                        ARROW_LEFT_KEY => "left",
+                        ARROW_RIGHT_KEY => "right",
+                        PAGE_UP_KEY => "pageup",
+                        PAGE_DOWN_KEY => "pagedown",
+                        BACKSPACE_KEY => "backspace",
+                        ENTER_KEY => "enter",
+                        DELETE_KEY => "delete",
+                        ESCAPE_KEY => "escape",
+                        _ => unmodified_chars,
+                    }
+                } else {
+                    return None;
+                };
+
+                let chars = native_event.characters();
+                let chars = CStr::from_ptr(chars.UTF8String() as *mut c_char)
+                    .to_str()
+                    .unwrap()
+                    .into();
+
+                Some(Self::KeyDown {
+                    keystroke: Keystroke {
+                        ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
+                        alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
+                        shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
+                        cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
+                        key: unmodified_chars.into(),
+                    },
+                    chars,
+                })
+            }
+            NSEventType::NSLeftMouseDown => {
+                window_height.map(|window_height| Self::LeftMouseDown {
+                    position: vec2f(
+                        native_event.locationInWindow().x as f32,
+                        window_height - native_event.locationInWindow().y as f32,
+                    ),
+                    cmd: native_event
+                        .modifierFlags()
+                        .contains(NSEventModifierFlags::NSCommandKeyMask),
+                })
+            }
+            NSEventType::NSLeftMouseUp => window_height.map(|window_height| Self::LeftMouseUp {
+                position: vec2f(
+                    native_event.locationInWindow().x as f32,
+                    window_height - native_event.locationInWindow().y as f32,
+                ),
+            }),
+            NSEventType::NSLeftMouseDragged => {
+                window_height.map(|window_height| Self::LeftMouseDragged {
+                    position: vec2f(
+                        native_event.locationInWindow().x as f32,
+                        window_height - native_event.locationInWindow().y as f32,
+                    ),
+                })
+            }
+            NSEventType::NSScrollWheel => window_height.map(|window_height| Self::ScrollWheel {
+                position: vec2f(
+                    native_event.locationInWindow().x as f32,
+                    window_height - native_event.locationInWindow().y as f32,
+                ),
+                delta: vec2f(
+                    native_event.scrollingDeltaX() as f32,
+                    native_event.scrollingDeltaY() as f32,
+                ),
+                precise: native_event.hasPreciseScrollingDeltas() == YES,
+            }),
+            _ => None,
+        }
+    }
+}

gpui/src/platform/mod.rs 🔗

@@ -0,0 +1,20 @@
+mod event;
+#[cfg(target_os = "macos")]
+pub mod mac;
+pub mod current {
+    #[cfg(target_os = "macos")]
+    pub use super::mac::*;
+}
+
+use std::path::PathBuf;
+
+use event::Event;
+
+pub trait App {
+    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;
+    fn on_event<F: 'static + FnMut(Event) -> bool>(self, callback: F) -> Self;
+    fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
+    fn run(self);
+}

zed/Cargo.toml 🔗

@@ -0,0 +1,12 @@
+[package]
+authors = ["Nathan Sobo <nathansobo@gmail.com>"]
+edition = "2018"
+name = "zed"
+version = "0.1.0"
+
+[dependencies]
+dirs = "3.0"
+gpui = {path = "../gpui"}
+libc = "0.2"
+log = "0.4"
+simplelog = "0.9"

zed/src/main.rs 🔗

@@ -0,0 +1,38 @@
+use std::fs;
+
+use fs::OpenOptions;
+use gpui::platform::{current as platform, App as _};
+use log::LevelFilter;
+use simplelog::SimpleLogger;
+
+fn main() {
+    init_logger();
+    platform::app()
+        .on_finish_launching(|| log::info!("finish launching"))
+        .run();
+}
+
+fn init_logger() {
+    let level = LevelFilter::Info;
+
+    if stdout_is_a_pty() {
+        SimpleLogger::init(level, Default::default()).expect("could not initialize logger");
+    } else {
+        let log_dir_path = dirs::home_dir()
+            .expect("could not locate home directory for logging")
+            .join("Library/Logs/");
+        let log_file_path = log_dir_path.join("Zed.log");
+        fs::create_dir_all(&log_dir_path).expect("could not create log directory");
+        let log_file = OpenOptions::new()
+            .create(true)
+            .append(true)
+            .open(log_file_path)
+            .expect("could not open logfile");
+        simplelog::WriteLogger::init(level, simplelog::Config::default(), log_file)
+            .expect("could not initialize logger");
+    }
+}
+
+fn stdout_is_a_pty() -> bool {
+    unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 }
+}