From 01eb2dce24ac00fc091f4c079f7f406c2beeee1f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 15 Apr 2022 16:55:26 +0200 Subject: [PATCH 01/18] WIP: Start on a new `cli` crate Co-Authored-By: Nathan Sobo --- Cargo.lock | 309 ++++++++++++++++++----- crates/cli/Cargo.toml | 12 + crates/cli/src/main.rs | 95 +++++++ crates/collab/Cargo.toml | 2 +- crates/gpui/src/platform/mac/platform.rs | 28 ++ 5 files changed, 377 insertions(+), 69 deletions(-) create mode 100644 crates/cli/Cargo.toml create mode 100644 crates/cli/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 37caa88364..b0ca3b628c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -309,7 +309,7 @@ dependencies = [ "polling", "vec-arena", "waker-fn", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -364,7 +364,7 @@ dependencies = [ "futures-lite", "once_cell", "signal-hook", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -458,7 +458,7 @@ dependencies = [ "async-io", "async-lock", "async-process", - "crossbeam-utils", + "crossbeam-utils 0.8.2", "futures-channel", "futures-core", "futures-io", @@ -550,7 +550,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -909,7 +909,7 @@ dependencies = [ "num-traits", "serde", "time 0.1.44", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -964,9 +964,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.0-beta.2" +version = "3.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c" dependencies = [ "atty", "bitflags", @@ -976,24 +976,34 @@ dependencies = [ "os_str_bytes", "strsim 0.10.0", "termcolor", - "textwrap 0.12.1", - "unicode-width", - "vec_map", + "textwrap 0.15.0", ] [[package]] name = "clap_derive" -version = "3.0.0-beta.2" +version = "3.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" dependencies = [ - "heck", + "heck 0.4.0", "proc-macro-error", "proc-macro2", "quote", "syn", ] +[[package]] +name = "cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 3.1.8", + "core-foundation", + "core-services", + "ipc-channel", + "serde", +] + [[package]] name = "client" version = "0.1.0" @@ -1087,7 +1097,7 @@ dependencies = [ "async-trait", "async-tungstenite", "base64 0.13.0", - "clap 3.0.0-beta.2", + "clap 3.1.8", "client", "collections", "comrak", @@ -1272,6 +1282,15 @@ dependencies = [ "libc", ] +[[package]] +name = "core-services" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b344b958cae90858bf6086f49599ecc5ec8698eacad0ea155509ba11fab347" +dependencies = [ + "core-foundation", +] + [[package]] name = "core-text" version = "19.2.0" @@ -1326,6 +1345,16 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + [[package]] name = "crossbeam-channel" version = "0.5.0" @@ -1333,7 +1362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils", + "crossbeam-utils 0.8.2", ] [[package]] @@ -1344,7 +1373,7 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils", + "crossbeam-utils 0.8.2", ] [[package]] @@ -1354,7 +1383,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d60ab4a8dba064f2fbb5aa270c28da5cf4bbd0e72dae1140a6b0353a779dbe00" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils", + "crossbeam-utils 0.8.2", "lazy_static", "loom", "memoffset", @@ -1368,7 +1397,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils", + "crossbeam-utils 0.8.2", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg 1.0.1", + "cfg-if 0.1.10", + "lazy_static", ] [[package]] @@ -1472,7 +1512,7 @@ dependencies = [ "openssl-sys", "schannel", "socket2 0.4.0", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1488,7 +1528,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1634,7 +1674,7 @@ checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", "redox_users", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1645,7 +1685,7 @@ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1668,7 +1708,7 @@ checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ "lazy_static", "libc", - "winapi", + "winapi 0.3.9", "wio", ] @@ -1865,9 +1905,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fastrand" -version = "1.4.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -1985,7 +2025,7 @@ dependencies = [ "pathfinder_simd", "servo-fontconfig", "walkdir", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2070,6 +2110,22 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + [[package]] name = "funty" version = "1.1.0" @@ -2201,7 +2257,7 @@ dependencies = [ "libc", "log", "rustc_version", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2449,6 +2505,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.18" @@ -2608,7 +2670,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c" dependencies = [ - "crossbeam-utils", + "crossbeam-utils 0.8.2", "globset", "lazy_static", "log", @@ -2673,6 +2735,34 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipc-channel" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cb1d9211085f0ea6f1379d944b93c4d07e8207aa3bcf49f37eda12b85081887" +dependencies = [ + "bincode", + "crossbeam-channel 0.4.4", + "fnv", + "lazy_static", + "libc", + "mio", + "rand 0.7.3", + "serde", + "tempfile", + "uuid", + "winapi 0.3.9", +] + [[package]] name = "isahc" version = "0.9.14" @@ -2680,7 +2770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2948a0ce43e2c2ef11d7edf6816508998d99e13badd1150be0914205df9388a" dependencies = [ "bytes 0.5.6", - "crossbeam-utils", + "crossbeam-utils 0.8.2", "curl", "curl-sys", "flume", @@ -2797,6 +2887,16 @@ dependencies = [ "sha2 0.9.5", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "kurbo" version = "0.8.1" @@ -2893,7 +2993,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ "cfg-if 1.0.0", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -3031,6 +3131,12 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "md-5" version = "0.9.1" @@ -3124,6 +3230,37 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + [[package]] name = "multimap" version = "0.8.3" @@ -3140,6 +3277,17 @@ dependencies = [ "socket2 0.3.19", ] +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + [[package]] name = "nom" version = "5.1.2" @@ -3361,9 +3509,12 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "2.4.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] [[package]] name = "outline" @@ -3421,7 +3572,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -3647,7 +3798,7 @@ dependencies = [ "libc", "log", "wepoll-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -3727,9 +3878,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] @@ -3823,7 +3974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "355f634b43cdd80724ee7848f95770e7e70eefa6dcf14fea676216573b8fd603" dependencies = [ "bytes 1.0.1", - "heck", + "heck 0.3.3", "itertools", "log", "multimap", @@ -3888,7 +4039,7 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4005,9 +4156,9 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ - "crossbeam-channel", + "crossbeam-channel 0.5.0", "crossbeam-deque", - "crossbeam-utils", + "crossbeam-utils 0.8.2", "lazy_static", "num_cpus", ] @@ -4069,7 +4220,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4109,7 +4260,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4311,7 +4462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4791,7 +4942,7 @@ checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4801,7 +4952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4875,9 +5026,9 @@ dependencies = [ "bytes 0.5.6", "chrono", "crc", - "crossbeam-channel", + "crossbeam-channel 0.5.0", "crossbeam-queue", - "crossbeam-utils", + "crossbeam-utils 0.8.2", "either", "futures-channel", "futures-core", @@ -4923,9 +5074,9 @@ dependencies = [ "byteorder", "bytes 1.0.1", "crc", - "crossbeam-channel", + "crossbeam-channel 0.5.0", "crossbeam-queue", - "crossbeam-utils", + "crossbeam-utils 0.8.2", "dirs 3.0.1", "either", "futures-channel", @@ -4971,7 +5122,7 @@ dependencies = [ "dotenv", "either", "futures", - "heck", + "heck 0.3.3", "lazy_static", "proc-macro2", "quote", @@ -4992,7 +5143,7 @@ dependencies = [ "dotenv", "either", "futures", - "heck", + "heck 0.3.3", "once_cell", "proc-macro2", "quote", @@ -5191,9 +5342,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.67" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" +checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" dependencies = [ "proc-macro2", "quote", @@ -5230,16 +5381,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", + "fastrand", "libc", - "rand 0.8.3", "redox_syscall", "remove_dir_all", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -5283,12 +5434,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" -dependencies = [ - "unicode-width", -] +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "theme" @@ -5410,7 +5558,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -5425,7 +5573,7 @@ dependencies = [ "stdweb", "time-macros", "version_check", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -5846,6 +5994,9 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.2", +] [[package]] name = "value-bag" @@ -5916,7 +6067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ "same-file", - "winapi", + "winapi 0.3.9", "winapi-util", ] @@ -6073,6 +6224,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -6083,6 +6240,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -6095,7 +6258,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -6110,7 +6273,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -6136,6 +6299,16 @@ dependencies = [ "util", ] +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "wyz" version = "0.2.0" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 0000000000..ee4b019418 --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cli" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +core-foundation = "0.9" +core-services = "0.2" +clap = { version = "3.1", features = ["derive"] } +ipc-channel = "0.16" +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs new file mode 100644 index 0000000000..a8531125c2 --- /dev/null +++ b/crates/cli/src/main.rs @@ -0,0 +1,95 @@ +use anyhow::{anyhow, Result}; +use clap::Parser; +use core_foundation::{ + array::{CFArray, CFIndex}, + string::kCFStringEncodingUTF8, + url::{CFURLCreateWithBytes, CFURL}, +}; +use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType}; +use ipc_channel::ipc::IpcOneShotServer; +use serde::{Deserialize, Serialize}; +use std::{path::PathBuf, process, ptr}; + +#[derive(Parser)] +#[clap(name = "zed")] +struct Args { + /// Wait for all of the given paths to be closed before exiting. + #[clap(short, long)] + wait: bool, + /// A sequence of space-separated paths that you want to open. + #[clap()] + paths: Vec, +} + +#[derive(Serialize, Deserialize)] +struct OpenResult { + exit_status: i32, + stdout_message: Option, + stderr_message: Option, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + let (server, server_name) = IpcOneShotServer::::new()?; + let app_path = locate_app()?; + launch_app(app_path, args.paths, server_name)?; + + let (_, result) = server.accept()?; + if let Some(message) = result.stdout_message { + println!("{}", message); + } + if let Some(message) = result.stderr_message { + eprintln!("{}", message); + } + + process::exit(result.exit_status) +} + +fn locate_app() -> Result { + Ok("/Applications/Zed.app".into()) +} + +fn launch_app(app_path: PathBuf, paths_to_open: Vec, server_name: String) -> Result<()> { + let status = unsafe { + let app_url = + CFURL::from_path(&app_path, true).ok_or_else(|| anyhow!("invalid app path"))?; + let mut urls_to_open = paths_to_open + .into_iter() + .map(|path| { + CFURL::from_path(&path, true).ok_or_else(|| anyhow!("{:?} is invalid", path)) + }) + .collect::>>()?; + + let server_url = format!("zed_cli_response://{server_name}"); + urls_to_open.push(CFURL::wrap_under_create_rule(CFURLCreateWithBytes( + ptr::null(), + server_url.as_ptr(), + server_url.len() as CFIndex, + kCFStringEncodingUTF8, + ptr::null(), + ))); + + let urls_to_open = CFArray::from_copyable( + &urls_to_open + .iter() + .map(|url| url.as_concrete_TypeRef()) + .collect::>(), + ); + LSOpenFromURLSpec( + &LSLaunchURLSpec { + appURL: app_url.as_concrete_TypeRef(), + itemURLs: urls_to_open.as_concrete_TypeRef(), + passThruParams: ptr::null(), + launchFlags: kLSLaunchDefaults, + asyncRefCon: ptr::null_mut(), + }, + ptr::null_mut(), + ) + }; + if status == 0 { + Ok(()) + } else { + Err(anyhow!("cannot start {:?}", app_path)) + } +} diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index c588cc50b2..34047406a7 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -22,7 +22,7 @@ async-std = { version = "1.8.0", features = ["attributes"] } async-trait = "0.1.50" async-tungstenite = "0.16" base64 = "0.13" -clap = "=3.0.0-beta.2" +clap = "3.1" comrak = "0.10" either = "1.6" envy = "0.4.2" diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index d8969e7957..eeadd0c476 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -94,6 +94,10 @@ unsafe fn build_classes() { sel!(application:openFiles:), open_files as extern "C" fn(&mut Object, Sel, id, id), ); + decl.add_method( + sel!(application:openURLs:), + open_urls as extern "C" fn(&mut Object, Sel, id, id), + ); decl.register() } } @@ -702,6 +706,30 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { } } +extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, paths: id) { + let paths = unsafe { + (0..paths.count()) + .into_iter() + .filter_map(|i| { + let path = paths.objectAtIndex(i); + match dbg!( + CStr::from_ptr(path.absoluteString().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::>() + }; + // let platform = unsafe { get_foreground_platform(this) }; + // if let Some(callback) = platform.0.borrow_mut().open_files.as_mut() { + // callback(paths); + // } +} + extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { unsafe { let platform = get_foreground_platform(this); From 75f0326e54a10ee110118772eb765252b229a426 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 15 Apr 2022 17:33:56 -0600 Subject: [PATCH 02/18] Use ipc_channel crate to communicate between cli and app We still aren't handling CLI requests in the app, but this lays the foundation for bi-directional communication. Co-Authored-By: Max Brunsfeld --- Cargo.lock | 1 + crates/cli/Cargo.toml | 8 +++ crates/cli/src/cli.rs | 21 +++++++ crates/cli/src/main.rs | 74 +++++++++++------------- crates/gpui/src/app.rs | 20 ++++++- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/platform.rs | 23 +++++--- crates/gpui/src/platform/test.rs | 2 + crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 35 ++++++++++- 10 files changed, 134 insertions(+), 52 deletions(-) create mode 100644 crates/cli/src/cli.rs diff --git a/Cargo.lock b/Cargo.lock index b0ca3b628c..69c75fef67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6344,6 +6344,7 @@ dependencies = [ "async-trait", "breadcrumbs", "chat_panel", + "cli", "client", "clock", "collections", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ee4b019418..011ed9caa1 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -3,6 +3,14 @@ name = "cli" version = "0.1.0" edition = "2021" +[lib] +path = "src/cli.rs" +doctest = false + +[[bin]] +name = "cli" +path = "src/main.rs" + [dependencies] anyhow = "1.0" core-foundation = "0.9" diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs new file mode 100644 index 0000000000..af7e78ea31 --- /dev/null +++ b/crates/cli/src/cli.rs @@ -0,0 +1,21 @@ +pub use ipc_channel::ipc; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Serialize, Deserialize)] +pub struct IpcHandshake { + pub requests: ipc::IpcSender, + pub responses: ipc::IpcReceiver, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum CliRequest { + Open { paths: Vec, wait: bool }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum CliResponse { + Stdout { message: String }, + Stderr { message: String }, + Exit { status: i32 }, +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a8531125c2..2977f97ad3 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,14 +1,14 @@ use anyhow::{anyhow, Result}; use clap::Parser; +use cli::{CliRequest, CliResponse, IpcHandshake}; use core_foundation::{ array::{CFArray, CFIndex}, string::kCFStringEncodingUTF8, url::{CFURLCreateWithBytes, CFURL}, }; use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType}; -use ipc_channel::ipc::IpcOneShotServer; -use serde::{Deserialize, Serialize}; -use std::{path::PathBuf, process, ptr}; +use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender}; +use std::{fs, path::PathBuf, ptr}; #[derive(Parser)] #[clap(name = "zed")] @@ -21,61 +21,55 @@ struct Args { paths: Vec, } -#[derive(Serialize, Deserialize)] -struct OpenResult { - exit_status: i32, - stdout_message: Option, - stderr_message: Option, -} - fn main() -> Result<()> { let args = Args::parse(); - let (server, server_name) = IpcOneShotServer::::new()?; let app_path = locate_app()?; - launch_app(app_path, args.paths, server_name)?; + let (tx, rx) = launch_app(app_path)?; - let (_, result) = server.accept()?; - if let Some(message) = result.stdout_message { - println!("{}", message); - } - if let Some(message) = result.stderr_message { - eprintln!("{}", message); + tx.send(CliRequest::Open { + paths: args + .paths + .into_iter() + .map(|path| fs::canonicalize(path).map_err(|error| anyhow!(error))) + .collect::>>()?, + wait: false, + })?; + + while let Ok(response) = rx.recv() { + match response { + CliResponse::Stdout { message } => println!("{message}"), + CliResponse::Stderr { message } => eprintln!("{message}"), + CliResponse::Exit { status } => std::process::exit(status), + } } - process::exit(result.exit_status) + Ok(()) } fn locate_app() -> Result { - Ok("/Applications/Zed.app".into()) + Ok("/Users/nathan/src/zed/target/debug/bundle/osx/Zed.app".into()) + // Ok("/Applications/Zed.app".into()) } -fn launch_app(app_path: PathBuf, paths_to_open: Vec, server_name: String) -> Result<()> { +fn launch_app(app_path: PathBuf) -> Result<(IpcSender, IpcReceiver)> { + let (server, server_name) = IpcOneShotServer::::new()?; + let status = unsafe { let app_url = CFURL::from_path(&app_path, true).ok_or_else(|| anyhow!("invalid app path"))?; - let mut urls_to_open = paths_to_open - .into_iter() - .map(|path| { - CFURL::from_path(&path, true).ok_or_else(|| anyhow!("{:?} is invalid", path)) - }) - .collect::>>()?; - let server_url = format!("zed_cli_response://{server_name}"); - urls_to_open.push(CFURL::wrap_under_create_rule(CFURLCreateWithBytes( + let url = format!("zed-cli://{server_name}"); + let url_to_open = CFURL::wrap_under_create_rule(CFURLCreateWithBytes( ptr::null(), - server_url.as_ptr(), - server_url.len() as CFIndex, + url.as_ptr(), + url.len() as CFIndex, kCFStringEncodingUTF8, ptr::null(), - ))); + )); + + let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]); - let urls_to_open = CFArray::from_copyable( - &urls_to_open - .iter() - .map(|url| url.as_concrete_TypeRef()) - .collect::>(), - ); LSOpenFromURLSpec( &LSLaunchURLSpec { appURL: app_url.as_concrete_TypeRef(), @@ -87,8 +81,10 @@ fn launch_app(app_path: PathBuf, paths_to_open: Vec, server_name: Strin ptr::null_mut(), ) }; + if status == 0 { - Ok(()) + let (_, handshake) = server.accept()?; + Ok((handshake.requests, handshake.responses)) } else { Err(anyhow!("cannot start {:?}", app_path)) } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 683ea46999..57df8879f7 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -248,7 +248,7 @@ impl App { self } - pub fn on_quit(self, mut callback: F) -> Self + pub fn on_quit(&mut self, mut callback: F) -> &mut Self where F: 'static + FnMut(&mut MutableAppContext), { @@ -260,7 +260,7 @@ impl App { self } - pub fn on_event(self, mut callback: F) -> Self + pub fn on_event(&mut self, mut callback: F) -> &mut Self where F: 'static + FnMut(Event, &mut MutableAppContext) -> bool, { @@ -274,7 +274,7 @@ impl App { self } - pub fn on_open_files(self, mut callback: F) -> Self + pub fn on_open_files(&mut self, mut callback: F) -> &mut Self where F: 'static + FnMut(Vec, &mut MutableAppContext), { @@ -288,6 +288,20 @@ impl App { self } + pub fn on_open_urls(&mut self, mut callback: F) -> &mut Self + where + F: 'static + FnMut(Vec, &mut MutableAppContext), + { + let cx = self.0.clone(); + self.0 + .borrow_mut() + .foreground_platform + .on_open_urls(Box::new(move |paths| { + callback(paths, &mut *cx.borrow_mut()) + })); + self + } + pub fn run(self, on_finish_launching: F) where F: 'static + FnOnce(&mut MutableAppContext), diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 9abff5b0f7..6d4215468a 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -64,6 +64,7 @@ pub(crate) trait ForegroundPlatform { fn on_quit(&self, callback: Box); fn on_event(&self, callback: Box bool>); fn on_open_files(&self, callback: Box)>); + fn on_open_urls(&self, callback: Box)>); fn run(&self, on_finish_launching: Box ()>); fn on_menu_command(&self, callback: Box); diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index eeadd0c476..813cf550d0 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -113,6 +113,7 @@ pub struct MacForegroundPlatformState { event: Option bool>>, menu_command: Option>, open_files: Option)>>, + open_urls: Option)>>, finish_launching: Option ()>>, menu_actions: Vec>, } @@ -218,6 +219,10 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { self.0.borrow_mut().open_files = Some(callback); } + fn on_open_urls(&self, callback: Box)>) { + self.0.borrow_mut().open_urls = Some(callback); + } + fn run(&self, on_finish_launching: Box ()>) { self.0.borrow_mut().finish_launching = Some(on_finish_launching); @@ -706,16 +711,16 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { } } -extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, paths: id) { - let paths = unsafe { - (0..paths.count()) +extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { + let urls = unsafe { + (0..urls.count()) .into_iter() .filter_map(|i| { - let path = paths.objectAtIndex(i); + let path = urls.objectAtIndex(i); match dbg!( CStr::from_ptr(path.absoluteString().UTF8String() as *mut c_char).to_str() ) { - Ok(string) => Some(PathBuf::from(string)), + Ok(string) => Some(string.to_string()), Err(err) => { log::error!("error converting path to string: {}", err); None @@ -724,10 +729,10 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, paths: id) { }) .collect::>() }; - // let platform = unsafe { get_foreground_platform(this) }; - // if let Some(callback) = platform.0.borrow_mut().open_files.as_mut() { - // callback(paths); - // } + let platform = unsafe { get_foreground_platform(this) }; + if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { + callback(urls); + } } extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index a18f52a4f6..e0dd3059fc 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -68,6 +68,8 @@ impl super::ForegroundPlatform for ForegroundPlatform { fn on_open_files(&self, _: Box)>) {} + fn on_open_urls(&self, _: Box)>) {} + fn run(&self, _on_finish_launching: Box ()>) { unimplemented!() } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index b4ae84a4f3..d327acb173 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -32,6 +32,7 @@ test-support = [ assets = { path = "../assets" } breadcrumbs = { path = "../breadcrumbs" } chat_panel = { path = "../chat_panel" } +cli = { path = "../cli" } collections = { path = "../collections" } command_palette = { path = "../command_palette" } client = { path = "../client" } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index cead3ac390..a70473087e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -3,6 +3,7 @@ use anyhow::{anyhow, Context, Result}; use assets::Assets; +use cli::{ipc, CliRequest, CliResponse, IpcHandshake}; use client::{self, http, ChannelList, UserStore}; use fs::OpenOptions; use futures::{channel::oneshot, StreamExt}; @@ -26,7 +27,7 @@ use zed::{ fn main() { init_logger(); - let app = gpui::App::new(Assets).unwrap(); + let mut app = gpui::App::new(Assets).unwrap(); load_embedded_fonts(&app); let fs = Arc::new(RealFs); @@ -87,6 +88,12 @@ fn main() { }) }; + app.on_open_urls(|urls, _| { + if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { + connect_to_cli(server_name).log_err(); + } + }); + app.run(move |cx| { let http = http::client(); let client = client::Client::new(http.clone()); @@ -292,3 +299,29 @@ fn load_config_files( .detach(); rx } + +fn connect_to_cli(server_name: &str) -> Result<()> { + let handshake_tx = cli::ipc::IpcSender::::connect(server_name.to_string()) + .context("error connecting to cli")?; + let (request_tx, request_rx) = ipc::channel::()?; + let (response_tx, response_rx) = ipc::channel::()?; + + handshake_tx + .send(IpcHandshake { + requests: request_tx, + responses: response_rx, + }) + .context("error sending ipc handshake")?; + + std::thread::spawn(move || { + while let Ok(cli_request) = request_rx.recv() { + log::info!("{cli_request:?}"); + response_tx.send(CliResponse::Stdout { + message: "Hi, CLI!".into(), + })?; + response_tx.send(CliResponse::Exit { status: 0 })?; + } + Ok::<_, anyhow::Error>(()) + }); + Ok(()) +} From 05c44b9414751933cd748ddd4a211e5911c04b9f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 15 Apr 2022 18:06:40 -0600 Subject: [PATCH 03/18] Process incoming CLI requests on the main thread --- crates/zed/src/main.rs | 59 ++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index a70473087e..03ceef3a43 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -3,17 +3,23 @@ use anyhow::{anyhow, Context, Result}; use assets::Assets; -use cli::{ipc, CliRequest, CliResponse, IpcHandshake}; +use cli::{ + ipc::{self, IpcSender}, + CliRequest, CliResponse, IpcHandshake, +}; use client::{self, http, ChannelList, UserStore}; use fs::OpenOptions; -use futures::{channel::oneshot, StreamExt}; +use futures::{ + channel::{mpsc, oneshot}, + SinkExt, StreamExt, +}; use gpui::{App, AssetSource, Task}; use log::LevelFilter; use parking_lot::Mutex; use project::Fs; use settings::{self, KeymapFile, Settings, SettingsFileContent}; use smol::process::Command; -use std::{env, fs, path::PathBuf, sync::Arc}; +use std::{env, fs, path::PathBuf, sync::Arc, thread}; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; use util::ResultExt; use workspace::{self, AppState, OpenNew, OpenPaths}; @@ -88,9 +94,33 @@ fn main() { }) }; - app.on_open_urls(|urls, _| { + app.on_open_urls(|urls, cx| { if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { - connect_to_cli(server_name).log_err(); + if let Some((mut requests, responses)) = connect_to_cli(server_name).log_err() { + cx.spawn(|_| async move { + for request in requests.next().await { + match request { + CliRequest::Open { paths, wait } => { + if wait { + todo!(); + } + + log::info!("open paths {:?}", paths); + responses + .send(CliResponse::Stdout { + message: "Hello CLI!".to_string(), + }) + .log_err(); + responses.send(CliResponse::Exit { status: 0 }).log_err(); + + // TODO... get rid of AppState so we can do this here? + // cx.update(|cx| open_paths(&paths, app_state, cx)); + } + } + } + }) + .detach(); + }; } }); @@ -300,7 +330,9 @@ fn load_config_files( rx } -fn connect_to_cli(server_name: &str) -> Result<()> { +fn connect_to_cli( + server_name: &str, +) -> Result<(mpsc::Receiver, IpcSender)> { let handshake_tx = cli::ipc::IpcSender::::connect(server_name.to_string()) .context("error connecting to cli")?; let (request_tx, request_rx) = ipc::channel::()?; @@ -313,15 +345,16 @@ fn connect_to_cli(server_name: &str) -> Result<()> { }) .context("error sending ipc handshake")?; - std::thread::spawn(move || { + let (mut async_request_tx, async_request_rx) = + futures::channel::mpsc::channel::(16); + thread::spawn(move || { while let Ok(cli_request) = request_rx.recv() { - log::info!("{cli_request:?}"); - response_tx.send(CliResponse::Stdout { - message: "Hi, CLI!".into(), - })?; - response_tx.send(CliResponse::Exit { status: 0 })?; + if smol::block_on(async_request_tx.send(cli_request)).is_err() { + break; + } } Ok::<_, anyhow::Error>(()) }); - Ok(()) + + Ok((async_request_rx, response_tx)) } From 43763fa2f86ed3b85a3e47ad2ac7e6d8369857f4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 19 Apr 2022 11:32:46 -0700 Subject: [PATCH 04/18] Allow opening paths from the CLI Co-Authored-By: Antonio Scandurra --- Cargo.lock | 1 + crates/cli/Cargo.toml | 1 + crates/cli/src/main.rs | 6 ++-- crates/zed/src/main.rs | 73 ++++++++++++++++++++++++------------------ 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69c75fef67..05382c74ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1000,6 +1000,7 @@ dependencies = [ "clap 3.1.8", "core-foundation", "core-services", + "dirs 3.0.1", "ipc-channel", "serde", ] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 011ed9caa1..367db26c8c 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -16,5 +16,6 @@ anyhow = "1.0" core-foundation = "0.9" core-services = "0.2" clap = { version = "3.1", features = ["derive"] } +dirs = "3.0" ipc-channel = "0.16" serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2977f97ad3..6620f3d08b 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -48,8 +48,10 @@ fn main() -> Result<()> { } fn locate_app() -> Result { - Ok("/Users/nathan/src/zed/target/debug/bundle/osx/Zed.app".into()) - // Ok("/Applications/Zed.app".into()) + Ok(std::env::current_exe()? + .parent() + .unwrap() + .join("bundle/osx/Zed.app")) } fn launch_app(app_path: PathBuf) -> Result<(IpcSender, IpcReceiver)> { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 03ceef3a43..6a6dc5ef49 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -13,7 +13,7 @@ use futures::{ channel::{mpsc, oneshot}, SinkExt, StreamExt, }; -use gpui::{App, AssetSource, Task}; +use gpui::{App, AssetSource, AsyncAppContext, Task}; use log::LevelFilter; use parking_lot::Mutex; use project::Fs; @@ -94,32 +94,14 @@ fn main() { }) }; - app.on_open_urls(|urls, cx| { + let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); + app.on_open_urls(move |urls, _| { if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { - if let Some((mut requests, responses)) = connect_to_cli(server_name).log_err() { - cx.spawn(|_| async move { - for request in requests.next().await { - match request { - CliRequest::Open { paths, wait } => { - if wait { - todo!(); - } - - log::info!("open paths {:?}", paths); - responses - .send(CliResponse::Stdout { - message: "Hello CLI!".to_string(), - }) - .log_err(); - responses.send(CliResponse::Exit { status: 0 }).log_err(); - - // TODO... get rid of AppState so we can do this here? - // cx.update(|cx| open_paths(&paths, app_state, cx)); - } - } - } - }) - .detach(); + if let Some(cli_connection) = connect_to_cli(server_name).log_err() { + cli_connections_tx + .unbounded_send(cli_connection) + .map_err(|_| anyhow!("no listener for cli connections")) + .log_err(); }; } }); @@ -207,13 +189,25 @@ fn main() { if stdout_is_a_pty() { cx.platform().activate(true); - } - - let paths = collect_path_args(); - if paths.is_empty() { - cx.dispatch_global_action(OpenNew(app_state.clone())); + let paths = collect_path_args(); + if paths.is_empty() { + cx.dispatch_global_action(OpenNew(app_state.clone())); + } else { + cx.dispatch_global_action(OpenPaths { paths, app_state }); + } } else { - cx.dispatch_global_action(OpenPaths { paths, app_state }); + if let Ok(Some(connection)) = cli_connections_rx.try_next() { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) + .detach(); + } else { + cx.dispatch_global_action(OpenNew(app_state.clone())); + } + cx.spawn(|cx| async move { + while let Some(connection) = cli_connections_rx.next().await { + handle_cli_connection(connection, app_state.clone(), cx.clone()).await; + } + }) + .detach(); } }); } @@ -358,3 +352,18 @@ fn connect_to_cli( Ok((async_request_rx, response_tx)) } + +async fn handle_cli_connection( + (mut requests, responses): (mpsc::Receiver, IpcSender), + app_state: Arc, + mut cx: AsyncAppContext, +) { + if let Some(request) = requests.next().await { + match request { + CliRequest::Open { paths, .. } => { + cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths, app_state })); + responses.send(CliResponse::Exit { status: 0 }).log_err(); + } + } + } +} From a81f7ebbf65946566e627f8fb935c12d2de66cfd Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 19 Apr 2022 13:42:33 -0700 Subject: [PATCH 05/18] Locate the Zed app from the CLI using NSWorkspace API --- Cargo.lock | 2 ++ crates/cli/Cargo.toml | 10 +++++++--- crates/cli/src/main.rs | 43 +++++++++++++++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05382c74ee..79ce441258 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -998,10 +998,12 @@ version = "0.1.0" dependencies = [ "anyhow", "clap 3.1.8", + "cocoa", "core-foundation", "core-services", "dirs 3.0.1", "ipc-channel", + "objc", "serde", ] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 367db26c8c..be58523140 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -13,9 +13,13 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" -core-foundation = "0.9" -core-services = "0.2" clap = { version = "3.1", features = ["derive"] } dirs = "3.0" ipc-channel = "0.16" -serde = { version = "1.0", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0", features = ["derive"] } + +[target.'cfg(target_os = "macos")'.dependencies] +cocoa = "0.24" +core-foundation = "0.9" +core-services = "0.2" +objc = "0.2" \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 6620f3d08b..159462fe57 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -8,7 +8,8 @@ use core_foundation::{ }; use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType}; use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender}; -use std::{fs, path::PathBuf, ptr}; +use objc::{class, msg_send, sel, sel_impl}; +use std::{ffi::CStr, fs, path::PathBuf, ptr}; #[derive(Parser)] #[clap(name = "zed")] @@ -48,20 +49,46 @@ fn main() -> Result<()> { } fn locate_app() -> Result { - Ok(std::env::current_exe()? - .parent() - .unwrap() - .join("bundle/osx/Zed.app")) + if cfg!(debug_assertions) { + Ok(std::env::current_exe()? + .parent() + .unwrap() + .join("bundle/osx/Zed.app")) + } else { + Ok(path_to_app_with_bundle_identifier("dev.zed.Zed") + .unwrap_or_else(|| "/Applications/Zed.dev".into())) + } +} + +fn path_to_app_with_bundle_identifier(bundle_id: &str) -> Option { + use cocoa::{ + base::{id, nil}, + foundation::{NSString, NSURL as _}, + }; + + unsafe { + let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; + let bundle_id = NSString::alloc(nil).init_str(bundle_id); + let app_url: id = msg_send![workspace, URLForApplicationWithBundleIdentifier: bundle_id]; + if !app_url.is_null() { + Some(PathBuf::from( + CStr::from_ptr(app_url.path().UTF8String()) + .to_string_lossy() + .to_string(), + )) + } else { + None + } + } } fn launch_app(app_path: PathBuf) -> Result<(IpcSender, IpcReceiver)> { let (server, server_name) = IpcOneShotServer::::new()?; + let url = format!("zed-cli://{server_name}"); let status = unsafe { let app_url = CFURL::from_path(&app_path, true).ok_or_else(|| anyhow!("invalid app path"))?; - - let url = format!("zed-cli://{server_name}"); let url_to_open = CFURL::wrap_under_create_rule(CFURLCreateWithBytes( ptr::null(), url.as_ptr(), @@ -69,9 +96,7 @@ fn launch_app(app_path: PathBuf) -> Result<(IpcSender, IpcReceiver Date: Tue, 19 Apr 2022 14:19:22 -0700 Subject: [PATCH 06/18] Update bundle script to include fat CLI binary --- script/bundle | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/script/bundle b/script/bundle index bcaa68c1e9..fc6bab355f 100755 --- a/script/bundle +++ b/script/bundle @@ -4,24 +4,33 @@ set -e export ZED_BUNDLE=true -# Install cargo-bundle 0.5.0 if it's not already installed +echo "Installing cargo bundle" cargo install cargo-bundle --version 0.5.0 # Deal with versions of macOS that don't include libstdc++ headers export CXXFLAGS="-stdlib=libc++" -# Build the app bundle for x86_64 -pushd crates/zed > /dev/null -cargo bundle --release --target x86_64-apple-darwin -popd > /dev/null +echo "Compiling binaries" +cargo build --release --package zed --target aarch64-apple-darwin +cargo build --release --package zed --target x86_64-apple-darwin +cargo build --release --package cli --target aarch64-apple-darwin +cargo build --release --package cli --target x86_64-apple-darwin -# Build the binary for aarch64 (Apple M1) -cargo build --release --target aarch64-apple-darwin +echo "Creating application bundle" +(cd crates/zed && cargo bundle --release --target x86_64-apple-darwin) -# Replace the bundle's binary with a "fat binary" that combines the two architecture-specific binaries -lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed +echo "Creating fat binaries" +lipo \ + -create \ + target/{x86_64-apple-darwin,aarch64-apple-darwin}/release/Zed \ + -output \ + target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed +lipo \ + -create \ + target/{x86_64-apple-darwin,aarch64-apple-darwin}/release/cli \ + -output \ + target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/cli -# Sign the app bundle with an ad-hoc signature so it runs on the M1. We need a real certificate but this works for now. if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then echo "Signing bundle with Apple-issued certificate" security create-keychain -p $MACOS_CERTIFICATE_PASSWORD zed.keychain || echo "" @@ -39,7 +48,6 @@ else codesign --force --deep --sign - target/x86_64-apple-darwin/release/bundle/osx/Zed.app -v fi -# Create a DMG echo "Creating DMG" mkdir -p target/release hdiutil create -volname Zed -srcfolder target/x86_64-apple-darwin/release/bundle/osx -ov -format UDZO target/release/Zed.dmg From eee1cec3d4f58b50728ecf5d99c3583d3ac2ae1f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 19 Apr 2022 14:34:01 -0700 Subject: [PATCH 07/18] :art: Remove unnecessary JoinProjectParams struct --- crates/contacts_panel/src/contacts_panel.rs | 9 ++++----- crates/workspace/src/workspace.rs | 13 +++++-------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index deb6f8e4a3..45b5f69b5e 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use client::{Contact, UserStore}; use gpui::{ elements::*, @@ -8,8 +6,9 @@ use gpui::{ Element, ElementBox, Entity, LayoutContext, ModelHandle, RenderContext, Subscription, View, ViewContext, }; -use workspace::{AppState, JoinProject, JoinProjectParams}; use settings::Settings; +use std::sync::Arc; +use workspace::{AppState, JoinProject}; pub struct ContactsPanel { contacts: ListState, @@ -207,10 +206,10 @@ impl ContactsPanel { }) .on_click(move |cx| { if !is_host && !is_guest { - cx.dispatch_global_action(JoinProject(JoinProjectParams { + cx.dispatch_global_action(JoinProject { project_id, app_state: app_state.clone(), - })); + }); } }) .flex(1., true) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c73a6bab5b..cfff617ff4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -97,7 +97,10 @@ pub struct OpenPaths { pub struct ToggleFollow(pub PeerId); #[derive(Clone)] -pub struct JoinProject(pub JoinProjectParams); +pub struct JoinProject { + pub project_id: u64, + pub app_state: Arc, +} impl_internal_actions!( workspace, @@ -115,7 +118,7 @@ pub fn init(client: &Arc, cx: &mut MutableAppContext) { open_new(&action.0, cx) }); cx.add_global_action(move |action: &JoinProject, cx: &mut MutableAppContext| { - join_project(action.0.project_id, &action.0.app_state, cx).detach(); + join_project(action.project_id, &action.app_state, cx).detach(); }); cx.add_action(Workspace::toggle_share); @@ -187,12 +190,6 @@ pub struct AppState { ) -> Workspace, } -#[derive(Clone)] -pub struct JoinProjectParams { - pub project_id: u64, - pub app_state: Arc, -} - pub trait Item: View { fn deactivated(&mut self, _: &mut ViewContext) {} fn navigate(&mut self, _: Box, _: &mut ViewContext) -> bool { From fbd1afc51f22ce22a7c4ad294f8ff4b9c40d70ac Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 19 Apr 2022 16:15:00 -0700 Subject: [PATCH 08/18] Add a command for installing the CLI --- crates/gpui/src/platform.rs | 3 +- crates/gpui/src/platform/mac/platform.rs | 66 +++++++++++------------- crates/gpui/src/platform/test.rs | 2 +- crates/zed/src/zed.rs | 17 +++++- 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 6d4215468a..b4f77df1eb 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -54,8 +54,7 @@ pub trait Platform: Send + Sync { fn set_cursor_style(&self, style: CursorStyle); fn local_timezone(&self) -> UtcOffset; - - fn path_for_resource(&self, name: Option<&str>, extension: Option<&str>) -> Result; + fn path_for_auxiliary_executable(&self, name: &str) -> Result; } pub(crate) trait ForegroundPlatform { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 813cf550d0..5789b2f611 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -14,9 +14,7 @@ use cocoa::{ NSPasteboardTypeString, NSSavePanel, NSWindow, }, base::{id, nil, selector, YES}, - foundation::{ - NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSString, NSUInteger, NSURL, - }, + foundation::{NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSString, NSURL}, }; use core_foundation::{ base::{CFType, CFTypeRef, OSStatus, TCFType as _}, @@ -38,8 +36,8 @@ use ptr::null_mut; use std::{ cell::{Cell, RefCell}, convert::TryInto, - ffi::{c_void, CStr}, - os::raw::c_char, + ffi::{c_void, CStr, OsStr}, + os::{raw::c_char, unix::ffi::OsStrExt}, path::{Path, PathBuf}, ptr, rc::Rc, @@ -48,9 +46,6 @@ use std::{ }; use time::UtcOffset; -#[allow(non_upper_case_globals)] -const NSUTF8StringEncoding: NSUInteger = 4; - const MAC_PLATFORM_IVAR: &'static str = "platform"; static mut APP_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); @@ -274,10 +269,9 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { for i in 0..urls.count() { let url = urls.objectAtIndex(i); if url.isFileURL() == YES { - let path = std::ffi::CStr::from_ptr(url.path().UTF8String()) - .to_string_lossy() - .to_string(); - result.push(PathBuf::from(path)); + if let Ok(path) = ns_url_to_path(url) { + result.push(path) + } } } Some(result) @@ -305,19 +299,13 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { let (done_tx, done_rx) = oneshot::channel(); let done_tx = Cell::new(Some(done_tx)); let block = ConcreteBlock::new(move |response: NSModalResponse| { - let result = if response == NSModalResponse::NSModalResponseOk { + let mut result = None; + if response == NSModalResponse::NSModalResponseOk { let url = panel.URL(); if url.isFileURL() == YES { - let path = std::ffi::CStr::from_ptr(url.path().UTF8String()) - .to_string_lossy() - .to_string(); - Some(PathBuf::from(path)) - } else { - None + result = ns_url_to_path(panel.URL()).ok() } - } else { - None - }; + } if let Some(mut done_tx) = done_tx.take() { let _ = postage::sink::Sink::try_send(&mut done_tx, result); @@ -612,22 +600,18 @@ impl platform::Platform for MacPlatform { } } - fn path_for_resource(&self, name: Option<&str>, extension: Option<&str>) -> Result { + fn path_for_auxiliary_executable(&self, name: &str) -> Result { unsafe { let bundle: id = NSBundle::mainBundle(); if bundle.is_null() { Err(anyhow!("app is not running inside a bundle")) } else { - let name = name.map_or(nil, |name| ns_string(name)); - let extension = extension.map_or(nil, |extension| ns_string(extension)); - let path: id = msg_send![bundle, pathForResource: name ofType: extension]; - if path.is_null() { - Err(anyhow!("resource could not be found")) + let name = ns_string(name); + let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name]; + if url.is_null() { + Err(anyhow!("resource not found")) } else { - let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; - let bytes = path.UTF8String() as *const u8; - let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); - Ok(PathBuf::from(path)) + ns_url_to_path(url) } } } @@ -717,9 +701,7 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { .into_iter() .filter_map(|i| { let path = urls.objectAtIndex(i); - match dbg!( - CStr::from_ptr(path.absoluteString().UTF8String() as *mut c_char).to_str() - ) { + match CStr::from_ptr(path.absoluteString().UTF8String() as *mut c_char).to_str() { Ok(string) => Some(string.to_string()), Err(err) => { log::error!("error converting path to string: {}", err); @@ -754,6 +736,20 @@ unsafe fn ns_string(string: &str) -> id { NSString::alloc(nil).init_str(string).autorelease() } +unsafe fn ns_url_to_path(url: id) -> Result { + let path: *mut c_char = msg_send![url, fileSystemRepresentation]; + if path.is_null() { + Err(anyhow!( + "url is not a file path: {}", + CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy() + )) + } else { + Ok(PathBuf::from(OsStr::from_bytes( + CStr::from_ptr(path).to_bytes(), + ))) + } +} + mod security { #![allow(non_upper_case_globals)] use super::*; diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index e0dd3059fc..296d4ea90d 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -163,7 +163,7 @@ impl super::Platform for Platform { UtcOffset::UTC } - fn path_for_resource(&self, _name: Option<&str>, _extension: Option<&str>) -> Result { + fn path_for_auxiliary_executable(&self, _name: &str) -> Result { Err(anyhow!("app not running inside a bundle")) } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e9ed886557..439b3e63ee 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -38,7 +38,8 @@ actions!( DebugElements, OpenSettings, IncreaseBufferFontSize, - DecreaseBufferFontSize + DecreaseBufferFontSize, + InstallCommandLineTool, ] ); @@ -66,6 +67,20 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { cx.refresh_windows(); }); }); + cx.add_global_action(move |_: &InstallCommandLineTool, cx| { + cx.spawn(|cx| async move { + let path = cx.platform().path_for_auxiliary_executable("cli")?; + let link_path = "/usr/local/bin/zed"; + smol::fs::unix::symlink(link_path, path.as_path()).await?; + log::info!( + "created symlink {} -> {}", + link_path, + path.to_string_lossy() + ); + Ok::<_, anyhow::Error>(()) + }) + .detach(); + }); cx.add_action({ let app_state = app_state.clone(); move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { From 0d9a0e2cbe3c16afa6436326ae0dfc94430c05ab Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 19 Apr 2022 17:00:41 -0700 Subject: [PATCH 09/18] Avoid permissions error when installing CLI symlink --- crates/zed/src/zed.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 439b3e63ee..caecfd5a92 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4,6 +4,7 @@ pub mod settings_file; #[cfg(any(test, feature = "test-support"))] pub mod test; +use anyhow::Context; use breadcrumbs::Breadcrumbs; use chat_panel::ChatPanel; pub use client; @@ -69,15 +70,22 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }); cx.add_global_action(move |_: &InstallCommandLineTool, cx| { cx.spawn(|cx| async move { - let path = cx.platform().path_for_auxiliary_executable("cli")?; - let link_path = "/usr/local/bin/zed"; - smol::fs::unix::symlink(link_path, path.as_path()).await?; + log::info!("installing command line launcher"); + let cli_path = cx + .platform() + .path_for_auxiliary_executable("cli") + .log_err()?; + let link_path = "/opt/homebrew/bin/zed"; + smol::fs::unix::symlink(cli_path.as_path(), link_path) + .await + .context("failed to install cli symlink") + .log_err()?; log::info!( "created symlink {} -> {}", link_path, - path.to_string_lossy() + cli_path.to_string_lossy() ); - Ok::<_, anyhow::Error>(()) + Some(()) }) .detach(); }); From b3f2b7a92cb66bfc2f407a1369c0fb52b65f7cc4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 10:22:20 +0200 Subject: [PATCH 10/18] Use osascript to escalate privileges and copy the CLI to /usr/local/bin --- crates/zed/src/zed.rs | 78 +++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index caecfd5a92..53a0875248 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4,7 +4,7 @@ pub mod settings_file; #[cfg(any(test, feature = "test-support"))] pub mod test; -use anyhow::Context; +use anyhow::{anyhow, Context, Result}; use breadcrumbs::Breadcrumbs; use chat_panel::ChatPanel; pub use client; @@ -16,7 +16,7 @@ use gpui::{ actions, geometry::vector::vec2f, platform::{WindowBounds, WindowOptions}, - ModelHandle, ViewContext, + AsyncAppContext, ModelHandle, ViewContext, }; use lazy_static::lazy_static; pub use lsp; @@ -26,7 +26,10 @@ use project_panel::ProjectPanel; use search::{BufferSearchBar, ProjectSearchBar}; use serde_json::to_string_pretty; use settings::Settings; -use std::{path::PathBuf, sync::Arc}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use util::ResultExt; pub use workspace; use workspace::{AppState, Workspace, WorkspaceParams}; @@ -69,25 +72,8 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { }); }); cx.add_global_action(move |_: &InstallCommandLineTool, cx| { - cx.spawn(|cx| async move { - log::info!("installing command line launcher"); - let cli_path = cx - .platform() - .path_for_auxiliary_executable("cli") - .log_err()?; - let link_path = "/opt/homebrew/bin/zed"; - smol::fs::unix::symlink(cli_path.as_path(), link_path) - .await - .context("failed to install cli symlink") - .log_err()?; - log::info!( - "created symlink {} -> {}", - link_path, - cli_path.to_string_lossy() - ); - Some(()) - }) - .detach(); + cx.spawn(|cx| async move { install_cli(&cx).await.context("error creating CLI symlink") }) + .detach_and_log_err(cx); }); cx.add_action({ let app_state = app_state.clone(); @@ -253,6 +239,54 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { cx.platform().quit(); } +async fn install_cli(cx: &AsyncAppContext) -> Result<()> { + let cli_path = cx.platform().path_for_auxiliary_executable("cli")?; + let link_path = Path::new("/usr/local/bin/zed"); + let bin_dir_path = link_path.parent().unwrap(); + + // Don't re-create symlink if it points to the same CLI binary. + if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) { + return Ok(()); + } + + // If the symlink is not there or is outdated, first try replacing it + // without escalating. + smol::fs::remove_file(link_path).await.log_err(); + if smol::fs::unix::symlink(&cli_path, link_path) + .await + .log_err() + .is_some() + { + return Ok(()); + } + + // The symlink could not be created, so use osascript with admin privileges + // to create it. + let status = smol::process::Command::new("osascript") + .args([ + "-e", + &dbg!(format!( + "do shell script \" \ + mkdir -p \'{}\' && \ + ln -sf \'{}\' \'{}\' \ + \" with administrator privileges", + bin_dir_path.to_string_lossy(), + cli_path.to_string_lossy(), + link_path.to_string_lossy(), + )), + ]) + .stdout(smol::process::Stdio::inherit()) + .stderr(smol::process::Stdio::inherit()) + .output() + .await? + .status; + if status.success() { + Ok(()) + } else { + Err(anyhow!("error running osascript")) + } +} + #[cfg(test)] mod tests { use super::*; From b013b1ba5d350713a8b1a2d8597f9b15aafc7ad9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 10:42:09 +0200 Subject: [PATCH 11/18] Call `language::init_test` in `test_single_file_worktrees_diagnostics` --- crates/project/src/project.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5c1064400e..e72ca7ac92 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5337,6 +5337,7 @@ mod tests { #[gpui::test] async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { cx.foreground().forbid_parking(); + language::init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( From f7055c2accb22fbba4397afd0d194206d482902b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 12:54:34 +0200 Subject: [PATCH 12/18] Implement `zed --wait` --- crates/cli/src/cli.rs | 1 + crates/cli/src/main.rs | 3 +- crates/gpui/src/app.rs | 8 +-- crates/journal/src/journal.rs | 2 +- crates/workspace/src/workspace.rs | 82 ++++++++++++++++++------------- crates/zed/src/main.rs | 80 ++++++++++++++++++++++++++++-- 6 files changed, 132 insertions(+), 44 deletions(-) diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index af7e78ea31..7cad42b534 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -15,6 +15,7 @@ pub enum CliRequest { #[derive(Debug, Serialize, Deserialize)] pub enum CliResponse { + Ping, Stdout { message: String }, Stderr { message: String }, Exit { status: i32 }, diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 159462fe57..9aac438724 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -34,11 +34,12 @@ fn main() -> Result<()> { .into_iter() .map(|path| fs::canonicalize(path).map_err(|error| anyhow!(error))) .collect::>>()?, - wait: false, + wait: args.wait, })?; while let Ok(response) = rx.recv() { match response { + CliResponse::Ping => {} CliResponse::Stdout { message } => println!("{message}"), CliResponse::Stderr { message } => eprintln!("{message}"), CliResponse::Exit { status } => std::process::exit(status), diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 57df8879f7..0f9a3041b3 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -733,7 +733,7 @@ type GlobalSubscriptionCallback = Box bool>; type FocusObservationCallback = Box bool>; type GlobalObservationCallback = Box; -type ReleaseObservationCallback = Box; +type ReleaseObservationCallback = Box; type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; pub struct MutableAppContext { @@ -1259,12 +1259,12 @@ impl MutableAppContext { } } - pub fn observe_release(&mut self, handle: &H, mut callback: F) -> Subscription + pub fn observe_release(&mut self, handle: &H, callback: F) -> Subscription where E: Entity, E::Event: 'static, H: Handle, - F: 'static + FnMut(&E, &mut Self), + F: 'static + FnOnce(&E, &mut Self), { let id = post_inc(&mut self.next_subscription_id); self.release_observations @@ -2211,7 +2211,7 @@ impl MutableAppContext { fn handle_entity_release_effect(&mut self, entity_id: usize, entity: &dyn Any) { let callbacks = self.release_observations.lock().remove(&entity_id); if let Some(callbacks) = callbacks { - for (_, mut callback) in callbacks { + for (_, callback) in callbacks { callback(entity, self); } } diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 910c2947b4..7aa8be4d97 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -43,7 +43,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut MutableAppContext) { cx.spawn(|mut cx| { async move { let (journal_dir, entry_path) = create_entry.await?; - let workspace = cx + let (workspace, _) = cx .update(|cx| workspace::open_paths(&[journal_dir], &app_state, cx)) .await; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cfff617ff4..62a79ff706 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -376,6 +376,11 @@ pub trait ItemHandle: 'static + fmt::Debug { -> Task>; fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option; fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; + fn on_release( + &self, + cx: &mut MutableAppContext, + callback: Box, + ) -> gpui::Subscription; } pub trait WeakItemHandle { @@ -411,6 +416,12 @@ impl ItemHandle for ViewHandle { Box::new(self.clone()) } + fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext) { + self.update(cx, |item, cx| { + item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx); + }) + } + fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option> { self.update(cx, |item, cx| { cx.add_option_view(|cx| item.clone_on_split(cx)) @@ -418,12 +429,6 @@ impl ItemHandle for ViewHandle { .map(|handle| Box::new(handle) as Box) } - fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext) { - self.update(cx, |item, cx| { - item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx); - }) - } - fn added_to_pane( &self, workspace: &mut Workspace, @@ -512,6 +517,30 @@ impl ItemHandle for ViewHandle { self.update(cx, |this, cx| this.navigate(data, cx)) } + fn id(&self) -> usize { + self.id() + } + + fn to_any(&self) -> AnyViewHandle { + self.into() + } + + fn is_dirty(&self, cx: &AppContext) -> bool { + self.read(cx).is_dirty(cx) + } + + fn has_conflict(&self, cx: &AppContext) -> bool { + self.read(cx).has_conflict(cx) + } + + fn can_save(&self, cx: &AppContext) -> bool { + self.read(cx).can_save(cx) + } + + fn can_save_as(&self, cx: &AppContext) -> bool { + self.read(cx).can_save_as(cx) + } + fn save(&self, project: ModelHandle, cx: &mut MutableAppContext) -> Task> { self.update(cx, |item, cx| item.save(project, cx)) } @@ -533,30 +562,6 @@ impl ItemHandle for ViewHandle { self.update(cx, |item, cx| item.reload(project, cx)) } - fn is_dirty(&self, cx: &AppContext) -> bool { - self.read(cx).is_dirty(cx) - } - - fn has_conflict(&self, cx: &AppContext) -> bool { - self.read(cx).has_conflict(cx) - } - - fn id(&self) -> usize { - self.id() - } - - fn to_any(&self) -> AnyViewHandle { - self.into() - } - - fn can_save(&self, cx: &AppContext) -> bool { - self.read(cx).can_save(cx) - } - - fn can_save_as(&self, cx: &AppContext) -> bool { - self.read(cx).can_save_as(cx) - } - fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option { self.read(cx).act_as_type(type_id, self, cx) } @@ -570,6 +575,14 @@ impl ItemHandle for ViewHandle { None } } + + fn on_release( + &self, + cx: &mut MutableAppContext, + callback: Box, + ) -> gpui::Subscription { + cx.observe_release(self, move |_, cx| callback(cx)) + } } impl Into for Box { @@ -2102,7 +2115,10 @@ pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, cx: &mut MutableAppContext, -) -> Task> { +) -> Task<( + ViewHandle, + Vec, Arc>>>, +)> { log::info!("open paths {:?}", abs_paths); // Open paths in existing workspace if possible @@ -2139,8 +2155,8 @@ pub fn open_paths( let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx)); cx.spawn(|_| async move { - task.await; - workspace + let items = task.await; + (workspace, items) }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6a6dc5ef49..5fbf634262 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -11,7 +11,7 @@ use client::{self, http, ChannelList, UserStore}; use fs::OpenOptions; use futures::{ channel::{mpsc, oneshot}, - SinkExt, StreamExt, + FutureExt, SinkExt, StreamExt, }; use gpui::{App, AssetSource, AsyncAppContext, Task}; use log::LevelFilter; @@ -19,7 +19,7 @@ use parking_lot::Mutex; use project::Fs; use settings::{self, KeymapFile, Settings, SettingsFileContent}; use smol::process::Command; -use std::{env, fs, path::PathBuf, sync::Arc, thread}; +use std::{env, fs, path::PathBuf, sync::Arc, thread, time::Duration}; use theme::{ThemeRegistry, DEFAULT_THEME_NAME}; use util::ResultExt; use workspace::{self, AppState, OpenNew, OpenPaths}; @@ -360,9 +360,79 @@ async fn handle_cli_connection( ) { if let Some(request) = requests.next().await { match request { - CliRequest::Open { paths, .. } => { - cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths, app_state })); - responses.send(CliResponse::Exit { status: 0 }).log_err(); + CliRequest::Open { paths, wait } => { + let (workspace, items) = cx + .update(|cx| workspace::open_paths(&paths, &app_state, cx)) + .await; + + let mut errored = false; + let mut futures = Vec::new(); + cx.update(|cx| { + for (item, path) in items.into_iter().zip(&paths) { + match item { + Some(Ok(item)) => { + let released = oneshot::channel(); + item.on_release( + cx, + Box::new(move |_| { + let _ = released.0.send(()); + }), + ) + .detach(); + futures.push(released.1); + } + Some(Err(err)) => { + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", path, err), + }) + .log_err(); + errored = true; + } + None => {} + } + } + }); + + if wait { + let background = cx.background(); + let wait = async move { + if paths.is_empty() { + let (done_tx, done_rx) = oneshot::channel(); + let _subscription = cx.update(|cx| { + cx.observe_release(&workspace, move |_, _| { + let _ = done_tx.send(()); + }) + }); + drop(workspace); + let _ = done_rx.await; + } else { + let _ = futures::future::try_join_all(futures).await; + }; + } + .fuse(); + futures::pin_mut!(wait); + + loop { + // Repeatedly check if CLI is still open to avoid wasting resources + // waiting for files or workspaces to close. + let mut timer = background.timer(Duration::from_secs(1)).fuse(); + futures::select_biased! { + _ = wait => break, + _ = timer => { + if responses.send(CliResponse::Ping).is_err() { + break; + } + } + } + } + } + + responses + .send(CliResponse::Exit { + status: if errored { 1 } else { 0 }, + }) + .log_err(); } } } From 5ab35bd6fdb63acbd840ba443968852cc8d47712 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 14:34:38 +0200 Subject: [PATCH 13/18] Remove stray dbg --- crates/zed/src/zed.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 53a0875248..379b6172ab 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -265,7 +265,7 @@ async fn install_cli(cx: &AsyncAppContext) -> Result<()> { let status = smol::process::Command::new("osascript") .args([ "-e", - &dbg!(format!( + &format!( "do shell script \" \ mkdir -p \'{}\' && \ ln -sf \'{}\' \'{}\' \ @@ -273,7 +273,7 @@ async fn install_cli(cx: &AsyncAppContext) -> Result<()> { bin_dir_path.to_string_lossy(), cli_path.to_string_lossy(), link_path.to_string_lossy(), - )), + ), ]) .stdout(smol::process::Stdio::inherit()) .stderr(smol::process::Stdio::inherit()) From 926c75dadff2e527d78d9bbc2e41a47ee83e9d8c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 16:00:41 +0200 Subject: [PATCH 14/18] Implement `zed --version` Co-Authored-By: Nathan Sobo --- Cargo.lock | 53 ++++++++++++++++++++++++++++++++++++++---- crates/cli/Cargo.toml | 3 ++- crates/cli/src/main.rs | 23 +++++++++++++++++- 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79ce441258..181a15fe69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1004,6 +1004,7 @@ dependencies = [ "dirs 3.0.1", "ipc-channel", "objc", + "plist", "serde", ] @@ -2595,7 +2596,7 @@ checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" dependencies = [ "bytes 1.0.1", "fnv", - "itoa", + "itoa 0.4.7", ] [[package]] @@ -2804,6 +2805,12 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "jobserver" version = "0.1.24" @@ -3027,6 +3034,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + [[package]] name = "lipsum" version = "0.8.0" @@ -3779,6 +3795,20 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +[[package]] +name = "plist" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +dependencies = [ + "base64 0.13.0", + "indexmap", + "line-wrap", + "serde", + "time 0.3.7", + "xml-rs", +] + [[package]] name = "png" version = "0.16.8" @@ -4440,6 +4470,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "salsa20" version = "0.8.0" @@ -4642,7 +4678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "indexmap", - "itoa", + "itoa 0.4.7", "ryu", "serde", ] @@ -4686,7 +4722,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" dependencies = [ "form_urlencoded", - "itoa", + "itoa 0.4.7", "ryu", "serde", ] @@ -5039,7 +5075,7 @@ dependencies = [ "hashlink 0.6.0", "hex", "hmac 0.10.1", - "itoa", + "itoa 0.4.7", "libc", "log", "md-5", @@ -5088,7 +5124,7 @@ dependencies = [ "hashlink 0.7.0", "hex", "hmac 0.10.1", - "itoa", + "itoa 0.4.7", "libc", "log", "md-5", @@ -5585,6 +5621,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ + "itoa 1.0.1", "libc", "num_threads", ] @@ -6324,6 +6361,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + [[package]] name = "xmlparser" version = "0.13.3" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index be58523140..a8836cf64e 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -22,4 +22,5 @@ serde = { version = "1.0", features = ["derive"] } cocoa = "0.24" core-foundation = "0.9" core-services = "0.2" -objc = "0.2" \ No newline at end of file +objc = "0.2" +plist = "1.3" \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 9aac438724..613f64215b 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -9,10 +9,11 @@ use core_foundation::{ use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType}; use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender}; use objc::{class, msg_send, sel, sel_impl}; +use serde::Deserialize; use std::{ffi::CStr, fs, path::PathBuf, ptr}; #[derive(Parser)] -#[clap(name = "zed")] +#[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))] struct Args { /// Wait for all of the given paths to be closed before exiting. #[clap(short, long)] @@ -20,12 +21,32 @@ struct Args { /// A sequence of space-separated paths that you want to open. #[clap()] paths: Vec, + /// Print Zed's version and the app path. + #[clap(short, long)] + version: bool, +} + +#[derive(Debug, Deserialize)] +struct InfoPlist { + #[serde(rename = "CFBundleShortVersionString")] + bundle_short_version_string: String, } fn main() -> Result<()> { let args = Args::parse(); let app_path = locate_app()?; + if args.version { + let plist_path = app_path.join("Contents/Info.plist"); + let plist = plist::from_file::<_, InfoPlist>(plist_path)?; + println!( + "Zed {} – {}", + plist.bundle_short_version_string, + app_path.to_string_lossy() + ); + return Ok(()); + } + let (tx, rx) = launch_app(app_path)?; tx.send(CliRequest::Open { From f77239bd965343718f7d7a4d0936b554e96d58f9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 16:34:01 +0200 Subject: [PATCH 15/18] Add application menu to install CLI Co-Authored-By: Nathan Sobo --- crates/project/src/project.rs | 1 - crates/zed/src/menus.rs | 6 ++++++ crates/zed/src/zed.rs | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e72ca7ac92..5c1064400e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5337,7 +5337,6 @@ mod tests { #[gpui::test] async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { cx.foreground().forbid_parking(); - language::init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 33ac76e63c..48883d5c05 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -14,6 +14,12 @@ pub fn menus(state: &Arc) -> Vec> { action: Box::new(super::About), }, MenuItem::Separator, + MenuItem::Action { + name: "Install CLI", + keystroke: None, + action: Box::new(super::InstallCommandLineInterface), + }, + MenuItem::Separator, MenuItem::Action { name: "Quit", keystroke: Some("cmd-q"), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 379b6172ab..4619979e7b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -43,7 +43,7 @@ actions!( OpenSettings, IncreaseBufferFontSize, DecreaseBufferFontSize, - InstallCommandLineTool, + InstallCommandLineInterface, ] ); @@ -71,7 +71,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { cx.refresh_windows(); }); }); - cx.add_global_action(move |_: &InstallCommandLineTool, cx| { + cx.add_global_action(move |_: &InstallCommandLineInterface, cx| { cx.spawn(|cx| async move { install_cli(&cx).await.context("error creating CLI symlink") }) .detach_and_log_err(cx); }); From 07562c2ccd954d0fa9d331c27fa50ee697a7f863 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 16:34:22 +0200 Subject: [PATCH 16/18] Locate app bundle based on location of CLI binary The app bundle can also be specified via `-b` or `--bundle-path`. Co-Authored-By: Nathan Sobo --- Cargo.lock | 2 -- crates/cli/Cargo.toml | 2 -- crates/cli/src/main.rs | 56 +++++++++++++++--------------------------- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 181a15fe69..f48e24c8d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -998,12 +998,10 @@ version = "0.1.0" dependencies = [ "anyhow", "clap 3.1.8", - "cocoa", "core-foundation", "core-services", "dirs 3.0.1", "ipc-channel", - "objc", "plist", "serde", ] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a8836cf64e..ec87bf20a4 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,8 +19,6 @@ ipc-channel = "0.16" serde = { version = "1.0", features = ["derive"] } [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.24" core-foundation = "0.9" core-services = "0.2" -objc = "0.2" plist = "1.3" \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 613f64215b..4bc2d6e73d 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -8,9 +8,8 @@ use core_foundation::{ }; use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType}; use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender}; -use objc::{class, msg_send, sel, sel_impl}; use serde::Deserialize; -use std::{ffi::CStr, fs, path::PathBuf, ptr}; +use std::{ffi::OsStr, fs, path::PathBuf, ptr}; #[derive(Parser)] #[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))] @@ -24,6 +23,9 @@ struct Args { /// Print Zed's version and the app path. #[clap(short, long)] version: bool, + /// Custom Zed.app path + #[clap(short, long)] + bundle_path: Option, } #[derive(Debug, Deserialize)] @@ -35,19 +37,24 @@ struct InfoPlist { fn main() -> Result<()> { let args = Args::parse(); - let app_path = locate_app()?; + let bundle_path = if let Some(bundle_path) = args.bundle_path { + bundle_path.canonicalize()? + } else { + locate_bundle()? + }; + if args.version { - let plist_path = app_path.join("Contents/Info.plist"); + let plist_path = bundle_path.join("Contents/Info.plist"); let plist = plist::from_file::<_, InfoPlist>(plist_path)?; println!( "Zed {} – {}", plist.bundle_short_version_string, - app_path.to_string_lossy() + bundle_path.to_string_lossy() ); return Ok(()); } - let (tx, rx) = launch_app(app_path)?; + let (tx, rx) = launch_app(bundle_path)?; tx.send(CliRequest::Open { paths: args @@ -70,38 +77,15 @@ fn main() -> Result<()> { Ok(()) } -fn locate_app() -> Result { - if cfg!(debug_assertions) { - Ok(std::env::current_exe()? - .parent() - .unwrap() - .join("bundle/osx/Zed.app")) - } else { - Ok(path_to_app_with_bundle_identifier("dev.zed.Zed") - .unwrap_or_else(|| "/Applications/Zed.dev".into())) - } -} - -fn path_to_app_with_bundle_identifier(bundle_id: &str) -> Option { - use cocoa::{ - base::{id, nil}, - foundation::{NSString, NSURL as _}, - }; - - unsafe { - let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; - let bundle_id = NSString::alloc(nil).init_str(bundle_id); - let app_url: id = msg_send![workspace, URLForApplicationWithBundleIdentifier: bundle_id]; - if !app_url.is_null() { - Some(PathBuf::from( - CStr::from_ptr(app_url.path().UTF8String()) - .to_string_lossy() - .to_string(), - )) - } else { - None +fn locate_bundle() -> Result { + let cli_path = std::env::current_exe()?.canonicalize()?; + let mut app_path = cli_path.clone(); + while app_path.extension() != Some(OsStr::new("app")) { + if !app_path.pop() { + return Err(anyhow!("cannot find app bundle containing {:?}", cli_path)); } } + Ok(app_path) } fn launch_app(app_path: PathBuf) -> Result<(IpcSender, IpcReceiver)> { From d725876e648e51d73f7a4ec60859c67329073604 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 17:36:03 +0200 Subject: [PATCH 17/18] :lipstick: Co-Authored-By: Nathan Sobo --- crates/cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ec87bf20a4..be7cc24b3e 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -21,4 +21,4 @@ serde = { version = "1.0", features = ["derive"] } [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9" core-services = "0.2" -plist = "1.3" \ No newline at end of file +plist = "1.3" From a210b05d00cd71c6ee75d0ebbd9d6918ece750e4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Apr 2022 17:36:27 +0200 Subject: [PATCH 18/18] Remove `App::on_open_files`, as it's a subset of `on_open_urls` Co-Authored-By: Nathan Sobo --- crates/gpui/src/app.rs | 14 ----------- crates/gpui/src/platform.rs | 1 - crates/gpui/src/platform/mac/platform.rs | 31 ------------------------ crates/gpui/src/platform/test.rs | 2 -- 4 files changed, 48 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 0f9a3041b3..37cf03a3cc 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -274,20 +274,6 @@ impl App { self } - pub fn on_open_files(&mut self, mut callback: F) -> &mut Self - where - F: 'static + FnMut(Vec, &mut MutableAppContext), - { - let cx = self.0.clone(); - self.0 - .borrow_mut() - .foreground_platform - .on_open_files(Box::new(move |paths| { - callback(paths, &mut *cx.borrow_mut()) - })); - self - } - pub fn on_open_urls(&mut self, mut callback: F) -> &mut Self where F: 'static + FnMut(Vec, &mut MutableAppContext), diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index b4f77df1eb..1e7ab84d50 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -62,7 +62,6 @@ pub(crate) trait ForegroundPlatform { fn on_resign_active(&self, callback: Box); fn on_quit(&self, callback: Box); fn on_event(&self, callback: Box bool>); - fn on_open_files(&self, callback: Box)>); fn on_open_urls(&self, callback: Box)>); fn run(&self, on_finish_launching: Box ()>); diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 5789b2f611..e637ebfa3e 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -85,10 +85,6 @@ unsafe fn build_classes() { sel!(handleGPUIMenuItem:), handle_menu_item as extern "C" fn(&mut Object, Sel, id), ); - decl.add_method( - sel!(application:openFiles:), - open_files as extern "C" fn(&mut Object, Sel, id, id), - ); decl.add_method( sel!(application:openURLs:), open_urls as extern "C" fn(&mut Object, Sel, id, id), @@ -107,7 +103,6 @@ pub struct MacForegroundPlatformState { quit: Option>, event: Option bool>>, menu_command: Option>, - open_files: Option)>>, open_urls: Option)>>, finish_launching: Option ()>>, menu_actions: Vec>, @@ -210,10 +205,6 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { self.0.borrow_mut().event = Some(callback); } - fn on_open_files(&self, callback: Box)>) { - self.0.borrow_mut().open_files = Some(callback); - } - fn on_open_urls(&self, callback: Box)>) { self.0.borrow_mut().open_urls = Some(callback); } @@ -673,28 +664,6 @@ extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { } } -extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) { - let paths = unsafe { - (0..paths.count()) - .into_iter() - .filter_map(|i| { - let path = paths.objectAtIndex(i); - match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() { - Ok(string) => Some(PathBuf::from(string)), - Err(err) => { - log::error!("error converting path to string: {}", err); - None - } - } - }) - .collect::>() - }; - let platform = unsafe { get_foreground_platform(this) }; - if let Some(callback) = platform.0.borrow_mut().open_files.as_mut() { - callback(paths); - } -} - extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { let urls = unsafe { (0..urls.count()) diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 296d4ea90d..85ef26cce3 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -66,8 +66,6 @@ impl super::ForegroundPlatform for ForegroundPlatform { fn on_event(&self, _: Box bool>) {} - fn on_open_files(&self, _: Box)>) {} - fn on_open_urls(&self, _: Box)>) {} fn run(&self, _on_finish_launching: Box ()>) {