Merge remote-tracking branch 'origin/main' into assistant-2
# Conflicts: # crates/ui2/src/components/icon.rs
This commit is contained in:
commit
14def2a1a3
41 changed files with 1667 additions and 1270 deletions
258
.github/workflows/ci.yml
vendored
258
.github/workflows/ci.yml
vendored
|
@ -1,144 +1,144 @@
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- "v[0-9]+.[0-9]+.x"
|
- "v[0-9]+.[0-9]+.x"
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
rustfmt:
|
rustfmt:
|
||||||
name: Check formatting
|
name: Check formatting
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- test
|
- test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Set up default .cargo/config.toml
|
- name: Set up default .cargo/config.toml
|
||||||
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
||||||
|
|
||||||
- name: Run rustfmt
|
- name: Run rustfmt
|
||||||
uses: ./.github/actions/check_formatting
|
uses: ./.github/actions/check_formatting
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- test
|
- test
|
||||||
needs: rustfmt
|
needs: rustfmt
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
uses: ./.github/actions/run_tests
|
uses: ./.github/actions/run_tests
|
||||||
|
|
||||||
- name: Build collab
|
- name: Build collab
|
||||||
run: cargo build -p collab
|
run: cargo build -p collab
|
||||||
|
|
||||||
- name: Build other binaries
|
- name: Build other binaries
|
||||||
run: cargo build --workspace --bins --all-features
|
run: cargo build --workspace --bins --all-features
|
||||||
|
|
||||||
bundle:
|
bundle:
|
||||||
name: Bundle app
|
name: Bundle app
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- bundle
|
- bundle
|
||||||
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
||||||
needs: tests
|
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: ""
|
|
||||||
env:
|
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 }}
|
||||||
|
|
160
.github/workflows/release_nightly.yml
vendored
160
.github/workflows/release_nightly.yml
vendored
|
@ -1,98 +1,98 @@
|
||||||
name: Release Nightly
|
name: Release Nightly
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
# Fire every night at 1:00am
|
# Fire every night at 1:00am
|
||||||
- cron: "0 1 * * *"
|
- cron: "0 1 * * *"
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "nightly"
|
- "nightly"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
rustfmt:
|
rustfmt:
|
||||||
name: Check formatting
|
name: Check formatting
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- test
|
- test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Run rustfmt
|
- name: Run rustfmt
|
||||||
uses: ./.github/actions/check_formatting
|
uses: ./.github/actions/check_formatting
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- test
|
- test
|
||||||
needs: rustfmt
|
needs: rustfmt
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
uses: ./.github/actions/run_tests
|
uses: ./.github/actions/run_tests
|
||||||
|
|
||||||
bundle:
|
bundle:
|
||||||
name: Bundle app
|
name: Bundle app
|
||||||
runs-on:
|
runs-on:
|
||||||
- self-hosted
|
- self-hosted
|
||||||
- bundle
|
- bundle
|
||||||
needs: tests
|
needs: tests
|
||||||
env:
|
env:
|
||||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
run: |
|
run: |
|
||||||
rustup set profile minimal
|
rustup set profile minimal
|
||||||
rustup update stable
|
rustup update stable
|
||||||
rustup target add aarch64-apple-darwin
|
rustup target add aarch64-apple-darwin
|
||||||
rustup target add x86_64-apple-darwin
|
rustup target add x86_64-apple-darwin
|
||||||
rustup target add wasm32-wasi
|
rustup target add wasm32-wasi
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Limit target directory size
|
- name: Limit target directory size
|
||||||
run: script/clear-target-dir-if-larger-than 100
|
run: script/clear-target-dir-if-larger-than 100
|
||||||
|
|
||||||
- name: Set release channel to nightly
|
- name: Set release channel to nightly
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
version=$(git rev-parse --short HEAD)
|
version=$(git rev-parse --short HEAD)
|
||||||
echo "Publishing version: ${version} on release channel nightly"
|
echo "Publishing version: ${version} on release channel nightly"
|
||||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||||
|
|
||||||
- name: Generate license file
|
- name: Generate license file
|
||||||
run: script/generate-licenses
|
run: script/generate-licenses
|
||||||
|
|
||||||
- name: Create app bundle
|
- name: Create app bundle
|
||||||
run: script/bundle -2
|
run: script/bundle -2
|
||||||
|
|
||||||
- name: Upload Zed Nightly
|
- name: Upload Zed Nightly
|
||||||
run: script/upload-nightly
|
run: script/upload-nightly
|
||||||
|
|
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -2159,6 +2159,7 @@ dependencies = [
|
||||||
"settings2",
|
"settings2",
|
||||||
"smol",
|
"smol",
|
||||||
"theme2",
|
"theme2",
|
||||||
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -11746,7 +11747,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.116.0"
|
version = "0.117.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
"ai",
|
"ai",
|
||||||
|
|
|
@ -23,11 +23,13 @@ pub type HashMap<K, V> = std::collections::HashMap<K, V>;
|
||||||
#[cfg(not(feature = "test-support"))]
|
#[cfg(not(feature = "test-support"))]
|
||||||
pub type HashSet<T> = std::collections::HashSet<T>;
|
pub type HashSet<T> = std::collections::HashSet<T>;
|
||||||
|
|
||||||
|
use std::any::TypeId;
|
||||||
pub use std::collections::*;
|
pub use std::collections::*;
|
||||||
|
|
||||||
// NEW TYPES
|
// NEW TYPES
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CommandPaletteFilter {
|
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| {
|
let filtered = cx.read(|cx| {
|
||||||
if cx.has_global::<CommandPaletteFilter>() {
|
if cx.has_global::<CommandPaletteFilter>() {
|
||||||
let filter = cx.global::<CommandPaletteFilter>();
|
let filter = cx.global::<CommandPaletteFilter>();
|
||||||
filter.filtered_namespaces.contains(action.namespace())
|
filter.hidden_namespaces.contains(action.namespace())
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -430,7 +430,7 @@ mod tests {
|
||||||
// Add namespace filter, and redeploy the palette
|
// Add namespace filter, and redeploy the palette
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
|
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| {
|
.filter_map(|action| {
|
||||||
let name = gpui::remove_the_2(action.name());
|
let name = gpui::remove_the_2(action.name());
|
||||||
let namespace = name.split("::").next().unwrap_or("malformed 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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,7 +436,7 @@ mod tests {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
cx.set_global(CommandPaletteFilter::default());
|
cx.set_global(CommandPaletteFilter::default());
|
||||||
cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
|
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| {
|
cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
|
||||||
match status {
|
match status {
|
||||||
Status::Disabled => {
|
Status::Disabled => {
|
||||||
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
|
filter.hidden_namespaces.insert(COPILOT_NAMESPACE);
|
||||||
filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
|
filter.hidden_namespaces.insert(COPILOT_AUTH_NAMESPACE);
|
||||||
}
|
}
|
||||||
Status::Authorized => {
|
Status::Authorized => {
|
||||||
filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
|
filter.hidden_namespaces.remove(COPILOT_NAMESPACE);
|
||||||
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
|
filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
|
filter.hidden_namespaces.insert(COPILOT_NAMESPACE);
|
||||||
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
|
filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,7 @@ theme = { package = "theme2", path = "../theme2" }
|
||||||
lsp = { package = "lsp2", path = "../lsp2" }
|
lsp = { package = "lsp2", path = "../lsp2" }
|
||||||
node_runtime = { path = "../node_runtime"}
|
node_runtime = { path = "../node_runtime"}
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
|
||||||
async-tar = "0.4.2"
|
async-tar = "0.4.2"
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
|
|
@ -22,6 +22,7 @@ use request::StatusNotification;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||||
use std::{
|
use std::{
|
||||||
|
any::TypeId,
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
mem,
|
mem,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
|
@ -32,13 +33,14 @@ use util::{
|
||||||
fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
|
fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo!()
|
actions!(
|
||||||
// const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
|
Suggest,
|
||||||
actions!(SignIn, SignOut);
|
NextSuggestion,
|
||||||
|
PreviousSuggestion,
|
||||||
// todo!()
|
Reinstall,
|
||||||
// const COPILOT_NAMESPACE: &'static str = "copilot";
|
SignIn,
|
||||||
actions!(Suggest, NextSuggestion, PreviousSuggestion, Reinstall);
|
SignOut
|
||||||
|
);
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
new_server_id: LanguageServerId,
|
new_server_id: LanguageServerId,
|
||||||
|
@ -51,52 +53,70 @@ pub fn init(
|
||||||
move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
|
move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
|
||||||
});
|
});
|
||||||
cx.set_global(copilot.clone());
|
cx.set_global(copilot.clone());
|
||||||
|
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>();
|
||||||
|
|
||||||
// TODO
|
match status {
|
||||||
// cx.observe(&copilot, |handle, cx| {
|
Status::Disabled => {
|
||||||
// let status = handle.read(cx).status();
|
filter.hidden_action_types.extend(copilot_action_types);
|
||||||
// cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
|
filter.hidden_action_types.extend(copilot_auth_action_types);
|
||||||
// match status {
|
filter
|
||||||
// Status::Disabled => {
|
.hidden_action_types
|
||||||
// filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
|
.extend(copilot_no_auth_action_types);
|
||||||
// filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
|
}
|
||||||
// }
|
Status::Authorized => {
|
||||||
// Status::Authorized => {
|
filter
|
||||||
// filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
|
.hidden_action_types
|
||||||
// filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
|
.extend(copilot_no_auth_action_types);
|
||||||
// }
|
for type_id in copilot_action_types
|
||||||
// _ => {
|
.iter()
|
||||||
// filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
|
.chain(&copilot_auth_action_types)
|
||||||
// filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
|
{
|
||||||
// }
|
filter.hidden_action_types.remove(type_id);
|
||||||
// }
|
}
|
||||||
// });
|
}
|
||||||
// })
|
_ => {
|
||||||
// .detach();
|
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);
|
sign_in::init(cx);
|
||||||
// cx.add_global_action(|_: &SignIn, cx| {
|
cx.on_action(|_: &SignIn, cx| {
|
||||||
// if let Some(copilot) = Copilot::global(cx) {
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
// copilot
|
copilot
|
||||||
// .update(cx, |copilot, cx| copilot.sign_in(cx))
|
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||||
// .detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// cx.add_global_action(|_: &SignOut, cx| {
|
cx.on_action(|_: &SignOut, cx| {
|
||||||
// if let Some(copilot) = Copilot::global(cx) {
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
// copilot
|
copilot
|
||||||
// .update(cx, |copilot, cx| copilot.sign_out(cx))
|
.update(cx, |copilot, cx| copilot.sign_out(cx))
|
||||||
// .detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
cx.on_action(|_: &Reinstall, cx| {
|
||||||
// cx.add_global_action(|_: &Reinstall, cx| {
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
// if let Some(copilot) = Copilot::global(cx) {
|
copilot
|
||||||
// copilot
|
.update(cx, |copilot, cx| copilot.reinstall(cx))
|
||||||
// .update(cx, |copilot, cx| copilot.reinstall(cx))
|
.detach();
|
||||||
// .detach();
|
}
|
||||||
// }
|
});
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CopilotServer {
|
enum CopilotServer {
|
||||||
|
|
|
@ -1,376 +1,213 @@
|
||||||
// TODO add logging in
|
use crate::{request::PromptUserDeviceFlow, Copilot, Status};
|
||||||
// use crate::{request::PromptUserDeviceFlow, Copilot, Status};
|
use gpui::{
|
||||||
// use gpui::{
|
div, size, AppContext, Bounds, ClipboardItem, Div, Element, GlobalPixels, InteractiveElement,
|
||||||
// elements::*,
|
IntoElement, ParentElement, Point, Render, Stateful, Styled, ViewContext, VisualContext,
|
||||||
// geometry::rect::RectF,
|
WindowBounds, WindowHandle, WindowKind, WindowOptions,
|
||||||
// platform::{WindowBounds, WindowKind, WindowOptions},
|
};
|
||||||
// AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
|
use theme::ActiveTheme;
|
||||||
// WindowHandle,
|
use ui::{prelude::*, Button, Icon, IconElement, Label};
|
||||||
// };
|
|
||||||
// use theme::ui::modal;
|
|
||||||
|
|
||||||
// #[derive(PartialEq, Eq, Debug, Clone)]
|
const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
|
||||||
// struct CopyUserCode;
|
|
||||||
|
|
||||||
// #[derive(PartialEq, Eq, Debug, Clone)]
|
pub fn init(cx: &mut AppContext) {
|
||||||
// struct OpenGithub;
|
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();
|
||||||
|
|
||||||
// const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pub fn init(cx: &mut AppContext) {
|
fn create_copilot_auth_window(
|
||||||
// if let Some(copilot) = Copilot::global(cx) {
|
cx: &mut AppContext,
|
||||||
// let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
|
status: &Status,
|
||||||
// cx.observe(&copilot, move |copilot, cx| {
|
) -> WindowHandle<CopilotCodeVerification> {
|
||||||
// let status = copilot.read(cx).status();
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// match &status {
|
pub struct CopilotCodeVerification {
|
||||||
// crate::Status::SigningIn { prompt } => {
|
status: Status,
|
||||||
// if let Some(window) = verification_window.as_mut() {
|
connect_clicked: bool,
|
||||||
// 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(
|
impl CopilotCodeVerification {
|
||||||
// cx: &mut AppContext,
|
pub fn new(status: Status) -> Self {
|
||||||
// status: &Status,
|
Self {
|
||||||
// ) -> WindowHandle<CopilotCodeVerification> {
|
status,
|
||||||
// let window_size = theme::current(cx).copilot.modal.dimensions();
|
connect_clicked: false,
|
||||||
// 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 {
|
pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
|
||||||
// status: Status,
|
self.status = status;
|
||||||
// connect_clicked: bool,
|
cx.notify();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// impl CopilotCodeVerification {
|
fn render_device_code(
|
||||||
// pub fn new(status: Status) -> Self {
|
data: &PromptUserDeviceFlow,
|
||||||
// Self {
|
cx: &mut ViewContext<Self>,
|
||||||
// status,
|
) -> impl IntoElement {
|
||||||
// connect_clicked: false,
|
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" }))
|
||||||
|
}
|
||||||
|
|
||||||
// pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
|
fn render_prompting_modal(
|
||||||
// self.status = status;
|
connect_clicked: bool,
|
||||||
// cx.notify();
|
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_device_code(
|
fn render_unauthorized_modal() -> impl Element {
|
||||||
// data: &PromptUserDeviceFlow,
|
v_stack()
|
||||||
// style: &theme::Copilot,
|
.child(Label::new(
|
||||||
// cx: &mut ViewContext<Self>,
|
"Enable Copilot by connecting your existing license.",
|
||||||
// ) -> impl IntoAnyElement<Self> {
|
))
|
||||||
// let copied = cx
|
.child(
|
||||||
// .read_from_clipboard()
|
Label::new("You must have an active Copilot license to use it in Zed.")
|
||||||
// .map(|item| item.text() == &data.user_code)
|
.color(Color::Warning),
|
||||||
// .unwrap_or(false);
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("copilot-subscribe-button", "Subscibe on Github").on_click(|_, cx| {
|
||||||
|
cx.remove_window();
|
||||||
|
cx.open_url(COPILOT_SIGN_UP_URL)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// let device_code_style = &style.auth.prompting.device_code;
|
impl Render for CopilotCodeVerification {
|
||||||
|
type Element = Stateful<Div>;
|
||||||
|
|
||||||
// MouseEventHandler::new::<Self, _>(0, cx, |state, _cx| {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
// Flex::row()
|
let prompt = match &self.status {
|
||||||
// .with_child(
|
Status::SigningIn {
|
||||||
// Label::new(data.user_code.clone(), device_code_style.text.clone())
|
prompt: Some(prompt),
|
||||||
// .aligned()
|
} => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
|
||||||
// .contained()
|
Status::Unauthorized => {
|
||||||
// .with_style(device_code_style.left_container)
|
self.connect_clicked = false;
|
||||||
// .constrained()
|
Self::render_unauthorized_modal().into_any_element()
|
||||||
// .with_width(device_code_style.left),
|
}
|
||||||
// )
|
Status::Authorized => {
|
||||||
// .with_child(
|
self.connect_clicked = false;
|
||||||
// Label::new(
|
Self::render_enabled_modal().into_any_element()
|
||||||
// if copied { "Copied!" } else { "Copy" },
|
}
|
||||||
// device_code_style.cta.style_for(state).text.clone(),
|
_ => div().into_any_element(),
|
||||||
// )
|
};
|
||||||
// .aligned()
|
div()
|
||||||
// .contained()
|
.id("copilot code verification")
|
||||||
// .with_style(*device_code_style.right_container.style_for(state))
|
.flex()
|
||||||
// .constrained()
|
.flex_col()
|
||||||
// .with_width(device_code_style.right),
|
.size_full()
|
||||||
// )
|
.items_center()
|
||||||
// .contained()
|
.p_10()
|
||||||
// .with_style(device_code_style.cta.style_for(state).container)
|
.bg(cx.theme().colors().element_background)
|
||||||
// })
|
.child(ui::Label::new("Connect Copilot to Zed"))
|
||||||
// .on_click(gpui::platform::MouseButton::Left, {
|
.child(IconElement::new(Icon::ZedXCopilot))
|
||||||
// let user_code = data.user_code.clone();
|
.child(prompt)
|
||||||
// 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()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
pub use toolbar_controls::ToolbarControls;
|
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 util::TryFutureExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
|
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
|
||||||
|
|
|
@ -993,7 +993,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::display_map::inlay_map::InlayMap;
|
use crate::display_map::inlay_map::InlayMap;
|
||||||
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
|
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 multi_buffer::MultiBuffer;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
|
@ -1185,11 +1185,7 @@ mod tests {
|
||||||
fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
|
fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
|
||||||
cx.update(|cx| init_test(cx));
|
cx.update(|cx| init_test(cx));
|
||||||
|
|
||||||
let font_id = cx
|
let font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
|
||||||
.test_platform
|
|
||||||
.text_system()
|
|
||||||
.font_id(&font("Helvetica"))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let text = "one two three\nfour five six\nseven eight";
|
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},
|
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||||
MultiBuffer,
|
MultiBuffer,
|
||||||
};
|
};
|
||||||
use gpui::{font, px, test::observe, Platform};
|
use gpui::{font, px, test::observe};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
|
|
|
@ -92,6 +92,7 @@ use std::{
|
||||||
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
|
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
sync::Weak,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
pub use sum_tree::Bias;
|
pub use sum_tree::Bias;
|
||||||
|
@ -420,6 +421,25 @@ pub fn init(cx: &mut AppContext) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.detach();
|
.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 {
|
trait InvalidationRegion {
|
||||||
|
|
|
@ -12,7 +12,7 @@ use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div,
|
div,
|
||||||
serde_json::{self, json},
|
serde_json::{self, json},
|
||||||
Div, Flatten, Platform, TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
|
Div, Flatten, TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
|
||||||
};
|
};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::{
|
use language::{
|
||||||
|
@ -3238,9 +3238,7 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
|
||||||
the lazy dog"});
|
the lazy dog"});
|
||||||
cx.update_editor(|e, cx| e.copy(&Copy, cx));
|
cx.update_editor(|e, cx| e.copy(&Copy, cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cx.test_platform
|
cx.read_from_clipboard().map(|item| item.text().to_owned()),
|
||||||
.read_from_clipboard()
|
|
||||||
.map(|item| item.text().to_owned()),
|
|
||||||
Some("fox jumps over\n".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 focus_handle = editor.focus_handle(cx);
|
||||||
let dispatch_context = self.editor.read(cx).dispatch_context(cx);
|
let dispatch_context = self.editor.read(cx).dispatch_context(cx);
|
||||||
cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
|
cx.with_key_dispatch(
|
||||||
self.register_actions(cx);
|
Some(dispatch_context),
|
||||||
self.register_key_listeners(cx);
|
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.
|
// We call with_z_index to establish a new stacking context.
|
||||||
cx.with_z_index(0, |cx| {
|
cx.with_z_index(0, |cx| {
|
||||||
cx.with_content_mask(Some(ContentMask { bounds }), |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
|
// Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
|
||||||
// take precedence.
|
// take precedence.
|
||||||
cx.with_z_index(0, |cx| {
|
cx.with_z_index(0, |cx| {
|
||||||
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, 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);
|
||||||
|
|
||||||
|
if !layout.blocks.is_empty() {
|
||||||
|
cx.with_element_id(Some("editor_blocks"), |cx| {
|
||||||
|
self.paint_blocks(bounds, &mut 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);
|
|
||||||
|
|
||||||
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 text::Selection;
|
||||||
use theme::{ActiveTheme, Theme};
|
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 util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, FollowEvent, FollowableItemHandle},
|
item::{BreadcrumbText, FollowEvent, FollowableItemHandle},
|
||||||
|
|
|
@ -6,7 +6,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use text::{Bias, Point};
|
use text::{Bias, Point};
|
||||||
use theme::ActiveTheme;
|
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;
|
use util::paths::FILE_ROW_COLUMN_DELIMITER;
|
||||||
|
|
||||||
actions!(Toggle);
|
actions!(Toggle);
|
||||||
|
|
|
@ -15,10 +15,10 @@ use smol::future::FutureExt;
|
||||||
pub use test_context::*;
|
pub use test_context::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView,
|
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
||||||
AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||||
DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
|
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,
|
PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
|
||||||
TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
|
TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
|
||||||
WindowHandle, WindowId,
|
WindowHandle, WindowId,
|
||||||
|
@ -39,7 +39,10 @@ use std::{
|
||||||
sync::{atomic::Ordering::SeqCst, Arc},
|
sync::{atomic::Ordering::SeqCst, Arc},
|
||||||
time::Duration,
|
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.
|
/// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
|
||||||
/// Strongly consider removing after stabilization.
|
/// Strongly consider removing after stabilization.
|
||||||
|
@ -201,7 +204,7 @@ pub struct AppContext {
|
||||||
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
|
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
|
||||||
pub(crate) keymap: Arc<Mutex<Keymap>>,
|
pub(crate) keymap: Arc<Mutex<Keymap>>,
|
||||||
pub(crate) global_action_listeners:
|
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>,
|
pending_effects: VecDeque<Effect>,
|
||||||
pub(crate) pending_notifications: HashSet<EntityId>,
|
pub(crate) pending_notifications: HashSet<EntityId>,
|
||||||
pub(crate) pending_global_notifications: HashSet<TypeId>,
|
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({
|
platform.on_quit(Box::new({
|
||||||
let cx = app.clone();
|
let cx = app.clone();
|
||||||
move || {
|
move || {
|
||||||
|
@ -425,6 +430,10 @@ impl AppContext {
|
||||||
.collect()
|
.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.
|
/// 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
|
/// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
|
||||||
/// functionality.
|
/// functionality.
|
||||||
|
@ -962,9 +971,9 @@ impl AppContext {
|
||||||
self.global_action_listeners
|
self.global_action_listeners
|
||||||
.entry(TypeId::of::<A>())
|
.entry(TypeId::of::<A>())
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(Box::new(move |action, phase, cx| {
|
.push(Rc::new(move |action, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
let action = action.as_any().downcast_ref().unwrap();
|
let action = action.downcast_ref().unwrap();
|
||||||
listener(action, cx)
|
listener(action, cx)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -1015,6 +1024,90 @@ impl AppContext {
|
||||||
activate();
|
activate();
|
||||||
subscription
|
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 {
|
impl Context for AppContext {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||||
BackgroundExecutor, Bounds, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
|
BackgroundExecutor, Bounds, ClipboardItem, Context, Div, Entity, EventEmitter,
|
||||||
KeyDownEvent, Keystroke, Model, ModelContext, Pixels, PlatformWindow, Point, Render, Result,
|
ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform,
|
||||||
Size, Task, TestDispatcher, TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext,
|
PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
|
||||||
VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions,
|
TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext,
|
||||||
|
WindowHandle, WindowOptions,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
|
@ -16,6 +17,7 @@ pub struct TestAppContext {
|
||||||
pub foreground_executor: ForegroundExecutor,
|
pub foreground_executor: ForegroundExecutor,
|
||||||
pub dispatcher: TestDispatcher,
|
pub dispatcher: TestDispatcher,
|
||||||
pub test_platform: Rc<TestPlatform>,
|
pub test_platform: Rc<TestPlatform>,
|
||||||
|
text_system: Arc<TextSystem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context for TestAppContext {
|
impl Context for TestAppContext {
|
||||||
|
@ -82,6 +84,7 @@ impl TestAppContext {
|
||||||
let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
|
let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
|
||||||
let asset_source = Arc::new(());
|
let asset_source = Arc::new(());
|
||||||
let http_client = util::http::FakeHttpClient::with_404_response();
|
let http_client = util::http::FakeHttpClient::with_404_response();
|
||||||
|
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
app: AppContext::new(platform.clone(), asset_source, http_client),
|
app: AppContext::new(platform.clone(), asset_source, http_client),
|
||||||
|
@ -89,6 +92,7 @@ impl TestAppContext {
|
||||||
foreground_executor,
|
foreground_executor,
|
||||||
dispatcher: dispatcher.clone(),
|
dispatcher: dispatcher.clone(),
|
||||||
test_platform: platform,
|
test_platform: platform,
|
||||||
|
text_system,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +159,18 @@ impl TestAppContext {
|
||||||
(view, Box::leak(cx))
|
(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(
|
pub fn simulate_new_path_selection(
|
||||||
&self,
|
&self,
|
||||||
select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
|
select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||||
E: Debug,
|
E: Debug,
|
||||||
{
|
{
|
||||||
if let Some(key_context) = key_context.try_into().log_err() {
|
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
|
self
|
||||||
}
|
}
|
||||||
|
@ -722,7 +722,7 @@ impl DivState {
|
||||||
|
|
||||||
pub struct Interactivity {
|
pub struct Interactivity {
|
||||||
pub element_id: Option<ElementId>,
|
pub element_id: Option<ElementId>,
|
||||||
pub key_context: KeyContext,
|
pub key_context: Option<KeyContext>,
|
||||||
pub focusable: bool,
|
pub focusable: bool,
|
||||||
pub tracked_focus_handle: Option<FocusHandle>,
|
pub tracked_focus_handle: Option<FocusHandle>,
|
||||||
pub scroll_handle: Option<ScrollHandle>,
|
pub scroll_handle: Option<ScrollHandle>,
|
||||||
|
@ -1238,7 +1238,7 @@ impl Default for Interactivity {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
element_id: None,
|
element_id: None,
|
||||||
key_context: KeyContext::default(),
|
key_context: None,
|
||||||
focusable: false,
|
focusable: false,
|
||||||
tracked_focus_handle: None,
|
tracked_focus_handle: None,
|
||||||
scroll_handle: None,
|
scroll_handle: None,
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub(crate) struct DispatchTree {
|
||||||
pub(crate) struct DispatchNode {
|
pub(crate) struct DispatchNode {
|
||||||
pub key_listeners: SmallVec<[KeyListener; 2]>,
|
pub key_listeners: SmallVec<[KeyListener; 2]>,
|
||||||
pub action_listeners: SmallVec<[DispatchActionListener; 16]>,
|
pub action_listeners: SmallVec<[DispatchActionListener; 16]>,
|
||||||
pub context: KeyContext,
|
pub context: Option<KeyContext>,
|
||||||
parent: Option<DispatchNodeId>,
|
parent: Option<DispatchNodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ impl DispatchTree {
|
||||||
self.keystroke_matchers.clear();
|
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 parent = self.node_stack.last().copied();
|
||||||
let node_id = DispatchNodeId(self.nodes.len());
|
let node_id = DispatchNodeId(self.nodes.len());
|
||||||
self.nodes.push(DispatchNode {
|
self.nodes.push(DispatchNode {
|
||||||
|
@ -69,34 +69,34 @@ impl DispatchTree {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
self.node_stack.push(node_id);
|
self.node_stack.push(node_id);
|
||||||
if !context.is_empty() {
|
if let Some(context) = context {
|
||||||
self.active_node().context = context.clone();
|
self.active_node().context = Some(context.clone());
|
||||||
self.context_stack.push(context);
|
self.context_stack.push(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_node(&mut self) {
|
pub fn pop_node(&mut self) {
|
||||||
let node_id = self.node_stack.pop().unwrap();
|
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();
|
self.context_stack.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_keystroke_matchers(&mut self) {
|
pub fn clear_pending_keystrokes(&mut self) {
|
||||||
self.keystroke_matchers.clear();
|
self.keystroke_matchers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preserve keystroke matchers from previous frames to support multi-stroke
|
/// Preserve keystroke matchers from previous frames to support multi-stroke
|
||||||
/// bindings across multiple frames.
|
/// 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)) {
|
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);
|
let dispatch_path = self.dispatch_path(node_id);
|
||||||
|
|
||||||
self.context_stack.clear();
|
self.context_stack.clear();
|
||||||
for node_id in dispatch_path {
|
for node_id in dispatch_path {
|
||||||
let node = self.node(node_id);
|
let node = self.node(node_id);
|
||||||
if !node.context.is_empty() {
|
if let Some(context) = node.context.clone() {
|
||||||
self.context_stack.push(node.context.clone());
|
self.context_stack.push(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((context_stack, matcher)) = old_tree
|
if let Some((context_stack, matcher)) = old_tree
|
||||||
|
@ -148,21 +148,33 @@ impl DispatchTree {
|
||||||
false
|
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();
|
let mut actions = Vec::new();
|
||||||
if let Some(node) = self.focusable_node_ids.get(&target) {
|
for node_id in self.dispatch_path(target) {
|
||||||
for node_id in self.dispatch_path(*node) {
|
let node = &self.nodes[node_id.0];
|
||||||
let node = &self.nodes[node_id.0];
|
for DispatchActionListener { action_type, .. } in &node.action_listeners {
|
||||||
for DispatchActionListener { action_type, .. } in &node.action_listeners {
|
// Intentionally silence these errors without logging.
|
||||||
// Intentionally silence these errors without logging.
|
// If an action cannot be built by default, it's not available.
|
||||||
// If an action cannot be built by default, it's not available.
|
actions.extend(self.action_registry.build_action_type(action_type).ok());
|
||||||
actions.extend(self.action_registry.build_action_type(action_type).ok());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actions
|
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(
|
pub fn bindings_for_action(
|
||||||
&self,
|
&self,
|
||||||
action: &dyn Action,
|
action: &dyn Action,
|
||||||
|
@ -236,6 +248,11 @@ impl DispatchTree {
|
||||||
self.focusable_node_ids.get(&target).copied()
|
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 {
|
fn active_node_id(&self) -> DispatchNodeId {
|
||||||
*self.node_stack.last().unwrap()
|
*self.node_stack.last().unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod app_menu;
|
||||||
mod keystroke;
|
mod keystroke;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod mac;
|
mod mac;
|
||||||
|
@ -5,10 +6,10 @@ mod mac;
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
point, size, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
|
point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
|
||||||
FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, LineLayout,
|
FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
|
||||||
Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
|
LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
|
||||||
SharedString, Size, TaskLabel,
|
Scene, SharedString, Size, TaskLabel,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
@ -32,6 +33,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub use app_menu::*;
|
||||||
pub use keystroke::*;
|
pub use keystroke::*;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub use mac::*;
|
pub use mac::*;
|
||||||
|
@ -44,7 +46,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||||
Rc::new(MacPlatform::new())
|
Rc::new(MacPlatform::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Platform: 'static {
|
pub(crate) trait Platform: 'static {
|
||||||
fn background_executor(&self) -> BackgroundExecutor;
|
fn background_executor(&self) -> BackgroundExecutor;
|
||||||
fn foreground_executor(&self) -> ForegroundExecutor;
|
fn foreground_executor(&self) -> ForegroundExecutor;
|
||||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
||||||
|
@ -59,7 +61,7 @@ pub trait Platform: 'static {
|
||||||
|
|
||||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||||
fn display(&self, id: DisplayId) -> Option<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(
|
fn open_window(
|
||||||
&self,
|
&self,
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
|
@ -90,6 +92,11 @@ pub trait Platform: 'static {
|
||||||
fn on_reopen(&self, callback: Box<dyn FnMut()>);
|
fn on_reopen(&self, callback: Box<dyn FnMut()>);
|
||||||
fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
|
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_name(&self) -> &'static str;
|
||||||
fn os_version(&self) -> Result<SemanticVersion>;
|
fn os_version(&self) -> Result<SemanticVersion>;
|
||||||
fn app_version(&self) -> Result<SemanticVersion>;
|
fn app_version(&self) -> Result<SemanticVersion>;
|
||||||
|
|
77
crates/gpui2/src/platform/app_menu.rs
Normal file
77
crates/gpui2/src/platform/app_menu.rs
Normal file
|
@ -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::{
|
use crate::{
|
||||||
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
|
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||||
InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow,
|
ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
|
||||||
PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result,
|
MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
|
||||||
SemanticVersion, VideoTimestamp, WindowOptions,
|
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{
|
appkit::{
|
||||||
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
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::{
|
foundation::{
|
||||||
NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
|
NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
|
||||||
NSUInteger, NSURL,
|
NSUInteger, NSURL,
|
||||||
|
@ -155,12 +156,12 @@ pub struct MacPlatformState {
|
||||||
reopen: Option<Box<dyn FnMut()>>,
|
reopen: Option<Box<dyn FnMut()>>,
|
||||||
quit: Option<Box<dyn FnMut()>>,
|
quit: Option<Box<dyn FnMut()>>,
|
||||||
event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
|
event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
|
||||||
// menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
|
menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||||
// validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||||
will_open_menu: Option<Box<dyn FnMut()>>,
|
will_open_menu: Option<Box<dyn FnMut()>>,
|
||||||
|
menu_actions: Vec<Box<dyn Action>>,
|
||||||
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
||||||
finish_launching: Option<Box<dyn FnOnce()>>,
|
finish_launching: Option<Box<dyn FnOnce()>>,
|
||||||
// menu_actions: Vec<Box<dyn Action>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacPlatform {
|
impl MacPlatform {
|
||||||
|
@ -179,12 +180,12 @@ impl MacPlatform {
|
||||||
reopen: None,
|
reopen: None,
|
||||||
quit: None,
|
quit: None,
|
||||||
event: None,
|
event: None,
|
||||||
|
menu_command: None,
|
||||||
|
validate_menu_command: None,
|
||||||
will_open_menu: None,
|
will_open_menu: None,
|
||||||
|
menu_actions: Default::default(),
|
||||||
open_urls: None,
|
open_urls: None,
|
||||||
finish_launching: 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(
|
unsafe fn create_menu_bar(
|
||||||
// &self,
|
&self,
|
||||||
// menus: Vec<Menu>,
|
menus: Vec<Menu>,
|
||||||
// delegate: id,
|
delegate: id,
|
||||||
// actions: &mut Vec<Box<dyn Action>>,
|
actions: &mut Vec<Box<dyn Action>>,
|
||||||
// keystroke_matcher: &KeymapMatcher,
|
keymap: &Keymap,
|
||||||
// ) -> id {
|
) -> id {
|
||||||
// let application_menu = NSMenu::new(nil).autorelease();
|
let application_menu = NSMenu::new(nil).autorelease();
|
||||||
// application_menu.setDelegate_(delegate);
|
application_menu.setDelegate_(delegate);
|
||||||
|
|
||||||
// for menu_config in menus {
|
for menu_config in menus {
|
||||||
// let menu = NSMenu::new(nil).autorelease();
|
let menu = NSMenu::new(nil).autorelease();
|
||||||
// menu.setTitle_(ns_string(menu_config.name));
|
menu.setTitle_(ns_string(menu_config.name));
|
||||||
// menu.setDelegate_(delegate);
|
menu.setDelegate_(delegate);
|
||||||
|
|
||||||
// for item_config in menu_config.items {
|
for item_config in menu_config.items {
|
||||||
// menu.addItem_(self.create_menu_item(
|
menu.addItem_(self.create_menu_item(item_config, delegate, actions, keymap));
|
||||||
// item_config,
|
}
|
||||||
// delegate,
|
|
||||||
// actions,
|
|
||||||
// keystroke_matcher,
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let menu_item = NSMenuItem::new(nil).autorelease();
|
let menu_item = NSMenuItem::new(nil).autorelease();
|
||||||
// menu_item.setSubmenu_(menu);
|
menu_item.setSubmenu_(menu);
|
||||||
// application_menu.addItem_(menu_item);
|
application_menu.addItem_(menu_item);
|
||||||
|
|
||||||
// if menu_config.name == "Window" {
|
if menu_config.name == "Window" {
|
||||||
// let app: id = msg_send![APP_CLASS, sharedApplication];
|
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
// app.setWindowsMenu_(menu);
|
app.setWindowsMenu_(menu);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// application_menu
|
application_menu
|
||||||
// }
|
}
|
||||||
|
|
||||||
// unsafe fn create_menu_item(
|
unsafe fn create_menu_item(
|
||||||
// &self,
|
&self,
|
||||||
// item: MenuItem,
|
item: MenuItem,
|
||||||
// delegate: id,
|
delegate: id,
|
||||||
// actions: &mut Vec<Box<dyn Action>>,
|
actions: &mut Vec<Box<dyn Action>>,
|
||||||
// keystroke_matcher: &KeymapMatcher,
|
keymap: &Keymap,
|
||||||
// ) -> id {
|
) -> id {
|
||||||
// match item {
|
match item {
|
||||||
// MenuItem::Separator => NSMenuItem::separatorItem(nil),
|
MenuItem::Separator => NSMenuItem::separatorItem(nil),
|
||||||
// MenuItem::Action {
|
MenuItem::Action {
|
||||||
// name,
|
name,
|
||||||
// action,
|
action,
|
||||||
// os_action,
|
os_action,
|
||||||
// } => {
|
} => {
|
||||||
// // TODO
|
let keystrokes = keymap
|
||||||
// let keystrokes = keystroke_matcher
|
.bindings_for_action(action.type_id())
|
||||||
// .bindings_for_action(action.id())
|
.find(|binding| binding.action().partial_eq(action.as_ref()))
|
||||||
// .find(|binding| binding.action().eq(action.as_ref()))
|
.map(|binding| binding.keystrokes());
|
||||||
// .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;
|
let selector = match os_action {
|
||||||
// if let Some(keystrokes) = keystrokes {
|
Some(crate::OsAction::Cut) => selector("cut:"),
|
||||||
// if keystrokes.len() == 1 {
|
Some(crate::OsAction::Copy) => selector("copy:"),
|
||||||
// let keystroke = &keystrokes[0];
|
Some(crate::OsAction::Paste) => selector("paste:"),
|
||||||
// let mut mask = NSEventModifierFlags::empty();
|
Some(crate::OsAction::SelectAll) => selector("selectAll:"),
|
||||||
// for (modifier, flag) in &[
|
Some(crate::OsAction::Undo) => selector("undo:"),
|
||||||
// (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
|
Some(crate::OsAction::Redo) => selector("redo:"),
|
||||||
// (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
|
None => selector("handleGPUIMenuItem:"),
|
||||||
// (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
|
};
|
||||||
// (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
|
|
||||||
// ] {
|
|
||||||
// if *modifier {
|
|
||||||
// mask |= *flag;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// item = NSMenuItem::alloc(nil)
|
let item;
|
||||||
// .initWithTitle_action_keyEquivalent_(
|
if let Some(keystrokes) = keystrokes {
|
||||||
// ns_string(name),
|
if keystrokes.len() == 1 {
|
||||||
// selector,
|
let keystroke = &keystrokes[0];
|
||||||
// ns_string(key_to_native(&keystroke.key).as_ref()),
|
let mut mask = NSEventModifierFlags::empty();
|
||||||
// )
|
for (modifier, flag) in &[
|
||||||
// .autorelease();
|
(
|
||||||
// item.setKeyEquivalentModifierMask_(mask);
|
keystroke.modifiers.command,
|
||||||
// }
|
NSEventModifierFlags::NSCommandKeyMask,
|
||||||
// // For multi-keystroke bindings, render the keystroke as part of the title.
|
),
|
||||||
// else {
|
(
|
||||||
// use std::fmt::Write;
|
keystroke.modifiers.control,
|
||||||
|
NSEventModifierFlags::NSControlKeyMask,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
keystroke.modifiers.alt,
|
||||||
|
NSEventModifierFlags::NSAlternateKeyMask,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
keystroke.modifiers.shift,
|
||||||
|
NSEventModifierFlags::NSShiftKeyMask,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
if *modifier {
|
||||||
|
mask |= *flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// let mut name = format!("{name} [");
|
item = NSMenuItem::alloc(nil)
|
||||||
// for (i, keystroke) in keystrokes.iter().enumerate() {
|
.initWithTitle_action_keyEquivalent_(
|
||||||
// if i > 0 {
|
ns_string(name),
|
||||||
// name.push(' ');
|
selector,
|
||||||
// }
|
ns_string(key_to_native(&keystroke.key).as_ref()),
|
||||||
// write!(&mut name, "{}", keystroke).unwrap();
|
)
|
||||||
// }
|
.autorelease();
|
||||||
// name.push(']');
|
item.setKeyEquivalentModifierMask_(mask);
|
||||||
|
}
|
||||||
|
// For multi-keystroke bindings, render the keystroke as part of the title.
|
||||||
|
else {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
// item = NSMenuItem::alloc(nil)
|
let mut name = format!("{name} [");
|
||||||
// .initWithTitle_action_keyEquivalent_(
|
for (i, keystroke) in keystrokes.iter().enumerate() {
|
||||||
// ns_string(&name),
|
if i > 0 {
|
||||||
// selector,
|
name.push(' ');
|
||||||
// ns_string(""),
|
}
|
||||||
// )
|
write!(&mut name, "{}", keystroke).unwrap();
|
||||||
// .autorelease();
|
}
|
||||||
// }
|
name.push(']');
|
||||||
// } else {
|
|
||||||
// item = NSMenuItem::alloc(nil)
|
|
||||||
// .initWithTitle_action_keyEquivalent_(
|
|
||||||
// ns_string(name),
|
|
||||||
// selector,
|
|
||||||
// ns_string(""),
|
|
||||||
// )
|
|
||||||
// .autorelease();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let tag = actions.len() as NSInteger;
|
item = NSMenuItem::alloc(nil)
|
||||||
// let _: () = msg_send![item, setTag: tag];
|
.initWithTitle_action_keyEquivalent_(
|
||||||
// actions.push(action);
|
ns_string(&name),
|
||||||
// item
|
selector,
|
||||||
// }
|
ns_string(""),
|
||||||
// MenuItem::Submenu(Menu { name, items }) => {
|
)
|
||||||
// let item = NSMenuItem::new(nil).autorelease();
|
.autorelease();
|
||||||
// let submenu = NSMenu::new(nil).autorelease();
|
}
|
||||||
// submenu.setDelegate_(delegate);
|
} else {
|
||||||
// for item in items {
|
item = NSMenuItem::alloc(nil)
|
||||||
// submenu.addItem_(self.create_menu_item(
|
.initWithTitle_action_keyEquivalent_(
|
||||||
// item,
|
ns_string(name),
|
||||||
// delegate,
|
selector,
|
||||||
// actions,
|
ns_string(""),
|
||||||
// keystroke_matcher,
|
)
|
||||||
// ));
|
.autorelease();
|
||||||
// }
|
}
|
||||||
// item.setSubmenu_(submenu);
|
|
||||||
// item.setTitle_(ns_string(name));
|
let tag = actions.len() as NSInteger;
|
||||||
// item
|
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 {
|
impl Platform for MacPlatform {
|
||||||
|
@ -479,8 +482,8 @@ impl Platform for MacPlatform {
|
||||||
MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
|
MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_window(&self) -> Option<AnyWindowHandle> {
|
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||||
MacWindow::main_window()
|
MacWindow::active_window()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_window(
|
fn open_window(
|
||||||
|
@ -631,6 +634,18 @@ impl Platform for MacPlatform {
|
||||||
self.0.lock().event = Some(callback);
|
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 {
|
fn os_name(&self) -> &'static str {
|
||||||
"macOS"
|
"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 {
|
fn local_timezone(&self) -> UtcOffset {
|
||||||
unsafe {
|
unsafe {
|
||||||
let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
|
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> {
|
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let bundle: id = NSBundle::mainBundle();
|
let bundle: id = NSBundle::mainBundle();
|
||||||
|
@ -956,7 +954,7 @@ unsafe fn path_from_objc(path: id) -> PathBuf {
|
||||||
PathBuf::from(path)
|
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);
|
let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
|
||||||
assert!(!platform_ptr.is_null());
|
assert!(!platform_ptr.is_null());
|
||||||
&*(platform_ptr as *const MacPlatform)
|
&*(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) {
|
extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some(event) = InputEvent::from_native(native_event, None) {
|
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 let Some(callback) = platform.0.lock().event.as_mut() {
|
||||||
if !callback(event) {
|
if !callback(event) {
|
||||||
return;
|
return;
|
||||||
|
@ -981,7 +979,7 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
|
||||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
|
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
|
||||||
|
|
||||||
let platform = get_foreground_platform(this);
|
let platform = get_mac_platform(this);
|
||||||
let callback = platform.0.lock().finish_launching.take();
|
let callback = platform.0.lock().finish_launching.take();
|
||||||
if let Some(callback) = callback {
|
if let Some(callback) = 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) {
|
extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
|
||||||
if !has_open_windows {
|
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() {
|
if let Some(callback) = platform.0.lock().reopen.as_mut() {
|
||||||
callback();
|
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) {
|
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() {
|
if let Some(callback) = platform.0.lock().become_active.as_mut() {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
|
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() {
|
if let Some(callback) = platform.0.lock().resign_active.as_mut() {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
|
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() {
|
if let Some(callback) = platform.0.lock().quit.as_mut() {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
@ -1035,49 +1033,47 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.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() {
|
if let Some(callback) = platform.0.lock().open_urls.as_mut() {
|
||||||
callback(urls);
|
callback(urls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_menu_item(__this: &mut Object, _: Sel, __item: id) {
|
extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
|
||||||
todo!()
|
unsafe {
|
||||||
// unsafe {
|
let platform = get_mac_platform(this);
|
||||||
// let platform = get_foreground_platform(this);
|
let mut platform = platform.0.lock();
|
||||||
// let mut platform = platform.0.lock();
|
if let Some(mut callback) = platform.menu_command.take() {
|
||||||
// if let Some(mut callback) = platform.menu_command.take() {
|
let tag: NSInteger = msg_send![item, tag];
|
||||||
// let tag: NSInteger = msg_send![item, tag];
|
let index = tag as usize;
|
||||||
// let index = tag as usize;
|
if let Some(action) = platform.menu_actions.get(index) {
|
||||||
// if let Some(action) = platform.menu_actions.get(index) {
|
callback(action.as_ref());
|
||||||
// callback(action.as_ref());
|
}
|
||||||
// }
|
platform.menu_command = Some(callback);
|
||||||
// platform.menu_command = Some(callback);
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn validate_menu_item(__this: &mut Object, _: Sel, __item: id) -> bool {
|
extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
|
||||||
todo!()
|
unsafe {
|
||||||
// unsafe {
|
let mut result = false;
|
||||||
// let mut result = false;
|
let platform = get_mac_platform(this);
|
||||||
// let platform = get_foreground_platform(this);
|
let mut platform = platform.0.lock();
|
||||||
// let mut platform = platform.0.lock();
|
if let Some(mut callback) = platform.validate_menu_command.take() {
|
||||||
// if let Some(mut callback) = platform.validate_menu_command.take() {
|
let tag: NSInteger = msg_send![item, tag];
|
||||||
// let tag: NSInteger = msg_send![item, tag];
|
let index = tag as usize;
|
||||||
// let index = tag as usize;
|
if let Some(action) = platform.menu_actions.get(index) {
|
||||||
// if let Some(action) = platform.menu_actions.get(index) {
|
result = callback(action.as_ref());
|
||||||
// result = callback(action.as_ref());
|
}
|
||||||
// }
|
platform.validate_menu_command = Some(callback);
|
||||||
// platform.validate_menu_command = Some(callback);
|
}
|
||||||
// }
|
result
|
||||||
// result
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
|
extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let platform = get_foreground_platform(this);
|
let platform = get_mac_platform(this);
|
||||||
let mut platform = platform.0.lock();
|
let mut platform = platform.0.lock();
|
||||||
if let Some(mut callback) = platform.will_open_menu.take() {
|
if let Some(mut callback) = platform.will_open_menu.take() {
|
||||||
callback();
|
callback();
|
||||||
|
|
|
@ -662,7 +662,7 @@ impl MacWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_window() -> Option<AnyWindowHandle> {
|
pub fn active_window() -> Option<AnyWindowHandle> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let app = NSApplication::sharedApplication(nil);
|
let app = NSApplication::sharedApplication(nil);
|
||||||
let main_window: id = msg_send![app, mainWindow];
|
let main_window: id = msg_send![app, mainWindow];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
|
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
|
||||||
Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
|
Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
|
@ -127,7 +127,7 @@ impl Platform for TestPlatform {
|
||||||
self.displays().iter().find(|d| d.id() == id).cloned()
|
self.displays().iter().find(|d| d.id() == id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_window(&self) -> Option<crate::AnyWindowHandle> {
|
fn active_window(&self) -> Option<crate::AnyWindowHandle> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +212,14 @@ impl Platform for TestPlatform {
|
||||||
unimplemented!()
|
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 {
|
fn os_name(&self) -> &'static str {
|
||||||
"test"
|
"test"
|
||||||
}
|
}
|
||||||
|
|
|
@ -430,7 +430,7 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window
|
self.window
|
||||||
.current_frame
|
.current_frame
|
||||||
.dispatch_tree
|
.dispatch_tree
|
||||||
.clear_keystroke_matchers();
|
.clear_pending_keystrokes();
|
||||||
self.app.push_effect(Effect::FocusChanged {
|
self.app.push_effect(Effect::FocusChanged {
|
||||||
window_handle: self.window.handle,
|
window_handle: self.window.handle,
|
||||||
focused: Some(focus_id),
|
focused: Some(focus_id),
|
||||||
|
@ -453,19 +453,21 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
||||||
if let Some(focus_handle) = self.focused() {
|
let focus_handle = self.focused();
|
||||||
self.defer(move |cx| {
|
|
||||||
if let Some(node_id) = cx
|
self.defer(move |cx| {
|
||||||
.window
|
let node_id = focus_handle
|
||||||
.current_frame
|
.and_then(|handle| {
|
||||||
.dispatch_tree
|
cx.window
|
||||||
.focusable_node_id(focus_handle.id)
|
.current_frame
|
||||||
{
|
.dispatch_tree
|
||||||
cx.propagate_event = true;
|
.focusable_node_id(handle.id)
|
||||||
cx.dispatch_action_on_node(node_id, action);
|
})
|
||||||
}
|
.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
|
/// 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.
|
/// The position of the mouse relative to the window.
|
||||||
pub fn mouse_position(&self) -> Point<Pixels> {
|
pub fn mouse_position(&self) -> Point<Pixels> {
|
||||||
self.window.mouse_position
|
self.window.mouse_position
|
||||||
|
@ -1154,8 +1172,19 @@ impl<'a> WindowContext<'a> {
|
||||||
self.start_frame();
|
self.start_frame();
|
||||||
|
|
||||||
self.with_z_index(0, |cx| {
|
self.with_z_index(0, |cx| {
|
||||||
let available_space = cx.window.viewport_size.map(Into::into);
|
cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
|
||||||
root_view.draw(Point::zero(), available_space, 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() {
|
if let Some(active_drag) = self.app.active_drag.take() {
|
||||||
|
@ -1177,7 +1206,7 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window
|
self.window
|
||||||
.current_frame
|
.current_frame
|
||||||
.dispatch_tree
|
.dispatch_tree
|
||||||
.preserve_keystroke_matchers(
|
.preserve_pending_keystrokes(
|
||||||
&mut self.window.previous_frame.dispatch_tree,
|
&mut self.window.previous_frame.dispatch_tree,
|
||||||
self.window.focus,
|
self.window.focus,
|
||||||
);
|
);
|
||||||
|
@ -1338,75 +1367,79 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_key_event(&mut self, event: &dyn Any) {
|
fn dispatch_key_event(&mut self, event: &dyn Any) {
|
||||||
if let Some(node_id) = self.window.focus.and_then(|focus_id| {
|
let node_id = self
|
||||||
self.window
|
.window
|
||||||
.current_frame
|
.focus
|
||||||
.dispatch_tree
|
.and_then(|focus_id| {
|
||||||
.focusable_node_id(focus_id)
|
self.window
|
||||||
}) {
|
.current_frame
|
||||||
let dispatch_path = self
|
.dispatch_tree
|
||||||
.window
|
.focusable_node_id(focus_id)
|
||||||
.current_frame
|
})
|
||||||
.dispatch_tree
|
.unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id());
|
||||||
.dispatch_path(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 actions: Vec<Box<dyn Action>> = Vec::new();
|
||||||
let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
|
|
||||||
self.propagate_event = true;
|
|
||||||
|
|
||||||
for node_id in &dispatch_path {
|
// Capture phase
|
||||||
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
|
||||||
|
self.propagate_event = true;
|
||||||
|
|
||||||
if !node.context.is_empty() {
|
for node_id in &dispatch_path {
|
||||||
context_stack.push(node.context.clone());
|
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||||
}
|
|
||||||
|
|
||||||
for key_listener in node.key_listeners.clone() {
|
if let Some(context) = node.context.clone() {
|
||||||
key_listener(event, DispatchPhase::Capture, self);
|
context_stack.push(context);
|
||||||
if !self.propagate_event {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bubble phase
|
for key_listener in node.key_listeners.clone() {
|
||||||
for node_id in dispatch_path.iter().rev() {
|
key_listener(event, DispatchPhase::Capture, self);
|
||||||
// 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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context_stack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for action in actions {
|
|
||||||
self.dispatch_action_on_node(node_id, action);
|
|
||||||
if !self.propagate_event {
|
if !self.propagate_event {
|
||||||
return;
|
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_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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for action in actions {
|
||||||
|
self.dispatch_action_on_node(node_id, action);
|
||||||
|
if !self.propagate_event {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
|
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
|
||||||
|
@ -1490,14 +1523,21 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
|
pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
|
||||||
if let Some(focus_id) = self.window.focus {
|
let node_id = self
|
||||||
self.window
|
.window
|
||||||
.current_frame
|
.focus
|
||||||
.dispatch_tree
|
.and_then(|focus_id| {
|
||||||
.available_actions(focus_id)
|
self.window
|
||||||
} else {
|
.current_frame
|
||||||
Vec::new()
|
.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> {
|
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
|
||||||
|
@ -1523,7 +1563,7 @@ impl<'a> WindowContext<'a> {
|
||||||
let context_stack = dispatch_tree
|
let context_stack = dispatch_tree
|
||||||
.dispatch_path(node_id)
|
.dispatch_path(node_id)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|node_id| dispatch_tree.node(node_id).context.clone())
|
.filter_map(|node_id| dispatch_tree.node(node_id).context.clone())
|
||||||
.collect();
|
.collect();
|
||||||
dispatch_tree.bindings_for_action(action, &context_stack)
|
dispatch_tree.bindings_for_action(action, &context_stack)
|
||||||
}
|
}
|
||||||
|
@ -1553,7 +1593,7 @@ impl<'a> WindowContext<'a> {
|
||||||
//========== ELEMENT RELATED FUNCTIONS ===========
|
//========== ELEMENT RELATED FUNCTIONS ===========
|
||||||
pub fn with_key_dispatch<R>(
|
pub fn with_key_dispatch<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: KeyContext,
|
context: Option<KeyContext>,
|
||||||
focus_handle: Option<FocusHandle>,
|
focus_handle: Option<FocusHandle>,
|
||||||
f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
|
f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
|
|
|
@ -132,39 +132,3 @@ pub fn load_default_keymap(cx: &mut AppContext) {
|
||||||
// KeymapFile::load_asset(asset_path, cx).unwrap();
|
// 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,
|
Option,
|
||||||
Return,
|
Return,
|
||||||
Update,
|
Update,
|
||||||
|
ZedXCopilot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Icon {
|
impl Icon {
|
||||||
|
@ -121,6 +122,7 @@ impl Icon {
|
||||||
Icon::Close => "icons/x.svg",
|
Icon::Close => "icons/x.svg",
|
||||||
Icon::Collab => "icons/user_group_16.svg",
|
Icon::Collab => "icons/user_group_16.svg",
|
||||||
Icon::Copilot => "icons/copilot.svg",
|
Icon::Copilot => "icons/copilot.svg",
|
||||||
|
|
||||||
Icon::CopilotInit => "icons/copilot_init.svg",
|
Icon::CopilotInit => "icons/copilot_init.svg",
|
||||||
Icon::CopilotError => "icons/copilot_error.svg",
|
Icon::CopilotError => "icons/copilot_error.svg",
|
||||||
Icon::CopilotDisabled => "icons/copilot_disabled.svg",
|
Icon::CopilotDisabled => "icons/copilot_disabled.svg",
|
||||||
|
@ -168,6 +170,7 @@ impl Icon {
|
||||||
Icon::Option => "icons/option.svg",
|
Icon::Option => "icons/option.svg",
|
||||||
Icon::Return => "icons/return.svg",
|
Icon::Return => "icons/return.svg",
|
||||||
Icon::Update => "icons/update.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::*;
|
pub use highlighted_label::*;
|
||||||
use crate::styled_ext::StyledExt;
|
pub use label::*;
|
||||||
use gpui::{relative, Div, HighlightStyle, IntoElement, StyledText, WindowContext};
|
pub use label_like::*;
|
||||||
|
|
||||||
#[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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
86
crates/ui2/src/components/label/highlighted_label.rs
Normal file
86
crates/ui2/src/components/label/highlighted_label.rs
Normal file
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
48
crates/ui2/src/components/label/label.rs
Normal file
48
crates/ui2/src/components/label/label.rs
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
102
crates/ui2/src/components/label/label_like.rs
Normal file
102
crates/ui2/src/components/label/label_like.rs
Normal file
|
@ -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!",
|
"Héllo, world!",
|
||||||
vec![0, 1, 3, 8, 9, 13],
|
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::fixed::*;
|
||||||
pub use crate::selectable::*;
|
pub use crate::selectable::*;
|
||||||
pub use crate::{h_stack, v_stack};
|
pub use crate::{h_stack, v_stack};
|
||||||
pub use crate::{ButtonCommon, Color, StyledExt};
|
pub use crate::{ButtonCommon, Color, LabelCommon, StyledExt};
|
||||||
pub use theme::ActiveTheme;
|
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
|
// will be initialized as disabled by default, so we filter its commands
|
||||||
// out when starting up.
|
// out when starting up.
|
||||||
cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
|
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| {
|
cx.update_global(|vim: &mut Vim, cx: &mut AppContext| {
|
||||||
vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
|
vim.set_enabled(settings::get::<VimModeSetting>(cx).0, cx)
|
||||||
|
@ -477,9 +477,9 @@ impl Vim {
|
||||||
|
|
||||||
cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
|
cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
|
||||||
if self.enabled {
|
if self.enabled {
|
||||||
filter.filtered_namespaces.remove("vim");
|
filter.hidden_namespaces.remove("vim");
|
||||||
} else {
|
} 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."
|
description = "The fast, collaborative code editor."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.116.0"
|
version = "0.117.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
175
crates/zed2/src/app_menus.rs
Normal file
175
crates/zed2/src/app_menus.rs
Normal file
|
@ -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 parking_lot::Mutex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{
|
use settings::{
|
||||||
default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file,
|
default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore,
|
||||||
Settings, SettingsStore,
|
|
||||||
};
|
};
|
||||||
use simplelog::ConfigBuilder;
|
use simplelog::ConfigBuilder;
|
||||||
use smol::process::Command;
|
use smol::process::Command;
|
||||||
|
@ -51,8 +50,9 @@ use uuid::Uuid;
|
||||||
use welcome::{show_welcome_experience, FIRST_OPEN};
|
use welcome::{show_welcome_experience, FIRST_OPEN};
|
||||||
use workspace::{AppState, WorkspaceStore};
|
use workspace::{AppState, WorkspaceStore};
|
||||||
use zed2::{
|
use zed2::{
|
||||||
build_window_options, ensure_only_instance, handle_cli_connection, initialize_workspace,
|
app_menus, build_window_options, ensure_only_instance, handle_cli_connection,
|
||||||
languages, Assets, IsOnlyInstance, OpenListener, OpenRequest,
|
handle_keymap_file_changes, initialize_workspace, languages, Assets, IsOnlyInstance,
|
||||||
|
OpenListener, OpenRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod open_listener;
|
mod open_listener;
|
||||||
|
@ -224,7 +224,7 @@ fn main() {
|
||||||
// feedback::init(cx);
|
// feedback::init(cx);
|
||||||
welcome::init(cx);
|
welcome::init(cx);
|
||||||
|
|
||||||
// cx.set_menus(menus::menus());
|
cx.set_menus(app_menus());
|
||||||
initialize_workspace(app_state.clone(), cx);
|
initialize_workspace(app_state.clone(), cx);
|
||||||
|
|
||||||
if stdout_is_a_pty() {
|
if stdout_is_a_pty() {
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
#![allow(unused_variables, unused_mut)]
|
#![allow(unused_variables, unused_mut)]
|
||||||
//todo!()
|
//todo!()
|
||||||
|
|
||||||
|
mod app_menus;
|
||||||
mod assets;
|
mod assets;
|
||||||
pub mod languages;
|
pub mod languages;
|
||||||
mod only_instance;
|
mod only_instance;
|
||||||
mod open_listener;
|
mod open_listener;
|
||||||
|
|
||||||
|
pub use app_menus::*;
|
||||||
pub use assets::*;
|
pub use assets::*;
|
||||||
use assistant::AssistantPanel;
|
use assistant::AssistantPanel;
|
||||||
use breadcrumbs::Breadcrumbs;
|
use breadcrumbs::Breadcrumbs;
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
use editor::{Editor, MultiBuffer};
|
use editor::{Editor, MultiBuffer};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions,
|
actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View,
|
||||||
ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
|
ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
pub use only_instance::*;
|
pub use only_instance::*;
|
||||||
pub use open_listener::*;
|
pub use open_listener::*;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as _};
|
use anyhow::{anyhow, Context as _};
|
||||||
|
use futures::{channel::mpsc, StreamExt};
|
||||||
use project_panel::ProjectPanel;
|
use project_panel::ProjectPanel;
|
||||||
use quick_action_bar::QuickActionBar;
|
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 std::{borrow::Cow, ops::Deref, sync::Arc};
|
||||||
use terminal_view::terminal_panel::TerminalPanel;
|
use terminal_view::terminal_panel::TerminalPanel;
|
||||||
use util::{
|
use util::{
|
||||||
|
@ -31,6 +34,7 @@ use util::{
|
||||||
ResultExt,
|
ResultExt,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use workspace::Pane;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
create_and_open_local_file, dock::PanelHandle,
|
create_and_open_local_file, dock::PanelHandle,
|
||||||
notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
|
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) {
|
pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
cx.observe_new_views(move |workspace: &mut Workspace, cx| {
|
cx.observe_new_views(move |workspace: &mut Workspace, cx| {
|
||||||
let workspace_handle = cx.view().clone();
|
let workspace_handle = cx.view().clone();
|
||||||
|
let center_pane = workspace.active_pane().clone();
|
||||||
|
initialize_pane(workspace, ¢er_pane, cx);
|
||||||
cx.subscribe(&workspace_handle, {
|
cx.subscribe(&workspace_handle, {
|
||||||
move |workspace, _, event, cx| {
|
move |workspace, _, event, cx| {
|
||||||
if let workspace::Event::PaneAdded(pane) = event {
|
if let workspace::Event::PaneAdded(pane) = event {
|
||||||
pane.update(cx, |pane, cx| {
|
initialize_pane(workspace, 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);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -435,6 +414,36 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
.detach();
|
.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>) {
|
fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
|
@ -560,6 +569,42 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||||
.detach();
|
.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(
|
fn open_local_settings_file(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
_: &OpenLocalSettings,
|
_: &OpenLocalSettings,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue