Merge remote-tracking branch 'origin/main' into assistant-2

Antonio Scandurra created

# Conflicts:
#	crates/ui2/src/components/icon.rs

Change summary

.github/workflows/ci.yml                             | 272 +++---
.github/workflows/release_nightly.yml                | 160 +-
Cargo.lock                                           |   3 
crates/collections/src/collections.rs                |   4 
crates/command_palette/src/command_palette.rs        |   4 
crates/command_palette2/src/command_palette.rs       |   7 
crates/copilot/src/copilot.rs                        |  12 
crates/copilot2/Cargo.toml                           |   1 
crates/copilot2/src/copilot2.rs                      | 126 +-
crates/copilot2/src/sign_in.rs                       | 589 +++++--------
crates/diagnostics2/src/diagnostics.rs               |   2 
crates/editor2/src/display_map/block_map.rs          |   8 
crates/editor2/src/display_map/wrap_map.rs           |   2 
crates/editor2/src/editor.rs                         |  20 
crates/editor2/src/editor_tests.rs                   |   6 
crates/editor2/src/element.rs                        |  63 
crates/editor2/src/items.rs                          |   2 
crates/go_to_line2/src/go_to_line.rs                 |   2 
crates/gpui2/src/app.rs                              | 107 ++
crates/gpui2/src/app/test_context.rs                 |  24 
crates/gpui2/src/elements/div.rs                     |   6 
crates/gpui2/src/key_dispatch.rs                     |  53 
crates/gpui2/src/platform.rs                         |  19 
crates/gpui2/src/platform/app_menu.rs                |  77 +
crates/gpui2/src/platform/mac/platform.rs            | 444 +++++-----
crates/gpui2/src/platform/mac/window.rs              |   2 
crates/gpui2/src/platform/test/platform.rs           |  12 
crates/gpui2/src/window.rs                           | 200 ++-
crates/settings2/src/settings_file.rs                |  36 
crates/ui2/src/components/icon.rs                    |   3 
crates/ui2/src/components/label.rs                   | 185 ----
crates/ui2/src/components/label/highlighted_label.rs |  86 ++
crates/ui2/src/components/label/label.rs             |  48 +
crates/ui2/src/components/label/label_like.rs        | 102 ++
crates/ui2/src/components/stories/label.rs           |   4 
crates/ui2/src/prelude.rs                            |   2 
crates/vim/src/vim.rs                                |   6 
crates/zed/Cargo.toml                                |   2 
crates/zed2/src/app_menus.rs                         | 175 ++++
crates/zed2/src/main.rs                              |  10 
crates/zed2/src/zed2.rs                              | 105 +
41 files changed, 1,694 insertions(+), 1,297 deletions(-)

Detailed changes

.github/workflows/ci.yml πŸ”—

@@ -1,144 +1,144 @@
 name: CI
 
 on:
-  push:
-    branches:
-      - main
-      - "v[0-9]+.[0-9]+.x"
-    tags:
-      - "v*"
-  pull_request:
-    branches:
-      - "**"
+    push:
+        branches:
+            - main
+            - "v[0-9]+.[0-9]+.x"
+        tags:
+            - "v*"
+    pull_request:
+        branches:
+            - "**"
 
 env:
-  CARGO_TERM_COLOR: always
-  CARGO_INCREMENTAL: 0
-  RUST_BACKTRACE: 1
+    CARGO_TERM_COLOR: always
+    CARGO_INCREMENTAL: 0
+    RUST_BACKTRACE: 1
 
 jobs:
-  rustfmt:
-    name: Check formatting
-    runs-on:
-      - self-hosted
-      - test
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v3
-        with:
-          clean: false
-          submodules: "recursive"
-
-      - name: Set up default .cargo/config.toml
-        run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
-
-      - name: Run rustfmt
-        uses: ./.github/actions/check_formatting
-
-  tests:
-    name: Run tests
-    runs-on:
-      - self-hosted
-      - test
-    needs: rustfmt
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v3
-        with:
-          clean: false
-          submodules: "recursive"
-
-      - name: Run tests
-        uses: ./.github/actions/run_tests
-
-      - name: Build collab
-        run: cargo build -p collab
-
-      - name: Build other binaries
-        run: cargo build --workspace --bins --all-features
-
-  bundle:
-    name: Bundle app
-    runs-on:
-      - self-hosted
-      - bundle
-    if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
-    needs: tests
-    env:
-      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
-      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
-      APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
-      APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
-    steps:
-      - name: Install Rust
-        run: |
-          rustup set profile minimal
-          rustup update stable
-          rustup target add aarch64-apple-darwin
-          rustup target add x86_64-apple-darwin
-          rustup target add wasm32-wasi
-
-      - name: Install Node
-        uses: actions/setup-node@v3
-        with:
-          node-version: "18"
-
-      - name: Checkout repo
-        uses: actions/checkout@v3
-        with:
-          clean: false
-          submodules: "recursive"
-
-      - name: Limit target directory size
-        run: script/clear-target-dir-if-larger-than 100
-
-      - name: Determine version and release channel
-        if: ${{ startsWith(github.ref, 'refs/tags/v') }}
-        run: |
-          set -eu
-
-          version=$(script/get-crate-version zed)
-          channel=$(cat crates/zed/RELEASE_CHANNEL)
-          echo "Publishing version: ${version} on release channel ${channel}"
-          echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
-
-          expected_tag_name=""
-          case ${channel} in
-            stable)
-              expected_tag_name="v${version}";;
-            preview)
-              expected_tag_name="v${version}-pre";;
-            nightly)
-              expected_tag_name="v${version}-nightly";;
-            *)
-              echo "can't publish a release on channel ${channel}"
-              exit 1;;
-          esac
-          if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
-            echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
-            exit 1
-          fi
-
-      - name: Generate license file
-        run: script/generate-licenses
-
-      - name: Create app bundle
-        run: script/bundle
-
-      - name: Upload app bundle to workflow run if main branch or specific label
-        uses: actions/upload-artifact@v3
-        if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
-        with:
-          name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
-          path: target/release/Zed.dmg
-
-      - uses: softprops/action-gh-release@v1
-        name: Upload app bundle to release
-        if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
-        with:
-          draft: true
-          prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
-          files: target/release/Zed.dmg
-          body: ""
+    rustfmt:
+        name: Check formatting
+        runs-on:
+            - self-hosted
+            - test
+        steps:
+            - name: Checkout repo
+              uses: actions/checkout@v3
+              with:
+                  clean: false
+                  submodules: "recursive"
+
+            - name: Set up default .cargo/config.toml
+              run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
+
+            - name: Run rustfmt
+              uses: ./.github/actions/check_formatting
+
+    tests:
+        name: Run tests
+        runs-on:
+            - self-hosted
+            - test
+        needs: rustfmt
+        steps:
+            - name: Checkout repo
+              uses: actions/checkout@v3
+              with:
+                  clean: false
+                  submodules: "recursive"
+
+            - name: Run tests
+              uses: ./.github/actions/run_tests
+
+            - name: Build collab
+              run: cargo build -p collab
+
+            - name: Build other binaries
+              run: cargo build --workspace --bins --all-features
+
+    bundle:
+        name: Bundle app
+        runs-on:
+            - self-hosted
+            - bundle
+        if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
+        needs: tests
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+            MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+            MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+            APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
+            APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
+        steps:
+            - name: Install Rust
+              run: |
+                  rustup set profile minimal
+                  rustup update stable
+                  rustup target add aarch64-apple-darwin
+                  rustup target add x86_64-apple-darwin
+                  rustup target add wasm32-wasi
+
+            - name: Install Node
+              uses: actions/setup-node@v3
+              with:
+                  node-version: "18"
+
+            - name: Checkout repo
+              uses: actions/checkout@v3
+              with:
+                  clean: false
+                  submodules: "recursive"
+
+            - name: Limit target directory size
+              run: script/clear-target-dir-if-larger-than 100
+
+            - name: Determine version and release channel
+              if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+              run: |
+                  set -eu
+
+                  version=$(script/get-crate-version zed)
+                  channel=$(cat crates/zed/RELEASE_CHANNEL)
+                  echo "Publishing version: ${version} on release channel ${channel}"
+                  echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
+
+                  expected_tag_name=""
+                  case ${channel} in
+                    stable)
+                      expected_tag_name="v${version}";;
+                    preview)
+                      expected_tag_name="v${version}-pre";;
+                    nightly)
+                      expected_tag_name="v${version}-nightly";;
+                    *)
+                      echo "can't publish a release on channel ${channel}"
+                      exit 1;;
+                  esac
+                  if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
+                    echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
+                    exit 1
+                  fi
+
+            - name: Generate license file
+              run: script/generate-licenses
+
+            - name: Create app bundle
+              run: script/bundle
+
+            - name: Upload app bundle to workflow run if main branch or specific label
+              uses: actions/upload-artifact@v3
+              if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
+              with:
+                  name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
+                  path: target/release/Zed.dmg
+
+            - uses: softprops/action-gh-release@v1
+              name: Upload app bundle to release
+              if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
+              with:
+                  draft: true
+                  prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
+                  files: target/release/Zed.dmg
+                  body: ""
+              env:
+                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/release_nightly.yml πŸ”—

@@ -1,98 +1,98 @@
 name: Release Nightly
 
 on:
-  schedule:
-    # Fire every night at 1:00am
-    - cron: "0 1 * * *"
-  push:
-    tags:
-      - "nightly"
+    schedule:
+        # Fire every night at 1:00am
+        - cron: "0 1 * * *"
+    push:
+        tags:
+            - "nightly"
 
 env:
-  CARGO_TERM_COLOR: always
-  CARGO_INCREMENTAL: 0
-  RUST_BACKTRACE: 1
+    CARGO_TERM_COLOR: always
+    CARGO_INCREMENTAL: 0
+    RUST_BACKTRACE: 1
 
 jobs:
-  rustfmt:
-    name: Check formatting
-    runs-on:
-      - self-hosted
-      - test
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v3
-        with:
-          clean: false
-          submodules: "recursive"
+    rustfmt:
+        name: Check formatting
+        runs-on:
+            - self-hosted
+            - test
+        steps:
+            - name: Checkout repo
+              uses: actions/checkout@v3
+              with:
+                  clean: false
+                  submodules: "recursive"
 
-      - name: Run rustfmt
-        uses: ./.github/actions/check_formatting
+            - name: Run rustfmt
+              uses: ./.github/actions/check_formatting
 
-  tests:
-    name: Run tests
-    runs-on:
-      - self-hosted
-      - test
-    needs: rustfmt
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v3
-        with:
-          clean: false
-          submodules: "recursive"
+    tests:
+        name: Run tests
+        runs-on:
+            - self-hosted
+            - test
+        needs: rustfmt
+        steps:
+            - name: Checkout repo
+              uses: actions/checkout@v3
+              with:
+                  clean: false
+                  submodules: "recursive"
 
-      - name: Run tests
-        uses: ./.github/actions/run_tests
+            - name: Run tests
+              uses: ./.github/actions/run_tests
 
-  bundle:
-    name: Bundle app
-    runs-on:
-      - self-hosted
-      - bundle
-    needs: tests
-    env:
-      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
-      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
-      APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
-      APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
-      DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
-      DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
-    steps:
-      - name: Install Rust
-        run: |
-          rustup set profile minimal
-          rustup update stable
-          rustup target add aarch64-apple-darwin
-          rustup target add x86_64-apple-darwin
-          rustup target add wasm32-wasi
+    bundle:
+        name: Bundle app
+        runs-on:
+            - self-hosted
+            - bundle
+        needs: tests
+        env:
+            MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+            MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+            APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
+            APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
+            DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
+            DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
+        steps:
+            - name: Install Rust
+              run: |
+                  rustup set profile minimal
+                  rustup update stable
+                  rustup target add aarch64-apple-darwin
+                  rustup target add x86_64-apple-darwin
+                  rustup target add wasm32-wasi
 
-      - name: Install Node
-        uses: actions/setup-node@v3
-        with:
-          node-version: "18"
+            - name: Install Node
+              uses: actions/setup-node@v3
+              with:
+                  node-version: "18"
 
-      - name: Checkout repo
-        uses: actions/checkout@v3
-        with:
-          clean: false
-          submodules: "recursive"
+            - name: Checkout repo
+              uses: actions/checkout@v3
+              with:
+                  clean: false
+                  submodules: "recursive"
 
-      - name: Limit target directory size
-        run: script/clear-target-dir-if-larger-than 100
+            - name: Limit target directory size
+              run: script/clear-target-dir-if-larger-than 100
 
-      - name: Set release channel to nightly
-        run: |
-          set -eu
-          version=$(git rev-parse --short HEAD)
-          echo "Publishing version: ${version} on release channel nightly"
-          echo "nightly" > crates/zed/RELEASE_CHANNEL
+            - name: Set release channel to nightly
+              run: |
+                  set -eu
+                  version=$(git rev-parse --short HEAD)
+                  echo "Publishing version: ${version} on release channel nightly"
+                  echo "nightly" > crates/zed/RELEASE_CHANNEL
 
-      - name: Generate license file
-        run: script/generate-licenses
+            - name: Generate license file
+              run: script/generate-licenses
 
-      - name: Create app bundle
-        run: script/bundle -2
+            - name: Create app bundle
+              run: script/bundle -2
 
-      - name: Upload Zed Nightly
-        run: script/upload-nightly
+            - name: Upload Zed Nightly
+              run: script/upload-nightly

Cargo.lock πŸ”—

@@ -2159,6 +2159,7 @@ dependencies = [
  "settings2",
  "smol",
  "theme2",
+ "ui2",
  "util",
 ]
 
@@ -11746,7 +11747,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.116.0"
+version = "0.117.0"
 dependencies = [
  "activity_indicator",
  "ai",

crates/collections/src/collections.rs πŸ”—

@@ -23,11 +23,13 @@ pub type HashMap<K, V> = std::collections::HashMap<K, V>;
 #[cfg(not(feature = "test-support"))]
 pub type HashSet<T> = std::collections::HashSet<T>;
 
+use std::any::TypeId;
 pub use std::collections::*;
 
 // NEW TYPES
 
 #[derive(Default)]
 pub struct CommandPaletteFilter {
-    pub filtered_namespaces: HashSet<&'static str>,
+    pub hidden_namespaces: HashSet<&'static str>,
+    pub hidden_action_types: HashSet<TypeId>,
 }

crates/command_palette/src/command_palette.rs πŸ”—

@@ -109,7 +109,7 @@ impl PickerDelegate for CommandPaletteDelegate {
                     let filtered = cx.read(|cx| {
                         if cx.has_global::<CommandPaletteFilter>() {
                             let filter = cx.global::<CommandPaletteFilter>();
-                            filter.filtered_namespaces.contains(action.namespace())
+                            filter.hidden_namespaces.contains(action.namespace())
                         } else {
                             false
                         }
@@ -430,7 +430,7 @@ mod tests {
         // Add namespace filter, and redeploy the palette
         cx.update(|cx| {
             cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
-                filter.filtered_namespaces.insert("editor");
+                filter.hidden_namespaces.insert("editor");
             })
         });
 

crates/command_palette2/src/command_palette.rs πŸ”—

@@ -49,7 +49,10 @@ impl CommandPalette {
             .filter_map(|action| {
                 let name = gpui::remove_the_2(action.name());
                 let namespace = name.split("::").next().unwrap_or("malformed action name");
-                if filter.is_some_and(|f| f.filtered_namespaces.contains(namespace)) {
+                if filter.is_some_and(|f| {
+                    f.hidden_namespaces.contains(namespace)
+                        || f.hidden_action_types.contains(&action.type_id())
+                }) {
                     return None;
                 }
 
@@ -433,7 +436,7 @@ mod tests {
         cx.update(|cx| {
             cx.set_global(CommandPaletteFilter::default());
             cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
-                filter.filtered_namespaces.insert("editor");
+                filter.hidden_namespaces.insert("editor");
             })
         });
 

crates/copilot/src/copilot.rs πŸ”—

@@ -58,16 +58,16 @@ pub fn init(
         cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
             match status {
                 Status::Disabled => {
-                    filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
-                    filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
+                    filter.hidden_namespaces.insert(COPILOT_NAMESPACE);
+                    filter.hidden_namespaces.insert(COPILOT_AUTH_NAMESPACE);
                 }
                 Status::Authorized => {
-                    filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
-                    filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
+                    filter.hidden_namespaces.remove(COPILOT_NAMESPACE);
+                    filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE);
                 }
                 _ => {
-                    filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
-                    filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
+                    filter.hidden_namespaces.insert(COPILOT_NAMESPACE);
+                    filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE);
                 }
             }
         });

crates/copilot2/Cargo.toml πŸ”—

@@ -28,6 +28,7 @@ theme = { package = "theme2", path = "../theme2" }
 lsp = { package = "lsp2", path = "../lsp2" }
 node_runtime = { path = "../node_runtime"}
 util = { path = "../util" }
+ui = { package = "ui2", path = "../ui2" }
 async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
 async-tar = "0.4.2"
 anyhow.workspace = true

crates/copilot2/src/copilot2.rs πŸ”—

@@ -22,6 +22,7 @@ use request::StatusNotification;
 use settings::SettingsStore;
 use smol::{fs, io::BufReader, stream::StreamExt};
 use std::{
+    any::TypeId,
     ffi::OsString,
     mem,
     ops::Range,
@@ -32,13 +33,14 @@ use util::{
     fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
 };
 
-// todo!()
-// const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
-actions!(SignIn, SignOut);
-
-// todo!()
-// const COPILOT_NAMESPACE: &'static str = "copilot";
-actions!(Suggest, NextSuggestion, PreviousSuggestion, Reinstall);
+actions!(
+    Suggest,
+    NextSuggestion,
+    PreviousSuggestion,
+    Reinstall,
+    SignIn,
+    SignOut
+);
 
 pub fn init(
     new_server_id: LanguageServerId,
@@ -51,52 +53,70 @@ pub fn init(
         move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
     });
     cx.set_global(copilot.clone());
-
-    // TODO
-    // cx.observe(&copilot, |handle, cx| {
-    //     let status = handle.read(cx).status();
-    //     cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
-    //         match status {
-    //             Status::Disabled => {
-    //                 filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
-    //                 filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
-    //             }
-    //             Status::Authorized => {
-    //                 filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
-    //                 filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
-    //             }
-    //             _ => {
-    //                 filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
-    //                 filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
-    //             }
-    //         }
-    //     });
-    // })
-    // .detach();
-
-    // sign_in::init(cx);
-    // cx.add_global_action(|_: &SignIn, cx| {
-    //     if let Some(copilot) = Copilot::global(cx) {
-    //         copilot
-    //             .update(cx, |copilot, cx| copilot.sign_in(cx))
-    //             .detach_and_log_err(cx);
-    //     }
-    // });
-    // cx.add_global_action(|_: &SignOut, cx| {
-    //     if let Some(copilot) = Copilot::global(cx) {
-    //         copilot
-    //             .update(cx, |copilot, cx| copilot.sign_out(cx))
-    //             .detach_and_log_err(cx);
-    //     }
-    // });
-
-    // cx.add_global_action(|_: &Reinstall, cx| {
-    //     if let Some(copilot) = Copilot::global(cx) {
-    //         copilot
-    //             .update(cx, |copilot, cx| copilot.reinstall(cx))
-    //             .detach();
-    //     }
-    // });
+    cx.observe(&copilot, |handle, cx| {
+        let copilot_action_types = [
+            TypeId::of::<Suggest>(),
+            TypeId::of::<NextSuggestion>(),
+            TypeId::of::<PreviousSuggestion>(),
+            TypeId::of::<Reinstall>(),
+        ];
+        let copilot_auth_action_types = [TypeId::of::<SignOut>()];
+        let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
+        let status = handle.read(cx).status();
+        let filter = cx.default_global::<collections::CommandPaletteFilter>();
+
+        match status {
+            Status::Disabled => {
+                filter.hidden_action_types.extend(copilot_action_types);
+                filter.hidden_action_types.extend(copilot_auth_action_types);
+                filter
+                    .hidden_action_types
+                    .extend(copilot_no_auth_action_types);
+            }
+            Status::Authorized => {
+                filter
+                    .hidden_action_types
+                    .extend(copilot_no_auth_action_types);
+                for type_id in copilot_action_types
+                    .iter()
+                    .chain(&copilot_auth_action_types)
+                {
+                    filter.hidden_action_types.remove(type_id);
+                }
+            }
+            _ => {
+                filter.hidden_action_types.extend(copilot_action_types);
+                filter.hidden_action_types.extend(copilot_auth_action_types);
+                for type_id in &copilot_no_auth_action_types {
+                    filter.hidden_action_types.remove(type_id);
+                }
+            }
+        }
+    })
+    .detach();
+
+    sign_in::init(cx);
+    cx.on_action(|_: &SignIn, cx| {
+        if let Some(copilot) = Copilot::global(cx) {
+            copilot
+                .update(cx, |copilot, cx| copilot.sign_in(cx))
+                .detach_and_log_err(cx);
+        }
+    });
+    cx.on_action(|_: &SignOut, cx| {
+        if let Some(copilot) = Copilot::global(cx) {
+            copilot
+                .update(cx, |copilot, cx| copilot.sign_out(cx))
+                .detach_and_log_err(cx);
+        }
+    });
+    cx.on_action(|_: &Reinstall, cx| {
+        if let Some(copilot) = Copilot::global(cx) {
+            copilot
+                .update(cx, |copilot, cx| copilot.reinstall(cx))
+                .detach();
+        }
+    });
 }
 
 enum CopilotServer {

crates/copilot2/src/sign_in.rs πŸ”—

@@ -1,376 +1,213 @@
-// TODO add logging in
-// use crate::{request::PromptUserDeviceFlow, Copilot, Status};
-// use gpui::{
-//     elements::*,
-//     geometry::rect::RectF,
-//     platform::{WindowBounds, WindowKind, WindowOptions},
-//     AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
-//     WindowHandle,
-// };
-// use theme::ui::modal;
-
-// #[derive(PartialEq, Eq, Debug, Clone)]
-// struct CopyUserCode;
-
-// #[derive(PartialEq, Eq, Debug, Clone)]
-// struct OpenGithub;
-
-// const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
-
-// pub fn init(cx: &mut AppContext) {
-//     if let Some(copilot) = Copilot::global(cx) {
-//         let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
-//         cx.observe(&copilot, move |copilot, cx| {
-//             let status = copilot.read(cx).status();
-
-//             match &status {
-//                 crate::Status::SigningIn { prompt } => {
-//                     if let Some(window) = verification_window.as_mut() {
-//                         let updated = window
-//                             .root(cx)
-//                             .map(|root| {
-//                                 root.update(cx, |verification, cx| {
-//                                     verification.set_status(status.clone(), cx);
-//                                     cx.activate_window();
-//                                 })
-//                             })
-//                             .is_some();
-//                         if !updated {
-//                             verification_window = Some(create_copilot_auth_window(cx, &status));
-//                         }
-//                     } else if let Some(_prompt) = prompt {
-//                         verification_window = Some(create_copilot_auth_window(cx, &status));
-//                     }
-//                 }
-//                 Status::Authorized | Status::Unauthorized => {
-//                     if let Some(window) = verification_window.as_ref() {
-//                         if let Some(verification) = window.root(cx) {
-//                             verification.update(cx, |verification, cx| {
-//                                 verification.set_status(status, cx);
-//                                 cx.platform().activate(true);
-//                                 cx.activate_window();
-//                             });
-//                         }
-//                     }
-//                 }
-//                 _ => {
-//                     if let Some(code_verification) = verification_window.take() {
-//                         code_verification.update(cx, |cx| cx.remove_window());
-//                     }
-//                 }
-//             }
-//         })
-//         .detach();
-//     }
-// }
-
-// fn create_copilot_auth_window(
-//     cx: &mut AppContext,
-//     status: &Status,
-// ) -> WindowHandle<CopilotCodeVerification> {
-//     let window_size = theme::current(cx).copilot.modal.dimensions();
-//     let window_options = WindowOptions {
-//         bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
-//         titlebar: None,
-//         center: true,
-//         focus: true,
-//         show: true,
-//         kind: WindowKind::Normal,
-//         is_movable: true,
-//         screen: None,
-//     };
-//     cx.add_window(window_options, |_cx| {
-//         CopilotCodeVerification::new(status.clone())
-//     })
-// }
-
-// pub struct CopilotCodeVerification {
-//     status: Status,
-//     connect_clicked: bool,
-// }
-
-// impl CopilotCodeVerification {
-//     pub fn new(status: Status) -> Self {
-//         Self {
-//             status,
-//             connect_clicked: false,
-//         }
-//     }
-
-//     pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
-//         self.status = status;
-//         cx.notify();
-//     }
-
-//     fn render_device_code(
-//         data: &PromptUserDeviceFlow,
-//         style: &theme::Copilot,
-//         cx: &mut ViewContext<Self>,
-//     ) -> impl IntoAnyElement<Self> {
-//         let copied = cx
-//             .read_from_clipboard()
-//             .map(|item| item.text() == &data.user_code)
-//             .unwrap_or(false);
-
-//         let device_code_style = &style.auth.prompting.device_code;
-
-//         MouseEventHandler::new::<Self, _>(0, cx, |state, _cx| {
-//             Flex::row()
-//                 .with_child(
-//                     Label::new(data.user_code.clone(), device_code_style.text.clone())
-//                         .aligned()
-//                         .contained()
-//                         .with_style(device_code_style.left_container)
-//                         .constrained()
-//                         .with_width(device_code_style.left),
-//                 )
-//                 .with_child(
-//                     Label::new(
-//                         if copied { "Copied!" } else { "Copy" },
-//                         device_code_style.cta.style_for(state).text.clone(),
-//                     )
-//                     .aligned()
-//                     .contained()
-//                     .with_style(*device_code_style.right_container.style_for(state))
-//                     .constrained()
-//                     .with_width(device_code_style.right),
-//                 )
-//                 .contained()
-//                 .with_style(device_code_style.cta.style_for(state).container)
-//         })
-//         .on_click(gpui::platform::MouseButton::Left, {
-//             let user_code = data.user_code.clone();
-//             move |_, _, cx| {
-//                 cx.platform()
-//                     .write_to_clipboard(ClipboardItem::new(user_code.clone()));
-//                 cx.notify();
-//             }
-//         })
-//         .with_cursor_style(gpui::platform::CursorStyle::PointingHand)
-//     }
-
-//     fn render_prompting_modal(
-//         connect_clicked: bool,
-//         data: &PromptUserDeviceFlow,
-//         style: &theme::Copilot,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum ConnectButton {}
-
-//         Flex::column()
-//             .with_child(
-//                 Flex::column()
-//                     .with_children([
-//                         Label::new(
-//                             "Enable Copilot by connecting",
-//                             style.auth.prompting.subheading.text.clone(),
-//                         )
-//                         .aligned(),
-//                         Label::new(
-//                             "your existing license.",
-//                             style.auth.prompting.subheading.text.clone(),
-//                         )
-//                         .aligned(),
-//                     ])
-//                     .align_children_center()
-//                     .contained()
-//                     .with_style(style.auth.prompting.subheading.container),
-//             )
-//             .with_child(Self::render_device_code(data, &style, cx))
-//             .with_child(
-//                 Flex::column()
-//                     .with_children([
-//                         Label::new(
-//                             "Paste this code into GitHub after",
-//                             style.auth.prompting.hint.text.clone(),
-//                         )
-//                         .aligned(),
-//                         Label::new(
-//                             "clicking the button below.",
-//                             style.auth.prompting.hint.text.clone(),
-//                         )
-//                         .aligned(),
-//                     ])
-//                     .align_children_center()
-//                     .contained()
-//                     .with_style(style.auth.prompting.hint.container.clone()),
-//             )
-//             .with_child(theme::ui::cta_button::<ConnectButton, _, _, _>(
-//                 if connect_clicked {
-//                     "Waiting for connection..."
-//                 } else {
-//                     "Connect to GitHub"
-//                 },
-//                 style.auth.content_width,
-//                 &style.auth.cta_button,
-//                 cx,
-//                 {
-//                     let verification_uri = data.verification_uri.clone();
-//                     move |_, verification, cx| {
-//                         cx.platform().open_url(&verification_uri);
-//                         verification.connect_clicked = true;
-//                     }
-//                 },
-//             ))
-//             .align_children_center()
-//             .into_any()
-//     }
-
-//     fn render_enabled_modal(
-//         style: &theme::Copilot,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum DoneButton {}
-
-//         let enabled_style = &style.auth.authorized;
-//         Flex::column()
-//             .with_child(
-//                 Label::new("Copilot Enabled!", enabled_style.subheading.text.clone())
-//                     .contained()
-//                     .with_style(enabled_style.subheading.container)
-//                     .aligned(),
-//             )
-//             .with_child(
-//                 Flex::column()
-//                     .with_children([
-//                         Label::new(
-//                             "You can update your settings or",
-//                             enabled_style.hint.text.clone(),
-//                         )
-//                         .aligned(),
-//                         Label::new(
-//                             "sign out from the Copilot menu in",
-//                             enabled_style.hint.text.clone(),
-//                         )
-//                         .aligned(),
-//                         Label::new("the status bar.", enabled_style.hint.text.clone()).aligned(),
-//                     ])
-//                     .align_children_center()
-//                     .contained()
-//                     .with_style(enabled_style.hint.container),
-//             )
-//             .with_child(theme::ui::cta_button::<DoneButton, _, _, _>(
-//                 "Done",
-//                 style.auth.content_width,
-//                 &style.auth.cta_button,
-//                 cx,
-//                 |_, _, cx| cx.remove_window(),
-//             ))
-//             .align_children_center()
-//             .into_any()
-//     }
-
-//     fn render_unauthorized_modal(
-//         style: &theme::Copilot,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         let unauthorized_style = &style.auth.not_authorized;
-
-//         Flex::column()
-//             .with_child(
-//                 Flex::column()
-//                     .with_children([
-//                         Label::new(
-//                             "Enable Copilot by connecting",
-//                             unauthorized_style.subheading.text.clone(),
-//                         )
-//                         .aligned(),
-//                         Label::new(
-//                             "your existing license.",
-//                             unauthorized_style.subheading.text.clone(),
-//                         )
-//                         .aligned(),
-//                     ])
-//                     .align_children_center()
-//                     .contained()
-//                     .with_style(unauthorized_style.subheading.container),
-//             )
-//             .with_child(
-//                 Flex::column()
-//                     .with_children([
-//                         Label::new(
-//                             "You must have an active copilot",
-//                             unauthorized_style.warning.text.clone(),
-//                         )
-//                         .aligned(),
-//                         Label::new(
-//                             "license to use it in Zed.",
-//                             unauthorized_style.warning.text.clone(),
-//                         )
-//                         .aligned(),
-//                     ])
-//                     .align_children_center()
-//                     .contained()
-//                     .with_style(unauthorized_style.warning.container),
-//             )
-//             .with_child(theme::ui::cta_button::<Self, _, _, _>(
-//                 "Subscribe on GitHub",
-//                 style.auth.content_width,
-//                 &style.auth.cta_button,
-//                 cx,
-//                 |_, _, cx| {
-//                     cx.remove_window();
-//                     cx.platform().open_url(COPILOT_SIGN_UP_URL)
-//                 },
-//             ))
-//             .align_children_center()
-//             .into_any()
-//     }
-// }
-
-// impl Entity for CopilotCodeVerification {
-//     type Event = ();
-// }
-
-// impl View for CopilotCodeVerification {
-//     fn ui_name() -> &'static str {
-//         "CopilotCodeVerification"
-//     }
-
-//     fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-//         cx.notify()
-//     }
-
-//     fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-//         cx.notify()
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         enum ConnectModal {}
-
-//         let style = theme::current(cx).clone();
-
-//         modal::<ConnectModal, _, _, _, _>(
-//             "Connect Copilot to Zed",
-//             &style.copilot.modal,
-//             cx,
-//             |cx| {
-//                 Flex::column()
-//                     .with_children([
-//                         theme::ui::icon(&style.copilot.auth.header).into_any(),
-//                         match &self.status {
-//                             Status::SigningIn {
-//                                 prompt: Some(prompt),
-//                             } => Self::render_prompting_modal(
-//                                 self.connect_clicked,
-//                                 &prompt,
-//                                 &style.copilot,
-//                                 cx,
-//                             ),
-//                             Status::Unauthorized => {
-//                                 self.connect_clicked = false;
-//                                 Self::render_unauthorized_modal(&style.copilot, cx)
-//                             }
-//                             Status::Authorized => {
-//                                 self.connect_clicked = false;
-//                                 Self::render_enabled_modal(&style.copilot, cx)
-//                             }
-//                             _ => Empty::new().into_any(),
-//                         },
-//                     ])
-//                     .align_children_center()
-//             },
-//         )
-//         .into_any()
-//     }
-// }
+use crate::{request::PromptUserDeviceFlow, Copilot, Status};
+use gpui::{
+    div, size, AppContext, Bounds, ClipboardItem, Div, Element, GlobalPixels, InteractiveElement,
+    IntoElement, ParentElement, Point, Render, Stateful, Styled, ViewContext, VisualContext,
+    WindowBounds, WindowHandle, WindowKind, WindowOptions,
+};
+use theme::ActiveTheme;
+use ui::{prelude::*, Button, Icon, IconElement, Label};
+
+const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
+
+pub fn init(cx: &mut AppContext) {
+    if let Some(copilot) = Copilot::global(cx) {
+        let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
+        cx.observe(&copilot, move |copilot, cx| {
+            let status = copilot.read(cx).status();
+
+            match &status {
+                crate::Status::SigningIn { prompt } => {
+                    if let Some(window) = verification_window.as_mut() {
+                        let updated = window
+                            .update(cx, |verification, cx| {
+                                verification.set_status(status.clone(), cx);
+                                cx.activate_window();
+                            })
+                            .is_ok();
+                        if !updated {
+                            verification_window = Some(create_copilot_auth_window(cx, &status));
+                        }
+                    } else if let Some(_prompt) = prompt {
+                        verification_window = Some(create_copilot_auth_window(cx, &status));
+                    }
+                }
+                Status::Authorized | Status::Unauthorized => {
+                    if let Some(window) = verification_window.as_ref() {
+                        window
+                            .update(cx, |verification, cx| {
+                                verification.set_status(status, cx);
+                                cx.activate(true);
+                                cx.activate_window();
+                            })
+                            .ok();
+                    }
+                }
+                _ => {
+                    if let Some(code_verification) = verification_window.take() {
+                        code_verification
+                            .update(cx, |_, cx| cx.remove_window())
+                            .ok();
+                    }
+                }
+            }
+        })
+        .detach();
+    }
+}
+
+fn create_copilot_auth_window(
+    cx: &mut AppContext,
+    status: &Status,
+) -> WindowHandle<CopilotCodeVerification> {
+    let window_size = size(GlobalPixels::from(280.), GlobalPixels::from(280.));
+    let window_options = WindowOptions {
+        bounds: WindowBounds::Fixed(Bounds::new(Point::default(), window_size)),
+        titlebar: None,
+        center: true,
+        focus: true,
+        show: true,
+        kind: WindowKind::PopUp,
+        is_movable: true,
+        display_id: None,
+    };
+    let window = cx.open_window(window_options, |cx| {
+        cx.build_view(|_| CopilotCodeVerification::new(status.clone()))
+    });
+    window
+}
+
+pub struct CopilotCodeVerification {
+    status: Status,
+    connect_clicked: bool,
+}
+
+impl CopilotCodeVerification {
+    pub fn new(status: Status) -> Self {
+        Self {
+            status,
+            connect_clicked: false,
+        }
+    }
+
+    pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
+        self.status = status;
+        cx.notify();
+    }
+
+    fn render_device_code(
+        data: &PromptUserDeviceFlow,
+        cx: &mut ViewContext<Self>,
+    ) -> impl IntoElement {
+        let copied = cx
+            .read_from_clipboard()
+            .map(|item| item.text() == &data.user_code)
+            .unwrap_or(false);
+        h_stack()
+            .cursor_pointer()
+            .justify_between()
+            .on_mouse_down(gpui::MouseButton::Left, {
+                let user_code = data.user_code.clone();
+                move |_, cx| {
+                    cx.write_to_clipboard(ClipboardItem::new(user_code.clone()));
+                    cx.notify();
+                }
+            })
+            .child(Label::new(data.user_code.clone()))
+            .child(div())
+            .child(Label::new(if copied { "Copied!" } else { "Copy" }))
+    }
+
+    fn render_prompting_modal(
+        connect_clicked: bool,
+        data: &PromptUserDeviceFlow,
+        cx: &mut ViewContext<Self>,
+    ) -> impl Element {
+        let connect_button_label = if connect_clicked {
+            "Waiting for connection..."
+        } else {
+            "Connect to Github"
+        };
+        v_stack()
+            .flex_1()
+            .items_center()
+            .justify_between()
+            .w_full()
+            .child(Label::new(
+                "Enable Copilot by connecting your existing license",
+            ))
+            .child(Self::render_device_code(data, cx))
+            .child(
+                Label::new("Paste this code into GitHub after clicking the button below.")
+                    .size(ui::LabelSize::Small),
+            )
+            .child(
+                Button::new("connect-button", connect_button_label).on_click({
+                    let verification_uri = data.verification_uri.clone();
+                    cx.listener(move |this, _, cx| {
+                        cx.open_url(&verification_uri);
+                        this.connect_clicked = true;
+                    })
+                }),
+            )
+    }
+    fn render_enabled_modal() -> impl Element {
+        v_stack()
+            .child(Label::new("Copilot Enabled!"))
+            .child(Label::new(
+                "You can update your settings or sign out from the Copilot menu in the status bar.",
+            ))
+            .child(
+                Button::new("copilot-enabled-done-button", "Done")
+                    .on_click(|_, cx| cx.remove_window()),
+            )
+    }
+
+    fn render_unauthorized_modal() -> impl Element {
+        v_stack()
+            .child(Label::new(
+                "Enable Copilot by connecting your existing license.",
+            ))
+            .child(
+                Label::new("You must have an active Copilot license to use it in Zed.")
+                    .color(Color::Warning),
+            )
+            .child(
+                Button::new("copilot-subscribe-button", "Subscibe on Github").on_click(|_, cx| {
+                    cx.remove_window();
+                    cx.open_url(COPILOT_SIGN_UP_URL)
+                }),
+            )
+    }
+}
+
+impl Render for CopilotCodeVerification {
+    type Element = Stateful<Div>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let prompt = match &self.status {
+            Status::SigningIn {
+                prompt: Some(prompt),
+            } => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
+            Status::Unauthorized => {
+                self.connect_clicked = false;
+                Self::render_unauthorized_modal().into_any_element()
+            }
+            Status::Authorized => {
+                self.connect_clicked = false;
+                Self::render_enabled_modal().into_any_element()
+            }
+            _ => div().into_any_element(),
+        };
+        div()
+            .id("copilot code verification")
+            .flex()
+            .flex_col()
+            .size_full()
+            .items_center()
+            .p_10()
+            .bg(cx.theme().colors().element_background)
+            .child(ui::Label::new("Connect Copilot to Zed"))
+            .child(IconElement::new(Icon::ZedXCopilot))
+            .child(prompt)
+    }
+}

crates/diagnostics2/src/diagnostics.rs πŸ”—

@@ -36,7 +36,7 @@ use std::{
 };
 use theme::ActiveTheme;
 pub use toolbar_controls::ToolbarControls;
-use ui::{h_stack, Color, HighlightedLabel, Icon, IconElement, Label};
+use ui::{h_stack, prelude::*, HighlightedLabel, Icon, IconElement, Label};
 use util::TryFutureExt;
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},

crates/editor2/src/display_map/block_map.rs πŸ”—

@@ -993,7 +993,7 @@ mod tests {
     use super::*;
     use crate::display_map::inlay_map::InlayMap;
     use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
-    use gpui::{div, font, px, Element, Platform as _};
+    use gpui::{div, font, px, Element};
     use multi_buffer::MultiBuffer;
     use rand::prelude::*;
     use settings::SettingsStore;
@@ -1185,11 +1185,7 @@ mod tests {
     fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
         cx.update(|cx| init_test(cx));
 
-        let font_id = cx
-            .test_platform
-            .text_system()
-            .font_id(&font("Helvetica"))
-            .unwrap();
+        let font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
 
         let text = "one two three\nfour five six\nseven eight";
 

crates/editor2/src/display_map/wrap_map.rs πŸ”—

@@ -1032,7 +1032,7 @@ mod tests {
         display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
         MultiBuffer,
     };
-    use gpui::{font, px, test::observe, Platform};
+    use gpui::{font, px, test::observe};
     use rand::prelude::*;
     use settings::SettingsStore;
     use smol::stream::StreamExt;

crates/editor2/src/editor.rs πŸ”—

@@ -92,6 +92,7 @@ use std::{
     ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
     path::Path,
     sync::Arc,
+    sync::Weak,
     time::{Duration, Instant},
 };
 pub use sum_tree::Bias;
@@ -420,6 +421,25 @@ pub fn init(cx: &mut AppContext) {
         },
     )
     .detach();
+
+    cx.on_action(move |_: &workspace::NewFile, cx| {
+        let app_state = cx.global::<Weak<workspace::AppState>>();
+        if let Some(app_state) = app_state.upgrade() {
+            workspace::open_new(&app_state, cx, |workspace, cx| {
+                Editor::new_file(workspace, &Default::default(), cx)
+            })
+            .detach();
+        }
+    });
+    cx.on_action(move |_: &workspace::NewWindow, cx| {
+        let app_state = cx.global::<Weak<workspace::AppState>>();
+        if let Some(app_state) = app_state.upgrade() {
+            workspace::open_new(&app_state, cx, |workspace, cx| {
+                Editor::new_file(workspace, &Default::default(), cx)
+            })
+            .detach();
+        }
+    });
 }
 
 trait InvalidationRegion {

crates/editor2/src/editor_tests.rs πŸ”—

@@ -12,7 +12,7 @@ use futures::StreamExt;
 use gpui::{
     div,
     serde_json::{self, json},
-    Div, Flatten, Platform, TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
+    Div, Flatten, TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
 };
 use indoc::indoc;
 use language::{
@@ -3238,9 +3238,7 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
         the lazy dog"});
     cx.update_editor(|e, cx| e.copy(&Copy, cx));
     assert_eq!(
-        cx.test_platform
-            .read_from_clipboard()
-            .map(|item| item.text().to_owned()),
+        cx.read_from_clipboard().map(|item| item.text().to_owned()),
         Some("fox jumps over\n".to_owned())
     );
 

crates/editor2/src/element.rs πŸ”—

@@ -2803,35 +2803,46 @@ impl Element for EditorElement {
 
         let focus_handle = editor.focus_handle(cx);
         let dispatch_context = self.editor.read(cx).dispatch_context(cx);
-        cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
-            self.register_actions(cx);
-            self.register_key_listeners(cx);
-
-            // We call with_z_index to establish a new stacking context.
-            cx.with_z_index(0, |cx| {
-                cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
-                    // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
-                    // take precedence.
-                    cx.with_z_index(0, |cx| {
-                        self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
-                    });
-                    let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
-                    cx.handle_input(&focus_handle, input_handler);
+        cx.with_key_dispatch(
+            Some(dispatch_context),
+            Some(focus_handle.clone()),
+            |_, cx| {
+                self.register_actions(cx);
+                self.register_key_listeners(cx);
+
+                // We call with_z_index to establish a new stacking context.
+                cx.with_z_index(0, |cx| {
+                    cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
+                        // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
+                        // take precedence.
+                        cx.with_z_index(0, |cx| {
+                            self.paint_mouse_listeners(
+                                bounds,
+                                gutter_bounds,
+                                text_bounds,
+                                &layout,
+                                cx,
+                            );
+                        });
+                        let input_handler =
+                            ElementInputHandler::new(bounds, self.editor.clone(), cx);
+                        cx.handle_input(&focus_handle, input_handler);
 
-                    self.paint_background(gutter_bounds, text_bounds, &layout, cx);
-                    if layout.gutter_size.width > Pixels::ZERO {
-                        self.paint_gutter(gutter_bounds, &mut layout, cx);
-                    }
-                    self.paint_text(text_bounds, &mut layout, cx);
+                        self.paint_background(gutter_bounds, text_bounds, &layout, cx);
+                        if layout.gutter_size.width > Pixels::ZERO {
+                            self.paint_gutter(gutter_bounds, &mut layout, cx);
+                        }
+                        self.paint_text(text_bounds, &mut layout, cx);
 
-                    if !layout.blocks.is_empty() {
-                        cx.with_element_id(Some("editor_blocks"), |cx| {
-                            self.paint_blocks(bounds, &mut layout, cx);
-                        })
-                    }
+                        if !layout.blocks.is_empty() {
+                            cx.with_element_id(Some("editor_blocks"), |cx| {
+                                self.paint_blocks(bounds, &mut layout, cx);
+                            })
+                        }
+                    });
                 });
-            });
-        })
+            },
+        )
     }
 }
 

crates/editor2/src/items.rs πŸ”—

@@ -32,7 +32,7 @@ use std::{
 };
 use text::Selection;
 use theme::{ActiveTheme, Theme};
-use ui::{h_stack, Color, Label};
+use ui::{h_stack, prelude::*, Label};
 use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
 use workspace::{
     item::{BreadcrumbText, FollowEvent, FollowableItemHandle},

crates/go_to_line2/src/go_to_line.rs πŸ”—

@@ -6,7 +6,7 @@ use gpui::{
 };
 use text::{Bias, Point};
 use theme::ActiveTheme;
-use ui::{h_stack, v_stack, Color, Label, StyledExt};
+use ui::{h_stack, prelude::*, v_stack, Label};
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
 
 actions!(Toggle);

crates/gpui2/src/app.rs πŸ”—

@@ -15,10 +15,10 @@ use smol::future::FutureExt;
 pub use test_context::*;
 
 use crate::{
-    current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView,
-    AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
+    current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
+    AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
     DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
-    ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform,
+    ForegroundExecutor, KeyBinding, Keymap, LayoutId, Menu, PathPromptOptions, Pixels, Platform,
     PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
     TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
     WindowHandle, WindowId,
@@ -39,7 +39,10 @@ use std::{
     sync::{atomic::Ordering::SeqCst, Arc},
     time::Duration,
 };
-use util::http::{self, HttpClient};
+use util::{
+    http::{self, HttpClient},
+    ResultExt,
+};
 
 /// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
 /// Strongly consider removing after stabilization.
@@ -201,7 +204,7 @@ pub struct AppContext {
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     pub(crate) keymap: Arc<Mutex<Keymap>>,
     pub(crate) global_action_listeners:
-        HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
+        HashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
     pending_effects: VecDeque<Effect>,
     pub(crate) pending_notifications: HashSet<EntityId>,
     pub(crate) pending_global_notifications: HashSet<TypeId>,
@@ -275,6 +278,8 @@ impl AppContext {
             }),
         });
 
+        init_app_menus(platform.as_ref(), &mut *app.borrow_mut());
+
         platform.on_quit(Box::new({
             let cx = app.clone();
             move || {
@@ -425,6 +430,10 @@ impl AppContext {
             .collect()
     }
 
+    pub fn active_window(&self) -> Option<AnyWindowHandle> {
+        self.platform.active_window()
+    }
+
     /// Opens a new window with the given option and the root view returned by the given function.
     /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
     /// functionality.
@@ -962,9 +971,9 @@ impl AppContext {
         self.global_action_listeners
             .entry(TypeId::of::<A>())
             .or_default()
-            .push(Box::new(move |action, phase, cx| {
+            .push(Rc::new(move |action, phase, cx| {
                 if phase == DispatchPhase::Bubble {
-                    let action = action.as_any().downcast_ref().unwrap();
+                    let action = action.downcast_ref().unwrap();
                     listener(action, cx)
                 }
             }));
@@ -1015,6 +1024,90 @@ impl AppContext {
         activate();
         subscription
     }
+
+    pub(crate) fn clear_pending_keystrokes(&mut self) {
+        for window in self.windows() {
+            window
+                .update(self, |_, cx| {
+                    cx.window
+                        .current_frame
+                        .dispatch_tree
+                        .clear_pending_keystrokes()
+                })
+                .ok();
+        }
+    }
+
+    pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
+        if let Some(window) = self.active_window() {
+            if let Ok(window_action_available) =
+                window.update(self, |_, cx| cx.is_action_available(action))
+            {
+                return window_action_available;
+            }
+        }
+
+        self.global_action_listeners
+            .contains_key(&action.as_any().type_id())
+    }
+
+    pub fn set_menus(&mut self, menus: Vec<Menu>) {
+        self.platform.set_menus(menus, &self.keymap.lock());
+    }
+
+    pub fn dispatch_action(&mut self, action: &dyn Action) {
+        if let Some(active_window) = self.active_window() {
+            active_window
+                .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
+                .log_err();
+        } else {
+            self.propagate_event = true;
+
+            if let Some(mut global_listeners) = self
+                .global_action_listeners
+                .remove(&action.as_any().type_id())
+            {
+                for listener in &global_listeners {
+                    listener(action.as_any(), DispatchPhase::Capture, self);
+                    if !self.propagate_event {
+                        break;
+                    }
+                }
+
+                global_listeners.extend(
+                    self.global_action_listeners
+                        .remove(&action.as_any().type_id())
+                        .unwrap_or_default(),
+                );
+
+                self.global_action_listeners
+                    .insert(action.as_any().type_id(), global_listeners);
+            }
+
+            if self.propagate_event {
+                if let Some(mut global_listeners) = self
+                    .global_action_listeners
+                    .remove(&action.as_any().type_id())
+                {
+                    for listener in global_listeners.iter().rev() {
+                        listener(action.as_any(), DispatchPhase::Bubble, self);
+                        if !self.propagate_event {
+                            break;
+                        }
+                    }
+
+                    global_listeners.extend(
+                        self.global_action_listeners
+                            .remove(&action.as_any().type_id())
+                            .unwrap_or_default(),
+                    );
+
+                    self.global_action_listeners
+                        .insert(action.as_any().type_id(), global_listeners);
+                }
+            }
+        }
+    }
 }
 
 impl Context for AppContext {

crates/gpui2/src/app/test_context.rs πŸ”—

@@ -1,9 +1,10 @@
 use crate::{
     div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
-    BackgroundExecutor, Bounds, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
-    KeyDownEvent, Keystroke, Model, ModelContext, Pixels, PlatformWindow, Point, Render, Result,
-    Size, Task, TestDispatcher, TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext,
-    VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions,
+    BackgroundExecutor, Bounds, ClipboardItem, Context, Div, Entity, EventEmitter,
+    ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform,
+    PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
+    TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext,
+    WindowHandle, WindowOptions,
 };
 use anyhow::{anyhow, bail};
 use futures::{Stream, StreamExt};
@@ -16,6 +17,7 @@ pub struct TestAppContext {
     pub foreground_executor: ForegroundExecutor,
     pub dispatcher: TestDispatcher,
     pub test_platform: Rc<TestPlatform>,
+    text_system: Arc<TextSystem>,
 }
 
 impl Context for TestAppContext {
@@ -82,6 +84,7 @@ impl TestAppContext {
         let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
         let asset_source = Arc::new(());
         let http_client = util::http::FakeHttpClient::with_404_response();
+        let text_system = Arc::new(TextSystem::new(platform.text_system()));
 
         Self {
             app: AppContext::new(platform.clone(), asset_source, http_client),
@@ -89,6 +92,7 @@ impl TestAppContext {
             foreground_executor,
             dispatcher: dispatcher.clone(),
             test_platform: platform,
+            text_system,
         }
     }
 
@@ -155,6 +159,18 @@ impl TestAppContext {
         (view, Box::leak(cx))
     }
 
+    pub fn text_system(&self) -> &Arc<TextSystem> {
+        &self.text_system
+    }
+
+    pub fn write_to_clipboard(&self, item: ClipboardItem) {
+        self.test_platform.write_to_clipboard(item)
+    }
+
+    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+        self.test_platform.read_from_clipboard()
+    }
+
     pub fn simulate_new_path_selection(
         &self,
         select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,

crates/gpui2/src/elements/div.rs πŸ”—

@@ -55,7 +55,7 @@ pub trait InteractiveElement: Sized + Element {
         E: Debug,
     {
         if let Some(key_context) = key_context.try_into().log_err() {
-            self.interactivity().key_context = key_context;
+            self.interactivity().key_context = Some(key_context);
         }
         self
     }
@@ -722,7 +722,7 @@ impl DivState {
 
 pub struct Interactivity {
     pub element_id: Option<ElementId>,
-    pub key_context: KeyContext,
+    pub key_context: Option<KeyContext>,
     pub focusable: bool,
     pub tracked_focus_handle: Option<FocusHandle>,
     pub scroll_handle: Option<ScrollHandle>,
@@ -1238,7 +1238,7 @@ impl Default for Interactivity {
     fn default() -> Self {
         Self {
             element_id: None,
-            key_context: KeyContext::default(),
+            key_context: None,
             focusable: false,
             tracked_focus_handle: None,
             scroll_handle: None,

crates/gpui2/src/key_dispatch.rs πŸ”—

@@ -28,7 +28,7 @@ pub(crate) struct DispatchTree {
 pub(crate) struct DispatchNode {
     pub key_listeners: SmallVec<[KeyListener; 2]>,
     pub action_listeners: SmallVec<[DispatchActionListener; 16]>,
-    pub context: KeyContext,
+    pub context: Option<KeyContext>,
     parent: Option<DispatchNodeId>,
 }
 
@@ -61,7 +61,7 @@ impl DispatchTree {
         self.keystroke_matchers.clear();
     }
 
-    pub fn push_node(&mut self, context: KeyContext) {
+    pub fn push_node(&mut self, context: Option<KeyContext>) {
         let parent = self.node_stack.last().copied();
         let node_id = DispatchNodeId(self.nodes.len());
         self.nodes.push(DispatchNode {
@@ -69,34 +69,34 @@ impl DispatchTree {
             ..Default::default()
         });
         self.node_stack.push(node_id);
-        if !context.is_empty() {
-            self.active_node().context = context.clone();
+        if let Some(context) = context {
+            self.active_node().context = Some(context.clone());
             self.context_stack.push(context);
         }
     }
 
     pub fn pop_node(&mut self) {
         let node_id = self.node_stack.pop().unwrap();
-        if !self.nodes[node_id.0].context.is_empty() {
+        if self.nodes[node_id.0].context.is_some() {
             self.context_stack.pop();
         }
     }
 
-    pub fn clear_keystroke_matchers(&mut self) {
+    pub fn clear_pending_keystrokes(&mut self) {
         self.keystroke_matchers.clear();
     }
 
     /// Preserve keystroke matchers from previous frames to support multi-stroke
     /// bindings across multiple frames.
-    pub fn preserve_keystroke_matchers(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
+    pub fn preserve_pending_keystrokes(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
         if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) {
             let dispatch_path = self.dispatch_path(node_id);
 
             self.context_stack.clear();
             for node_id in dispatch_path {
                 let node = self.node(node_id);
-                if !node.context.is_empty() {
-                    self.context_stack.push(node.context.clone());
+                if let Some(context) = node.context.clone() {
+                    self.context_stack.push(context);
                 }
 
                 if let Some((context_stack, matcher)) = old_tree
@@ -148,21 +148,33 @@ impl DispatchTree {
         false
     }
 
-    pub fn available_actions(&self, target: FocusId) -> Vec<Box<dyn Action>> {
+    pub fn available_actions(&self, target: DispatchNodeId) -> Vec<Box<dyn Action>> {
         let mut actions = Vec::new();
-        if let Some(node) = self.focusable_node_ids.get(&target) {
-            for node_id in self.dispatch_path(*node) {
-                let node = &self.nodes[node_id.0];
-                for DispatchActionListener { action_type, .. } in &node.action_listeners {
-                    // Intentionally silence these errors without logging.
-                    // If an action cannot be built by default, it's not available.
-                    actions.extend(self.action_registry.build_action_type(action_type).ok());
-                }
+        for node_id in self.dispatch_path(target) {
+            let node = &self.nodes[node_id.0];
+            for DispatchActionListener { action_type, .. } in &node.action_listeners {
+                // Intentionally silence these errors without logging.
+                // If an action cannot be built by default, it's not available.
+                actions.extend(self.action_registry.build_action_type(action_type).ok());
             }
         }
         actions
     }
 
+    pub fn is_action_available(&self, action: &dyn Action, target: DispatchNodeId) -> bool {
+        for node_id in self.dispatch_path(target) {
+            let node = &self.nodes[node_id.0];
+            if node
+                .action_listeners
+                .iter()
+                .any(|listener| listener.action_type == action.as_any().type_id())
+            {
+                return true;
+            }
+        }
+        false
+    }
+
     pub fn bindings_for_action(
         &self,
         action: &dyn Action,
@@ -236,6 +248,11 @@ impl DispatchTree {
         self.focusable_node_ids.get(&target).copied()
     }
 
+    pub fn root_node_id(&self) -> DispatchNodeId {
+        debug_assert!(!self.nodes.is_empty());
+        DispatchNodeId(0)
+    }
+
     fn active_node_id(&self) -> DispatchNodeId {
         *self.node_stack.last().unwrap()
     }

crates/gpui2/src/platform.rs πŸ”—

@@ -1,3 +1,4 @@
+mod app_menu;
 mod keystroke;
 #[cfg(target_os = "macos")]
 mod mac;
@@ -5,10 +6,10 @@ mod mac;
 mod test;
 
 use crate::{
-    point, size, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
-    FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout,
-    Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
-    SharedString, Size, TaskLabel,
+    point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
+    FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
+    LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
+    Scene, SharedString, Size, TaskLabel,
 };
 use anyhow::{anyhow, bail};
 use async_task::Runnable;
@@ -32,6 +33,7 @@ use std::{
 };
 use uuid::Uuid;
 
+pub use app_menu::*;
 pub use keystroke::*;
 #[cfg(target_os = "macos")]
 pub use mac::*;
@@ -44,7 +46,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
     Rc::new(MacPlatform::new())
 }
 
-pub trait Platform: 'static {
+pub(crate) trait Platform: 'static {
     fn background_executor(&self) -> BackgroundExecutor;
     fn foreground_executor(&self) -> ForegroundExecutor;
     fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
@@ -59,7 +61,7 @@ pub trait Platform: 'static {
 
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
-    fn main_window(&self) -> Option<AnyWindowHandle>;
+    fn active_window(&self) -> Option<AnyWindowHandle>;
     fn open_window(
         &self,
         handle: AnyWindowHandle,
@@ -90,6 +92,11 @@ pub trait Platform: 'static {
     fn on_reopen(&self, callback: Box<dyn FnMut()>);
     fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
 
+    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
+    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
+    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
+    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
+
     fn os_name(&self) -> &'static str;
     fn os_version(&self) -> Result<SemanticVersion>;
     fn app_version(&self) -> Result<SemanticVersion>;

crates/gpui2/src/platform/app_menu.rs πŸ”—

@@ -0,0 +1,77 @@
+use crate::{Action, AppContext, Platform};
+use util::ResultExt;
+
+pub struct Menu<'a> {
+    pub name: &'a str,
+    pub items: Vec<MenuItem<'a>>,
+}
+
+pub enum MenuItem<'a> {
+    Separator,
+    Submenu(Menu<'a>),
+    Action {
+        name: &'a str,
+        action: Box<dyn Action>,
+        os_action: Option<OsAction>,
+    },
+}
+
+impl<'a> MenuItem<'a> {
+    pub fn separator() -> Self {
+        Self::Separator
+    }
+
+    pub fn submenu(menu: Menu<'a>) -> Self {
+        Self::Submenu(menu)
+    }
+
+    pub fn action(name: &'a str, action: impl Action) -> Self {
+        Self::Action {
+            name,
+            action: Box::new(action),
+            os_action: None,
+        }
+    }
+
+    pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
+        Self::Action {
+            name,
+            action: Box::new(action),
+            os_action: Some(os_action),
+        }
+    }
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub enum OsAction {
+    Cut,
+    Copy,
+    Paste,
+    SelectAll,
+    Undo,
+    Redo,
+}
+
+pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &mut AppContext) {
+    platform.on_will_open_app_menu(Box::new({
+        let cx = cx.to_async();
+        move || {
+            cx.update(|cx| cx.clear_pending_keystrokes()).ok();
+        }
+    }));
+
+    platform.on_validate_app_menu_command(Box::new({
+        let cx = cx.to_async();
+        move |action| {
+            cx.update(|cx| cx.is_action_available(action))
+                .unwrap_or(false)
+        }
+    }));
+
+    platform.on_app_menu_action(Box::new({
+        let cx = cx.to_async();
+        move |action| {
+            cx.update(|cx| cx.dispatch_action(action)).log_err();
+        }
+    }));
+}

crates/gpui2/src/platform/mac/platform.rs πŸ”—

@@ -1,18 +1,19 @@
-use super::BoolExt;
+use super::{events::key_to_native, BoolExt};
 use crate::{
-    AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
-    InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow,
-    PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result,
-    SemanticVersion, VideoTimestamp, WindowOptions,
+    Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
+    ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
+    MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
+    PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
 };
 use anyhow::anyhow;
 use block::ConcreteBlock;
 use cocoa::{
     appkit::{
         NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
-        NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow,
+        NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
+        NSPasteboardTypeString, NSSavePanel, NSWindow,
     },
-    base::{id, nil, BOOL, YES},
+    base::{id, nil, selector, BOOL, YES},
     foundation::{
         NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
         NSUInteger, NSURL,
@@ -155,12 +156,12 @@ pub struct MacPlatformState {
     reopen: Option<Box<dyn FnMut()>>,
     quit: Option<Box<dyn FnMut()>>,
     event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
-    // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
-    // validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
+    menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
+    validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
     will_open_menu: Option<Box<dyn FnMut()>>,
+    menu_actions: Vec<Box<dyn Action>>,
     open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
     finish_launching: Option<Box<dyn FnOnce()>>,
-    // menu_actions: Vec<Box<dyn Action>>,
 }
 
 impl MacPlatform {
@@ -179,12 +180,12 @@ impl MacPlatform {
             reopen: None,
             quit: None,
             event: None,
+            menu_command: None,
+            validate_menu_command: None,
             will_open_menu: None,
+            menu_actions: Default::default(),
             open_urls: None,
             finish_launching: None,
-            // menu_command: None,
-            // validate_menu_command: None,
-            // menu_actions: Default::default(),
         }))
     }
 
@@ -200,151 +201,153 @@ impl MacPlatform {
         }
     }
 
-    // unsafe fn create_menu_bar(
-    //     &self,
-    //     menus: Vec<Menu>,
-    //     delegate: id,
-    //     actions: &mut Vec<Box<dyn Action>>,
-    //     keystroke_matcher: &KeymapMatcher,
-    // ) -> id {
-    //     let application_menu = NSMenu::new(nil).autorelease();
-    //     application_menu.setDelegate_(delegate);
-
-    //     for menu_config in menus {
-    //         let menu = NSMenu::new(nil).autorelease();
-    //         menu.setTitle_(ns_string(menu_config.name));
-    //         menu.setDelegate_(delegate);
-
-    //         for item_config in menu_config.items {
-    //             menu.addItem_(self.create_menu_item(
-    //                 item_config,
-    //                 delegate,
-    //                 actions,
-    //                 keystroke_matcher,
-    //             ));
-    //         }
-
-    //         let menu_item = NSMenuItem::new(nil).autorelease();
-    //         menu_item.setSubmenu_(menu);
-    //         application_menu.addItem_(menu_item);
-
-    //         if menu_config.name == "Window" {
-    //             let app: id = msg_send![APP_CLASS, sharedApplication];
-    //             app.setWindowsMenu_(menu);
-    //         }
-    //     }
-
-    //     application_menu
-    // }
+    unsafe fn create_menu_bar(
+        &self,
+        menus: Vec<Menu>,
+        delegate: id,
+        actions: &mut Vec<Box<dyn Action>>,
+        keymap: &Keymap,
+    ) -> id {
+        let application_menu = NSMenu::new(nil).autorelease();
+        application_menu.setDelegate_(delegate);
+
+        for menu_config in menus {
+            let menu = NSMenu::new(nil).autorelease();
+            menu.setTitle_(ns_string(menu_config.name));
+            menu.setDelegate_(delegate);
+
+            for item_config in menu_config.items {
+                menu.addItem_(self.create_menu_item(item_config, delegate, actions, keymap));
+            }
 
-    // unsafe fn create_menu_item(
-    //     &self,
-    //     item: MenuItem,
-    //     delegate: id,
-    //     actions: &mut Vec<Box<dyn Action>>,
-    //     keystroke_matcher: &KeymapMatcher,
-    // ) -> id {
-    //     match item {
-    //         MenuItem::Separator => NSMenuItem::separatorItem(nil),
-    //         MenuItem::Action {
-    //             name,
-    //             action,
-    //             os_action,
-    //         } => {
-    //             // TODO
-    //             let keystrokes = keystroke_matcher
-    //                 .bindings_for_action(action.id())
-    //                 .find(|binding| binding.action().eq(action.as_ref()))
-    //                 .map(|binding| binding.keystrokes());
-    //             let selector = match os_action {
-    //                 Some(crate::OsAction::Cut) => selector("cut:"),
-    //                 Some(crate::OsAction::Copy) => selector("copy:"),
-    //                 Some(crate::OsAction::Paste) => selector("paste:"),
-    //                 Some(crate::OsAction::SelectAll) => selector("selectAll:"),
-    //                 Some(crate::OsAction::Undo) => selector("undo:"),
-    //                 Some(crate::OsAction::Redo) => selector("redo:"),
-    //                 None => selector("handleGPUIMenuItem:"),
-    //             };
-
-    //             let item;
-    //             if let Some(keystrokes) = keystrokes {
-    //                 if keystrokes.len() == 1 {
-    //                     let keystroke = &keystrokes[0];
-    //                     let mut mask = NSEventModifierFlags::empty();
-    //                     for (modifier, flag) in &[
-    //                         (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
-    //                         (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
-    //                         (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
-    //                         (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
-    //                     ] {
-    //                         if *modifier {
-    //                             mask |= *flag;
-    //                         }
-    //                     }
-
-    //                     item = NSMenuItem::alloc(nil)
-    //                         .initWithTitle_action_keyEquivalent_(
-    //                             ns_string(name),
-    //                             selector,
-    //                             ns_string(key_to_native(&keystroke.key).as_ref()),
-    //                         )
-    //                         .autorelease();
-    //                     item.setKeyEquivalentModifierMask_(mask);
-    //                 }
-    //                 // For multi-keystroke bindings, render the keystroke as part of the title.
-    //                 else {
-    //                     use std::fmt::Write;
-
-    //                     let mut name = format!("{name} [");
-    //                     for (i, keystroke) in keystrokes.iter().enumerate() {
-    //                         if i > 0 {
-    //                             name.push(' ');
-    //                         }
-    //                         write!(&mut name, "{}", keystroke).unwrap();
-    //                     }
-    //                     name.push(']');
-
-    //                     item = NSMenuItem::alloc(nil)
-    //                         .initWithTitle_action_keyEquivalent_(
-    //                             ns_string(&name),
-    //                             selector,
-    //                             ns_string(""),
-    //                         )
-    //                         .autorelease();
-    //                 }
-    //             } else {
-    //                 item = NSMenuItem::alloc(nil)
-    //                     .initWithTitle_action_keyEquivalent_(
-    //                         ns_string(name),
-    //                         selector,
-    //                         ns_string(""),
-    //                     )
-    //                     .autorelease();
-    //             }
-
-    //             let tag = actions.len() as NSInteger;
-    //             let _: () = msg_send![item, setTag: tag];
-    //             actions.push(action);
-    //             item
-    //         }
-    //         MenuItem::Submenu(Menu { name, items }) => {
-    //             let item = NSMenuItem::new(nil).autorelease();
-    //             let submenu = NSMenu::new(nil).autorelease();
-    //             submenu.setDelegate_(delegate);
-    //             for item in items {
-    //                 submenu.addItem_(self.create_menu_item(
-    //                     item,
-    //                     delegate,
-    //                     actions,
-    //                     keystroke_matcher,
-    //                 ));
-    //             }
-    //             item.setSubmenu_(submenu);
-    //             item.setTitle_(ns_string(name));
-    //             item
-    //         }
-    //     }
-    // }
+            let menu_item = NSMenuItem::new(nil).autorelease();
+            menu_item.setSubmenu_(menu);
+            application_menu.addItem_(menu_item);
+
+            if menu_config.name == "Window" {
+                let app: id = msg_send![APP_CLASS, sharedApplication];
+                app.setWindowsMenu_(menu);
+            }
+        }
+
+        application_menu
+    }
+
+    unsafe fn create_menu_item(
+        &self,
+        item: MenuItem,
+        delegate: id,
+        actions: &mut Vec<Box<dyn Action>>,
+        keymap: &Keymap,
+    ) -> id {
+        match item {
+            MenuItem::Separator => NSMenuItem::separatorItem(nil),
+            MenuItem::Action {
+                name,
+                action,
+                os_action,
+            } => {
+                let keystrokes = keymap
+                    .bindings_for_action(action.type_id())
+                    .find(|binding| binding.action().partial_eq(action.as_ref()))
+                    .map(|binding| binding.keystrokes());
+
+                let selector = match os_action {
+                    Some(crate::OsAction::Cut) => selector("cut:"),
+                    Some(crate::OsAction::Copy) => selector("copy:"),
+                    Some(crate::OsAction::Paste) => selector("paste:"),
+                    Some(crate::OsAction::SelectAll) => selector("selectAll:"),
+                    Some(crate::OsAction::Undo) => selector("undo:"),
+                    Some(crate::OsAction::Redo) => selector("redo:"),
+                    None => selector("handleGPUIMenuItem:"),
+                };
+
+                let item;
+                if let Some(keystrokes) = keystrokes {
+                    if keystrokes.len() == 1 {
+                        let keystroke = &keystrokes[0];
+                        let mut mask = NSEventModifierFlags::empty();
+                        for (modifier, flag) in &[
+                            (
+                                keystroke.modifiers.command,
+                                NSEventModifierFlags::NSCommandKeyMask,
+                            ),
+                            (
+                                keystroke.modifiers.control,
+                                NSEventModifierFlags::NSControlKeyMask,
+                            ),
+                            (
+                                keystroke.modifiers.alt,
+                                NSEventModifierFlags::NSAlternateKeyMask,
+                            ),
+                            (
+                                keystroke.modifiers.shift,
+                                NSEventModifierFlags::NSShiftKeyMask,
+                            ),
+                        ] {
+                            if *modifier {
+                                mask |= *flag;
+                            }
+                        }
+
+                        item = NSMenuItem::alloc(nil)
+                            .initWithTitle_action_keyEquivalent_(
+                                ns_string(name),
+                                selector,
+                                ns_string(key_to_native(&keystroke.key).as_ref()),
+                            )
+                            .autorelease();
+                        item.setKeyEquivalentModifierMask_(mask);
+                    }
+                    // For multi-keystroke bindings, render the keystroke as part of the title.
+                    else {
+                        use std::fmt::Write;
+
+                        let mut name = format!("{name} [");
+                        for (i, keystroke) in keystrokes.iter().enumerate() {
+                            if i > 0 {
+                                name.push(' ');
+                            }
+                            write!(&mut name, "{}", keystroke).unwrap();
+                        }
+                        name.push(']');
+
+                        item = NSMenuItem::alloc(nil)
+                            .initWithTitle_action_keyEquivalent_(
+                                ns_string(&name),
+                                selector,
+                                ns_string(""),
+                            )
+                            .autorelease();
+                    }
+                } else {
+                    item = NSMenuItem::alloc(nil)
+                        .initWithTitle_action_keyEquivalent_(
+                            ns_string(name),
+                            selector,
+                            ns_string(""),
+                        )
+                        .autorelease();
+                }
+
+                let tag = actions.len() as NSInteger;
+                let _: () = msg_send![item, setTag: tag];
+                actions.push(action);
+                item
+            }
+            MenuItem::Submenu(Menu { name, items }) => {
+                let item = NSMenuItem::new(nil).autorelease();
+                let submenu = NSMenu::new(nil).autorelease();
+                submenu.setDelegate_(delegate);
+                for item in items {
+                    submenu.addItem_(self.create_menu_item(item, delegate, actions, keymap));
+                }
+                item.setSubmenu_(submenu);
+                item.setTitle_(ns_string(name));
+                item
+            }
+        }
+    }
 }
 
 impl Platform for MacPlatform {
@@ -479,8 +482,8 @@ impl Platform for MacPlatform {
         MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
     }
 
-    fn main_window(&self) -> Option<AnyWindowHandle> {
-        MacWindow::main_window()
+    fn active_window(&self) -> Option<AnyWindowHandle> {
+        MacWindow::active_window()
     }
 
     fn open_window(
@@ -631,6 +634,18 @@ impl Platform for MacPlatform {
         self.0.lock().event = Some(callback);
     }
 
+    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+        self.0.lock().menu_command = Some(callback);
+    }
+
+    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().will_open_menu = Some(callback);
+    }
+
+    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+        self.0.lock().validate_menu_command = Some(callback);
+    }
+
     fn os_name(&self) -> &'static str {
         "macOS"
     }
@@ -673,6 +688,15 @@ impl Platform for MacPlatform {
         }
     }
 
+    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {
+        unsafe {
+            let app: id = msg_send![APP_CLASS, sharedApplication];
+            let mut state = self.0.lock();
+            let actions = &mut state.menu_actions;
+            app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), actions, keymap));
+        }
+    }
+
     fn local_timezone(&self) -> UtcOffset {
         unsafe {
             let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
@@ -681,32 +705,6 @@ impl Platform for MacPlatform {
         }
     }
 
-    // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-    //     self.0.lock().menu_command = Some(callback);
-    // }
-
-    // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
-    //     self.0.lock().will_open_menu = Some(callback);
-    // }
-
-    // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-    //     self.0.lock().validate_menu_command = Some(callback);
-    // }
-
-    // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
-    //     unsafe {
-    //         let app: id = msg_send![APP_CLASS, sharedApplication];
-    //         let mut state = self.0.lock();
-    //         let actions = &mut state.menu_actions;
-    //         app.setMainMenu_(self.create_menu_bar(
-    //             menus,
-    //             app.delegate(),
-    //             actions,
-    //             keystroke_matcher,
-    //         ));
-    //     }
-    // }
-
     fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
         unsafe {
             let bundle: id = NSBundle::mainBundle();
@@ -956,7 +954,7 @@ unsafe fn path_from_objc(path: id) -> PathBuf {
     PathBuf::from(path)
 }
 
-unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform {
+unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
     let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
     assert!(!platform_ptr.is_null());
     &*(platform_ptr as *const MacPlatform)
@@ -965,7 +963,7 @@ unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform {
 extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
     unsafe {
         if let Some(event) = InputEvent::from_native(native_event, None) {
-            let platform = get_foreground_platform(this);
+            let platform = get_mac_platform(this);
             if let Some(callback) = platform.0.lock().event.as_mut() {
                 if !callback(event) {
                     return;
@@ -981,7 +979,7 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
         let app: id = msg_send![APP_CLASS, sharedApplication];
         app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
 
-        let platform = get_foreground_platform(this);
+        let platform = get_mac_platform(this);
         let callback = platform.0.lock().finish_launching.take();
         if let Some(callback) = callback {
             callback();
@@ -991,7 +989,7 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
 
 extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
     if !has_open_windows {
-        let platform = unsafe { get_foreground_platform(this) };
+        let platform = unsafe { get_mac_platform(this) };
         if let Some(callback) = platform.0.lock().reopen.as_mut() {
             callback();
         }
@@ -999,21 +997,21 @@ extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_wi
 }
 
 extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_foreground_platform(this) };
+    let platform = unsafe { get_mac_platform(this) };
     if let Some(callback) = platform.0.lock().become_active.as_mut() {
         callback();
     }
 }
 
 extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_foreground_platform(this) };
+    let platform = unsafe { get_mac_platform(this) };
     if let Some(callback) = platform.0.lock().resign_active.as_mut() {
         callback();
     }
 }
 
 extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_foreground_platform(this) };
+    let platform = unsafe { get_mac_platform(this) };
     if let Some(callback) = platform.0.lock().quit.as_mut() {
         callback();
     }
@@ -1035,49 +1033,47 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
             })
             .collect::<Vec<_>>()
     };
-    let platform = unsafe { get_foreground_platform(this) };
+    let platform = unsafe { get_mac_platform(this) };
     if let Some(callback) = platform.0.lock().open_urls.as_mut() {
         callback(urls);
     }
 }
 
-extern "C" fn handle_menu_item(__this: &mut Object, _: Sel, __item: id) {
-    todo!()
-    // unsafe {
-    //     let platform = get_foreground_platform(this);
-    //     let mut platform = platform.0.lock();
-    //     if let Some(mut callback) = platform.menu_command.take() {
-    //         let tag: NSInteger = msg_send![item, tag];
-    //         let index = tag as usize;
-    //         if let Some(action) = platform.menu_actions.get(index) {
-    //             callback(action.as_ref());
-    //         }
-    //         platform.menu_command = Some(callback);
-    //     }
-    // }
+extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
+    unsafe {
+        let platform = get_mac_platform(this);
+        let mut platform = platform.0.lock();
+        if let Some(mut callback) = platform.menu_command.take() {
+            let tag: NSInteger = msg_send![item, tag];
+            let index = tag as usize;
+            if let Some(action) = platform.menu_actions.get(index) {
+                callback(action.as_ref());
+            }
+            platform.menu_command = Some(callback);
+        }
+    }
 }
 
-extern "C" fn validate_menu_item(__this: &mut Object, _: Sel, __item: id) -> bool {
-    todo!()
-    // unsafe {
-    //     let mut result = false;
-    //     let platform = get_foreground_platform(this);
-    //     let mut platform = platform.0.lock();
-    //     if let Some(mut callback) = platform.validate_menu_command.take() {
-    //         let tag: NSInteger = msg_send![item, tag];
-    //         let index = tag as usize;
-    //         if let Some(action) = platform.menu_actions.get(index) {
-    //             result = callback(action.as_ref());
-    //         }
-    //         platform.validate_menu_command = Some(callback);
-    //     }
-    //     result
-    // }
+extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
+    unsafe {
+        let mut result = false;
+        let platform = get_mac_platform(this);
+        let mut platform = platform.0.lock();
+        if let Some(mut callback) = platform.validate_menu_command.take() {
+            let tag: NSInteger = msg_send![item, tag];
+            let index = tag as usize;
+            if let Some(action) = platform.menu_actions.get(index) {
+                result = callback(action.as_ref());
+            }
+            platform.validate_menu_command = Some(callback);
+        }
+        result
+    }
 }
 
 extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
     unsafe {
-        let platform = get_foreground_platform(this);
+        let platform = get_mac_platform(this);
         let mut platform = platform.0.lock();
         if let Some(mut callback) = platform.will_open_menu.take() {
             callback();

crates/gpui2/src/platform/mac/window.rs πŸ”—

@@ -662,7 +662,7 @@ impl MacWindow {
         }
     }
 
-    pub fn main_window() -> Option<AnyWindowHandle> {
+    pub fn active_window() -> Option<AnyWindowHandle> {
         unsafe {
             let app = NSApplication::sharedApplication(nil);
             let main_window: id = msg_send![app, mainWindow];

crates/gpui2/src/platform/test/platform.rs πŸ”—

@@ -1,6 +1,6 @@
 use crate::{
     AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
-    Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
+    Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
 };
 use anyhow::{anyhow, Result};
 use collections::VecDeque;
@@ -127,7 +127,7 @@ impl Platform for TestPlatform {
         self.displays().iter().find(|d| d.id() == id).cloned()
     }
 
-    fn main_window(&self) -> Option<crate::AnyWindowHandle> {
+    fn active_window(&self) -> Option<crate::AnyWindowHandle> {
         unimplemented!()
     }
 
@@ -212,6 +212,14 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
+    fn set_menus(&self, _menus: Vec<crate::Menu>, _keymap: &Keymap) {}
+
+    fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
+
+    fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {}
+
+    fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
+
     fn os_name(&self) -> &'static str {
         "test"
     }

crates/gpui2/src/window.rs πŸ”—

@@ -430,7 +430,7 @@ impl<'a> WindowContext<'a> {
         self.window
             .current_frame
             .dispatch_tree
-            .clear_keystroke_matchers();
+            .clear_pending_keystrokes();
         self.app.push_effect(Effect::FocusChanged {
             window_handle: self.window.handle,
             focused: Some(focus_id),
@@ -453,19 +453,21 @@ impl<'a> WindowContext<'a> {
     }
 
     pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
-        if let Some(focus_handle) = self.focused() {
-            self.defer(move |cx| {
-                if let Some(node_id) = cx
-                    .window
-                    .current_frame
-                    .dispatch_tree
-                    .focusable_node_id(focus_handle.id)
-                {
-                    cx.propagate_event = true;
-                    cx.dispatch_action_on_node(node_id, action);
-                }
-            })
-        }
+        let focus_handle = self.focused();
+
+        self.defer(move |cx| {
+            let node_id = focus_handle
+                .and_then(|handle| {
+                    cx.window
+                        .current_frame
+                        .dispatch_tree
+                        .focusable_node_id(handle.id)
+                })
+                .unwrap_or_else(|| cx.window.current_frame.dispatch_tree.root_node_id());
+
+            cx.propagate_event = true;
+            cx.dispatch_action_on_node(node_id, action);
+        })
     }
 
     /// Schedules the given function to be run at the end of the current effect cycle, allowing entities
@@ -802,6 +804,22 @@ impl<'a> WindowContext<'a> {
         );
     }
 
+    pub fn is_action_available(&self, action: &dyn Action) -> bool {
+        let target = self
+            .focused()
+            .and_then(|focused_handle| {
+                self.window
+                    .current_frame
+                    .dispatch_tree
+                    .focusable_node_id(focused_handle.id)
+            })
+            .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
+        self.window
+            .current_frame
+            .dispatch_tree
+            .is_action_available(action, target)
+    }
+
     /// The position of the mouse relative to the window.
     pub fn mouse_position(&self) -> Point<Pixels> {
         self.window.mouse_position
@@ -1154,8 +1172,19 @@ impl<'a> WindowContext<'a> {
         self.start_frame();
 
         self.with_z_index(0, |cx| {
-            let available_space = cx.window.viewport_size.map(Into::into);
-            root_view.draw(Point::zero(), available_space, cx);
+            cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
+                for (action_type, action_listeners) in &cx.app.global_action_listeners {
+                    for action_listener in action_listeners.iter().cloned() {
+                        cx.window.current_frame.dispatch_tree.on_action(
+                            *action_type,
+                            Rc::new(move |action, phase, cx| action_listener(action, phase, cx)),
+                        )
+                    }
+                }
+
+                let available_space = cx.window.viewport_size.map(Into::into);
+                root_view.draw(Point::zero(), available_space, cx);
+            })
         });
 
         if let Some(active_drag) = self.app.active_drag.take() {
@@ -1177,7 +1206,7 @@ impl<'a> WindowContext<'a> {
         self.window
             .current_frame
             .dispatch_tree
-            .preserve_keystroke_matchers(
+            .preserve_pending_keystrokes(
                 &mut self.window.previous_frame.dispatch_tree,
                 self.window.focus,
             );
@@ -1338,73 +1367,77 @@ impl<'a> WindowContext<'a> {
     }
 
     fn dispatch_key_event(&mut self, event: &dyn Any) {
-        if let Some(node_id) = self.window.focus.and_then(|focus_id| {
-            self.window
-                .current_frame
-                .dispatch_tree
-                .focusable_node_id(focus_id)
-        }) {
-            let dispatch_path = self
-                .window
-                .current_frame
-                .dispatch_tree
-                .dispatch_path(node_id);
+        let node_id = self
+            .window
+            .focus
+            .and_then(|focus_id| {
+                self.window
+                    .current_frame
+                    .dispatch_tree
+                    .focusable_node_id(focus_id)
+            })
+            .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
 
-            let mut actions: Vec<Box<dyn Action>> = Vec::new();
+        let dispatch_path = self
+            .window
+            .current_frame
+            .dispatch_tree
+            .dispatch_path(node_id);
 
-            // Capture phase
-            let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
-            self.propagate_event = true;
+        let mut actions: Vec<Box<dyn Action>> = Vec::new();
 
-            for node_id in &dispatch_path {
-                let node = self.window.current_frame.dispatch_tree.node(*node_id);
+        // Capture phase
+        let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
+        self.propagate_event = true;
 
-                if !node.context.is_empty() {
-                    context_stack.push(node.context.clone());
-                }
+        for node_id in &dispatch_path {
+            let node = self.window.current_frame.dispatch_tree.node(*node_id);
 
-                for key_listener in node.key_listeners.clone() {
-                    key_listener(event, DispatchPhase::Capture, self);
-                    if !self.propagate_event {
-                        return;
-                    }
+            if let Some(context) = node.context.clone() {
+                context_stack.push(context);
+            }
+
+            for key_listener in node.key_listeners.clone() {
+                key_listener(event, DispatchPhase::Capture, self);
+                if !self.propagate_event {
+                    return;
                 }
             }
+        }
 
-            // Bubble phase
-            for node_id in dispatch_path.iter().rev() {
-                // Handle low level key events
-                let node = self.window.current_frame.dispatch_tree.node(*node_id);
-                for key_listener in node.key_listeners.clone() {
-                    key_listener(event, DispatchPhase::Bubble, self);
-                    if !self.propagate_event {
-                        return;
-                    }
+        // Bubble phase
+        for node_id in dispatch_path.iter().rev() {
+            // Handle low level key events
+            let node = self.window.current_frame.dispatch_tree.node(*node_id);
+            for key_listener in node.key_listeners.clone() {
+                key_listener(event, DispatchPhase::Bubble, self);
+                if !self.propagate_event {
+                    return;
                 }
+            }
 
-                // Match keystrokes
-                let node = self.window.current_frame.dispatch_tree.node(*node_id);
-                if !node.context.is_empty() {
-                    if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
-                        if let Some(found) = self
-                            .window
-                            .current_frame
-                            .dispatch_tree
-                            .dispatch_key(&key_down_event.keystroke, &context_stack)
-                        {
-                            actions.push(found.boxed_clone())
-                        }
+            // Match keystrokes
+            let node = self.window.current_frame.dispatch_tree.node(*node_id);
+            if node.context.is_some() {
+                if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
+                    if let Some(found) = self
+                        .window
+                        .current_frame
+                        .dispatch_tree
+                        .dispatch_key(&key_down_event.keystroke, &context_stack)
+                    {
+                        actions.push(found.boxed_clone())
                     }
-
-                    context_stack.pop();
                 }
+
+                context_stack.pop();
             }
+        }
 
-            for action in actions {
-                self.dispatch_action_on_node(node_id, action);
-                if !self.propagate_event {
-                    return;
-                }
+        for action in actions {
+            self.dispatch_action_on_node(node_id, action);
+            if !self.propagate_event {
+                return;
             }
         }
     }
@@ -1490,14 +1523,21 @@ impl<'a> WindowContext<'a> {
     }
 
     pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
-        if let Some(focus_id) = self.window.focus {
-            self.window
-                .current_frame
-                .dispatch_tree
-                .available_actions(focus_id)
-        } else {
-            Vec::new()
-        }
+        let node_id = self
+            .window
+            .focus
+            .and_then(|focus_id| {
+                self.window
+                    .current_frame
+                    .dispatch_tree
+                    .focusable_node_id(focus_id)
+            })
+            .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
+
+        self.window
+            .current_frame
+            .dispatch_tree
+            .available_actions(node_id)
     }
 
     pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
@@ -1523,7 +1563,7 @@ impl<'a> WindowContext<'a> {
         let context_stack = dispatch_tree
             .dispatch_path(node_id)
             .into_iter()
-            .map(|node_id| dispatch_tree.node(node_id).context.clone())
+            .filter_map(|node_id| dispatch_tree.node(node_id).context.clone())
             .collect();
         dispatch_tree.bindings_for_action(action, &context_stack)
     }
@@ -1553,7 +1593,7 @@ impl<'a> WindowContext<'a> {
     //========== ELEMENT RELATED FUNCTIONS ===========
     pub fn with_key_dispatch<R>(
         &mut self,
-        context: KeyContext,
+        context: Option<KeyContext>,
         focus_handle: Option<FocusHandle>,
         f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
     ) -> R {

crates/settings2/src/settings_file.rs πŸ”—

@@ -132,39 +132,3 @@ pub fn load_default_keymap(cx: &mut AppContext) {
     //     KeymapFile::load_asset(asset_path, cx).unwrap();
     // }
 }
-
-pub fn handle_keymap_file_changes(
-    mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
-    cx: &mut AppContext,
-) {
-    cx.spawn(move |cx| async move {
-        //  let mut settings_subscription = None;
-        while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
-            if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
-                cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok();
-
-                // todo!()
-                // let mut old_base_keymap = cx.read(|cx| *settings::get::<BaseKeymap>(cx));
-                // drop(settings_subscription);
-                // settings_subscription = Some(cx.update(|cx| {
-                //     cx.observe_global::<SettingsStore, _>(move |cx| {
-                //         let new_base_keymap = *settings::get::<BaseKeymap>(cx);
-                //         if new_base_keymap != old_base_keymap {
-                //             old_base_keymap = new_base_keymap.clone();
-                //             reload_keymaps(cx, &keymap_content);
-                //         }
-                //     })
-                // }));
-            }
-        }
-    })
-    .detach();
-}
-
-fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
-    // todo!()
-    // cx.clear_bindings();
-    load_default_keymap(cx);
-    keymap_content.clone().add_to_cx(cx).log_err();
-    // cx.set_menus(menus::menus());
-}

crates/ui2/src/components/icon.rs πŸ”—

@@ -93,6 +93,7 @@ pub enum Icon {
     Option,
     Return,
     Update,
+    ZedXCopilot,
 }
 
 impl Icon {
@@ -121,6 +122,7 @@ impl Icon {
             Icon::Close => "icons/x.svg",
             Icon::Collab => "icons/user_group_16.svg",
             Icon::Copilot => "icons/copilot.svg",
+
             Icon::CopilotInit => "icons/copilot_init.svg",
             Icon::CopilotError => "icons/copilot_error.svg",
             Icon::CopilotDisabled => "icons/copilot_disabled.svg",
@@ -168,6 +170,7 @@ impl Icon {
             Icon::Option => "icons/option.svg",
             Icon::Return => "icons/return.svg",
             Icon::Update => "icons/update.svg",
+            Icon::ZedXCopilot => "icons/zed_x_copilot.svg",
         }
     }
 }

crates/ui2/src/components/label.rs πŸ”—

@@ -1,180 +1,7 @@
-use std::ops::Range;
+mod highlighted_label;
+mod label;
+mod label_like;
 
-use crate::prelude::*;
-use crate::styled_ext::StyledExt;
-use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext};
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
-pub enum LabelSize {
-    #[default]
-    Default,
-    Small,
-}
-
-#[derive(Default, PartialEq, Copy, Clone)]
-pub enum LineHeightStyle {
-    #[default]
-    TextLabel,
-    /// Sets the line height to 1
-    UILabel,
-}
-
-#[derive(IntoElement, Clone)]
-pub struct Label {
-    label: SharedString,
-    size: LabelSize,
-    line_height_style: LineHeightStyle,
-    color: Color,
-    strikethrough: bool,
-}
-
-impl RenderOnce for Label {
-    type Rendered = Div;
-
-    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        div()
-            .when(self.strikethrough, |this| {
-                this.relative().child(
-                    div()
-                        .absolute()
-                        .top_1_2()
-                        .w_full()
-                        .h_px()
-                        .bg(Color::Hidden.color(cx)),
-                )
-            })
-            .map(|this| match self.size {
-                LabelSize::Default => this.text_ui(),
-                LabelSize::Small => this.text_ui_sm(),
-            })
-            .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
-                this.line_height(relative(1.))
-            })
-            .text_color(self.color.color(cx))
-            .child(self.label.clone())
-    }
-}
-
-impl Label {
-    pub fn new(label: impl Into<SharedString>) -> Self {
-        Self {
-            label: label.into(),
-            size: LabelSize::Default,
-            line_height_style: LineHeightStyle::default(),
-            color: Color::Default,
-            strikethrough: false,
-        }
-    }
-
-    pub fn size(mut self, size: LabelSize) -> Self {
-        self.size = size;
-        self
-    }
-
-    pub fn color(mut self, color: Color) -> Self {
-        self.color = color;
-        self
-    }
-
-    pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
-        self.line_height_style = line_height_style;
-        self
-    }
-
-    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
-        self.strikethrough = strikethrough;
-        self
-    }
-}
-
-#[derive(IntoElement)]
-pub struct HighlightedLabel {
-    label: SharedString,
-    size: LabelSize,
-    color: Color,
-    highlight_indices: Vec<usize>,
-    strikethrough: bool,
-}
-
-impl RenderOnce for HighlightedLabel {
-    type Rendered = Div;
-
-    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let highlight_color = cx.theme().colors().text_accent;
-
-        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
-        let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
-
-        while let Some(start_ix) = highlight_indices.next() {
-            let mut end_ix = start_ix;
-
-            loop {
-                end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8();
-                if let Some(&next_ix) = highlight_indices.peek() {
-                    if next_ix == end_ix {
-                        end_ix = next_ix;
-                        highlight_indices.next();
-                        continue;
-                    }
-                }
-                break;
-            }
-
-            highlights.push((
-                start_ix..end_ix,
-                HighlightStyle {
-                    color: Some(highlight_color),
-                    ..Default::default()
-                },
-            ));
-        }
-
-        div()
-            .flex()
-            .when(self.strikethrough, |this| {
-                this.relative().child(
-                    div()
-                        .absolute()
-                        .top_px()
-                        .my_auto()
-                        .w_full()
-                        .h_px()
-                        .bg(Color::Hidden.color(cx)),
-                )
-            })
-            .map(|this| match self.size {
-                LabelSize::Default => this.text_ui(),
-                LabelSize::Small => this.text_ui_sm(),
-            })
-            .child(StyledText::new(self.label).with_highlights(&cx.text_style(), highlights))
-    }
-}
-
-impl HighlightedLabel {
-    /// shows a label with the given characters highlighted.
-    /// characters are identified by utf8 byte position.
-    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
-        Self {
-            label: label.into(),
-            size: LabelSize::Default,
-            color: Color::Default,
-            highlight_indices,
-            strikethrough: false,
-        }
-    }
-
-    pub fn size(mut self, size: LabelSize) -> Self {
-        self.size = size;
-        self
-    }
-
-    pub fn color(mut self, color: Color) -> Self {
-        self.color = color;
-        self
-    }
-
-    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
-        self.strikethrough = strikethrough;
-        self
-    }
-}
+pub use highlighted_label::*;
+pub use label::*;
+pub use label_like::*;

crates/ui2/src/components/label/highlighted_label.rs πŸ”—

@@ -0,0 +1,86 @@
+use std::ops::Range;
+
+use gpui::{HighlightStyle, StyledText};
+
+use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
+
+#[derive(IntoElement)]
+pub struct HighlightedLabel {
+    base: LabelLike,
+    label: SharedString,
+    highlight_indices: Vec<usize>,
+}
+
+impl HighlightedLabel {
+    /// Constructs a label with the given characters highlighted.
+    /// Characters are identified by UTF-8 byte position.
+    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
+        Self {
+            base: LabelLike::new(),
+            label: label.into(),
+            highlight_indices,
+        }
+    }
+}
+
+impl LabelCommon for HighlightedLabel {
+    fn size(mut self, size: LabelSize) -> Self {
+        self.base = self.base.size(size);
+        self
+    }
+
+    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
+        self.base = self.base.line_height_style(line_height_style);
+        self
+    }
+
+    fn color(mut self, color: Color) -> Self {
+        self.base = self.base.color(color);
+        self
+    }
+
+    fn strikethrough(mut self, strikethrough: bool) -> Self {
+        self.base = self.base.strikethrough(strikethrough);
+        self
+    }
+}
+
+impl RenderOnce for HighlightedLabel {
+    type Rendered = LabelLike;
+
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        let highlight_color = cx.theme().colors().text_accent;
+
+        let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
+        let mut highlights: Vec<(Range<usize>, HighlightStyle)> = Vec::new();
+
+        while let Some(start_ix) = highlight_indices.next() {
+            let mut end_ix = start_ix;
+
+            loop {
+                end_ix = end_ix + self.label[end_ix..].chars().next().unwrap().len_utf8();
+                if let Some(&next_ix) = highlight_indices.peek() {
+                    if next_ix == end_ix {
+                        end_ix = next_ix;
+                        highlight_indices.next();
+                        continue;
+                    }
+                }
+                break;
+            }
+
+            highlights.push((
+                start_ix..end_ix,
+                HighlightStyle {
+                    color: Some(highlight_color),
+                    ..Default::default()
+                },
+            ));
+        }
+
+        let mut text_style = cx.text_style().clone();
+        text_style.color = self.base.color.color(cx);
+
+        LabelLike::new().child(StyledText::new(self.label).with_highlights(&text_style, highlights))
+    }
+}

crates/ui2/src/components/label/label.rs πŸ”—

@@ -0,0 +1,48 @@
+use gpui::WindowContext;
+
+use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
+
+#[derive(IntoElement)]
+pub struct Label {
+    base: LabelLike,
+    label: SharedString,
+}
+
+impl Label {
+    pub fn new(label: impl Into<SharedString>) -> Self {
+        Self {
+            base: LabelLike::new(),
+            label: label.into(),
+        }
+    }
+}
+
+impl LabelCommon for Label {
+    fn size(mut self, size: LabelSize) -> Self {
+        self.base = self.base.size(size);
+        self
+    }
+
+    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
+        self.base = self.base.line_height_style(line_height_style);
+        self
+    }
+
+    fn color(mut self, color: Color) -> Self {
+        self.base = self.base.color(color);
+        self
+    }
+
+    fn strikethrough(mut self, strikethrough: bool) -> Self {
+        self.base = self.base.strikethrough(strikethrough);
+        self
+    }
+}
+
+impl RenderOnce for Label {
+    type Rendered = LabelLike;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        self.base.child(self.label)
+    }
+}

crates/ui2/src/components/label/label_like.rs πŸ”—

@@ -0,0 +1,102 @@
+use gpui::{relative, AnyElement, Div, Styled};
+use smallvec::SmallVec;
+
+use crate::prelude::*;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
+pub enum LabelSize {
+    #[default]
+    Default,
+    Small,
+}
+
+#[derive(Default, PartialEq, Copy, Clone)]
+pub enum LineHeightStyle {
+    #[default]
+    TextLabel,
+    /// Sets the line height to 1
+    UILabel,
+}
+
+pub trait LabelCommon {
+    fn size(self, size: LabelSize) -> Self;
+    fn line_height_style(self, line_height_style: LineHeightStyle) -> Self;
+    fn color(self, color: Color) -> Self;
+    fn strikethrough(self, strikethrough: bool) -> Self;
+}
+
+#[derive(IntoElement)]
+pub struct LabelLike {
+    size: LabelSize,
+    line_height_style: LineHeightStyle,
+    pub(crate) color: Color,
+    strikethrough: bool,
+    children: SmallVec<[AnyElement; 2]>,
+}
+
+impl LabelLike {
+    pub fn new() -> Self {
+        Self {
+            size: LabelSize::Default,
+            line_height_style: LineHeightStyle::default(),
+            color: Color::Default,
+            strikethrough: false,
+            children: SmallVec::new(),
+        }
+    }
+}
+
+impl LabelCommon for LabelLike {
+    fn size(mut self, size: LabelSize) -> Self {
+        self.size = size;
+        self
+    }
+
+    fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
+        self.line_height_style = line_height_style;
+        self
+    }
+
+    fn color(mut self, color: Color) -> Self {
+        self.color = color;
+        self
+    }
+
+    fn strikethrough(mut self, strikethrough: bool) -> Self {
+        self.strikethrough = strikethrough;
+        self
+    }
+}
+
+impl ParentElement for LabelLike {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}
+
+impl RenderOnce for LabelLike {
+    type Rendered = Div;
+
+    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
+        div()
+            .when(self.strikethrough, |this| {
+                this.relative().child(
+                    div()
+                        .absolute()
+                        .top_1_2()
+                        .w_full()
+                        .h_px()
+                        .bg(Color::Hidden.color(cx)),
+                )
+            })
+            .map(|this| match self.size {
+                LabelSize::Default => this.text_ui(),
+                LabelSize::Small => this.text_ui_sm(),
+            })
+            .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
+                this.line_height(relative(1.))
+            })
+            .text_color(self.color.color(cx))
+            .children(self.children)
+    }
+}

crates/ui2/src/components/stories/label.rs πŸ”—

@@ -23,5 +23,9 @@ impl Render for LabelStory {
                 "HΓ©llo, world!",
                 vec![0, 1, 3, 8, 9, 13],
             ))
+            .child(Story::label("Highlighted with `color`"))
+            .child(
+                HighlightedLabel::new("Hello, world!", vec![0, 1, 2, 7, 8, 12]).color(Color::Error),
+            )
     }
 }

crates/ui2/src/prelude.rs πŸ”—

@@ -9,5 +9,5 @@ pub use crate::disableable::*;
 pub use crate::fixed::*;
 pub use crate::selectable::*;
 pub use crate::{h_stack, v_stack};
-pub use crate::{ButtonCommon, Color, StyledExt};
+pub use crate::{ButtonCommon, Color, LabelCommon, StyledExt};
 pub use theme::ActiveTheme;

crates/vim/src/vim.rs πŸ”—

@@ -101,7 +101,7 @@ pub fn init(cx: &mut AppContext) {
     // will be initialized as disabled by default, so we filter its commands
     // out when starting up.
     cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
-        filter.filtered_namespaces.insert("vim");
+        filter.hidden_namespaces.insert("vim");
     });
     cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
         vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
@@ -477,9 +477,9 @@ impl Vim {
 
             cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
                 if self.enabled {
-                    filter.filtered_namespaces.remove("vim");
+                    filter.hidden_namespaces.remove("vim");
                 } else {
-                    filter.filtered_namespaces.insert("vim");
+                    filter.hidden_namespaces.insert("vim");
                 }
             });
 

crates/zed/Cargo.toml πŸ”—

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 description = "The fast, collaborative code editor."
 edition = "2021"
 name = "zed"
-version = "0.116.0"
+version = "0.117.0"
 publish = false
 
 [lib]

crates/zed2/src/app_menus.rs πŸ”—

@@ -0,0 +1,175 @@
+use gpui::{Menu, MenuItem, OsAction};
+
+#[cfg(target_os = "macos")]
+pub fn app_menus() -> Vec<Menu<'static>> {
+    vec![
+        Menu {
+            name: "Zed",
+            items: vec![
+                MenuItem::action("About Zed…", super::About),
+                MenuItem::action("Check for Updates", auto_update::Check),
+                MenuItem::separator(),
+                MenuItem::submenu(Menu {
+                    name: "Preferences",
+                    items: vec![
+                        MenuItem::action("Open Settings", super::OpenSettings),
+                        MenuItem::action("Open Key Bindings", super::OpenKeymap),
+                        MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
+                        MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
+                        MenuItem::action("Open Local Settings", super::OpenLocalSettings),
+                        MenuItem::action("Select Theme", theme_selector::Toggle),
+                    ],
+                }),
+                MenuItem::action("Install CLI", install_cli::Install),
+                MenuItem::separator(),
+                MenuItem::action("Hide Zed", super::Hide),
+                MenuItem::action("Hide Others", super::HideOthers),
+                MenuItem::action("Show All", super::ShowAll),
+                MenuItem::action("Quit", super::Quit),
+            ],
+        },
+        Menu {
+            name: "File",
+            items: vec![
+                MenuItem::action("New", workspace::NewFile),
+                MenuItem::action("New Window", workspace::NewWindow),
+                MenuItem::separator(),
+                MenuItem::action("Open…", workspace::Open),
+                // MenuItem::action("Open Recent...", recent_projects::OpenRecent),
+                MenuItem::separator(),
+                MenuItem::action("Add Folder to Project…", workspace::AddFolderToProject),
+                MenuItem::action("Save", workspace::Save { save_intent: None }),
+                MenuItem::action("Save As…", workspace::SaveAs),
+                MenuItem::action("Save All", workspace::SaveAll { save_intent: None }),
+                MenuItem::action(
+                    "Close Editor",
+                    workspace::CloseActiveItem { save_intent: None },
+                ),
+                MenuItem::action("Close Window", workspace::CloseWindow),
+            ],
+        },
+        Menu {
+            name: "Edit",
+            items: vec![
+                MenuItem::os_action("Undo", editor::Undo, OsAction::Undo),
+                MenuItem::os_action("Redo", editor::Redo, OsAction::Redo),
+                MenuItem::separator(),
+                MenuItem::os_action("Cut", editor::Cut, OsAction::Cut),
+                MenuItem::os_action("Copy", editor::Copy, OsAction::Copy),
+                MenuItem::os_action("Paste", editor::Paste, OsAction::Paste),
+                MenuItem::separator(),
+                MenuItem::action("Find", search::buffer_search::Deploy { focus: true }),
+                MenuItem::action("Find In Project", workspace::NewSearch),
+                MenuItem::separator(),
+                MenuItem::action("Toggle Line Comment", editor::ToggleComments::default()),
+                MenuItem::action("Emoji & Symbols", editor::ShowCharacterPalette),
+            ],
+        },
+        Menu {
+            name: "Selection",
+            items: vec![
+                MenuItem::os_action("Select All", editor::SelectAll, OsAction::SelectAll),
+                MenuItem::action("Expand Selection", editor::SelectLargerSyntaxNode),
+                MenuItem::action("Shrink Selection", editor::SelectSmallerSyntaxNode),
+                MenuItem::separator(),
+                MenuItem::action("Add Cursor Above", editor::AddSelectionAbove),
+                MenuItem::action("Add Cursor Below", editor::AddSelectionBelow),
+                MenuItem::action(
+                    "Select Next Occurrence",
+                    editor::SelectNext {
+                        replace_newest: false,
+                    },
+                ),
+                MenuItem::separator(),
+                MenuItem::action("Move Line Up", editor::MoveLineUp),
+                MenuItem::action("Move Line Down", editor::MoveLineDown),
+                MenuItem::action("Duplicate Selection", editor::DuplicateLine),
+            ],
+        },
+        Menu {
+            name: "View",
+            items: vec![
+                MenuItem::action("Zoom In", super::IncreaseBufferFontSize),
+                MenuItem::action("Zoom Out", super::DecreaseBufferFontSize),
+                MenuItem::action("Reset Zoom", super::ResetBufferFontSize),
+                MenuItem::separator(),
+                MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock),
+                MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock),
+                MenuItem::action("Toggle Bottom Dock", workspace::ToggleBottomDock),
+                MenuItem::action("Close All Docks", workspace::CloseAllDocks),
+                MenuItem::submenu(Menu {
+                    name: "Editor Layout",
+                    items: vec![
+                        MenuItem::action("Split Up", workspace::SplitUp),
+                        MenuItem::action("Split Down", workspace::SplitDown),
+                        MenuItem::action("Split Left", workspace::SplitLeft),
+                        MenuItem::action("Split Right", workspace::SplitRight),
+                    ],
+                }),
+                MenuItem::separator(),
+                MenuItem::action("Project Panel", project_panel::ToggleFocus),
+                MenuItem::action("Command Palette", command_palette::Toggle),
+                MenuItem::action("Diagnostics", diagnostics::Deploy),
+                MenuItem::separator(),
+            ],
+        },
+        Menu {
+            name: "Go",
+            items: vec![
+                MenuItem::action("Back", workspace::GoBack),
+                MenuItem::action("Forward", workspace::GoForward),
+                MenuItem::separator(),
+                MenuItem::action("Go to File", file_finder::Toggle),
+                // MenuItem::action("Go to Symbol in Project", project_symbols::Toggle),
+                MenuItem::action("Go to Symbol in Editor", outline::Toggle),
+                MenuItem::action("Go to Definition", editor::GoToDefinition),
+                MenuItem::action("Go to Type Definition", editor::GoToTypeDefinition),
+                MenuItem::action("Find All References", editor::FindAllReferences),
+                MenuItem::action("Go to Line/Column", go_to_line::Toggle),
+                MenuItem::separator(),
+                MenuItem::action("Next Problem", editor::GoToDiagnostic),
+                MenuItem::action("Previous Problem", editor::GoToPrevDiagnostic),
+            ],
+        },
+        Menu {
+            name: "Window",
+            items: vec![
+                MenuItem::action("Minimize", super::Minimize),
+                MenuItem::action("Zoom", super::Zoom),
+                MenuItem::separator(),
+            ],
+        },
+        Menu {
+            name: "Help",
+            items: vec![
+                MenuItem::action("Command Palette", command_palette::Toggle),
+                MenuItem::separator(),
+                MenuItem::action("View Telemetry", crate::OpenTelemetryLog),
+                MenuItem::action("View Dependency Licenses", crate::OpenLicenses),
+                MenuItem::action("Show Welcome", workspace::Welcome),
+                MenuItem::separator(),
+                // todo!(): Needs `feedback2` crate.
+                // MenuItem::action("Give us feedback", feedback::feedback_editor::GiveFeedback),
+                // MenuItem::action(
+                //     "Copy System Specs Into Clipboard",
+                //     feedback::CopySystemSpecsIntoClipboard,
+                // ),
+                // MenuItem::action("File Bug Report", feedback::FileBugReport),
+                // MenuItem::action("Request Feature", feedback::RequestFeature),
+                MenuItem::separator(),
+                MenuItem::action(
+                    "Documentation",
+                    crate::OpenBrowser {
+                        url: "https://zed.dev/docs".into(),
+                    },
+                ),
+                MenuItem::action(
+                    "Zed Twitter",
+                    crate::OpenBrowser {
+                        url: "https://twitter.com/zeddotdev".into(),
+                    },
+                ),
+            ],
+        },
+    ]
+}

crates/zed2/src/main.rs πŸ”—

@@ -22,8 +22,7 @@ use node_runtime::RealNodeRuntime;
 use parking_lot::Mutex;
 use serde::{Deserialize, Serialize};
 use settings::{
-    default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file,
-    Settings, SettingsStore,
+    default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore,
 };
 use simplelog::ConfigBuilder;
 use smol::process::Command;
@@ -51,8 +50,9 @@ use uuid::Uuid;
 use welcome::{show_welcome_experience, FIRST_OPEN};
 use workspace::{AppState, WorkspaceStore};
 use zed2::{
-    build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace,
-    languages, Assets, IsOnlyInstance, OpenListener, OpenRequest,
+    app_menus, build_window_options, ensure_only_instance, handle_cli_connection,
+    handle_keymap_file_changes, initialize_workspace, languages, Assets, IsOnlyInstance,
+    OpenListener, OpenRequest,
 };
 
 mod open_listener;
@@ -224,7 +224,7 @@ fn main() {
         // feedback::init(cx);
         welcome::init(cx);
 
-        // cx.set_menus(menus::menus());
+        cx.set_menus(app_menus());
         initialize_workspace(app_state.clone(), cx);
 
         if stdout_is_a_pty() {

crates/zed2/src/zed2.rs πŸ”—

@@ -1,27 +1,30 @@
 #![allow(unused_variables, unused_mut)]
 //todo!()
 
+mod app_menus;
 mod assets;
 pub mod languages;
 mod only_instance;
 mod open_listener;
 
+pub use app_menus::*;
 pub use assets::*;
 use assistant::AssistantPanel;
 use breadcrumbs::Breadcrumbs;
 use collections::VecDeque;
 use editor::{Editor, MultiBuffer};
 use gpui::{
-    actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions,
+    actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View,
     ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
 };
 pub use only_instance::*;
 pub use open_listener::*;
 
 use anyhow::{anyhow, Context as _};
+use futures::{channel::mpsc, StreamExt};
 use project_panel::ProjectPanel;
 use quick_action_bar::QuickActionBar;
-use settings::{initial_local_settings_content, Settings};
+use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings};
 use std::{borrow::Cow, ops::Deref, sync::Arc};
 use terminal_view::terminal_panel::TerminalPanel;
 use util::{
@@ -31,6 +34,7 @@ use util::{
     ResultExt,
 };
 use uuid::Uuid;
+use workspace::Pane;
 use workspace::{
     create_and_open_local_file, dock::PanelHandle,
     notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
@@ -93,37 +97,12 @@ pub fn build_window_options(
 pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
     cx.observe_new_views(move |workspace: &mut Workspace, cx| {
         let workspace_handle = cx.view().clone();
+        let center_pane = workspace.active_pane().clone();
+        initialize_pane(workspace, &center_pane, cx);
         cx.subscribe(&workspace_handle, {
             move |workspace, _, event, cx| {
                 if let workspace::Event::PaneAdded(pane) = event {
-                    pane.update(cx, |pane, cx| {
-                        pane.toolbar().update(cx, |toolbar, cx| {
-                            let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
-                            toolbar.add_item(breadcrumbs, cx);
-                            let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
-                            toolbar.add_item(buffer_search_bar.clone(), cx);
-
-                            let quick_action_bar = cx
-                                .build_view(|_| QuickActionBar::new(buffer_search_bar, workspace));
-                            toolbar.add_item(quick_action_bar, cx);
-                            let diagnostic_editor_controls =
-                                cx.build_view(|_| diagnostics::ToolbarControls::new());
-                            //     toolbar.add_item(diagnostic_editor_controls, cx);
-                            //     let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
-                            //     toolbar.add_item(project_search_bar, cx);
-                            //     let submit_feedback_button =
-                            //         cx.add_view(|_| SubmitFeedbackButton::new());
-                            //     toolbar.add_item(submit_feedback_button, cx);
-                            //     let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
-                            //     toolbar.add_item(feedback_info_text, cx);
-                            //     let lsp_log_item =
-                            //         cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
-                            //     toolbar.add_item(lsp_log_item, cx);
-                            //     let syntax_tree_item = cx
-                            //         .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
-                            //     toolbar.add_item(syntax_tree_item, cx);
-                        })
-                    });
+                    initialize_pane(workspace, pane, cx);
                 }
             }
         })
@@ -435,6 +414,36 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
     .detach();
 }
 
+fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
+    pane.update(cx, |pane, cx| {
+        pane.toolbar().update(cx, |toolbar, cx| {
+            let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
+            toolbar.add_item(breadcrumbs, cx);
+            let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
+            toolbar.add_item(buffer_search_bar.clone(), cx);
+
+            let quick_action_bar =
+                cx.build_view(|_| QuickActionBar::new(buffer_search_bar, workspace));
+            toolbar.add_item(quick_action_bar, cx);
+            let diagnostic_editor_controls = cx.build_view(|_| diagnostics::ToolbarControls::new());
+            //     toolbar.add_item(diagnostic_editor_controls, cx);
+            //     let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
+            //     toolbar.add_item(project_search_bar, cx);
+            //     let submit_feedback_button =
+            //         cx.add_view(|_| SubmitFeedbackButton::new());
+            //     toolbar.add_item(submit_feedback_button, cx);
+            //     let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
+            //     toolbar.add_item(feedback_info_text, cx);
+            //     let lsp_log_item =
+            //         cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
+            //     toolbar.add_item(lsp_log_item, cx);
+            //     let syntax_tree_item = cx
+            //         .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
+            //     toolbar.add_item(syntax_tree_item, cx);
+        })
+    });
+}
+
 fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
     use std::fmt::Write as _;
 
@@ -560,6 +569,42 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
         .detach();
 }
 
+pub fn handle_keymap_file_changes(
+    mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
+    cx: &mut AppContext,
+) {
+    cx.spawn(move |cx| async move {
+        //  let mut settings_subscription = None;
+        while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
+            if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
+                cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok();
+
+                // todo!()
+                // let mut old_base_keymap = cx.read(|cx| *settings::get::<BaseKeymap>(cx));
+                // drop(settings_subscription);
+                // settings_subscription = Some(cx.update(|cx| {
+                //     cx.observe_global::<SettingsStore, _>(move |cx| {
+                //         let new_base_keymap = *settings::get::<BaseKeymap>(cx);
+                //         if new_base_keymap != old_base_keymap {
+                //             old_base_keymap = new_base_keymap.clone();
+                //             reload_keymaps(cx, &keymap_content);
+                //         }
+                //     })
+                // }));
+            }
+        }
+    })
+    .detach();
+}
+
+fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
+    // todo!()
+    // cx.clear_bindings();
+    load_default_keymap(cx);
+    keymap_content.clone().add_to_cx(cx).log_err();
+    cx.set_menus(app_menus());
+}
+
 fn open_local_settings_file(
     workspace: &mut Workspace,
     _: &OpenLocalSettings,