Detailed changes
@@ -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 }}
@@ -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
@@ -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",
@@ -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>,
}
@@ -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");
})
});
@@ -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");
})
});
@@ -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);
}
}
});
@@ -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
@@ -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 {
@@ -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)
+ }
+}
@@ -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},
@@ -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";
@@ -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;
@@ -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 {
@@ -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())
);
@@ -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);
+ })
+ }
+ });
});
- });
- })
+ },
+ )
}
}
@@ -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},
@@ -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);
@@ -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 {
@@ -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>,
@@ -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,
@@ -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()
}
@@ -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>;
@@ -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();
+ }
+ }));
+}
@@ -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();
@@ -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];
@@ -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"
}
@@ -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 {
@@ -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());
-}
@@ -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",
}
}
}
@@ -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::*;
@@ -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))
+ }
+}
@@ -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)
+ }
+}
@@ -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)
+ }
+}
@@ -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),
+ )
}
}
@@ -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;
@@ -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");
}
});
@@ -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]
@@ -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(),
+ },
+ ),
+ ],
+ },
+ ]
+}
@@ -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() {
@@ -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, ¢er_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,