diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5da8c8945e..a32f25fbe9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,26 @@ env:
RUST_BACKTRACE: 1
jobs:
+ rustfmt:
+ name: Check formatting
+ runs-on:
+ - self-hosted
+ - test
+ steps:
+ - name: Install Rust
+ run: |
+ rustup set profile minimal
+ rustup update stable
+
+ - name: Checkout repo
+ uses: actions/checkout@v2
+ with:
+ clean: false
+ submodules: 'recursive'
+
+ - name: cargo fmt
+ run: cargo fmt --all -- --check
+
tests:
name: Run tests
runs-on:
diff --git a/Cargo.lock b/Cargo.lock
index e8410b25f0..ec3399f791 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -518,6 +518,7 @@ dependencies = [
"menu",
"project",
"serde",
+ "serde_derive",
"serde_json",
"settings",
"smol",
@@ -784,6 +785,7 @@ dependencies = [
"gpui",
"itertools",
"language",
+ "outline",
"project",
"search",
"settings",
@@ -794,7 +796,7 @@ dependencies = [
[[package]]
name = "bromberg_sl2"
version = "0.6.0"
-source = "git+https://github.com/zed-industries/bromberg_sl2?rev=dac565a90e8f9245f48ff46225c915dc50f76920#dac565a90e8f9245f48ff46225c915dc50f76920"
+source = "git+https://github.com/zed-industries/bromberg_sl2?rev=950bc5482c216c395049ae33ae4501e08975f17f#950bc5482c216c395049ae33ae4501e08975f17f"
dependencies = [
"digest 0.9.0",
"lazy_static",
@@ -1097,6 +1099,7 @@ dependencies = [
"ipc-channel",
"plist",
"serde",
+ "serde_derive",
]
[[package]]
@@ -1111,7 +1114,6 @@ dependencies = [
"futures 0.3.25",
"gpui",
"image",
- "isahc",
"lazy_static",
"log",
"parking_lot 0.11.2",
@@ -1119,6 +1121,7 @@ dependencies = [
"rand 0.8.5",
"rpc",
"serde",
+ "serde_derive",
"settings",
"smol",
"sum_tree",
@@ -1188,7 +1191,7 @@ dependencies = [
[[package]]
name = "collab"
-version = "0.5.4"
+version = "0.8.2"
dependencies = [
"anyhow",
"async-tungstenite",
@@ -1228,6 +1231,7 @@ dependencies = [
"sea-orm",
"sea-query",
"serde",
+ "serde_derive",
"serde_json",
"settings",
"sha-1 0.9.8",
@@ -1257,7 +1261,9 @@ dependencies = [
"client",
"clock",
"collections",
+ "context_menu",
"editor",
+ "feedback",
"futures 0.3.25",
"fuzzy",
"gpui",
@@ -1267,6 +1273,7 @@ dependencies = [
"postage",
"project",
"serde",
+ "serde_derive",
"settings",
"theme",
"util",
@@ -1325,6 +1332,48 @@ dependencies = [
"theme",
]
+[[package]]
+name = "copilot"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-compression",
+ "async-tar",
+ "client",
+ "collections",
+ "context_menu",
+ "futures 0.3.25",
+ "gpui",
+ "language",
+ "log",
+ "lsp",
+ "node_runtime",
+ "serde",
+ "serde_derive",
+ "settings",
+ "smol",
+ "theme",
+ "util",
+ "workspace",
+]
+
+[[package]]
+name = "copilot_button"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "context_menu",
+ "copilot",
+ "editor",
+ "futures 0.3.25",
+ "gpui",
+ "settings",
+ "smol",
+ "theme",
+ "util",
+ "workspace",
+]
+
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -1737,6 +1786,7 @@ dependencies = [
"log",
"parking_lot 0.11.2",
"serde",
+ "serde_derive",
"smol",
"sqlez",
"sqlez_macros",
@@ -1924,6 +1974,7 @@ dependencies = [
"clock",
"collections",
"context_menu",
+ "copilot",
"ctor",
"db",
"drag_and_drop",
@@ -1945,6 +1996,7 @@ dependencies = [
"rand 0.8.5",
"rpc",
"serde",
+ "serde_derive",
"settings",
"smallvec",
"smol",
@@ -2098,6 +2150,7 @@ dependencies = [
"project",
"search",
"serde",
+ "serde_derive",
"settings",
"sysinfo",
"theme",
@@ -2294,6 +2347,7 @@ dependencies = [
"regex",
"rope",
"serde",
+ "serde_derive",
"serde_json",
"smol",
"tempfile",
@@ -2580,9 +2634,9 @@ dependencies = [
[[package]]
name = "glob"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
@@ -2662,8 +2716,10 @@ dependencies = [
"postage",
"rand 0.8.5",
"resvg",
+ "schemars",
"seahash",
"serde",
+ "serde_derive",
"serde_json",
"simplelog",
"smallvec",
@@ -3017,6 +3073,17 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3"
+[[package]]
+name = "install_cli"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "gpui",
+ "log",
+ "smol",
+ "util",
+]
+
[[package]]
name = "instant"
version = "0.1.12"
@@ -3156,6 +3223,7 @@ dependencies = [
name = "journal"
version = "0.1.0"
dependencies = [
+ "anyhow",
"chrono",
"dirs 4.0.0",
"editor",
@@ -3261,6 +3329,7 @@ dependencies = [
"regex",
"rpc",
"serde",
+ "serde_derive",
"serde_json",
"settings",
"similar",
@@ -3284,6 +3353,22 @@ dependencies = [
"util",
]
+[[package]]
+name = "language_selector"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "editor",
+ "fuzzy",
+ "gpui",
+ "language",
+ "picker",
+ "project",
+ "settings",
+ "theme",
+ "workspace",
+]
+
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -3440,6 +3525,7 @@ dependencies = [
"parking_lot 0.11.2",
"postage",
"serde",
+ "serde_derive",
"serde_json",
"sha2 0.10.6",
"simplelog",
@@ -3460,6 +3546,7 @@ dependencies = [
"prost-types 0.8.0",
"reqwest",
"serde",
+ "serde_derive",
"sha2 0.10.6",
]
@@ -3500,6 +3587,7 @@ dependencies = [
"parking_lot 0.11.2",
"postage",
"serde",
+ "serde_derive",
"serde_json",
"smol",
"unindent",
@@ -3865,6 +3953,23 @@ dependencies = [
"memoffset 0.6.5",
]
+[[package]]
+name = "node_runtime"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-compression",
+ "async-tar",
+ "futures 0.3.25",
+ "gpui",
+ "parking_lot 0.11.2",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "smol",
+ "util",
+]
+
[[package]]
name = "nom"
version = "7.1.1"
@@ -4419,6 +4524,7 @@ dependencies = [
"bincode",
"plugin_macros",
"serde",
+ "serde_derive",
]
[[package]]
@@ -4429,6 +4535,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
+ "serde_derive",
"syn",
]
@@ -4440,6 +4547,7 @@ dependencies = [
"bincode",
"pollster",
"serde",
+ "serde_derive",
"serde_json",
"smol",
"wasi-common",
@@ -4577,12 +4685,15 @@ dependencies = [
"client",
"clock",
"collections",
+ "ctor",
"db",
+ "env_logger",
"fs",
"fsevent",
"futures 0.3.25",
"fuzzy",
"git",
+ "glob",
"gpui",
"ignore",
"language",
@@ -4591,11 +4702,13 @@ dependencies = [
"lsp",
"parking_lot 0.11.2",
"postage",
+ "pretty_assertions",
"pulldown-cmark",
"rand 0.8.5",
"regex",
"rpc",
"serde",
+ "serde_derive",
"serde_json",
"settings",
"sha2 0.10.6",
@@ -4961,6 +5074,7 @@ dependencies = [
"settings",
"smol",
"text",
+ "util",
"workspace",
]
@@ -5218,6 +5332,7 @@ dependencies = [
"rand 0.8.5",
"rsa",
"serde",
+ "serde_derive",
"smol",
"smol-timeout",
"tempdir",
@@ -5641,6 +5756,7 @@ dependencies = [
"postage",
"project",
"serde",
+ "serde_derive",
"serde_json",
"settings",
"smallvec",
@@ -5827,8 +5943,10 @@ dependencies = [
"gpui",
"json_comments",
"postage",
+ "pretty_assertions",
"schemars",
"serde",
+ "serde_derive",
"serde_json",
"serde_path_to_error",
"sqlez",
@@ -6449,6 +6567,7 @@ dependencies = [
"procinfo",
"rand 0.8.5",
"serde",
+ "serde_derive",
"settings",
"shellexpand",
"smallvec",
@@ -6480,6 +6599,7 @@ dependencies = [
"project",
"rand 0.8.5",
"serde",
+ "serde_derive",
"settings",
"shellexpand",
"smallvec",
@@ -6539,6 +6659,7 @@ dependencies = [
"indexmap",
"parking_lot 0.11.2",
"serde",
+ "serde_derive",
"serde_json",
"serde_path_to_error",
"toml",
@@ -7441,11 +7562,15 @@ dependencies = [
"dirs 3.0.2",
"futures 0.3.25",
"git2",
+ "isahc",
"lazy_static",
"log",
"rand 0.8.5",
+ "serde",
"serde_json",
+ "smol",
"tempdir",
+ "url",
]
[[package]]
@@ -7532,6 +7657,7 @@ dependencies = [
"project",
"search",
"serde",
+ "serde_derive",
"serde_json",
"settings",
"tokio",
@@ -8011,6 +8137,26 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
+[[package]]
+name = "welcome"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "db",
+ "editor",
+ "fuzzy",
+ "gpui",
+ "install_cli",
+ "log",
+ "picker",
+ "project",
+ "settings",
+ "theme",
+ "theme_selector",
+ "util",
+ "workspace",
+]
+
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
@@ -8286,6 +8432,7 @@ dependencies = [
"futures 0.3.25",
"gpui",
"indoc",
+ "install_cli",
"language",
"lazy_static",
"log",
@@ -8294,9 +8441,11 @@ dependencies = [
"postage",
"project",
"serde",
+ "serde_derive",
"serde_json",
"settings",
"smallvec",
+ "terminal",
"theme",
"util",
"uuid 1.2.2",
@@ -8356,7 +8505,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zed"
-version = "0.75.0"
+version = "0.81.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -8377,7 +8526,10 @@ dependencies = [
"collections",
"command_palette",
"context_menu",
+ "copilot",
+ "copilot_button",
"ctor",
+ "db",
"diagnostics",
"easy-parallel",
"editor",
@@ -8393,13 +8545,16 @@ dependencies = [
"ignore",
"image",
"indexmap",
+ "install_cli",
"isahc",
"journal",
"language",
+ "language_selector",
"lazy_static",
"libc",
"log",
"lsp",
+ "node_runtime",
"num_cpus",
"outline",
"parking_lot 0.11.2",
@@ -8416,6 +8571,7 @@ dependencies = [
"rust-embed",
"search",
"serde",
+ "serde_derive",
"serde_json",
"serde_path_to_error",
"settings",
@@ -8457,6 +8613,7 @@ dependencies = [
"util",
"uuid 1.2.2",
"vim",
+ "welcome",
"workspace",
]
diff --git a/Cargo.toml b/Cargo.toml
index c74a76ccce..8fad52c8f4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,8 @@ members = [
"crates/collections",
"crates/command_palette",
"crates/context_menu",
+ "crates/copilot",
+ "crates/copilot_button",
"crates/db",
"crates/diagnostics",
"crates/drag_and_drop",
@@ -26,13 +28,16 @@ members = [
"crates/go_to_line",
"crates/gpui",
"crates/gpui_macros",
+ "crates/install_cli",
"crates/journal",
"crates/language",
+ "crates/language_selector",
"crates/live_kit_client",
"crates/live_kit_server",
"crates/lsp",
"crates/media",
"crates/menu",
+ "crates/node_runtime",
"crates/outline",
"crates/picker",
"crates/plugin",
@@ -58,6 +63,7 @@ members = [
"crates/util",
"crates/vim",
"crates/workspace",
+ "crates/welcome",
"crates/zed",
]
default-members = ["crates/zed"]
@@ -65,8 +71,10 @@ resolver = "2"
[workspace.dependencies]
serde = { version = "1.0", features = ["derive", "rc"] }
+serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
rand = { version = "0.8" }
+postage = { version = "0.4.1", features = ["futures-traits"] }
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" }
diff --git a/README.md b/README.md
index b9c12abea2..d23744aac0 100644
--- a/README.md
+++ b/README.md
@@ -23,10 +23,18 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
git clone https://github.com/zed-industries/zed.dev
```
-* Set up a local `zed` database and seed it with some initial users:
+* Initialize submodules
```
- script/bootstrap
+ git submodule update --init --recursive
+ ```
+
+* Set up a local `zed` database and seed it with some initial users:
+
+ Create a personal GitHub token to run `script/bootstrap` once successfully. Then delete that token.
+
+ ```
+ GITHUB_TOKEN=<$token> script/bootstrap
```
### Testing against locally-running servers
diff --git a/assets/icons/copilot_16.svg b/assets/icons/copilot_16.svg
new file mode 100644
index 0000000000..e14b61ce8b
--- /dev/null
+++ b/assets/icons/copilot_16.svg
@@ -0,0 +1,12 @@
+
diff --git a/assets/icons/copilot_disabled_16.svg b/assets/icons/copilot_disabled_16.svg
new file mode 100644
index 0000000000..eba36a2b69
--- /dev/null
+++ b/assets/icons/copilot_disabled_16.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/icons/copilot_error_16.svg b/assets/icons/copilot_error_16.svg
new file mode 100644
index 0000000000..6069c554f1
--- /dev/null
+++ b/assets/icons/copilot_error_16.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/icons/copilot_init_16.svg b/assets/icons/copilot_init_16.svg
new file mode 100644
index 0000000000..6cbf63fb49
--- /dev/null
+++ b/assets/icons/copilot_init_16.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/ellipsis_14.svg b/assets/icons/ellipsis_14.svg
new file mode 100644
index 0000000000..5d45af2b6f
--- /dev/null
+++ b/assets/icons/ellipsis_14.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/feedback_16.svg b/assets/icons/feedback_16.svg
new file mode 100644
index 0000000000..b85a40b353
--- /dev/null
+++ b/assets/icons/feedback_16.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/github-copilot-dummy.svg b/assets/icons/github-copilot-dummy.svg
new file mode 100644
index 0000000000..4a7ded3976
--- /dev/null
+++ b/assets/icons/github-copilot-dummy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/leave_12.svg b/assets/icons/leave_12.svg
new file mode 100644
index 0000000000..84491384b8
--- /dev/null
+++ b/assets/icons/leave_12.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/link_out_12.svg b/assets/icons/link_out_12.svg
new file mode 100644
index 0000000000..561f012452
--- /dev/null
+++ b/assets/icons/link_out_12.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/logo_96.svg b/assets/icons/logo_96.svg
new file mode 100644
index 0000000000..dc98bb8bc2
--- /dev/null
+++ b/assets/icons/logo_96.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/speech_bubble_12.svg b/assets/icons/speech_bubble_12.svg
new file mode 100644
index 0000000000..f5f330056a
--- /dev/null
+++ b/assets/icons/speech_bubble_12.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/user_plus_12.svg b/assets/icons/user_plus_12.svg
index 61f61e3929..535d04af45 100644
--- a/assets/icons/user_plus_12.svg
+++ b/assets/icons/user_plus_12.svg
@@ -1,3 +1,5 @@
diff --git a/assets/icons/user_plus_16.svg b/assets/icons/user_plus_16.svg
index 3fd6e13554..150392f6e0 100644
--- a/assets/icons/user_plus_16.svg
+++ b/assets/icons/user_plus_16.svg
@@ -1,3 +1,5 @@
diff --git a/assets/icons/zed_plus_copilot_32.svg b/assets/icons/zed_plus_copilot_32.svg
new file mode 100644
index 0000000000..d024678c50
--- /dev/null
+++ b/assets/icons/zed_plus_copilot_32.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json
new file mode 100644
index 0000000000..f73b5e0e22
--- /dev/null
+++ b/assets/keymaps/atom.json
@@ -0,0 +1,68 @@
+[
+ {
+ "bindings": {
+ "cmd-k cmd-p": "workspace::ActivatePreviousPane",
+ "cmd-k cmd-n": "workspace::ActivateNextPane"
+ }
+ },
+ {
+ "context": "Editor",
+ "bindings": {
+ "cmd-b": "editor::GoToDefinition",
+ "cmd-<": "editor::ScrollCursorCenter",
+ "cmd-g": [
+ "editor::SelectNext",
+ {
+ "replace_newest": true
+ }
+ ],
+ "ctrl-shift-down": "editor::AddSelectionBelow",
+ "ctrl-shift-up": "editor::AddSelectionAbove",
+ "cmd-shift-backspace": "editor::DeleteToBeginningOfLine"
+ }
+ },
+ {
+ "context": "Editor && mode == full",
+ "bindings": {
+ "cmd-r": "outline::Toggle"
+ }
+ },
+ {
+ "context": "BufferSearchBar",
+ "bindings": {
+ "cmd-f3": "search::SelectNextMatch",
+ "cmd-shift-f3": "search::SelectPrevMatch"
+ }
+ },
+ {
+ "context": "Workspace",
+ "bindings": {
+ "cmd-\\": "workspace::ToggleLeftSidebar",
+ "cmd-k cmd-b": "workspace::ToggleLeftSidebar",
+ "cmd-t": "file_finder::Toggle",
+ "cmd-shift-r": "project_symbols::Toggle"
+ }
+ },
+ {
+ "context": "Pane",
+ "bindings": {
+ "alt-cmd-/": "search::ToggleRegex",
+ "ctrl-0": "project_panel::ToggleFocus"
+ }
+ },
+ {
+ "context": "ProjectPanel",
+ "bindings": {
+ "ctrl-[": "project_panel::CollapseSelectedEntry",
+ "ctrl-b": "project_panel::CollapseSelectedEntry",
+ "alt-b": "project_panel::CollapseSelectedEntry",
+ "ctrl-]": "project_panel::ExpandSelectedEntry",
+ "ctrl-f": "project_panel::ExpandSelectedEntry",
+ "ctrl-shift-c": "project_panel::CopyPath"
+ }
+ },
+ {
+ "context": "Dock",
+ "bindings": {}
+ }
+]
diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json
index e8f055cb7d..1a8350bb53 100644
--- a/assets/keymaps/default.json
+++ b/assets/keymaps/default.json
@@ -176,7 +176,10 @@
{
"focus": false
}
- ]
+ ],
+ "alt-]": "copilot::NextSuggestion",
+ "alt-[": "copilot::PreviousSuggestion",
+ "alt-\\": "copilot::Toggle"
}
},
{
@@ -228,6 +231,7 @@
"replace_newest": true
}
],
+ "cmd-k cmd-i": "editor::Hover",
"cmd-/": [
"editor::ToggleComments",
{
@@ -248,7 +252,8 @@
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"ctrl-space": "editor::ShowCompletions",
- "cmd-.": "editor::ToggleCodeActions"
+ "cmd-.": "editor::ToggleCodeActions",
+ "alt-cmd-r": "editor::RevealInFinder"
}
},
{
@@ -352,7 +357,8 @@
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",
"cmd-shift-e": "project_panel::ToggleFocus",
- "cmd-alt-s": "workspace::SaveAll"
+ "cmd-alt-s": "workspace::SaveAll",
+ "cmd-k m": "language_selector::Toggle"
}
},
// Bindings from Sublime Text
@@ -418,7 +424,7 @@
{
"bindings": {
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
- "cmd-shift-c": "collab::ToggleCollaborationMenu",
+ "cmd-shift-c": "collab::ToggleContactsMenu",
"cmd-alt-i": "zed::DebugElements"
}
},
@@ -456,7 +462,7 @@
}
},
{
- "context": "Dock",
+ "context": "Pane && docked",
"bindings": {
"shift-escape": "dock::HideDock",
"cmd-escape": "dock::RemoveTabFromDock"
@@ -472,7 +478,8 @@
"cmd-v": "project_panel::Paste",
"cmd-alt-c": "project_panel::CopyPath",
"f2": "project_panel::Rename",
- "backspace": "project_panel::Delete"
+ "backspace": "project_panel::Delete",
+ "alt-cmd-r": "project_panel::RevealInFinder"
}
},
{
@@ -536,4 +543,4 @@
]
}
}
-]
\ No newline at end of file
+]
diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json
new file mode 100644
index 0000000000..921efdc929
--- /dev/null
+++ b/assets/keymaps/jetbrains.json
@@ -0,0 +1,78 @@
+[
+ {
+ "bindings": {
+ "cmd-shift-[": "pane::ActivatePrevItem",
+ "cmd-shift-]": "pane::ActivateNextItem"
+ }
+ },
+ {
+ "context": "Editor",
+ "bindings": {
+ "ctrl->": "zed::IncreaseBufferFontSize",
+ "ctrl-<": "zed::DecreaseBufferFontSize",
+ "cmd-d": "editor::DuplicateLine",
+ "cmd-pagedown": "editor::MovePageDown",
+ "cmd-pageup": "editor::MovePageUp",
+ "ctrl-alt-shift-b": "editor::SelectToPreviousWordStart",
+ "shift-enter": "editor::NewlineBelow",
+ "cmd--": "editor::Fold",
+ "cmd-=": "editor::UnfoldLines",
+ "alt-shift-g": "editor::SplitSelectionIntoLines",
+ "ctrl-g": [
+ "editor::SelectNext",
+ {
+ "replace_newest": false
+ }
+ ],
+ "cmd-/": [
+ "editor::ToggleComments",
+ {
+ "advance_downwards": true
+ }
+ ],
+ "shift-alt-up": "editor::MoveLineUp",
+ "shift-alt-down": "editor::MoveLineDown",
+ "cmd-[": "pane::GoBack",
+ "cmd-]": "pane::GoForward",
+ "alt-f7": "editor::FindAllReferences",
+ "cmd-alt-f7": "editor::FindAllReferences",
+ "cmd-b": "editor::GoToDefinition",
+ "cmd-alt-b": "editor::GoToDefinition",
+ "cmd-shift-b": "editor::GoToTypeDefinition",
+ "alt-enter": "editor::ToggleCodeActions",
+ "f2": "editor::GoToDiagnostic",
+ "cmd-f2": "editor::GoToPrevDiagnostic",
+ "ctrl-alt-shift-down": "editor::GoToHunk",
+ "ctrl-alt-shift-up": "editor::GoToPrevHunk",
+ "cmd-home": "editor::MoveToBeginning",
+ "cmd-end": "editor::MoveToEnd",
+ "cmd-shift-home": "editor::SelectToBeginning",
+ "cmd-shift-end": "editor::SelectToEnd"
+ }
+ },
+ {
+ "context": "Editor && mode == full",
+ "bindings": {
+ "cmd-f12": "outline::Toggle",
+ "cmd-7": "outline::Toggle",
+ "cmd-shift-o": "file_finder::Toggle",
+ "cmd-l": "go_to_line::Toggle"
+ }
+ },
+ {
+ "context": "Workspace",
+ "bindings": {
+ "cmd-shift-a": "command_palette::Toggle",
+ "cmd-alt-o": "project_symbols::Toggle",
+ "cmd-1": "workspace::ToggleLeftSidebar",
+ "cmd-6": "diagnostics::Deploy",
+ "alt-f12": "dock::FocusDock"
+ }
+ },
+ {
+ "context": "Dock",
+ "bindings": {
+ "alt-f12": "dock::HideDock"
+ }
+ }
+]
diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json
new file mode 100644
index 0000000000..89cfa4b262
--- /dev/null
+++ b/assets/keymaps/sublime_text.json
@@ -0,0 +1,60 @@
+[
+ {
+ "bindings": {
+ "cmd-shift-[": "pane::ActivatePrevItem",
+ "cmd-shift-]": "pane::ActivateNextItem",
+ "ctrl-pagedown": "pane::ActivatePrevItem",
+ "ctrl-pageup": "pane::ActivateNextItem",
+ "ctrl-shift-tab": "pane::ActivateNextItem",
+ "ctrl-tab": "pane::ActivatePrevItem",
+ "cmd-+": "zed::IncreaseBufferFontSize"
+ }
+ },
+ {
+ "context": "Editor",
+ "bindings": {
+ "ctrl-shift-up": "editor::AddSelectionAbove",
+ "ctrl-shift-down": "editor::AddSelectionBelow",
+ "cmd-shift-space": "editor::SelectAll",
+ "ctrl-shift-m": "editor::SelectLargerSyntaxNode",
+ "cmd-shift-a": "editor::SelectLargerSyntaxNode",
+ "shift-f12": "editor::FindAllReferences",
+ "alt-cmd-down": "editor::GoToDefinition",
+ "alt-shift-cmd-down": "editor::FindAllReferences",
+ "ctrl-.": "editor::GoToHunk",
+ "ctrl-,": "editor::GoToPrevHunk",
+ "ctrl-backspace": "editor::DeleteToPreviousWordStart",
+ "ctrl-delete": "editor::DeleteToNextWordEnd"
+ }
+ },
+ {
+ "context": "Editor && mode == full",
+ "bindings": {
+ "cmd-r": "outline::Toggle"
+ }
+ },
+ {
+ "context": "Pane",
+ "bindings": {
+ "f4": "search::SelectNextMatch",
+ "shift-f4": "search::SelectPrevMatch"
+ }
+ },
+ {
+ "context": "Workspace",
+ "bindings": {
+ "ctrl-`": "dock::FocusDock",
+ "cmd-k cmd-b": "workspace::ToggleLeftSidebar",
+ "cmd-t": "file_finder::Toggle",
+ "shift-cmd-r": "project_symbols::Toggle",
+ // Currently busted: https://github.com/zed-industries/feedback/issues/898
+ "ctrl-0": "project_panel::ToggleFocus"
+ }
+ },
+ {
+ "context": "Dock",
+ "bindings": {
+ "ctrl-`": "dock::HideDock"
+ }
+ }
+]
diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json
new file mode 100644
index 0000000000..88648b89e3
--- /dev/null
+++ b/assets/keymaps/textmate.json
@@ -0,0 +1,90 @@
+[
+ {
+ "bindings": {
+ "cmd-shift-o": "projects::OpenRecent",
+ "cmd-alt-tab": "project_panel::ToggleFocus"
+ }
+ },
+ {
+ "context": "Editor",
+ "bindings": {
+ "cmd-l": "go_to_line::Toggle",
+ "ctrl-shift-d": "editor::DuplicateLine",
+ "cmd-b": "editor::GoToDefinition",
+ "cmd-j": "editor::ScrollCursorCenter",
+ "cmd-enter": "editor::NewlineBelow",
+ "cmd-shift-l": "editor::SelectLine",
+ "cmd-shift-t": "outline::Toggle",
+ "alt-backspace": "editor::DeleteToPreviousWordStart",
+ "alt-shift-backspace": "editor::DeleteToNextWordEnd",
+ "alt-delete": "editor::DeleteToNextWordEnd",
+ "alt-shift-delete": "editor::DeleteToNextWordEnd",
+ "ctrl-backspace": "editor::DeleteToPreviousSubwordStart",
+ "ctrl-delete": "editor::DeleteToNextSubwordEnd",
+ "alt-left": [
+ "editor::MoveToPreviousWordStart",
+ {
+ "stop_at_soft_wraps": true
+ }
+ ],
+ "alt-right": [
+ "editor::MoveToNextWordEnd",
+ {
+ "stop_at_soft_wraps": true
+ }
+ ],
+ "ctrl-left": "editor::MoveToPreviousSubwordStart",
+ "ctrl-right": "editor::MoveToNextSubwordEnd",
+ "cmd-shift-left": "editor::SelectToBeginningOfLine",
+ "cmd-shift-right": "editor::SelectToEndOfLine",
+ "alt-shift-left": [
+ "editor::SelectToBeginningOfLine",
+ {
+ "stop_at_soft_wraps": true
+ }
+ ],
+ "alt-shift-right": [
+ "editor::SelectToEndOfLine",
+ {
+ "stop_at_soft_wraps": true
+ }
+ ],
+ "ctrl-shift-left": "editor::SelectToPreviousSubwordStart",
+ "ctrl-shift-right": "editor::SelectToNextSubwordEnd"
+ }
+ },
+ {
+ "context": "Editor && mode == full",
+ "bindings": {}
+ },
+ {
+ "context": "BufferSearchBar",
+ "bindings": {
+ "ctrl-s": "search::SelectNextMatch",
+ "ctrl-shift-s": "search::SelectPrevMatch"
+ }
+ },
+ {
+ "context": "Workspace",
+ "bindings": {
+ "cmd-alt-ctrl-d": "workspace::ToggleLeftSidebar",
+ "cmd-t": "file_finder::Toggle",
+ "cmd-shift-t": "project_symbols::Toggle"
+ }
+ },
+ {
+ "context": "Pane",
+ "bindings": {
+ "alt-cmd-r": "search::ToggleRegex",
+ "ctrl-tab": "project_panel::ToggleFocus"
+ }
+ },
+ {
+ "context": "ProjectPanel",
+ "bindings": {}
+ },
+ {
+ "context": "Dock",
+ "bindings": {}
+ }
+]
diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json
index 824fb63c0f..332e3a7414 100644
--- a/assets/keymaps/vim.json
+++ b/assets/keymaps/vim.json
@@ -27,6 +27,7 @@
"h": "vim::Left",
"backspace": "vim::Backspace",
"j": "vim::Down",
+ "enter": "vim::NextLineStart",
"k": "vim::Up",
"l": "vim::Right",
"$": "vim::EndOfLine",
@@ -233,7 +234,8 @@
"escape": [
"vim::SwitchMode",
"Normal"
- ]
+ ],
+ "d": "editor::GoToDefinition"
}
},
{
diff --git a/assets/settings/default.json b/assets/settings/default.json
index f6fb61d65c..fbb52e00dc 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -3,11 +3,21 @@
"theme": "One Dark",
// The name of a font to use for rendering text in the editor
"buffer_font_family": "Zed Mono",
+ // The OpenType features to enable for text in the editor.
+ "buffer_font_features": {
+ // Disable ligatures:
+ // "calt": false
+ },
// The default font size for text in the editor
"buffer_font_size": 15,
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
+ // Enable / disable copilot integration.
+ "enable_copilot_integration": true,
+ // Controls whether copilot provides suggestion immediately
+ // or waits for a `copilot::Toggle`
+ "copilot": "on",
// Whether to enable vim modes and key bindings
"vim_mode": false,
// Whether to show the informational hover box when moving the mouse
@@ -20,13 +30,8 @@
// Whether to pop the completions menu while typing in an editor without
// explicitly requesting it.
"show_completions_on_input": true,
- // Whether the screen sharing icon is showed in the os status bar.
+ // Whether the screen sharing icon is shown in the os status bar.
"show_call_status_icon": true,
- // Whether new projects should start out 'online'. Online projects
- // appear in the contacts panel under your name, so that your contacts
- // can see which projects you are working on. Regardless of this
- // setting, projects keep their last online status when you reopen them.
- "projects_online_by_default": true,
// Whether to use language servers to provide code intelligence.
"enable_language_server": true,
// When to automatically save edited buffers. This setting can
@@ -50,7 +55,13 @@
// "default_dock_anchor": "right"
// 3. Position the dock full screen over the entire workspace"
// "default_dock_anchor": "expanded"
- "default_dock_anchor": "right",
+ "default_dock_anchor": "bottom",
+ // Whether or not to remove any trailing whitespace from lines of a buffer
+ // before saving it.
+ "remove_trailing_whitespace_on_save": true,
+ // Whether or not to ensure there's a single newline at the end of a buffer
+ // when saving it.
+ "ensure_final_newline_on_save": true,
// Whether or not to perform a buffer format before saving
"format_on_save": "on",
// How to perform a buffer format. This setting can take two values:
@@ -83,7 +94,7 @@
"hard_tabs": false,
// How many columns a tab should occupy.
"tab_size": 4,
- // Control what info Zed sends to our servers
+ // Control what info is collected by Zed.
"telemetry": {
// Send debug info like crash reports.
"diagnostics": true,
@@ -114,7 +125,7 @@
// Settings specific to the terminal
"terminal": {
// What shell to use when opening a terminal. May take 3 values:
- // 1. Use the system's default terminal configuration (e.g. $TERM).
+ // 1. Use the system's default terminal configuration in /etc/passwd
// "shell": "system"
// 2. A program:
// "shell": {
@@ -194,13 +205,9 @@
// Different settings for specific languages.
"languages": {
"Plain Text": {
- "soft_wrap": "preferred_line_length"
- },
- "C": {
- "tab_size": 2
- },
- "C++": {
- "tab_size": 2
+ "soft_wrap": "preferred_line_length",
+ // Copilot can be a little strange on non-code files
+ "copilot": "off"
},
"Elixir": {
"tab_size": 2
@@ -210,10 +217,9 @@
"hard_tabs": true
},
"Markdown": {
- "soft_wrap": "preferred_line_length"
- },
- "Rust": {
- "tab_size": 4
+ "soft_wrap": "preferred_line_length",
+ // Copilot can be a little strange on non-code files
+ "copilot": "off"
},
"JavaScript": {
"tab_size": 2
@@ -226,6 +232,9 @@
},
"YAML": {
"tab_size": 2
+ },
+ "JSON": {
+ "copilot": "off"
}
},
// LSP Specific settings.
diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs
index f3a6f7328a..2041bbc793 100644
--- a/crates/activity_indicator/src/activity_indicator.rs
+++ b/crates/activity_indicator/src/activity_indicator.rs
@@ -33,6 +33,19 @@ struct LspStatus {
status: LanguageServerBinaryStatus,
}
+struct PendingWork<'a> {
+ language_server_name: &'a str,
+ progress_token: &'a str,
+ progress: &'a LanguageServerProgress,
+}
+
+#[derive(Default)]
+struct Content {
+ icon: Option<&'static str>,
+ message: String,
+ action: Option>,
+}
+
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ActivityIndicator::show_error_message);
cx.add_action(ActivityIndicator::dismiss_error_message);
@@ -69,6 +82,8 @@ impl ActivityIndicator {
if let Some(auto_updater) = auto_updater.as_ref() {
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
}
+ cx.observe_active_labeled_tasks(|_, cx| cx.notify())
+ .detach();
Self {
statuses: Default::default(),
@@ -130,7 +145,7 @@ impl ActivityIndicator {
fn pending_language_server_work<'a>(
&self,
cx: &'a AppContext,
- ) -> impl Iterator- {
+ ) -> impl Iterator
- > {
self.project
.read(cx)
.language_server_statuses()
@@ -142,23 +157,29 @@ impl ActivityIndicator {
let mut pending_work = status
.pending_work
.iter()
- .map(|(token, progress)| (status.name.as_str(), token.as_str(), progress))
+ .map(|(token, progress)| PendingWork {
+ language_server_name: status.name.as_str(),
+ progress_token: token.as_str(),
+ progress,
+ })
.collect::>();
- pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at));
+ pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at));
Some(pending_work)
}
})
.flatten()
}
- fn content_to_render(
- &mut self,
- cx: &mut RenderContext,
- ) -> (Option<&'static str>, String, Option>) {
+ fn content_to_render(&mut self, cx: &mut RenderContext) -> Content {
// Show any language server has pending activity.
let mut pending_work = self.pending_language_server_work(cx);
- if let Some((lang_server_name, progress_token, progress)) = pending_work.next() {
- let mut message = lang_server_name.to_string();
+ if let Some(PendingWork {
+ language_server_name,
+ progress_token,
+ progress,
+ }) = pending_work.next()
+ {
+ let mut message = language_server_name.to_string();
message.push_str(": ");
if let Some(progress_message) = progress.message.as_ref() {
@@ -176,7 +197,11 @@ impl ActivityIndicator {
write!(&mut message, " + {} more", additional_work_count).unwrap();
}
- return (None, message, None);
+ return Content {
+ icon: None,
+ message,
+ action: None,
+ };
}
// Show any language server installation info.
@@ -199,19 +224,19 @@ impl ActivityIndicator {
}
if !downloading.is_empty() {
- return (
- Some(DOWNLOAD_ICON),
- format!(
+ return Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: format!(
"Downloading {} language server{}...",
downloading.join(", "),
if downloading.len() > 1 { "s" } else { "" }
),
- None,
- );
+ action: None,
+ };
} else if !checking_for_update.is_empty() {
- return (
- Some(DOWNLOAD_ICON),
- format!(
+ return Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: format!(
"Checking for updates to {} language server{}...",
checking_for_update.join(", "),
if checking_for_update.len() > 1 {
@@ -220,53 +245,61 @@ impl ActivityIndicator {
""
}
),
- None,
- );
+ action: None,
+ };
} else if !failed.is_empty() {
- return (
- Some(WARNING_ICON),
- format!(
+ return Content {
+ icon: Some(WARNING_ICON),
+ message: format!(
"Failed to download {} language server{}. Click to show error.",
failed.join(", "),
if failed.len() > 1 { "s" } else { "" }
),
- Some(Box::new(ShowErrorMessage)),
- );
+ action: Some(Box::new(ShowErrorMessage)),
+ };
}
// Show any application auto-update info.
if let Some(updater) = &self.auto_updater {
- match &updater.read(cx).status() {
- AutoUpdateStatus::Checking => (
- Some(DOWNLOAD_ICON),
- "Checking for Zed updates…".to_string(),
- None,
- ),
- AutoUpdateStatus::Downloading => (
- Some(DOWNLOAD_ICON),
- "Downloading Zed update…".to_string(),
- None,
- ),
- AutoUpdateStatus::Installing => (
- Some(DOWNLOAD_ICON),
- "Installing Zed update…".to_string(),
- None,
- ),
- AutoUpdateStatus::Updated => (
- None,
- "Click to restart and update Zed".to_string(),
- Some(Box::new(workspace::Restart)),
- ),
- AutoUpdateStatus::Errored => (
- Some(WARNING_ICON),
- "Auto update failed".to_string(),
- Some(Box::new(DismissErrorMessage)),
- ),
+ return match &updater.read(cx).status() {
+ AutoUpdateStatus::Checking => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Checking for Zed updates…".to_string(),
+ action: None,
+ },
+ AutoUpdateStatus::Downloading => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Downloading Zed update…".to_string(),
+ action: None,
+ },
+ AutoUpdateStatus::Installing => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Installing Zed update…".to_string(),
+ action: None,
+ },
+ AutoUpdateStatus::Updated => Content {
+ icon: None,
+ message: "Click to restart and update Zed".to_string(),
+ action: Some(Box::new(workspace::Restart)),
+ },
+ AutoUpdateStatus::Errored => Content {
+ icon: Some(WARNING_ICON),
+ message: "Auto update failed".to_string(),
+ action: Some(Box::new(DismissErrorMessage)),
+ },
AutoUpdateStatus::Idle => Default::default(),
- }
- } else {
- Default::default()
+ };
}
+
+ if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() {
+ return Content {
+ icon: None,
+ message: most_recent_active_task.to_string(),
+ action: None,
+ };
+ }
+
+ Default::default()
}
}
@@ -280,7 +313,11 @@ impl View for ActivityIndicator {
}
fn render(&mut self, cx: &mut RenderContext) -> ElementBox {
- let (icon, message, action) = self.content_to_render(cx);
+ let Content {
+ icon,
+ message,
+ action,
+ } = self.content_to_render(cx);
let mut element = MouseEventHandler::::new(0, cx, |state, cx| {
let theme = &cx
diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml
index 5f672e759f..6b11f5ddbc 100644
--- a/crates/auto_update/Cargo.toml
+++ b/crates/auto_update/Cargo.toml
@@ -22,7 +22,8 @@ anyhow = "1.0.38"
isahc = "1.7"
lazy_static = "1.4"
log = "0.4"
-serde = { version = "1.0", features = ["derive", "rc"] }
-serde_json = { version = "1.0", features = ["preserve_order"] }
+serde = { workspace = true }
+serde_derive = { workspace = true }
+serde_json = { workspace = true }
smol = "1.2.5"
tempdir = "0.3.7"
diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs
index 4272d7b1af..3ad3380d26 100644
--- a/crates/auto_update/src/auto_update.rs
+++ b/crates/auto_update/src/auto_update.rs
@@ -1,8 +1,7 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
-use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN};
-use client::{ZED_APP_PATH, ZED_APP_VERSION};
+use client::{ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
use db::kvp::KEY_VALUE_STORE;
use gpui::{
actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
@@ -14,6 +13,7 @@ use smol::{fs::File, io::AsyncReadExt, process::Command};
use std::{ffi::OsString, sync::Arc, time::Duration};
use update_notification::UpdateNotification;
use util::channel::ReleaseChannel;
+use util::http::HttpClient;
use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs
index e6b285b072..eaf958572a 100644
--- a/crates/auto_update/src/update_notification.rs
+++ b/crates/auto_update/src/update_notification.rs
@@ -78,7 +78,7 @@ impl View for UpdateNotification {
)
.with_child({
let style = theme.action_message.style_for(state, false);
- Text::new("View the release notes".to_string(), style.text.clone())
+ Text::new("View the release notes", style.text.clone())
.contained()
.with_style(style.container)
.boxed()
diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml
index 99476fdc0a..412a79a317 100644
--- a/crates/breadcrumbs/Cargo.toml
+++ b/crates/breadcrumbs/Cargo.toml
@@ -18,6 +18,7 @@ search = { path = "../search" }
settings = { path = "../settings" }
theme = { path = "../theme" }
workspace = { path = "../workspace" }
+outline = { path = "../outline" }
itertools = "0.10"
[dev-dependencies]
diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs
index 278b8f39e2..184dbe8468 100644
--- a/crates/breadcrumbs/src/breadcrumbs.rs
+++ b/crates/breadcrumbs/src/breadcrumbs.rs
@@ -1,5 +1,6 @@
use gpui::{
- elements::*, AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
+ elements::*, AppContext, Entity, MouseButton, RenderContext, Subscription, View, ViewContext,
+ ViewHandle,
};
use itertools::Itertools;
use search::ProjectSearchView;
@@ -14,6 +15,7 @@ pub enum Event {
}
pub struct Breadcrumbs {
+ pane_focused: bool,
active_item: Option>,
project_search: Option>,
subscription: Option,
@@ -22,6 +24,7 @@ pub struct Breadcrumbs {
impl Breadcrumbs {
pub fn new() -> Self {
Self {
+ pane_focused: false,
active_item: Default::default(),
subscription: Default::default(),
project_search: Default::default(),
@@ -39,24 +42,53 @@ impl View for Breadcrumbs {
}
fn render(&mut self, cx: &mut RenderContext) -> ElementBox {
+ let active_item = match &self.active_item {
+ Some(active_item) => active_item,
+ None => return Empty::new().boxed(),
+ };
+ let not_editor = active_item.downcast::().is_none();
+
let theme = cx.global::().theme.clone();
- if let Some(breadcrumbs) = self
- .active_item
- .as_ref()
- .and_then(|item| item.breadcrumbs(&theme, cx))
- {
- Flex::row()
- .with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
- Label::new(" 〉 ".to_string(), theme.breadcrumbs.text.clone()).boxed()
- }))
- .contained()
- .with_style(theme.breadcrumbs.container)
+ let style = &theme.workspace.breadcrumbs;
+
+ let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
+ Some(breadcrumbs) => breadcrumbs,
+ None => return Empty::new().boxed(),
+ };
+
+ let crumbs = Flex::row()
+ .with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
+ Label::new(" 〉 ", style.default.text.clone()).boxed()
+ }))
+ .constrained()
+ .with_height(theme.workspace.breadcrumb_height)
+ .contained();
+
+ if not_editor || !self.pane_focused {
+ return crumbs
+ .with_style(style.default.container)
.aligned()
.left()
- .boxed()
- } else {
- Empty::new().boxed()
+ .boxed();
}
+
+ MouseEventHandler::::new(0, cx, |state, _| {
+ let style = style.style_for(state, false);
+ crumbs.with_style(style.container).boxed()
+ })
+ .on_click(MouseButton::Left, |_, cx| {
+ cx.dispatch_action(outline::Toggle);
+ })
+ .with_tooltip::(
+ 0,
+ "Show symbol outline".to_owned(),
+ Some(Box::new(outline::Toggle)),
+ theme.tooltip.clone(),
+ cx,
+ )
+ .aligned()
+ .left()
+ .boxed()
}
}
@@ -103,4 +135,8 @@ impl ToolbarItemView for Breadcrumbs {
current_location
}
}
+
+ fn pane_focus_update(&mut self, pane_focused: bool, _: &mut gpui::MutableAppContext) {
+ self.pane_focused = pane_focused;
+ }
}
diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml
index 54546adb55..4e738c0651 100644
--- a/crates/call/Cargo.toml
+++ b/crates/call/Cargo.toml
@@ -34,7 +34,7 @@ util = { path = "../util" }
anyhow = "1.0.38"
async-broadcast = "0.4"
futures = "0.3"
-postage = { version = "0.4.1", features = ["futures-traits"] }
+postage = { workspace = true }
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs
index 64584e6140..f9edddf374 100644
--- a/crates/call/src/call.rs
+++ b/crates/call/src/call.rs
@@ -264,12 +264,13 @@ impl ActiveCall {
Ok(())
}
- pub fn hang_up(&mut self, cx: &mut ModelContext) -> Result<()> {
+ pub fn hang_up(&mut self, cx: &mut ModelContext) -> Task> {
+ cx.notify();
if let Some((room, _)) = self.room.take() {
- room.update(cx, |room, cx| room.leave(cx))?;
- cx.notify();
+ room.update(cx, |room, cx| room.leave(cx))
+ } else {
+ Task::ready(Ok(()))
}
- Ok(())
}
pub fn share_project(
@@ -284,6 +285,18 @@ impl ActiveCall {
}
}
+ pub fn unshare_project(
+ &mut self,
+ project: ModelHandle,
+ cx: &mut ModelContext,
+ ) -> Result<()> {
+ if let Some((room, _)) = self.room.as_ref() {
+ room.update(cx, |room, cx| room.unshare_project(project, cx))
+ } else {
+ Err(anyhow!("no active call"))
+ }
+ }
+
pub fn set_location(
&mut self,
project: Option<&ModelHandle>,
diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs
index 7527a69326..eeb8a6a5d8 100644
--- a/crates/call/src/room.rs
+++ b/crates/call/src/room.rs
@@ -17,10 +17,10 @@ use language::LanguageRegistry;
use live_kit_client::{LocalTrackPublication, LocalVideoTrack, RemoteVideoTrackUpdate};
use postage::stream::Stream;
use project::Project;
-use std::{mem, sync::Arc, time::Duration};
+use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration};
use util::{post_inc, ResultExt, TryFutureExt};
-pub const RECONNECT_TIMEOUT: Duration = client::RECEIVE_TIMEOUT;
+pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
@@ -55,6 +55,7 @@ pub struct Room {
leave_when_empty: bool,
client: Arc,
user_store: ModelHandle,
+ follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec>,
subscriptions: Vec,
pending_room_update: Option>,
maintain_connection: Option>>,
@@ -63,10 +64,27 @@ pub struct Room {
impl Entity for Room {
type Event = Event;
- fn release(&mut self, _: &mut MutableAppContext) {
+ fn release(&mut self, cx: &mut MutableAppContext) {
if self.status.is_online() {
- log::info!("room was released, sending leave message");
- let _ = self.client.send(proto::LeaveRoom {});
+ self.leave_internal(cx).detach_and_log_err(cx);
+ }
+ }
+
+ fn app_will_quit(
+ &mut self,
+ cx: &mut MutableAppContext,
+ ) -> Option>>> {
+ if self.status.is_online() {
+ let leave = self.leave_internal(cx);
+ Some(
+ cx.background()
+ .spawn(async move {
+ leave.await.log_err();
+ })
+ .boxed(),
+ )
+ } else {
+ None
}
}
}
@@ -148,6 +166,7 @@ impl Room {
pending_room_update: None,
client,
user_store,
+ follows_by_leader_id_project_id: Default::default(),
maintain_connection: Some(maintain_connection),
}
}
@@ -232,13 +251,17 @@ impl Room {
&& self.pending_call_count == 0
}
- pub(crate) fn leave(&mut self, cx: &mut ModelContext) -> Result<()> {
- if self.status.is_offline() {
- return Err(anyhow!("room is offline"));
- }
-
+ pub(crate) fn leave(&mut self, cx: &mut ModelContext) -> Task> {
cx.notify();
cx.emit(Event::Left);
+ self.leave_internal(cx)
+ }
+
+ fn leave_internal(&mut self, cx: &mut MutableAppContext) -> Task> {
+ if self.status.is_offline() {
+ return Task::ready(Err(anyhow!("room is offline")));
+ }
+
log::info!("leaving room");
for project in self.shared_projects.drain() {
@@ -252,6 +275,7 @@ impl Room {
if let Some(project) = project.upgrade(cx) {
project.update(cx, |project, cx| {
project.disconnected_from_host(cx);
+ project.close(cx);
});
}
}
@@ -264,8 +288,12 @@ impl Room {
self.live_kit.take();
self.pending_room_update.take();
self.maintain_connection.take();
- self.client.send(proto::LeaveRoom {})?;
- Ok(())
+
+ let leave_room = self.client.request(proto::LeaveRoom {});
+ cx.background().spawn(async move {
+ leave_room.await?;
+ anyhow::Ok(())
+ })
}
async fn maintain_connection(
@@ -275,14 +303,12 @@ impl Room {
) -> Result<()> {
let mut client_status = client.status();
loop {
- let is_connected = client_status
- .next()
- .await
- .map_or(false, |s| s.is_connected());
-
+ let _ = client_status.try_recv();
+ let is_connected = client_status.borrow().is_connected();
// Even if we're initially connected, any future change of the status means we momentarily disconnected.
if !is_connected || client_status.next().await.is_some() {
log::info!("detected client disconnection");
+
this.upgrade(&cx)
.ok_or_else(|| anyhow!("room was dropped"))?
.update(&mut cx, |this, cx| {
@@ -296,12 +322,7 @@ impl Room {
let client_reconnection = async {
let mut remaining_attempts = 3;
while remaining_attempts > 0 {
- log::info!(
- "waiting for client status change, remaining attempts {}",
- remaining_attempts
- );
- let Some(status) = client_status.next().await else { break };
- if status.is_connected() {
+ if client_status.borrow().is_connected() {
log::info!("client reconnected, attempting to rejoin room");
let Some(this) = this.upgrade(&cx) else { break };
@@ -315,7 +336,15 @@ impl Room {
} else {
remaining_attempts -= 1;
}
+ } else if client_status.borrow().is_signed_out() {
+ return false;
}
+
+ log::info!(
+ "waiting for client status change, remaining attempts {}",
+ remaining_attempts
+ );
+ client_status.next().await;
}
false
}
@@ -337,18 +366,20 @@ impl Room {
}
}
- // The client failed to re-establish a connection to the server
- // or an error occurred while trying to re-join the room. Either way
- // we leave the room and return an error.
- if let Some(this) = this.upgrade(&cx) {
- log::info!("reconnection failed, leaving room");
- let _ = this.update(&mut cx, |this, cx| this.leave(cx));
- }
- return Err(anyhow!(
- "can't reconnect to room: client failed to re-establish connection"
- ));
+ break;
}
}
+
+ // The client failed to re-establish a connection to the server
+ // or an error occurred while trying to re-join the room. Either way
+ // we leave the room and return an error.
+ if let Some(this) = this.upgrade(&cx) {
+ log::info!("reconnection failed, leaving room");
+ let _ = this.update(&mut cx, |this, cx| this.leave(cx));
+ }
+ Err(anyhow!(
+ "can't reconnect to room: client failed to re-establish connection"
+ ))
}
fn rejoin(&mut self, cx: &mut ModelContext) -> Task> {
@@ -457,6 +488,12 @@ impl Room {
self.participant_user_ids.contains(&user_id)
}
+ pub fn followers_for(&self, leader_id: PeerId, project_id: u64) -> &[PeerId] {
+ self.follows_by_leader_id_project_id
+ .get(&(leader_id, project_id))
+ .map_or(&[], |v| v.as_slice())
+ }
+
async fn handle_room_updated(
this: ModelHandle,
envelope: TypedEnvelope,
@@ -487,11 +524,13 @@ impl Room {
.iter()
.map(|p| p.user_id)
.collect::>();
+
let remote_participant_user_ids = room
.participants
.iter()
.map(|p| p.user_id)
.collect::>();
+
let (remote_participants, pending_participants) =
self.user_store.update(cx, move |user_store, cx| {
(
@@ -499,6 +538,7 @@ impl Room {
user_store.get_users(pending_participant_user_ids, cx),
)
});
+
self.pending_room_update = Some(cx.spawn(|this, mut cx| async move {
let (remote_participants, pending_participants) =
futures::join!(remote_participants, pending_participants);
@@ -587,7 +627,7 @@ impl Room {
if let Some(live_kit) = this.live_kit.as_ref() {
let tracks =
- live_kit.room.remote_video_tracks(&peer_id.to_string());
+ live_kit.room.remote_video_tracks(&user.id.to_string());
for track in tracks {
this.remote_video_track_updated(
RemoteVideoTrackUpdate::Subscribed(track),
@@ -620,6 +660,27 @@ impl Room {
}
}
+ this.follows_by_leader_id_project_id.clear();
+ for follower in room.followers {
+ let project_id = follower.project_id;
+ let (leader, follower) = match (follower.leader_id, follower.follower_id) {
+ (Some(leader), Some(follower)) => (leader, follower),
+
+ _ => {
+ log::error!("Follower message {follower:?} missing some state");
+ continue;
+ }
+ };
+
+ let list = this
+ .follows_by_leader_id_project_id
+ .entry((leader, project_id))
+ .or_insert(Vec::new());
+ if !list.contains(&follower) {
+ list.push(follower);
+ }
+ }
+
this.pending_room_update.take();
if this.should_leave() {
log::info!("room is empty, leaving");
@@ -723,10 +784,10 @@ impl Room {
this.update(&mut cx, |this, cx| {
this.pending_call_count -= 1;
if this.should_leave() {
- this.leave(cx)?;
+ this.leave(cx).detach_and_log_err(cx);
}
- result
- })?;
+ });
+ result?;
Ok(())
})
}
@@ -793,6 +854,20 @@ impl Room {
})
}
+ pub(crate) fn unshare_project(
+ &mut self,
+ project: ModelHandle,
+ cx: &mut ModelContext,
+ ) -> Result<()> {
+ let project_id = match project.read(cx).remote_id() {
+ Some(project_id) => project_id,
+ None => return Ok(()),
+ };
+
+ self.client.send(proto::UnshareProject { project_id })?;
+ project.update(cx, |this, cx| this.unshare(cx))
+ }
+
pub(crate) fn set_location(
&mut self,
project: Option<&ModelHandle>,
diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml
index f2bab22ea7..6b814941b8 100644
--- a/crates/cli/Cargo.toml
+++ b/crates/cli/Cargo.toml
@@ -17,7 +17,8 @@ anyhow = "1.0"
clap = { version = "3.1", features = ["derive"] }
dirs = "3.0"
ipc-channel = "0.16"
-serde = { version = "1.0", features = ["derive", "rc"] }
+serde = { workspace = true }
+serde_derive = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9"
diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml
index 347424d34e..c75adf5bfa 100644
--- a/crates/client/Cargo.toml
+++ b/crates/client/Cargo.toml
@@ -23,11 +23,10 @@ async-recursion = "0.3"
async-tungstenite = { version = "0.16", features = ["async-tls"] }
futures = "0.3"
image = "0.23"
-isahc = "1.7"
lazy_static = "1.4.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
parking_lot = "0.11.1"
-postage = { version = "0.4.1", features = ["futures-traits"] }
+postage = { workspace = true }
rand = "0.8.3"
smol = "1.2.5"
thiserror = "1.0.29"
@@ -35,7 +34,8 @@ time = { version = "0.3", features = ["serde", "serde-well-known"] }
tiny_http = "0.8"
uuid = { version = "1.1.2", features = ["v4"] }
url = "2.2"
-serde = { version = "*", features = ["derive"] }
+serde = { workspace = true }
+serde_derive = { workspace = true }
settings = { path = "../settings" }
tempfile = "3"
diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs
index eba58304d7..76004f14a4 100644
--- a/crates/client/src/client.rs
+++ b/crates/client/src/client.rs
@@ -1,7 +1,6 @@
#[cfg(any(test, feature = "test-support"))]
pub mod test;
-pub mod http;
pub mod telemetry;
pub mod user;
@@ -18,7 +17,6 @@ use gpui::{
AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AppVersion,
AsyncAppContext, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle,
};
-use http::HttpClient;
use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
@@ -41,6 +39,7 @@ use telemetry::Telemetry;
use thiserror::Error;
use url::Url;
use util::channel::ReleaseChannel;
+use util::http::HttpClient;
use util::{ResultExt, TryFutureExt};
pub use rpc::*;
@@ -66,12 +65,12 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
-actions!(client, [Authenticate]);
+actions!(client, [SignIn, SignOut]);
pub fn init(client: Arc, cx: &mut MutableAppContext) {
cx.add_global_action({
let client = client.clone();
- move |_: &Authenticate, cx| {
+ move |_: &SignIn, cx| {
let client = client.clone();
cx.spawn(
|cx| async move { client.authenticate_and_connect(true, &cx).log_err().await },
@@ -79,6 +78,16 @@ pub fn init(client: Arc, cx: &mut MutableAppContext) {
.detach();
}
});
+ cx.add_global_action({
+ let client = client.clone();
+ move |_: &SignOut, cx| {
+ let client = client.clone();
+ cx.spawn(|cx| async move {
+ client.disconnect(&cx);
+ })
+ .detach();
+ }
+ });
}
pub struct Client {
@@ -120,7 +129,7 @@ pub enum EstablishConnectionError {
#[error("{0}")]
Other(#[from] anyhow::Error),
#[error("{0}")]
- Http(#[from] http::Error),
+ Http(#[from] util::http::Error),
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
@@ -169,6 +178,10 @@ impl Status {
pub fn is_connected(&self) -> bool {
matches!(self, Self::Connected { .. })
}
+
+ pub fn is_signed_out(&self) -> bool {
+ matches!(self, Self::SignedOut | Self::UpgradeRequired)
+ }
}
struct ClientState {
@@ -280,7 +293,7 @@ impl PendingEntitySubscription {
state
.entities_by_type_and_remote_id
- .insert(id, WeakSubscriber::Model(model.downgrade().into()));
+ .insert(id, WeakSubscriber::Model(model.downgrade().into_any()));
drop(state);
for message in messages {
self.client.handle_message(message, cx);
@@ -447,7 +460,7 @@ impl Client {
self.state
.write()
.entities_by_type_and_remote_id
- .insert(id, WeakSubscriber::View(cx.weak_handle().into()));
+ .insert(id, WeakSubscriber::View(cx.weak_handle().into_any()));
Subscription::Entity {
client: Arc::downgrade(self),
id,
@@ -491,7 +504,7 @@ impl Client {
let mut state = self.state.write();
state
.models_by_message_type
- .insert(message_type_id, model.downgrade().into());
+ .insert(message_type_id, model.downgrade().into_any());
let prev_handler = state.message_handlers.insert(
message_type_id,
@@ -1152,11 +1165,9 @@ impl Client {
})
}
- pub fn disconnect(self: &Arc, cx: &AsyncAppContext) -> Result<()> {
- let conn_id = self.connection_id()?;
- self.peer.disconnect(conn_id);
+ pub fn disconnect(self: &Arc, cx: &AsyncAppContext) {
+ self.peer.teardown();
self.set_status(Status::SignedOut, cx);
- Ok(())
}
fn connection_id(&self) -> Result {
@@ -1384,10 +1395,11 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
#[cfg(test)]
mod tests {
use super::*;
- use crate::test::{FakeHttpClient, FakeServer};
+ use crate::test::FakeServer;
use gpui::{executor::Deterministic, TestAppContext};
use parking_lot::Mutex;
use std::future;
+ use util::http::FakeHttpClient;
#[gpui::test(iterations = 10)]
async fn test_reconnection(cx: &mut TestAppContext) {
diff --git a/crates/client/src/http.rs b/crates/client/src/http.rs
deleted file mode 100644
index 0757cebf3a..0000000000
--- a/crates/client/src/http.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-pub use anyhow::{anyhow, Result};
-use futures::future::BoxFuture;
-use isahc::{
- config::{Configurable, RedirectPolicy},
- AsyncBody,
-};
-pub use isahc::{
- http::{Method, Uri},
- Error,
-};
-use smol::future::FutureExt;
-use std::{sync::Arc, time::Duration};
-pub use url::Url;
-
-pub type Request = isahc::Request;
-pub type Response = isahc::Response;
-
-pub trait HttpClient: Send + Sync {
- fn send(&self, req: Request) -> BoxFuture>;
-
- fn get<'a>(
- &'a self,
- uri: &str,
- body: AsyncBody,
- follow_redirects: bool,
- ) -> BoxFuture<'a, Result> {
- let request = isahc::Request::builder()
- .redirect_policy(if follow_redirects {
- RedirectPolicy::Follow
- } else {
- RedirectPolicy::None
- })
- .method(Method::GET)
- .uri(uri)
- .body(body);
- match request {
- Ok(request) => self.send(request),
- Err(error) => async move { Err(error.into()) }.boxed(),
- }
- }
-}
-
-pub fn client() -> Arc {
- Arc::new(
- isahc::HttpClient::builder()
- .connect_timeout(Duration::from_secs(5))
- .low_speed_timeout(100, Duration::from_secs(5))
- .build()
- .unwrap(),
- )
-}
-
-impl HttpClient for isahc::HttpClient {
- fn send(&self, req: Request) -> BoxFuture> {
- Box::pin(async move { self.send_async(req).await })
- }
-}
diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs
index 748eb48f7e..7ee099dfab 100644
--- a/crates/client/src/telemetry.rs
+++ b/crates/client/src/telemetry.rs
@@ -1,11 +1,9 @@
-use crate::http::HttpClient;
use db::kvp::KEY_VALUE_STORE;
use gpui::{
executor::Background,
serde_json::{self, value::Map, Value},
AppContext, Task,
};
-use isahc::Request;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use serde::Serialize;
@@ -19,6 +17,7 @@ use std::{
time::{Duration, SystemTime, UNIX_EPOCH},
};
use tempfile::NamedTempFile;
+use util::http::HttpClient;
use util::{channel::ReleaseChannel, post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
@@ -220,11 +219,11 @@ impl Telemetry {
"App": true
}),
}])?;
- let request = Request::post(MIXPANEL_ENGAGE_URL)
- .header("Content-Type", "application/json")
- .body(json_bytes.into())?;
- this.http_client.send(request).await?;
- Ok(())
+
+ this.http_client
+ .post_json(MIXPANEL_ENGAGE_URL, json_bytes.into())
+ .await?;
+ anyhow::Ok(())
}
.log_err(),
)
@@ -316,11 +315,10 @@ impl Telemetry {
json_bytes.clear();
serde_json::to_writer(&mut json_bytes, &events)?;
- let request = Request::post(MIXPANEL_EVENTS_URL)
- .header("Content-Type", "application/json")
- .body(json_bytes.into())?;
- this.http_client.send(request).await?;
- Ok(())
+ this.http_client
+ .post_json(MIXPANEL_EVENTS_URL, json_bytes.into())
+ .await?;
+ anyhow::Ok(())
}
.log_err(),
)
diff --git a/crates/client/src/test.rs b/crates/client/src/test.rs
index db9e0d8c48..4c12a20566 100644
--- a/crates/client/src/test.rs
+++ b/crates/client/src/test.rs
@@ -1,16 +1,14 @@
-use crate::{
- http::{self, HttpClient, Request, Response},
- Client, Connection, Credentials, EstablishConnectionError, UserStore,
-};
+use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
use anyhow::{anyhow, Result};
-use futures::{future::BoxFuture, stream::BoxStream, Future, StreamExt};
+use futures::{stream::BoxStream, StreamExt};
use gpui::{executor, ModelHandle, TestAppContext};
use parking_lot::Mutex;
use rpc::{
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
ConnectionId, Peer, Receipt, TypedEnvelope,
};
-use std::{fmt, rc::Rc, sync::Arc};
+use std::{rc::Rc, sync::Arc};
+use util::http::FakeHttpClient;
pub struct FakeServer {
peer: Arc,
@@ -219,46 +217,3 @@ impl Drop for FakeServer {
self.disconnect();
}
}
-
-pub struct FakeHttpClient {
- handler: Box<
- dyn 'static
- + Send
- + Sync
- + Fn(Request) -> BoxFuture<'static, Result>,
- >,
-}
-
-impl FakeHttpClient {
- pub fn create(handler: F) -> Arc
- where
- Fut: 'static + Send + Future