.gitignore 🔗
@@ -0,0 +1,2 @@
+/target
+.DS_Store
Nathan Sobo created
.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(+)
@@ -0,0 +1,2 @@
+/target
+.DS_Store
@@ -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",
+]
@@ -0,0 +1,2 @@
+[workspace]
+members = ["zed", "gpui"]
@@ -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"
@@ -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
+ }
+ }
+ }
+}
@@ -0,0 +1,5 @@
+pub mod keymap;
+pub mod platform;
+
+pub use pathfinder_color as color;
+pub use pathfinder_geometry as geometry;
@@ -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,
+ },
+}
@@ -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);
+ }
+}
@@ -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,
+ }
+ }
+}
@@ -0,0 +1,8 @@
+use super::*;
+
+mod app;
+mod event;
+
+pub fn app() -> impl App {
+ app::App::default()
+}
@@ -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);
+}
@@ -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"
@@ -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 }
+}