Compare commits
1 Commits
deeper-doc
...
move-messa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db9f079598 |
15
.cargo/ci-config.toml
Normal file
15
.cargo/ci-config.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# This config is different from config.toml in this directory, as the latter is recognized by Cargo.
|
||||||
|
# This file is placed in $HOME/.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with $HOME/.cargo/config.toml
|
||||||
|
# with preference for settings from Zeds config.toml.
|
||||||
|
# TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence.
|
||||||
|
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
|
||||||
|
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
|
||||||
|
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
|
||||||
|
# would be incovenient.
|
||||||
|
# We *could* override things like RUSTFLAGS manually by setting them as environment variables, but that is less DRY; worse yet, if you forget to set proper environment variables
|
||||||
|
# in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it.
|
||||||
|
[build]
|
||||||
|
rustflags = ["-D", "warnings"]
|
||||||
|
|
||||||
|
[alias]
|
||||||
|
xtask = "run --package xtask --"
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# This file is used to build collab in a Docker image.
|
|
||||||
# In particular, we don't use clang.
|
|
||||||
[build]
|
|
||||||
# v0 mangling scheme provides more detailed backtraces around closures
|
|
||||||
rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]
|
|
||||||
@@ -4,11 +4,3 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]
|
|||||||
|
|
||||||
[alias]
|
[alias]
|
||||||
xtask = "run --package xtask --"
|
xtask = "run --package xtask --"
|
||||||
|
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|
||||||
|
|
||||||
[target.aarch64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|
||||||
|
|||||||
38
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
38
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
@@ -2,23 +2,23 @@ name: Feature Request
|
|||||||
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
|
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
|
||||||
labels: ["admin read", "triage", "enhancement"]
|
labels: ["admin read", "triage", "enhancement"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Check for existing issues
|
label: Check for existing issues
|
||||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||||
options:
|
options:
|
||||||
- label: Completed
|
- label: Completed
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Describe the feature
|
||||||
|
description: A clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe the feature
|
label: |
|
||||||
description: A clear and concise description of what you want to happen.
|
If applicable, add mockups / screenshots to help present your vision of the feature
|
||||||
validations:
|
description: Drag images into the text input below
|
||||||
required: true
|
validations:
|
||||||
- type: textarea
|
required: false
|
||||||
attributes:
|
|
||||||
label: |
|
|
||||||
If applicable, add mockups / screenshots to help present your vision of the feature
|
|
||||||
description: Drag images into the text input below
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|||||||
75
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
75
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -1,45 +1,40 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: |
|
description: |
|
||||||
Use this template for **non-crash-related** bug reports.
|
Use this template for **non-crash-related** bug reports.
|
||||||
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
||||||
labels: ["admin read", "triage", "defect"]
|
labels: ["admin read", "triage", "defect"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Check for existing issues
|
label: Check for existing issues
|
||||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||||
options:
|
options:
|
||||||
- label: Completed
|
- label: Completed
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug / provide steps to reproduce it
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
id: environment
|
||||||
label: Describe the bug / provide steps to reproduce it
|
attributes:
|
||||||
description: A clear and concise description of what the bug is.
|
label: Environment
|
||||||
validations:
|
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||||
required: true
|
validations:
|
||||||
- type: textarea
|
required: true
|
||||||
id: environment
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Environment
|
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
description: Drag issues into the text input below
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: false
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||||
description: Drag issues into the text input below
|
description: |
|
||||||
validations:
|
Drag Zed.log into the text input below.
|
||||||
required: false
|
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||||
- type: textarea
|
validations:
|
||||||
attributes:
|
required: false
|
||||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
|
||||||
description: |
|
|
||||||
Drag Zed.log into the text input below.
|
|
||||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
|
||||||
value: |
|
|
||||||
<details><summary>Zed.log</summary><pre>
|
|
||||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
|
||||||
|
|
||||||
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|||||||
61
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
61
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
@@ -1,38 +1,33 @@
|
|||||||
name: Crash Report
|
name: Crash Report
|
||||||
description: |
|
description: |
|
||||||
Use this template for crash reports.
|
Use this template for crash reports.
|
||||||
labels: ["admin read", "triage", "defect", "panic / crash"]
|
labels: ["admin read", "triage", "defect", "panic / crash"]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Check for existing issues
|
label: Check for existing issues
|
||||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||||
options:
|
options:
|
||||||
- label: Completed
|
- label: Completed
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug / provide steps to reproduce it
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
id: environment
|
||||||
label: Describe the bug / provide steps to reproduce it
|
attributes:
|
||||||
description: A clear and concise description of what the bug is.
|
label: Environment
|
||||||
validations:
|
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||||
required: true
|
validations:
|
||||||
- type: textarea
|
required: true
|
||||||
id: environment
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Environment
|
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
description: |
|
||||||
validations:
|
Drag Zed.log into the text input below.
|
||||||
required: true
|
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||||
- type: textarea
|
validations:
|
||||||
attributes:
|
required: false
|
||||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
|
||||||
description: |
|
|
||||||
Drag Zed.log into the text input below.
|
|
||||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
|
||||||
value: |
|
|
||||||
<details><summary>Zed.log</summary><pre>
|
|
||||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
|
||||||
|
|
||||||
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|||||||
110
.github/workflows/ci.yml
vendored
110
.github/workflows/ci.yml
vendored
@@ -38,6 +38,9 @@ jobs:
|
|||||||
- name: Remove untracked files
|
- name: Remove untracked files
|
||||||
run: git clean -df
|
run: git clean -df
|
||||||
|
|
||||||
|
- name: Set up default .cargo/config.toml
|
||||||
|
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
run: |
|
run: |
|
||||||
if ! which typos > /dev/null; then
|
if ! which typos > /dev/null; then
|
||||||
@@ -51,9 +54,6 @@ jobs:
|
|||||||
- name: Check unused dependencies
|
- name: Check unused dependencies
|
||||||
uses: bnjbvr/cargo-machete@main
|
uses: bnjbvr/cargo-machete@main
|
||||||
|
|
||||||
- name: Check licenses are present
|
|
||||||
run: script/check-licenses
|
|
||||||
|
|
||||||
- name: Check license generation
|
- name: Check license generation
|
||||||
run: script/generate-licenses /tmp/zed_licenses_output
|
run: script/generate-licenses /tmp/zed_licenses_output
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@ jobs:
|
|||||||
version: v1.29.0
|
version: v1.29.0
|
||||||
- uses: bufbuild/buf-breaking-action@v1
|
- uses: bufbuild/buf-breaking-action@v1
|
||||||
with:
|
with:
|
||||||
input: "crates/proto/proto/"
|
input: "crates/rpc/proto/"
|
||||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"
|
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/rpc/proto/"
|
||||||
|
|
||||||
macos_tests:
|
macos_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
run: ./script/clippy
|
run: cargo xtask clippy
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
uses: ./.github/actions/run_tests
|
uses: ./.github/actions/run_tests
|
||||||
@@ -101,6 +101,7 @@ jobs:
|
|||||||
- name: Build other binaries and features
|
- name: Build other binaries and features
|
||||||
run: cargo build --workspace --bins --all-features; cargo check -p gpui --features "macos-blade"
|
run: cargo build --workspace --bins --all-features; cargo check -p gpui --features "macos-blade"
|
||||||
|
|
||||||
|
# todo(linux): Actually run the tests
|
||||||
linux_tests:
|
linux_tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: (Linux) Run Clippy and tests
|
name: (Linux) Run Clippy and tests
|
||||||
@@ -117,10 +118,7 @@ jobs:
|
|||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
run: ./script/clippy
|
run: cargo xtask clippy
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
uses: ./.github/actions/run_tests
|
|
||||||
|
|
||||||
- name: Build Zed
|
- name: Build Zed
|
||||||
run: cargo build -p zed
|
run: cargo build -p zed
|
||||||
@@ -142,7 +140,7 @@ jobs:
|
|||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
run: ./script/clippy
|
run: cargo xtask clippy
|
||||||
|
|
||||||
- name: Build Zed
|
- name: Build Zed
|
||||||
run: cargo build -p zed
|
run: cargo build -p zed
|
||||||
@@ -307,7 +305,10 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Create Linux .tar.gz bundle
|
- name: Generate license file
|
||||||
|
run: script/generate-licenses
|
||||||
|
|
||||||
|
- name: Create and upload Linux .tar.gz bundle
|
||||||
run: script/bundle-linux
|
run: script/bundle-linux
|
||||||
|
|
||||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||||
@@ -315,7 +316,7 @@ jobs:
|
|||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||||
with:
|
with:
|
||||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||||
path: target/release/zed-*.tar.gz
|
path: zed-*.tar.gz
|
||||||
|
|
||||||
- name: Upload app bundle to release
|
- name: Upload app bundle to release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@@ -327,86 +328,3 @@ jobs:
|
|||||||
body: ""
|
body: ""
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
bundle-linux-aarch64:
|
|
||||||
timeout-minutes: 60
|
|
||||||
name: Create arm64 Linux bundle
|
|
||||||
runs-on:
|
|
||||||
- hosted-linux-arm-1
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
|
||||||
needs: [linux_tests]
|
|
||||||
env:
|
|
||||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
clean: false
|
|
||||||
- name: "Setup jq"
|
|
||||||
uses: dcarbone/install-jq-action@v2
|
|
||||||
|
|
||||||
- name: Set up Clang
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
|
||||||
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- uses: rui314/setup-mold@v1
|
|
||||||
with:
|
|
||||||
mold-version: 2.32.0
|
|
||||||
|
|
||||||
- name: rustup
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
||||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- 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: Create and upload Linux .tar.gz bundle
|
|
||||||
run: script/bundle-linux
|
|
||||||
|
|
||||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
|
||||||
with:
|
|
||||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
|
||||||
path: target/release/zed-*.tar.gz
|
|
||||||
|
|
||||||
- name: Upload app bundle to release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
if: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
|
||||||
with:
|
|
||||||
draft: true
|
|
||||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
|
||||||
files: target/release/zed-linux-aarch64.tar.gz
|
|
||||||
body: ""
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|||||||
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: pnpm/action-setup@v3
|
- uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 9
|
version: 8
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|||||||
5
.github/workflows/deploy_collab.yml
vendored
5
.github/workflows/deploy_collab.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
uses: ./.github/actions/check_style
|
uses: ./.github/actions/check_style
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: ./script/clippy
|
run: cargo xtask clippy
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
@@ -75,9 +75,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
- name: Set up default .cargo/config.toml
|
|
||||||
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
|
|
||||||
|
|
||||||
- name: Build docker image
|
- name: Build docker image
|
||||||
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/release_nightly.yml
vendored
2
.github/workflows/release_nightly.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
uses: ./.github/actions/check_style
|
uses: ./.github/actions/check_style
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: ./script/clippy
|
run: cargo xtask clippy
|
||||||
tests:
|
tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: Run tests
|
name: Run tests
|
||||||
|
|||||||
17
.mailmap
17
.mailmap
@@ -9,18 +9,12 @@
|
|||||||
# Keep these entries sorted alphabetically.
|
# Keep these entries sorted alphabetically.
|
||||||
# In Zed: `editor: sort lines case sensitive`
|
# In Zed: `editor: sort lines case sensitive`
|
||||||
|
|
||||||
Alex Viscreanu <alexviscreanu@gmail.com>
|
|
||||||
Alex Viscreanu <alexviscreanu@gmail.com> <alexandru.viscreanu@kiwi.com>
|
|
||||||
Antonio Scandurra <me@as-cii.com>
|
Antonio Scandurra <me@as-cii.com>
|
||||||
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
||||||
Bennet Bo Fenner <bennet@zed.dev>
|
|
||||||
Bennet Bo Fenner <bennet@zed.dev> <53836821+bennetbo@users.noreply.github.com>
|
|
||||||
Bennet Bo Fenner <bennet@zed.dev> <bennetbo@gmx.de>
|
|
||||||
Christian Bergschneider <christian.bergschneider@gmx.de>
|
Christian Bergschneider <christian.bergschneider@gmx.de>
|
||||||
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
|
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
|
||||||
Conrad Irwin <conrad@zed.dev>
|
Conrad Irwin <conrad@zed.dev>
|
||||||
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
||||||
Evren Sen <146845123+evrsen@users.noreply.github.com>
|
|
||||||
Fernando Tagawa <tagawafernando@gmail.com>
|
Fernando Tagawa <tagawafernando@gmail.com>
|
||||||
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
|
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
|
||||||
Greg Morenz <greg-morenz@droid.cafe>
|
Greg Morenz <greg-morenz@droid.cafe>
|
||||||
@@ -54,23 +48,12 @@ Nate Butler <iamnbutler@gmail.com> <nate@zed.dev>
|
|||||||
Nathan Sobo <nathan@zed.dev>
|
Nathan Sobo <nathan@zed.dev>
|
||||||
Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
|
Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
|
||||||
Nathan Sobo <nathan@zed.dev> <nathansobo@gmail.com>
|
Nathan Sobo <nathan@zed.dev> <nathansobo@gmail.com>
|
||||||
Peter Tripp <peter@zed.dev>
|
|
||||||
Peter Tripp <peter@zed.dev> <petertripp@gmail.com>
|
|
||||||
Petros Amoiridis <petros@hey.com>
|
Petros Amoiridis <petros@hey.com>
|
||||||
Petros Amoiridis <petros@hey.com> <petros@zed.dev>
|
Petros Amoiridis <petros@hey.com> <petros@zed.dev>
|
||||||
Piotr Osiewicz <piotr@zed.dev>
|
Piotr Osiewicz <piotr@zed.dev>
|
||||||
Piotr Osiewicz <piotr@zed.dev> <24362066+osiewicz@users.noreply.github.com>
|
Piotr Osiewicz <piotr@zed.dev> <24362066+osiewicz@users.noreply.github.com>
|
||||||
Rashid Almheiri <r.muhairi@pm.me>
|
|
||||||
Rashid Almheiri <r.muhairi@pm.me> <69181766+huwaireb@users.noreply.github.com>
|
|
||||||
Richard Feldman <oss@rtfeldman.com>
|
|
||||||
Richard Feldman <oss@rtfeldman.com> <richard@zed.dev>
|
|
||||||
Robert Clover <git@clo4.net>
|
Robert Clover <git@clo4.net>
|
||||||
Robert Clover <git@clo4.net> <robert@clover.gdn>
|
Robert Clover <git@clo4.net> <robert@clover.gdn>
|
||||||
Sergey Onufrienko <sergey@onufrienko.com>
|
|
||||||
Thorsten Ball <thorsten@zed.dev>
|
Thorsten Ball <thorsten@zed.dev>
|
||||||
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.com>
|
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.com>
|
||||||
Thorsten Ball <thorsten@zed.dev> <mrnugget@gmail.com>
|
Thorsten Ball <thorsten@zed.dev> <mrnugget@gmail.com>
|
||||||
Vitaly Slobodin <vitaliy.slobodin@gmail.com>
|
|
||||||
Vitaly Slobodin <vitaliy.slobodin@gmail.com> <vitaly_slobodin@fastmail.com>
|
|
||||||
WindSoilder <WindSoilder@outlook.com>
|
|
||||||
张小白 <364772080@qq.com>
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"label": "clippy",
|
"label": "clippy",
|
||||||
"command": "./script/clippy",
|
"command": "cargo",
|
||||||
"args": []
|
"args": ["xtask", "clippy"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
604
Cargo.lock
generated
604
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
49
Cargo.toml
49
Cargo.toml
@@ -61,28 +61,22 @@ members = [
|
|||||||
"crates/multi_buffer",
|
"crates/multi_buffer",
|
||||||
"crates/node_runtime",
|
"crates/node_runtime",
|
||||||
"crates/notifications",
|
"crates/notifications",
|
||||||
"crates/ollama",
|
|
||||||
"crates/open_ai",
|
"crates/open_ai",
|
||||||
"crates/outline",
|
"crates/outline",
|
||||||
"crates/outline_panel",
|
|
||||||
"crates/paths",
|
|
||||||
"crates/picker",
|
"crates/picker",
|
||||||
"crates/prettier",
|
"crates/prettier",
|
||||||
"crates/project",
|
"crates/project",
|
||||||
"crates/project_panel",
|
"crates/project_panel",
|
||||||
"crates/project_symbols",
|
"crates/project_symbols",
|
||||||
"crates/proto",
|
|
||||||
"crates/quick_action_bar",
|
"crates/quick_action_bar",
|
||||||
"crates/recent_projects",
|
"crates/recent_projects",
|
||||||
"crates/refineable",
|
"crates/refineable",
|
||||||
"crates/refineable/derive_refineable",
|
"crates/refineable/derive_refineable",
|
||||||
"crates/release_channel",
|
"crates/release_channel",
|
||||||
"crates/dev_server_projects",
|
"crates/dev_server_projects",
|
||||||
"crates/repl",
|
|
||||||
"crates/rich_text",
|
"crates/rich_text",
|
||||||
"crates/rope",
|
"crates/rope",
|
||||||
"crates/rpc",
|
"crates/rpc",
|
||||||
"crates/rustdoc",
|
|
||||||
"crates/task",
|
"crates/task",
|
||||||
"crates/tasks_ui",
|
"crates/tasks_ui",
|
||||||
"crates/search",
|
"crates/search",
|
||||||
@@ -136,7 +130,6 @@ members = [
|
|||||||
"extensions/prisma",
|
"extensions/prisma",
|
||||||
"extensions/purescript",
|
"extensions/purescript",
|
||||||
"extensions/ruby",
|
"extensions/ruby",
|
||||||
"extensions/snippets",
|
|
||||||
"extensions/svelte",
|
"extensions/svelte",
|
||||||
"extensions/terraform",
|
"extensions/terraform",
|
||||||
"extensions/toml",
|
"extensions/toml",
|
||||||
@@ -170,6 +163,7 @@ clock = { path = "crates/clock" }
|
|||||||
collab = { path = "crates/collab" }
|
collab = { path = "crates/collab" }
|
||||||
collab_ui = { path = "crates/collab_ui" }
|
collab_ui = { path = "crates/collab_ui" }
|
||||||
collections = { path = "crates/collections" }
|
collections = { path = "crates/collections" }
|
||||||
|
color = { path = "crates/color" }
|
||||||
command_palette = { path = "crates/command_palette" }
|
command_palette = { path = "crates/command_palette" }
|
||||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||||
copilot = { path = "crates/copilot" }
|
copilot = { path = "crates/copilot" }
|
||||||
@@ -213,17 +207,13 @@ menu = { path = "crates/menu" }
|
|||||||
multi_buffer = { path = "crates/multi_buffer" }
|
multi_buffer = { path = "crates/multi_buffer" }
|
||||||
node_runtime = { path = "crates/node_runtime" }
|
node_runtime = { path = "crates/node_runtime" }
|
||||||
notifications = { path = "crates/notifications" }
|
notifications = { path = "crates/notifications" }
|
||||||
ollama = { path = "crates/ollama" }
|
|
||||||
open_ai = { path = "crates/open_ai" }
|
open_ai = { path = "crates/open_ai" }
|
||||||
outline = { path = "crates/outline" }
|
outline = { path = "crates/outline" }
|
||||||
outline_panel = { path = "crates/outline_panel" }
|
|
||||||
paths = { path = "crates/paths" }
|
|
||||||
picker = { path = "crates/picker" }
|
picker = { path = "crates/picker" }
|
||||||
plugin = { path = "crates/plugin" }
|
plugin = { path = "crates/plugin" }
|
||||||
plugin_macros = { path = "crates/plugin_macros" }
|
plugin_macros = { path = "crates/plugin_macros" }
|
||||||
prettier = { path = "crates/prettier" }
|
prettier = { path = "crates/prettier" }
|
||||||
project = { path = "crates/project" }
|
project = { path = "crates/project" }
|
||||||
proto = { path = "crates/proto" }
|
|
||||||
worktree = { path = "crates/worktree" }
|
worktree = { path = "crates/worktree" }
|
||||||
project_panel = { path = "crates/project_panel" }
|
project_panel = { path = "crates/project_panel" }
|
||||||
project_symbols = { path = "crates/project_symbols" }
|
project_symbols = { path = "crates/project_symbols" }
|
||||||
@@ -231,11 +221,9 @@ quick_action_bar = { path = "crates/quick_action_bar" }
|
|||||||
recent_projects = { path = "crates/recent_projects" }
|
recent_projects = { path = "crates/recent_projects" }
|
||||||
release_channel = { path = "crates/release_channel" }
|
release_channel = { path = "crates/release_channel" }
|
||||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
dev_server_projects = { path = "crates/dev_server_projects" }
|
||||||
repl = { path = "crates/repl" }
|
|
||||||
rich_text = { path = "crates/rich_text" }
|
rich_text = { path = "crates/rich_text" }
|
||||||
rope = { path = "crates/rope" }
|
rope = { path = "crates/rope" }
|
||||||
rpc = { path = "crates/rpc" }
|
rpc = { path = "crates/rpc" }
|
||||||
rustdoc = { path = "crates/rustdoc" }
|
|
||||||
task = { path = "crates/task" }
|
task = { path = "crates/task" }
|
||||||
tasks_ui = { path = "crates/tasks_ui" }
|
tasks_ui = { path = "crates/tasks_ui" }
|
||||||
search = { path = "crates/search" }
|
search = { path = "crates/search" }
|
||||||
@@ -269,32 +257,29 @@ workspace = { path = "crates/workspace" }
|
|||||||
zed = { path = "crates/zed" }
|
zed = { path = "crates/zed" }
|
||||||
zed_actions = { path = "crates/zed_actions" }
|
zed_actions = { path = "crates/zed_actions" }
|
||||||
|
|
||||||
alacritty_terminal = "0.23"
|
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
any_vec = "0.13"
|
any_vec = "0.13"
|
||||||
ashpd = "0.8.0"
|
ashpd = "0.8.0"
|
||||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||||
async-dispatcher = { version = "0.1"}
|
|
||||||
async-fs = "1.6"
|
async-fs = "1.6"
|
||||||
async-recursion = "1.0.0"
|
async-recursion = "1.0.0"
|
||||||
async-tar = "0.4.2"
|
async-tar = "0.4.2"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||||
bitflags = "2.4.2"
|
bitflags = "2.4.2"
|
||||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
blade-graphics = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
|
||||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
blade-macros = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
|
||||||
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
|
blade-util = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
|
||||||
cap-std = "3.0"
|
cap-std = "3.0"
|
||||||
cargo_toml = "0.20"
|
cargo_toml = "0.20"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
clickhouse = { version = "0.11.6" }
|
clickhouse = { version = "0.11.6" }
|
||||||
cocoa = "0.25"
|
|
||||||
ctor = "0.2.6"
|
ctor = "0.2.6"
|
||||||
|
signal-hook = "0.3.17"
|
||||||
core-foundation = { version = "0.9.3" }
|
core-foundation = { version = "0.9.3" }
|
||||||
core-foundation-sys = "0.8.6"
|
core-foundation-sys = "0.8.6"
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
dirs = "4.0"
|
|
||||||
emojis = "0.6.1"
|
emojis = "0.6.1"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
exec = "0.3.1"
|
exec = "0.3.1"
|
||||||
@@ -302,17 +287,18 @@ fork = "0.1.23"
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-batch = "0.6.1"
|
futures-batch = "0.6.1"
|
||||||
futures-lite = "1.13"
|
futures-lite = "1.13"
|
||||||
git2 = { version = "0.19", default-features = false }
|
git2 = { version = "0.18", default-features = false }
|
||||||
globset = "0.4"
|
globset = "0.4"
|
||||||
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
html5ever = "0.27.0"
|
html5ever = "0.27.0"
|
||||||
ignore = "0.4.22"
|
ignore = "0.4.22"
|
||||||
image = "0.23"
|
|
||||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
|
||||||
indoc = "1"
|
indoc = "1"
|
||||||
# We explicitly disable http2 support in isahc.
|
# We explicitly disable http2 support in isahc.
|
||||||
isahc = { version = "1.7.2", default-features = false, features = [ "text-decoding" ] }
|
isahc = { version = "1.7.2", default-features = false, features = [
|
||||||
|
"static-curl",
|
||||||
|
"text-decoding",
|
||||||
|
] }
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
@@ -321,7 +307,6 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
|||||||
markup5ever_rcdom = "0.3.0"
|
markup5ever_rcdom = "0.3.0"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
nix = "0.28"
|
nix = "0.28"
|
||||||
num-format = "0.4.4"
|
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
ordered-float = "2.1.1"
|
ordered-float = "2.1.1"
|
||||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||||
@@ -338,7 +323,6 @@ rand = "0.8.5"
|
|||||||
refineable = { path = "./crates/refineable" }
|
refineable = { path = "./crates/refineable" }
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
repair_json = "0.1.0"
|
repair_json = "0.1.0"
|
||||||
runtimelib = { version="0.12", default-features = false, features = ["async-dispatcher-runtime"] }
|
|
||||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
@@ -354,8 +338,6 @@ serde_repr = "0.1"
|
|||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
shellexpand = "2.1.0"
|
shellexpand = "2.1.0"
|
||||||
shlex = "1.3.0"
|
shlex = "1.3.0"
|
||||||
signal-hook = "0.3.17"
|
|
||||||
similar = "1.3"
|
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
strum = { version = "0.25.0", features = ["derive"] }
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
@@ -417,13 +399,12 @@ wit-component = "0.201"
|
|||||||
sys-locale = "0.3.1"
|
sys-locale = "0.3.1"
|
||||||
|
|
||||||
[workspace.dependencies.windows]
|
[workspace.dependencies.windows]
|
||||||
version = "0.57"
|
version = "0.56.0"
|
||||||
features = [
|
features = [
|
||||||
"implement",
|
"implement",
|
||||||
"Foundation_Numerics",
|
"Foundation_Numerics",
|
||||||
"System",
|
"System",
|
||||||
"System_Threading",
|
"System_Threading",
|
||||||
"UI_ViewManagement",
|
|
||||||
"Wdk_System_SystemServices",
|
"Wdk_System_SystemServices",
|
||||||
"Win32_Globalization",
|
"Win32_Globalization",
|
||||||
"Win32_Graphics_Direct2D",
|
"Win32_Graphics_Direct2D",
|
||||||
@@ -459,12 +440,11 @@ features = [
|
|||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7b4894ba2ae81b988846676f54c0988d4027ef4f" }
|
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7b4894ba2ae81b988846676f54c0988d4027ef4f" }
|
||||||
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
|
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
|
||||||
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "4968e819c0d9b015437ffc694511e175801a17c7" }
|
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
split-debuginfo = "unpacked"
|
split-debuginfo = "unpacked"
|
||||||
debug = "limited"
|
debug = "limited"
|
||||||
codegen-units = 16
|
|
||||||
|
|
||||||
[profile.dev.package]
|
[profile.dev.package]
|
||||||
taffy = { opt-level = 3 }
|
taffy = { opt-level = 3 }
|
||||||
@@ -483,11 +463,6 @@ codegen-units = 1
|
|||||||
[profile.release.package]
|
[profile.release.package]
|
||||||
zed = { codegen-units = 16 }
|
zed = { codegen-units = 16 }
|
||||||
|
|
||||||
[profile.release-fast]
|
|
||||||
inherits = "release"
|
|
||||||
lto = false
|
|
||||||
codegen-units = 16
|
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
dbg_macro = "deny"
|
dbg_macro = "deny"
|
||||||
todo = "deny"
|
todo = "deny"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax = docker/dockerfile:1.2
|
# syntax = docker/dockerfile:1.2
|
||||||
|
|
||||||
FROM rust:1.79-bookworm as builder
|
FROM rust:1.78-bookworm as builder
|
||||||
WORKDIR app
|
WORKDIR app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M9 5H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M10.5 8H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M9 10.9502H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 683 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-tree"><path d="M21 12h-8"/><path d="M21 6H8"/><path d="M21 18h-8"/><path d="M3 6v4c0 1.1.9 2 2 2h3"/><path d="M3 10v6c0 1.1.9 2 2 2h3"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 349 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 302 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-cw"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 303 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M9.88889 1H2.11111C1.49746 1 1 1.49746 1 2.11111V9.88889C1 10.5025 1.49746 11 2.11111 11H9.88889C10.5025 11 11 10.5025 11 9.88889V2.11111C11 1.49746 10.5025 1 9.88889 1Z" stroke="#C56757" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 369 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 345 B |
@@ -25,8 +25,7 @@
|
|||||||
],
|
],
|
||||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
"ctrl-shift-down": "editor::AddSelectionBelow",
|
||||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
"ctrl-shift-up": "editor::AddSelectionAbove",
|
||||||
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
|
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine"
|
||||||
"ctrl-shift-m": "markdown::OpenPreviewToTheSide"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[
|
[
|
||||||
|
// todo(linux): Review the editor bindings
|
||||||
// Standard Linux bindings
|
// Standard Linux bindings
|
||||||
{
|
{
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -42,30 +43,40 @@
|
|||||||
"shift-tab": "editor::TabPrev",
|
"shift-tab": "editor::TabPrev",
|
||||||
"ctrl-k": "editor::CutToEndOfLine",
|
"ctrl-k": "editor::CutToEndOfLine",
|
||||||
"ctrl-t": "editor::Transpose",
|
"ctrl-t": "editor::Transpose",
|
||||||
|
// "ctrl-backspace": "editor::DeleteToBeginningOfLine",
|
||||||
|
// "ctrl-delete": "editor::DeleteToEndOfLine",
|
||||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||||
|
// "ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||||
|
// "alt-h": "editor::DeleteToPreviousWordStart",
|
||||||
|
// "alt-d": "editor::DeleteToNextWordEnd",
|
||||||
"ctrl-x": "editor::Cut",
|
"ctrl-x": "editor::Cut",
|
||||||
"ctrl-insert": "editor::Copy",
|
|
||||||
"ctrl-c": "editor::Copy",
|
"ctrl-c": "editor::Copy",
|
||||||
"shift-insert": "editor::Paste",
|
"ctrl-insert": "editor::Copy",
|
||||||
"ctrl-v": "editor::Paste",
|
"ctrl-v": "editor::Paste",
|
||||||
"ctrl-y": "editor::Redo",
|
"shift-insert": "editor::Paste",
|
||||||
"ctrl-z": "editor::Undo",
|
"ctrl-z": "editor::Undo",
|
||||||
"ctrl-shift-z": "editor::Redo",
|
"ctrl-shift-z": "editor::Redo",
|
||||||
"up": "editor::MoveUp",
|
"up": "editor::MoveUp",
|
||||||
"ctrl-up": "editor::LineUp",
|
// "ctrl-up": "editor::MoveToStartOfParagraph", todo(linux) Should be "scroll down by 1 line"
|
||||||
"ctrl-down": "editor::LineDown",
|
|
||||||
"pageup": "editor::PageUp",
|
"pageup": "editor::PageUp",
|
||||||
"shift-pageup": "editor::SelectPageUp",
|
// "shift-pageup": "editor::MovePageUp", todo(linux) should be 'select page up'
|
||||||
"home": "editor::MoveToBeginningOfLine",
|
"home": "editor::MoveToBeginningOfLine",
|
||||||
"down": "editor::MoveDown",
|
"down": "editor::MoveDown",
|
||||||
|
// "ctrl-down": "editor::MoveToEndOfParagraph", todo(linux) should be "scroll up by 1 line"
|
||||||
"pagedown": "editor::PageDown",
|
"pagedown": "editor::PageDown",
|
||||||
"shift-pagedown": "editor::SelectPageDown",
|
// "shift-pagedown": "editor::MovePageDown", todo(linux) should be 'select page down'
|
||||||
"end": "editor::MoveToEndOfLine",
|
"end": "editor::MoveToEndOfLine",
|
||||||
"left": "editor::MoveLeft",
|
"left": "editor::MoveLeft",
|
||||||
"right": "editor::MoveRight",
|
"right": "editor::MoveRight",
|
||||||
"ctrl-left": "editor::MoveToPreviousWordStart",
|
"ctrl-left": "editor::MoveToPreviousWordStart",
|
||||||
|
// "alt-b": "editor::MoveToPreviousWordStart",
|
||||||
"ctrl-right": "editor::MoveToNextWordEnd",
|
"ctrl-right": "editor::MoveToNextWordEnd",
|
||||||
|
// "alt-f": "editor::MoveToNextWordEnd",
|
||||||
|
// "cmd-left": "editor::MoveToBeginningOfLine",
|
||||||
|
// "ctrl-a": "editor::MoveToBeginningOfLine",
|
||||||
|
// "cmd-right": "editor::MoveToEndOfLine",
|
||||||
|
// "ctrl-e": "editor::MoveToEndOfLine",
|
||||||
"ctrl-home": "editor::MoveToBeginning",
|
"ctrl-home": "editor::MoveToBeginning",
|
||||||
"ctrl-end": "editor::MoveToEnd",
|
"ctrl-end": "editor::MoveToEnd",
|
||||||
"shift-up": "editor::SelectUp",
|
"shift-up": "editor::SelectUp",
|
||||||
@@ -76,6 +87,8 @@
|
|||||||
"ctrl-shift-right": "editor::SelectToNextWordEnd",
|
"ctrl-shift-right": "editor::SelectToNextWordEnd",
|
||||||
"ctrl-shift-up": "editor::AddSelectionAbove",
|
"ctrl-shift-up": "editor::AddSelectionAbove",
|
||||||
"ctrl-shift-down": "editor::AddSelectionBelow",
|
"ctrl-shift-down": "editor::AddSelectionBelow",
|
||||||
|
// "ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
||||||
|
// "ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
||||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||||
"ctrl-shift-end": "editor::SelectToEnd",
|
"ctrl-shift-end": "editor::SelectToEnd",
|
||||||
"ctrl-a": "editor::SelectAll",
|
"ctrl-a": "editor::SelectAll",
|
||||||
@@ -282,13 +295,6 @@
|
|||||||
"ctrl-alt-shift-x": "search::ToggleRegex"
|
"ctrl-alt-shift-x": "search::ToggleRegex"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "Terminal",
|
|
||||||
"bindings": {
|
|
||||||
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
|
||||||
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Bindings from VS Code
|
// Bindings from VS Code
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
@@ -413,7 +419,7 @@
|
|||||||
"alt-8": ["workspace::ActivatePane", 7],
|
"alt-8": ["workspace::ActivatePane", 7],
|
||||||
"alt-9": ["workspace::ActivatePane", 8],
|
"alt-9": ["workspace::ActivatePane", 8],
|
||||||
"ctrl-alt-b": "workspace::ToggleLeftDock",
|
"ctrl-alt-b": "workspace::ToggleLeftDock",
|
||||||
"ctrl-b": "workspace::ToggleLeftDock",
|
"ctrl-b": "workspace::ToggleRightDock",
|
||||||
"ctrl-j": "workspace::ToggleBottomDock",
|
"ctrl-j": "workspace::ToggleBottomDock",
|
||||||
"ctrl-alt-y": "workspace::CloseAllDocks",
|
"ctrl-alt-y": "workspace::CloseAllDocks",
|
||||||
"ctrl-shift-f": "pane::DeploySearch",
|
"ctrl-shift-f": "pane::DeploySearch",
|
||||||
@@ -433,7 +439,6 @@
|
|||||||
"ctrl-shift-p": "command_palette::Toggle",
|
"ctrl-shift-p": "command_palette::Toggle",
|
||||||
"ctrl-shift-m": "diagnostics::Deploy",
|
"ctrl-shift-m": "diagnostics::Deploy",
|
||||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||||
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
|
||||||
"ctrl-?": "assistant::ToggleFocus",
|
"ctrl-?": "assistant::ToggleFocus",
|
||||||
"ctrl-alt-s": "workspace::SaveAll",
|
"ctrl-alt-s": "workspace::SaveAll",
|
||||||
"ctrl-k m": "language_selector::Toggle",
|
"ctrl-k m": "language_selector::Toggle",
|
||||||
@@ -557,19 +562,6 @@
|
|||||||
"ctrl-enter": "project_search::SearchInNew"
|
"ctrl-enter": "project_search::SearchInNew"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "OutlinePanel",
|
|
||||||
"bindings": {
|
|
||||||
"left": "outline_panel::CollapseSelectedEntry",
|
|
||||||
"right": "outline_panel::ExpandSelectedEntry",
|
|
||||||
"ctrl-alt-c": "outline_panel::CopyPath",
|
|
||||||
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
|
|
||||||
"alt-ctrl-r": "outline_panel::RevealInFinder",
|
|
||||||
"space": "outline_panel::Open",
|
|
||||||
"shift-down": "menu::SelectNext",
|
|
||||||
"shift-up": "menu::SelectPrev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel",
|
"context": "ProjectPanel",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -591,10 +583,7 @@
|
|||||||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||||
"alt-ctrl-r": "project_panel::RevealInFinder",
|
"alt-ctrl-r": "project_panel::RevealInFinder",
|
||||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
"alt-shift-f": "project_panel::NewSearchInDirectory"
|
||||||
"shift-down": "menu::SelectNext",
|
|
||||||
"shift-up": "menu::SelectPrev",
|
|
||||||
"escape": "menu::Cancel"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -475,7 +475,6 @@
|
|||||||
"cmd-shift-p": "command_palette::Toggle",
|
"cmd-shift-p": "command_palette::Toggle",
|
||||||
"cmd-shift-m": "diagnostics::Deploy",
|
"cmd-shift-m": "diagnostics::Deploy",
|
||||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||||
"cmd-shift-b": "outline_panel::ToggleFocus",
|
|
||||||
"cmd-?": "assistant::ToggleFocus",
|
"cmd-?": "assistant::ToggleFocus",
|
||||||
"cmd-alt-s": "workspace::SaveAll",
|
"cmd-alt-s": "workspace::SaveAll",
|
||||||
"cmd-k m": "language_selector::Toggle",
|
"cmd-k m": "language_selector::Toggle",
|
||||||
@@ -585,19 +584,6 @@
|
|||||||
"cmd-enter": "project_search::SearchInNew"
|
"cmd-enter": "project_search::SearchInNew"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "OutlinePanel",
|
|
||||||
"bindings": {
|
|
||||||
"left": "outline_panel::CollapseSelectedEntry",
|
|
||||||
"right": "outline_panel::ExpandSelectedEntry",
|
|
||||||
"cmd-alt-c": "outline_panel::CopyPath",
|
|
||||||
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
|
|
||||||
"alt-cmd-r": "outline_panel::RevealInFinder",
|
|
||||||
"space": "outline_panel::Open",
|
|
||||||
"shift-down": "menu::SelectNext",
|
|
||||||
"shift-up": "menu::SelectPrev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "ProjectPanel",
|
"context": "ProjectPanel",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
"right": "vim::Right",
|
"right": "vim::Right",
|
||||||
"space": "vim::Space",
|
"space": "vim::Space",
|
||||||
"$": "vim::EndOfLine",
|
"$": "vim::EndOfLine",
|
||||||
"end": "vim::EndOfLine",
|
|
||||||
"^": "vim::FirstNonWhitespace",
|
"^": "vim::FirstNonWhitespace",
|
||||||
"_": "vim::StartOfLineDownward",
|
"_": "vim::StartOfLineDownward",
|
||||||
"g _": "vim::EndOfLineDownward",
|
"g _": "vim::EndOfLineDownward",
|
||||||
@@ -81,7 +80,6 @@
|
|||||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||||
|
|
||||||
"/": "vim::Search",
|
"/": "vim::Search",
|
||||||
"g /": "pane::DeploySearch",
|
|
||||||
"?": [
|
"?": [
|
||||||
"vim::Search",
|
"vim::Search",
|
||||||
{
|
{
|
||||||
@@ -141,8 +139,7 @@
|
|||||||
"ctrl-q": "vim::ToggleVisualBlock",
|
"ctrl-q": "vim::ToggleVisualBlock",
|
||||||
"shift-k": "editor::Hover",
|
"shift-k": "editor::Hover",
|
||||||
"shift-r": "vim::ToggleReplace",
|
"shift-r": "vim::ToggleReplace",
|
||||||
"0": "vim::StartOfLine",
|
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||||
"home": "vim::StartOfLine",
|
|
||||||
"ctrl-f": "vim::PageDown",
|
"ctrl-f": "vim::PageDown",
|
||||||
"pagedown": "vim::PageDown",
|
"pagedown": "vim::PageDown",
|
||||||
"ctrl-b": "vim::PageUp",
|
"ctrl-b": "vim::PageUp",
|
||||||
@@ -248,10 +245,9 @@
|
|||||||
"displayLines": true
|
"displayLines": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"g v": "vim::RestoreVisualSelection",
|
|
||||||
"g ]": "editor::GoToDiagnostic",
|
"g ]": "editor::GoToDiagnostic",
|
||||||
"g [": "editor::GoToPrevDiagnostic",
|
"g [": "editor::GoToPrevDiagnostic",
|
||||||
"g i": "vim::InsertAtPrevious",
|
"g i": ["workspace::SendKeystrokes", "` ^ i"],
|
||||||
"g ,": "vim::ChangeListNewer",
|
"g ,": "vim::ChangeListNewer",
|
||||||
"g ;": "vim::ChangeListOlder",
|
"g ;": "vim::ChangeListOlder",
|
||||||
"shift-h": "vim::WindowTop",
|
"shift-h": "vim::WindowTop",
|
||||||
@@ -385,10 +381,6 @@
|
|||||||
"shift-s": "vim::SubstituteLine",
|
"shift-s": "vim::SubstituteLine",
|
||||||
">": ["vim::PushOperator", "Indent"],
|
">": ["vim::PushOperator", "Indent"],
|
||||||
"<": ["vim::PushOperator", "Outdent"],
|
"<": ["vim::PushOperator", "Outdent"],
|
||||||
"g u": ["vim::PushOperator", "Lowercase"],
|
|
||||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
|
||||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
|
||||||
"\"": ["vim::PushOperator", "Register"],
|
|
||||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||||
// tree-sitter related commands
|
// tree-sitter related commands
|
||||||
@@ -403,14 +395,13 @@
|
|||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
|
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"\"": ["vim::PushOperator", "Register"],
|
|
||||||
// tree-sitter related commands
|
// tree-sitter related commands
|
||||||
"[ x": "editor::SelectLargerSyntaxNode",
|
"[ x": "editor::SelectLargerSyntaxNode",
|
||||||
"] x": "editor::SelectSmallerSyntaxNode"
|
"] x": "editor::SelectSmallerSyntaxNode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && VimCount && vim_mode != insert",
|
"context": "Editor && VimCount",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"0": ["vim::Number", 0]
|
"0": ["vim::Number", 0]
|
||||||
}
|
}
|
||||||
@@ -439,27 +430,6 @@
|
|||||||
"d": "vim::CurrentLine"
|
"d": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "Editor && vim_operator == gu",
|
|
||||||
"bindings": {
|
|
||||||
"g u": "vim::CurrentLine",
|
|
||||||
"u": "vim::CurrentLine"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Editor && vim_operator == gU",
|
|
||||||
"bindings": {
|
|
||||||
"g shift-u": "vim::CurrentLine",
|
|
||||||
"shift-u": "vim::CurrentLine"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Editor && vim_operator == g~",
|
|
||||||
"bindings": {
|
|
||||||
"g ~": "vim::CurrentLine",
|
|
||||||
"~": "vim::CurrentLine"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == normal && vim_operator == d",
|
"context": "Editor && vim_mode == normal && vim_operator == d",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -612,13 +582,13 @@
|
|||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == normal && !VimWaiting",
|
"context": "Editor && vim_mode == normal && !VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g c c": "vim::ToggleComments"
|
"g c c": "editor::ToggleComments"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == visual",
|
"context": "Editor && vim_mode == visual",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g c": "vim::ToggleComments"
|
"g c": "editor::ToggleComments"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -636,7 +606,8 @@
|
|||||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||||
"ctrl-t": "vim::Indent",
|
"ctrl-t": "vim::Indent",
|
||||||
"ctrl-d": "vim::Outdent",
|
"ctrl-d": "vim::Outdent",
|
||||||
"ctrl-r": ["vim::PushOperator", "Register"]
|
"ctrl-r \"": "editor::Paste",
|
||||||
|
"ctrl-r +": "editor::Paste"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -645,13 +616,11 @@
|
|||||||
"escape": "vim::NormalBefore",
|
"escape": "vim::NormalBefore",
|
||||||
"ctrl-c": "vim::NormalBefore",
|
"ctrl-c": "vim::NormalBefore",
|
||||||
"ctrl-[": "vim::NormalBefore",
|
"ctrl-[": "vim::NormalBefore",
|
||||||
"tab": "vim::Tab",
|
|
||||||
"enter": "vim::Enter",
|
|
||||||
"backspace": "vim::UndoReplace"
|
"backspace": "vim::UndoReplace"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode != replace && VimWaiting",
|
"context": "Editor && VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "vim::Tab",
|
"tab": "vim::Tab",
|
||||||
"enter": "vim::Enter",
|
"enter": "vim::Enter",
|
||||||
@@ -659,13 +628,6 @@
|
|||||||
"ctrl-[": ["vim::SwitchMode", "Normal"]
|
"ctrl-[": ["vim::SwitchMode", "Normal"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"context": "Editor && vim_mode == insert && VimWaiting",
|
|
||||||
"bindings": {
|
|
||||||
"escape": "vim::NormalBefore",
|
|
||||||
"ctrl-[": "vim::NormalBefore"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"context": "BufferSearchBar && !in_replace",
|
"context": "BufferSearchBar && !in_replace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|||||||
@@ -119,10 +119,10 @@
|
|||||||
// The debounce delay before re-querying the language server for completion
|
// The debounce delay before re-querying the language server for completion
|
||||||
// documentation when not included in original completion list.
|
// documentation when not included in original completion list.
|
||||||
"completion_documentation_secondary_query_debounce": 300,
|
"completion_documentation_secondary_query_debounce": 300,
|
||||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
// Whether to show wrap guides in the editor. Setting this to true will
|
||||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
// show a guide at the 'preferred_line_length' value if 'soft_wrap' is set to
|
||||||
// if softwrap is set to 'preferred_line_length', and will show any
|
// 'preferred_line_length', and will show any additional guides as specified
|
||||||
// additional guides as specified by the 'wrap_guides' setting.
|
// by the 'wrap_guides' setting.
|
||||||
"show_wrap_guides": true,
|
"show_wrap_guides": true,
|
||||||
// Character counts at which to show wrap guides in the editor.
|
// Character counts at which to show wrap guides in the editor.
|
||||||
"wrap_guides": [],
|
"wrap_guides": [],
|
||||||
@@ -131,14 +131,7 @@
|
|||||||
// The default number of lines to expand excerpts in the multibuffer by.
|
// The default number of lines to expand excerpts in the multibuffer by.
|
||||||
"expand_excerpt_lines": 3,
|
"expand_excerpt_lines": 3,
|
||||||
// Globs to match against file paths to determine if a file is private.
|
// Globs to match against file paths to determine if a file is private.
|
||||||
"private_files": [
|
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
|
||||||
"**/.env*",
|
|
||||||
"**/*.pem",
|
|
||||||
"**/*.key",
|
|
||||||
"**/*.cert",
|
|
||||||
"**/*.crt",
|
|
||||||
"**/secrets.yml"
|
|
||||||
],
|
|
||||||
// Whether to use additional LSP queries to format (and amend) the code after
|
// Whether to use additional LSP queries to format (and amend) the code after
|
||||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||||
"use_on_type_format": true,
|
"use_on_type_format": true,
|
||||||
@@ -146,19 +139,15 @@
|
|||||||
// opening parenthesis, bracket, brace, single or double quote characters.
|
// opening parenthesis, bracket, brace, single or double quote characters.
|
||||||
// For example, when you type (, Zed will add a closing ) at the correct position.
|
// For example, when you type (, Zed will add a closing ) at the correct position.
|
||||||
"use_autoclose": true,
|
"use_autoclose": true,
|
||||||
// Whether to automatically surround selected text when typing opening parenthesis,
|
|
||||||
// bracket, brace, single or double quote characters.
|
|
||||||
// For example, when you select text and type (, Zed will surround the text with ().
|
|
||||||
"use_auto_surround": true,
|
|
||||||
// Controls how the editor handles the autoclosed characters.
|
// Controls how the editor handles the autoclosed characters.
|
||||||
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
||||||
// happen only for auto-inserted characters.
|
// happen only for auto-inserted characters.
|
||||||
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
|
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
|
||||||
// no matter how they were inserted.
|
// no matter how they were inserted.
|
||||||
"always_treat_brackets_as_autoclosed": false,
|
"always_treat_brackets_as_autoclosed": false,
|
||||||
// Controls whether inline completions are shown immediately (true)
|
// Controls whether copilot provides suggestion immediately
|
||||||
// or manually by triggering `editor::ShowInlineCompletion` (false).
|
// or waits for a `copilot::Toggle`
|
||||||
"show_inline_completions": true,
|
"show_copilot_suggestions": true,
|
||||||
// Whether to show tabs and spaces in the editor.
|
// Whether to show tabs and spaces in the editor.
|
||||||
// This setting can take three values:
|
// This setting can take three values:
|
||||||
//
|
//
|
||||||
@@ -187,9 +176,7 @@
|
|||||||
// Whether to show breadcrumbs.
|
// Whether to show breadcrumbs.
|
||||||
"breadcrumbs": true,
|
"breadcrumbs": true,
|
||||||
// Whether to show quick action buttons.
|
// Whether to show quick action buttons.
|
||||||
"quick_actions": true,
|
"quick_actions": true
|
||||||
// Whether to show the Selections menu in the editor toolbar
|
|
||||||
"selections_menu": true
|
|
||||||
},
|
},
|
||||||
// Scrollbar related settings
|
// Scrollbar related settings
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
@@ -231,8 +218,6 @@
|
|||||||
"line_numbers": true,
|
"line_numbers": true,
|
||||||
// Whether to show code action buttons in the gutter.
|
// Whether to show code action buttons in the gutter.
|
||||||
"code_actions": true,
|
"code_actions": true,
|
||||||
// Whether to show runnables buttons in the gutter.
|
|
||||||
"runnables": true,
|
|
||||||
// Whether to show fold buttons in the gutter.
|
// Whether to show fold buttons in the gutter.
|
||||||
"folds": true
|
"folds": true
|
||||||
},
|
},
|
||||||
@@ -241,8 +226,6 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
/// The width of the indent guides in pixels, between 1 and 10.
|
/// The width of the indent guides in pixels, between 1 and 10.
|
||||||
"line_width": 1,
|
"line_width": 1,
|
||||||
/// The width of the active indent guide in pixels, between 1 and 10.
|
|
||||||
"active_line_width": 1,
|
|
||||||
/// Determines how indent guides are colored.
|
/// Determines how indent guides are colored.
|
||||||
/// This setting can take the following three values:
|
/// This setting can take the following three values:
|
||||||
///
|
///
|
||||||
@@ -257,8 +240,6 @@
|
|||||||
/// 2. "indent_aware"
|
/// 2. "indent_aware"
|
||||||
"background_coloring": "disabled"
|
"background_coloring": "disabled"
|
||||||
},
|
},
|
||||||
// Whether the editor will scroll beyond the last line.
|
|
||||||
"scroll_beyond_last_line": "one_page",
|
|
||||||
// The number of lines to keep above/below the cursor when scrolling.
|
// The number of lines to keep above/below the cursor when scrolling.
|
||||||
"vertical_scroll_margin": 3,
|
"vertical_scroll_margin": 3,
|
||||||
// Scroll sensitivity multiplier. This multiplier is applied
|
// Scroll sensitivity multiplier. This multiplier is applied
|
||||||
@@ -312,37 +293,7 @@
|
|||||||
"auto_reveal_entries": true,
|
"auto_reveal_entries": true,
|
||||||
/// Whether to fold directories automatically
|
/// Whether to fold directories automatically
|
||||||
/// when a directory has only one directory inside.
|
/// when a directory has only one directory inside.
|
||||||
"auto_fold_dirs": false,
|
"auto_fold_dirs": false
|
||||||
/// Scrollbar-related settings
|
|
||||||
"scrollbar": {
|
|
||||||
/// When to show the scrollbar in the project panel.
|
|
||||||
///
|
|
||||||
/// Default: always
|
|
||||||
"show": "always"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"outline_panel": {
|
|
||||||
// Whether to show the outline panel button in the status bar
|
|
||||||
"button": true,
|
|
||||||
// Default width of the outline panel.
|
|
||||||
"default_width": 300,
|
|
||||||
// Where to dock the outline panel. Can be 'left' or 'right'.
|
|
||||||
"dock": "left",
|
|
||||||
// Whether to show file icons in the outline panel.
|
|
||||||
"file_icons": true,
|
|
||||||
// Whether to show folder icons or chevrons for directories in the outline panel.
|
|
||||||
"folder_icons": true,
|
|
||||||
// Whether to show the git status in the outline panel.
|
|
||||||
"git_status": true,
|
|
||||||
// Amount of indentation for nested items.
|
|
||||||
"indent_size": 20,
|
|
||||||
// Whether to reveal it in the outline panel automatically,
|
|
||||||
// when a corresponding outline entry becomes active.
|
|
||||||
// Gitignored entries are never auto revealed.
|
|
||||||
"auto_reveal_entries": true,
|
|
||||||
/// Whether to fold directories automatically
|
|
||||||
/// when a directory has only one directory inside.
|
|
||||||
"auto_fold_dirs": true
|
|
||||||
},
|
},
|
||||||
"collaboration_panel": {
|
"collaboration_panel": {
|
||||||
// Whether to show the collaboration panel button in the status bar.
|
// Whether to show the collaboration panel button in the status bar.
|
||||||
@@ -403,9 +354,6 @@
|
|||||||
"show_call_status_icon": true,
|
"show_call_status_icon": true,
|
||||||
// Whether to use language servers to provide code intelligence.
|
// Whether to use language servers to provide code intelligence.
|
||||||
"enable_language_server": true,
|
"enable_language_server": true,
|
||||||
// Whether to perform linked edits of associated ranges, if the language server supports it.
|
|
||||||
// For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.
|
|
||||||
"linked_edits": true,
|
|
||||||
// The list of language servers to use (or disable) for all languages.
|
// The list of language servers to use (or disable) for all languages.
|
||||||
//
|
//
|
||||||
// This is typically customized on a per-language basis.
|
// This is typically customized on a per-language basis.
|
||||||
@@ -477,16 +425,16 @@
|
|||||||
// or falling back to formatting via language server:
|
// or falling back to formatting via language server:
|
||||||
// "formatter": "auto"
|
// "formatter": "auto"
|
||||||
"formatter": "auto",
|
"formatter": "auto",
|
||||||
// How to soft-wrap long lines of text.
|
// How to soft-wrap long lines of text. This setting can take
|
||||||
// Possible values:
|
// three values:
|
||||||
//
|
//
|
||||||
// 1. Do not soft wrap.
|
// 1. Do not soft wrap.
|
||||||
// "soft_wrap": "none",
|
// "soft_wrap": "none",
|
||||||
// 2. Prefer a single line generally, unless an overly long line is encountered.
|
// 2. Prefer a single line generally, unless an overly long line is encountered.
|
||||||
// "soft_wrap": "prefer_line",
|
// "soft_wrap": "prefer_line",
|
||||||
// 3. Soft wrap lines that overflow the editor.
|
// 3. Soft wrap lines that overflow the editor:
|
||||||
// "soft_wrap": "editor_width",
|
// "soft_wrap": "editor_width",
|
||||||
// 4. Soft wrap lines at the preferred line length.
|
// 4. Soft wrap lines at the preferred line length
|
||||||
// "soft_wrap": "preferred_line_length",
|
// "soft_wrap": "preferred_line_length",
|
||||||
"soft_wrap": "prefer_line",
|
"soft_wrap": "prefer_line",
|
||||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||||
@@ -542,8 +490,9 @@
|
|||||||
// "delay_ms": 600
|
// "delay_ms": 600
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inline_completions": {
|
"copilot": {
|
||||||
// A list of globs representing files that inline completions should be disabled for.
|
// The set of glob patterns for which copilot should be disabled
|
||||||
|
// in any matching file.
|
||||||
"disabled_globs": [".env"]
|
"disabled_globs": [".env"]
|
||||||
},
|
},
|
||||||
// Settings specific to journaling
|
// Settings specific to journaling
|
||||||
@@ -748,7 +697,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"JavaScript": {
|
"JavaScript": {
|
||||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
@@ -791,7 +740,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TSX": {
|
"TSX": {
|
||||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
@@ -802,7 +751,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TypeScript": {
|
"TypeScript": {
|
||||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"allowed": true
|
"allowed": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,22 @@ use editor::Editor;
|
|||||||
use extension::ExtensionStore;
|
use extension::ExtensionStore;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, anchored, deferred, percentage, Animation, AnimationExt as _, AppContext, CursorStyle,
|
actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
|
||||||
DismissEvent, EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render,
|
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
|
||||||
SharedString, StatefulInteractiveElement, Styled, Transformation, View, ViewContext,
|
ViewContext, VisualContext as _,
|
||||||
VisualContext as _,
|
|
||||||
};
|
|
||||||
use language::{
|
|
||||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
|
|
||||||
};
|
};
|
||||||
|
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||||
use project::{LanguageServerProgress, Project};
|
use project::{LanguageServerProgress, Project};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
use std::{cmp::Reverse, fmt::Write, sync::Arc};
|
||||||
use ui::{prelude::*, ContextMenu};
|
use ui::prelude::*;
|
||||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||||
|
|
||||||
actions!(activity_indicator, [ShowErrorMessage]);
|
actions!(activity_indicator, [ShowErrorMessage]);
|
||||||
|
|
||||||
|
const DOWNLOAD_ICON: &str = "icons/download.svg";
|
||||||
|
const WARNING_ICON: &str = "icons/warning.svg";
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ShowError { lsp_name: Arc<str>, error: String },
|
ShowError { lsp_name: Arc<str>, error: String },
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,6 @@ pub struct ActivityIndicator {
|
|||||||
statuses: Vec<LspStatus>,
|
statuses: Vec<LspStatus>,
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
auto_updater: Option<Model<AutoUpdater>>,
|
auto_updater: Option<Model<AutoUpdater>>,
|
||||||
context_menu: Option<View<ContextMenu>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LspStatus {
|
struct LspStatus {
|
||||||
@@ -36,14 +35,14 @@ struct LspStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct PendingWork<'a> {
|
struct PendingWork<'a> {
|
||||||
language_server_id: LanguageServerId,
|
language_server_name: &'a str,
|
||||||
progress_token: &'a str,
|
progress_token: &'a str,
|
||||||
progress: &'a LanguageServerProgress,
|
progress: &'a LanguageServerProgress,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Content {
|
struct Content {
|
||||||
icon: Option<gpui::AnyElement>,
|
icon: Option<&'static str>,
|
||||||
message: String,
|
message: String,
|
||||||
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
||||||
}
|
}
|
||||||
@@ -79,7 +78,6 @@ impl ActivityIndicator {
|
|||||||
statuses: Default::default(),
|
statuses: Default::default(),
|
||||||
project: project.clone(),
|
project: project.clone(),
|
||||||
auto_updater,
|
auto_updater,
|
||||||
context_menu: None,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,7 +151,7 @@ impl ActivityIndicator {
|
|||||||
.read(cx)
|
.read(cx)
|
||||||
.language_server_statuses()
|
.language_server_statuses()
|
||||||
.rev()
|
.rev()
|
||||||
.filter_map(|(server_id, status)| {
|
.filter_map(|status| {
|
||||||
if status.pending_work.is_empty() {
|
if status.pending_work.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -161,7 +159,7 @@ impl ActivityIndicator {
|
|||||||
.pending_work
|
.pending_work
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(token, progress)| PendingWork {
|
.map(|(token, progress)| PendingWork {
|
||||||
language_server_id: server_id,
|
language_server_name: status.name.as_str(),
|
||||||
progress_token: token.as_str(),
|
progress_token: token.as_str(),
|
||||||
progress,
|
progress,
|
||||||
})
|
})
|
||||||
@@ -177,44 +175,33 @@ impl ActivityIndicator {
|
|||||||
// Show any language server has pending activity.
|
// Show any language server has pending activity.
|
||||||
let mut pending_work = self.pending_language_server_work(cx);
|
let mut pending_work = self.pending_language_server_work(cx);
|
||||||
if let Some(PendingWork {
|
if let Some(PendingWork {
|
||||||
|
language_server_name,
|
||||||
progress_token,
|
progress_token,
|
||||||
progress,
|
progress,
|
||||||
..
|
|
||||||
}) = pending_work.next()
|
}) = pending_work.next()
|
||||||
{
|
{
|
||||||
let mut message = progress
|
let mut message = language_server_name.to_string();
|
||||||
.title
|
|
||||||
.as_deref()
|
message.push_str(": ");
|
||||||
.unwrap_or(progress_token)
|
if let Some(progress_message) = progress.message.as_ref() {
|
||||||
.to_string();
|
message.push_str(progress_message);
|
||||||
|
} else {
|
||||||
|
message.push_str(progress_token);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(percentage) = progress.percentage {
|
if let Some(percentage) = progress.percentage {
|
||||||
write!(&mut message, " ({}%)", percentage).unwrap();
|
write!(&mut message, " ({}%)", percentage).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(progress_message) = progress.message.as_ref() {
|
|
||||||
message.push_str(": ");
|
|
||||||
message.push_str(progress_message);
|
|
||||||
}
|
|
||||||
|
|
||||||
let additional_work_count = pending_work.count();
|
let additional_work_count = pending_work.count();
|
||||||
if additional_work_count > 0 {
|
if additional_work_count > 0 {
|
||||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(
|
icon: None,
|
||||||
Icon::new(IconName::ArrowCircle)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.with_animation(
|
|
||||||
"arrow-circle",
|
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
|
||||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message,
|
message,
|
||||||
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
on_click: None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,11 +222,7 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
if !downloading.is_empty() {
|
if !downloading.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(
|
icon: Some(DOWNLOAD_ICON),
|
||||||
Icon::new(IconName::Download)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: format!("Downloading {}...", downloading.join(", "),),
|
message: format!("Downloading {}...", downloading.join(", "),),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
};
|
};
|
||||||
@@ -247,11 +230,7 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
if !checking_for_update.is_empty() {
|
if !checking_for_update.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(
|
icon: Some(DOWNLOAD_ICON),
|
||||||
Icon::new(IconName::Download)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: format!(
|
message: format!(
|
||||||
"Checking for updates to {}...",
|
"Checking for updates to {}...",
|
||||||
checking_for_update.join(", "),
|
checking_for_update.join(", "),
|
||||||
@@ -262,11 +241,7 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
if !failed.is_empty() {
|
if !failed.is_empty() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(
|
icon: Some(WARNING_ICON),
|
||||||
Icon::new(IconName::ExclamationTriangle)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: format!(
|
message: format!(
|
||||||
"Failed to download {}. Click to show error.",
|
"Failed to download {}. Click to show error.",
|
||||||
failed.join(", "),
|
failed.join(", "),
|
||||||
@@ -280,11 +255,7 @@ impl ActivityIndicator {
|
|||||||
// Show any formatting failure
|
// Show any formatting failure
|
||||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(
|
icon: Some(WARNING_ICON),
|
||||||
Icon::new(IconName::ExclamationTriangle)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
||||||
on_click: Some(Arc::new(|_, cx| {
|
on_click: Some(Arc::new(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||||
@@ -296,29 +267,17 @@ impl ActivityIndicator {
|
|||||||
if let Some(updater) = &self.auto_updater {
|
if let Some(updater) = &self.auto_updater {
|
||||||
return match &updater.read(cx).status() {
|
return match &updater.read(cx).status() {
|
||||||
AutoUpdateStatus::Checking => Content {
|
AutoUpdateStatus::Checking => Content {
|
||||||
icon: Some(
|
icon: Some(DOWNLOAD_ICON),
|
||||||
Icon::new(IconName::Download)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: "Checking for Zed updates…".to_string(),
|
message: "Checking for Zed updates…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Downloading => Content {
|
AutoUpdateStatus::Downloading => Content {
|
||||||
icon: Some(
|
icon: Some(DOWNLOAD_ICON),
|
||||||
Icon::new(IconName::Download)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: "Downloading Zed update…".to_string(),
|
message: "Downloading Zed update…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Installing => Content {
|
AutoUpdateStatus::Installing => Content {
|
||||||
icon: Some(
|
icon: Some(DOWNLOAD_ICON),
|
||||||
Icon::new(IconName::Download)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: "Installing Zed update…".to_string(),
|
message: "Installing Zed update…".to_string(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
},
|
},
|
||||||
@@ -326,18 +285,14 @@ impl ActivityIndicator {
|
|||||||
icon: None,
|
icon: None,
|
||||||
message: "Click to restart and update Zed".to_string(),
|
message: "Click to restart and update Zed".to_string(),
|
||||||
on_click: Some(Arc::new({
|
on_click: Some(Arc::new({
|
||||||
let reload = workspace::Reload {
|
let restart = workspace::Restart {
|
||||||
binary_path: Some(binary_path.clone()),
|
binary_path: Some(binary_path.clone()),
|
||||||
};
|
};
|
||||||
move |_, cx| workspace::reload(&reload, cx)
|
move |_, cx| workspace::restart(&restart, cx)
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
AutoUpdateStatus::Errored => Content {
|
AutoUpdateStatus::Errored => Content {
|
||||||
icon: Some(
|
icon: Some(WARNING_ICON),
|
||||||
Icon::new(IconName::ExclamationTriangle)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: "Auto update failed".to_string(),
|
message: "Auto update failed".to_string(),
|
||||||
on_click: Some(Arc::new(|this, cx| {
|
on_click: Some(Arc::new(|this, cx| {
|
||||||
this.dismiss_error_message(&Default::default(), cx)
|
this.dismiss_error_message(&Default::default(), cx)
|
||||||
@@ -352,11 +307,7 @@ impl ActivityIndicator {
|
|||||||
{
|
{
|
||||||
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
|
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
|
||||||
return Content {
|
return Content {
|
||||||
icon: Some(
|
icon: Some(DOWNLOAD_ICON),
|
||||||
Icon::new(IconName::Download)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
message: format!("Updating {extension_id} extension…"),
|
message: format!("Updating {extension_id} extension…"),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
};
|
};
|
||||||
@@ -365,75 +316,6 @@ impl ActivityIndicator {
|
|||||||
|
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
if self.context_menu.take().is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.build_lsp_work_context_menu(cx);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_lsp_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
let mut has_work = false;
|
|
||||||
let this = cx.view().downgrade();
|
|
||||||
let context_menu = ContextMenu::build(cx, |mut menu, cx| {
|
|
||||||
for work in self.pending_language_server_work(cx) {
|
|
||||||
has_work = true;
|
|
||||||
|
|
||||||
let this = this.clone();
|
|
||||||
let title = SharedString::from(
|
|
||||||
work.progress
|
|
||||||
.title
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or(work.progress_token)
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
if work.progress.is_cancellable {
|
|
||||||
let language_server_id = work.language_server_id;
|
|
||||||
let token = work.progress_token.to_string();
|
|
||||||
menu = menu.custom_entry(
|
|
||||||
move |_| {
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.justify_between()
|
|
||||||
.child(Label::new(title.clone()))
|
|
||||||
.child(Icon::new(IconName::XCircle))
|
|
||||||
.into_any_element()
|
|
||||||
},
|
|
||||||
move |cx| {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.project.update(cx, |project, cx| {
|
|
||||||
project.cancel_language_server_work(
|
|
||||||
language_server_id,
|
|
||||||
Some(token.clone()),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
this.context_menu.take();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
menu = menu.label(title.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
menu
|
|
||||||
});
|
|
||||||
|
|
||||||
if has_work {
|
|
||||||
cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
|
|
||||||
this.context_menu.take();
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
cx.focus_view(&context_menu);
|
|
||||||
self.context_menu = Some(context_menu);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<Event> for ActivityIndicator {}
|
impl EventEmitter<Event> for ActivityIndicator {}
|
||||||
@@ -456,17 +338,8 @@ impl Render for ActivityIndicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
.gap_2()
|
.children(content.icon.map(|icon| svg().path(icon)))
|
||||||
.children(content.icon)
|
|
||||||
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
||||||
.children(self.context_menu.as_ref().map(|menu| {
|
|
||||||
deferred(
|
|
||||||
anchored()
|
|
||||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
|
||||||
.child(menu.clone()),
|
|
||||||
)
|
|
||||||
.with_priority(1)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ pub const ANTHROPIC_API_URL: &'static str = "https://api.anthropic.com";
|
|||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||||
pub enum Model {
|
pub enum Model {
|
||||||
#[default]
|
#[default]
|
||||||
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3-5-sonnet-20240620")]
|
|
||||||
Claude3_5Sonnet,
|
|
||||||
#[serde(alias = "claude-3-opus", rename = "claude-3-opus-20240229")]
|
#[serde(alias = "claude-3-opus", rename = "claude-3-opus-20240229")]
|
||||||
Claude3Opus,
|
Claude3Opus,
|
||||||
#[serde(alias = "claude-3-sonnet", rename = "claude-3-sonnet-20240229")]
|
#[serde(alias = "claude-3-sonnet", rename = "claude-3-sonnet-20240229")]
|
||||||
@@ -24,9 +22,7 @@ pub enum Model {
|
|||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub fn from_id(id: &str) -> Result<Self> {
|
pub fn from_id(id: &str) -> Result<Self> {
|
||||||
if id.starts_with("claude-3-5-sonnet") {
|
if id.starts_with("claude-3-opus") {
|
||||||
Ok(Self::Claude3_5Sonnet)
|
|
||||||
} else if id.starts_with("claude-3-opus") {
|
|
||||||
Ok(Self::Claude3Opus)
|
Ok(Self::Claude3Opus)
|
||||||
} else if id.starts_with("claude-3-sonnet") {
|
} else if id.starts_with("claude-3-sonnet") {
|
||||||
Ok(Self::Claude3Sonnet)
|
Ok(Self::Claude3Sonnet)
|
||||||
@@ -39,7 +35,6 @@ impl Model {
|
|||||||
|
|
||||||
pub fn id(&self) -> &'static str {
|
pub fn id(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Model::Claude3_5Sonnet => "claude-3-5-sonnet-20240620",
|
|
||||||
Model::Claude3Opus => "claude-3-opus-20240229",
|
Model::Claude3Opus => "claude-3-opus-20240229",
|
||||||
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
||||||
Model::Claude3Haiku => "claude-3-opus-20240307",
|
Model::Claude3Haiku => "claude-3-opus-20240307",
|
||||||
@@ -48,7 +43,6 @@ impl Model {
|
|||||||
|
|
||||||
pub fn display_name(&self) -> &'static str {
|
pub fn display_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
|
||||||
Self::Claude3Opus => "Claude 3 Opus",
|
Self::Claude3Opus => "Claude 3 Opus",
|
||||||
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
||||||
Self::Claude3Haiku => "Claude 3 Haiku",
|
Self::Claude3Haiku => "Claude 3 Haiku",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ doctest = false
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
anthropic = { workspace = true, features = ["schemars"] }
|
anthropic = { workspace = true, features = ["schemars"] }
|
||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
async-watch.workspace = true
|
|
||||||
cargo_toml.workspace = true
|
cargo_toml.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
@@ -36,27 +35,22 @@ language.workspace = true
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
multi_buffer.workspace = true
|
multi_buffer.workspace = true
|
||||||
ollama = { workspace = true, features = ["schemars"] }
|
|
||||||
open_ai = { workspace = true, features = ["schemars"] }
|
open_ai = { workspace = true, features = ["schemars"] }
|
||||||
ordered-float.workspace = true
|
ordered-float.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
rope.workspace = true
|
rope.workspace = true
|
||||||
rustdoc.workspace = true
|
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
search.workspace = true
|
search.workspace = true
|
||||||
semantic_index.workspace = true
|
semantic_index.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
similar.workspace = true
|
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
strsim = "0.11"
|
strsim = "0.11"
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
terminal_view.workspace = true
|
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
tiktoken-rs.workspace = true
|
tiktoken-rs.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ mod streaming_diff;
|
|||||||
|
|
||||||
pub use assistant_panel::AssistantPanel;
|
pub use assistant_panel::AssistantPanel;
|
||||||
|
|
||||||
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaModel, OpenAiModel};
|
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OpenAiModel};
|
||||||
use assistant_slash_command::SlashCommandRegistry;
|
use assistant_slash_command::SlashCommandRegistry;
|
||||||
use client::{proto, Client};
|
use client::{proto, Client};
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
@@ -21,19 +21,19 @@ pub(crate) use context_store::*;
|
|||||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||||
pub(crate) use inline_assistant::*;
|
pub(crate) use inline_assistant::*;
|
||||||
pub(crate) use model_selector::*;
|
pub(crate) use model_selector::*;
|
||||||
use rustdoc::RustdocStore;
|
|
||||||
use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
|
use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use slash_command::{
|
use slash_command::{
|
||||||
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
|
active_command, default_command, fetch_command, file_command, project_command, prompt_command,
|
||||||
project_command, prompt_command, rustdoc_command, search_command, tabs_command, term_command,
|
rustdoc_command, search_command, tabs_command,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
pub(crate) use streaming_diff::*;
|
pub(crate) use streaming_diff::*;
|
||||||
|
use util::paths::EMBEDDINGS_DIR;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
assistant,
|
assistant,
|
||||||
@@ -91,7 +91,6 @@ pub enum LanguageModel {
|
|||||||
Cloud(CloudModel),
|
Cloud(CloudModel),
|
||||||
OpenAi(OpenAiModel),
|
OpenAi(OpenAiModel),
|
||||||
Anthropic(AnthropicModel),
|
Anthropic(AnthropicModel),
|
||||||
Ollama(OllamaModel),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LanguageModel {
|
impl Default for LanguageModel {
|
||||||
@@ -106,7 +105,6 @@ impl LanguageModel {
|
|||||||
LanguageModel::OpenAi(model) => format!("openai/{}", model.id()),
|
LanguageModel::OpenAi(model) => format!("openai/{}", model.id()),
|
||||||
LanguageModel::Anthropic(model) => format!("anthropic/{}", model.id()),
|
LanguageModel::Anthropic(model) => format!("anthropic/{}", model.id()),
|
||||||
LanguageModel::Cloud(model) => format!("zed.dev/{}", model.id()),
|
LanguageModel::Cloud(model) => format!("zed.dev/{}", model.id()),
|
||||||
LanguageModel::Ollama(model) => format!("ollama/{}", model.id()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +113,6 @@ impl LanguageModel {
|
|||||||
LanguageModel::OpenAi(model) => model.display_name().into(),
|
LanguageModel::OpenAi(model) => model.display_name().into(),
|
||||||
LanguageModel::Anthropic(model) => model.display_name().into(),
|
LanguageModel::Anthropic(model) => model.display_name().into(),
|
||||||
LanguageModel::Cloud(model) => model.display_name().into(),
|
LanguageModel::Cloud(model) => model.display_name().into(),
|
||||||
LanguageModel::Ollama(model) => model.display_name().into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +121,6 @@ impl LanguageModel {
|
|||||||
LanguageModel::OpenAi(model) => model.max_token_count(),
|
LanguageModel::OpenAi(model) => model.max_token_count(),
|
||||||
LanguageModel::Anthropic(model) => model.max_token_count(),
|
LanguageModel::Anthropic(model) => model.max_token_count(),
|
||||||
LanguageModel::Cloud(model) => model.max_token_count(),
|
LanguageModel::Cloud(model) => model.max_token_count(),
|
||||||
LanguageModel::Ollama(model) => model.max_token_count(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +129,6 @@ impl LanguageModel {
|
|||||||
LanguageModel::OpenAi(model) => model.id(),
|
LanguageModel::OpenAi(model) => model.id(),
|
||||||
LanguageModel::Anthropic(model) => model.id(),
|
LanguageModel::Anthropic(model) => model.id(),
|
||||||
LanguageModel::Cloud(model) => model.id(),
|
LanguageModel::Cloud(model) => model.id(),
|
||||||
LanguageModel::Ollama(model) => model.id(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,12 +179,8 @@ impl LanguageModelRequest {
|
|||||||
match &self.model {
|
match &self.model {
|
||||||
LanguageModel::OpenAi(_) => {}
|
LanguageModel::OpenAi(_) => {}
|
||||||
LanguageModel::Anthropic(_) => {}
|
LanguageModel::Anthropic(_) => {}
|
||||||
LanguageModel::Ollama(_) => {}
|
|
||||||
LanguageModel::Cloud(model) => match model {
|
LanguageModel::Cloud(model) => match model {
|
||||||
CloudModel::Claude3Opus
|
CloudModel::Claude3Opus | CloudModel::Claude3Sonnet | CloudModel::Claude3Haiku => {
|
||||||
| CloudModel::Claude3Sonnet
|
|
||||||
| CloudModel::Claude3Haiku
|
|
||||||
| CloudModel::Claude3_5Sonnet => {
|
|
||||||
preprocess_anthropic_request(self);
|
preprocess_anthropic_request(self);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -273,7 +264,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
async move {
|
async move {
|
||||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||||
let semantic_index = SemanticIndex::new(
|
let semantic_index = SemanticIndex::new(
|
||||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
|
||||||
Arc::new(embedding_provider),
|
Arc::new(embedding_provider),
|
||||||
&mut cx,
|
&mut cx,
|
||||||
)
|
)
|
||||||
@@ -289,7 +280,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
register_slash_commands(cx);
|
register_slash_commands(cx);
|
||||||
assistant_panel::init(cx);
|
assistant_panel::init(cx);
|
||||||
inline_assistant::init(client.telemetry().clone(), cx);
|
inline_assistant::init(client.telemetry().clone(), cx);
|
||||||
RustdocStore::init_global(cx);
|
|
||||||
|
|
||||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||||
filter.hide_namespace(Assistant::NAMESPACE);
|
filter.hide_namespace(Assistant::NAMESPACE);
|
||||||
@@ -317,9 +307,6 @@ fn register_slash_commands(cx: &mut AppContext) {
|
|||||||
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
||||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
||||||
slash_command_registry.register_command(term_command::TermSlashCommand, true);
|
|
||||||
slash_command_registry.register_command(now_command::NowSlashCommand, true);
|
|
||||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
|
|
||||||
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
||||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,36 +15,32 @@ use anyhow::{anyhow, Result};
|
|||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{BTreeSet, HashMap, HashSet};
|
use collections::{BTreeSet, HashMap, HashSet};
|
||||||
|
use editor::actions::ShowCompletions;
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
|
actions::{FoldAt, MoveToEndOfLine, Newline, UnfoldAt},
|
||||||
display_map::{
|
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, ToDisplayPoint},
|
||||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, RenderBlock, ToDisplayPoint,
|
|
||||||
},
|
|
||||||
scroll::{Autoscroll, AutoscrollStrategy},
|
scroll::{Autoscroll, AutoscrollStrategy},
|
||||||
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
|
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
|
||||||
};
|
};
|
||||||
use editor::{display_map::CreaseId, FoldPlaceholder};
|
use editor::{display_map::FlapId, FoldPlaceholder};
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::future::Shared;
|
use futures::future::Shared;
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, percentage, point, rems, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
|
div, point, rems, Action, AnyElement, AnyView, AppContext, AsyncAppContext, AsyncWindowContext,
|
||||||
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context as _, Empty, EventEmitter,
|
ClipboardItem, Context as _, Empty, EventEmitter, FocusHandle, FocusableView,
|
||||||
FocusHandle, FocusOutEvent, FocusableView, InteractiveElement, IntoElement, Model,
|
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
|
||||||
ModelContext, ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled,
|
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, UpdateGlobal, View,
|
||||||
Subscription, Task, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
|
ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
WindowContext,
|
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::SoftWrap, AnchorRangeExt as _, AutoindentMode, Buffer, LanguageRegistry,
|
language_settings::SoftWrap, AnchorRangeExt, AutoindentMode, Buffer, LanguageRegistry,
|
||||||
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
|
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use paths::contexts_dir;
|
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
|
use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
|
||||||
use rustdoc::{CrateName, RustdocStore};
|
|
||||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -58,10 +54,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
use telemetry_events::AssistantKind;
|
use telemetry_events::AssistantKind;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
|
popover_menu, prelude::*, ButtonLike, ContextMenu, ElevationIndex, KeyBinding, ListItem,
|
||||||
ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tab, TabBar, Tooltip,
|
ListItemSpacing, PopoverMenuHandle, Tab, TabBar, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{post_inc, ResultExt, TryFutureExt};
|
use util::{paths::CONTEXTS_DIR, post_inc, ResultExt, TryFutureExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use workspace::NewFile;
|
use workspace::NewFile;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
@@ -83,6 +79,7 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||||
})
|
})
|
||||||
.register_action(AssistantPanel::inline_assist)
|
.register_action(AssistantPanel::inline_assist)
|
||||||
|
.register_action(AssistantPanel::cancel_last_inline_assist)
|
||||||
.register_action(ContextEditor::quote_selection);
|
.register_action(ContextEditor::quote_selection);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -300,7 +297,7 @@ impl AssistantPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
|
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.toolbar
|
self.toolbar
|
||||||
.update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
|
.update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -424,6 +421,19 @@ impl AssistantPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cancel_last_inline_assist(
|
||||||
|
_workspace: &mut Workspace,
|
||||||
|
_: &editor::actions::Cancel,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
let canceled = InlineAssistant::update_global(cx, |assistant, cx| {
|
||||||
|
assistant.cancel_last_inline_assist(cx)
|
||||||
|
});
|
||||||
|
if !canceled {
|
||||||
|
cx.propagate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
||||||
let workspace = self.workspace.upgrade()?;
|
let workspace = self.workspace.upgrade()?;
|
||||||
|
|
||||||
@@ -580,7 +590,7 @@ impl AssistantPanel {
|
|||||||
fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let assistant = cx.view().clone();
|
let assistant = cx.view().clone();
|
||||||
let zoomed = self.zoomed;
|
let zoomed = self.zoomed;
|
||||||
PopoverMenu::new("assistant-popover")
|
popover_menu("assistant-popover")
|
||||||
.trigger(IconButton::new("trigger", IconName::Menu))
|
.trigger(IconButton::new("trigger", IconName::Menu))
|
||||||
.menu(move |cx| {
|
.menu(move |cx| {
|
||||||
let assistant = assistant.clone();
|
let assistant = assistant.clone();
|
||||||
@@ -622,7 +632,7 @@ impl AssistantPanel {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
PopoverMenu::new("inject-context-menu")
|
popover_menu("inject-context-menu")
|
||||||
.trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
|
.trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
|
||||||
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
|
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
|
||||||
}))
|
}))
|
||||||
@@ -1014,7 +1024,6 @@ pub struct Context {
|
|||||||
edit_suggestions: Vec<EditSuggestion>,
|
edit_suggestions: Vec<EditSuggestion>,
|
||||||
pending_slash_commands: Vec<PendingSlashCommand>,
|
pending_slash_commands: Vec<PendingSlashCommand>,
|
||||||
edits_since_last_slash_command_parse: language::Subscription,
|
edits_since_last_slash_command_parse: language::Subscription,
|
||||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
|
||||||
message_anchors: Vec<MessageAnchor>,
|
message_anchors: Vec<MessageAnchor>,
|
||||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
next_message_id: MessageId,
|
next_message_id: MessageId,
|
||||||
@@ -1056,7 +1065,6 @@ impl Context {
|
|||||||
next_message_id: Default::default(),
|
next_message_id: Default::default(),
|
||||||
edit_suggestions: Vec::new(),
|
edit_suggestions: Vec::new(),
|
||||||
pending_slash_commands: Vec::new(),
|
pending_slash_commands: Vec::new(),
|
||||||
slash_command_output_sections: Vec::new(),
|
|
||||||
edits_since_last_slash_command_parse,
|
edits_since_last_slash_command_parse,
|
||||||
summary: None,
|
summary: None,
|
||||||
pending_summary: Task::ready(None),
|
pending_summary: Task::ready(None),
|
||||||
@@ -1093,12 +1101,11 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn serialize(&self, cx: &AppContext) -> SavedContext {
|
fn serialize(&self, cx: &AppContext) -> SavedContext {
|
||||||
let buffer = self.buffer.read(cx);
|
|
||||||
SavedContext {
|
SavedContext {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
zed: "context".into(),
|
zed: "context".into(),
|
||||||
version: SavedContext::VERSION.into(),
|
version: SavedContext::VERSION.into(),
|
||||||
text: buffer.text(),
|
text: self.buffer.read(cx).text(),
|
||||||
message_metadata: self.messages_metadata.clone(),
|
message_metadata: self.messages_metadata.clone(),
|
||||||
messages: self
|
messages: self
|
||||||
.messages(cx)
|
.messages(cx)
|
||||||
@@ -1112,22 +1119,6 @@ impl Context {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|summary| summary.text.clone())
|
.map(|summary| summary.text.clone())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
slash_command_output_sections: self
|
|
||||||
.slash_command_output_sections
|
|
||||||
.iter()
|
|
||||||
.filter_map(|section| {
|
|
||||||
let range = section.range.to_offset(buffer);
|
|
||||||
if section.range.start.is_valid(buffer) && !range.is_empty() {
|
|
||||||
Some(SlashCommandOutputSection {
|
|
||||||
range,
|
|
||||||
icon: section.icon,
|
|
||||||
label: section.label.clone(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1179,19 +1170,6 @@ impl Context {
|
|||||||
next_message_id,
|
next_message_id,
|
||||||
edit_suggestions: Vec::new(),
|
edit_suggestions: Vec::new(),
|
||||||
pending_slash_commands: Vec::new(),
|
pending_slash_commands: Vec::new(),
|
||||||
slash_command_output_sections: saved_context
|
|
||||||
.slash_command_output_sections
|
|
||||||
.into_iter()
|
|
||||||
.map(|section| {
|
|
||||||
let buffer = buffer.read(cx);
|
|
||||||
SlashCommandOutputSection {
|
|
||||||
range: buffer.anchor_after(section.range.start)
|
|
||||||
..buffer.anchor_before(section.range.end),
|
|
||||||
icon: section.icon,
|
|
||||||
label: section.label,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
edits_since_last_slash_command_parse,
|
edits_since_last_slash_command_parse,
|
||||||
summary: Some(Summary {
|
summary: Some(Summary {
|
||||||
text: saved_context.summary,
|
text: saved_context.summary,
|
||||||
@@ -1244,10 +1222,6 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn token_count(&self) -> Option<usize> {
|
|
||||||
self.token_count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
|
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
let request = self.to_completion_request(cx);
|
let request = self.to_completion_request(cx);
|
||||||
self.pending_token_count = cx.spawn(|this, mut cx| {
|
self.pending_token_count = cx.spawn(|this, mut cx| {
|
||||||
@@ -1490,17 +1464,10 @@ impl Context {
|
|||||||
.map(|section| SlashCommandOutputSection {
|
.map(|section| SlashCommandOutputSection {
|
||||||
range: buffer.anchor_after(start + section.range.start)
|
range: buffer.anchor_after(start + section.range.start)
|
||||||
..buffer.anchor_before(start + section.range.end),
|
..buffer.anchor_before(start + section.range.end),
|
||||||
icon: section.icon,
|
render_placeholder: section.render_placeholder,
|
||||||
label: section.label,
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||||
|
|
||||||
this.slash_command_output_sections
|
|
||||||
.extend(sections.iter().cloned());
|
|
||||||
this.slash_command_output_sections
|
|
||||||
.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
|
||||||
|
|
||||||
ContextEvent::SlashCommandFinished {
|
ContextEvent::SlashCommandFinished {
|
||||||
output_range: buffer.anchor_after(start)
|
output_range: buffer.anchor_after(start)
|
||||||
..buffer.anchor_before(new_end),
|
..buffer.anchor_before(new_end),
|
||||||
@@ -2044,7 +2011,7 @@ impl Context {
|
|||||||
let mut discriminant = 1;
|
let mut discriminant = 1;
|
||||||
let mut new_path;
|
let mut new_path;
|
||||||
loop {
|
loop {
|
||||||
new_path = contexts_dir().join(&format!(
|
new_path = CONTEXTS_DIR.join(&format!(
|
||||||
"{} - {}.zed.json",
|
"{} - {}.zed.json",
|
||||||
summary.trim(),
|
summary.trim(),
|
||||||
discriminant
|
discriminant
|
||||||
@@ -2058,7 +2025,7 @@ impl Context {
|
|||||||
new_path
|
new_path
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.create_dir(contexts_dir().as_ref()).await?;
|
fs.create_dir(CONTEXTS_DIR.as_ref()).await?;
|
||||||
fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
|
fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
|
||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, _| this.path = Some(path))?;
|
this.update(&mut cx, |this, _| this.path = Some(path))?;
|
||||||
@@ -2201,8 +2168,7 @@ pub struct ContextEditor {
|
|||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
blocks: HashSet<BlockId>,
|
blocks: HashSet<BlockId>,
|
||||||
scroll_position: Option<ScrollPosition>,
|
scroll_position: Option<ScrollPosition>,
|
||||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
pending_slash_command_flaps: HashMap<Range<language::Anchor>, FlapId>,
|
||||||
pending_slash_command_blocks: HashMap<Range<language::Anchor>, BlockId>,
|
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2253,7 +2219,6 @@ impl ContextEditor {
|
|||||||
editor.set_show_line_numbers(false, cx);
|
editor.set_show_line_numbers(false, cx);
|
||||||
editor.set_show_git_diff_gutter(false, cx);
|
editor.set_show_git_diff_gutter(false, cx);
|
||||||
editor.set_show_code_actions(false, cx);
|
editor.set_show_code_actions(false, cx);
|
||||||
editor.set_show_runnables(false, cx);
|
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_completion_provider(Box::new(completion_provider));
|
editor.set_completion_provider(Box::new(completion_provider));
|
||||||
@@ -2266,7 +2231,6 @@ impl ContextEditor {
|
|||||||
cx.subscribe(&editor, Self::handle_editor_event),
|
cx.subscribe(&editor, Self::handle_editor_event),
|
||||||
];
|
];
|
||||||
|
|
||||||
let sections = context.read(cx).slash_command_output_sections.clone();
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
context,
|
context,
|
||||||
editor,
|
editor,
|
||||||
@@ -2276,12 +2240,10 @@ impl ContextEditor {
|
|||||||
scroll_position: None,
|
scroll_position: None,
|
||||||
fs,
|
fs,
|
||||||
workspace: workspace.downgrade(),
|
workspace: workspace.downgrade(),
|
||||||
pending_slash_command_creases: HashMap::default(),
|
pending_slash_command_flaps: HashMap::default(),
|
||||||
pending_slash_command_blocks: HashMap::default(),
|
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
};
|
};
|
||||||
this.update_message_headers(cx);
|
this.update_message_headers(cx);
|
||||||
this.insert_slash_command_output_sections(sections, cx);
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2398,7 +2360,7 @@ impl ContextEditor {
|
|||||||
editor.insert(&format!("/{name}"), cx);
|
editor.insert(&format!("/{name}"), cx);
|
||||||
if command.requires_argument() {
|
if command.requires_argument() {
|
||||||
editor.insert(" ", cx);
|
editor.insert(" ", cx);
|
||||||
editor.show_completions(&ShowCompletions::default(), cx);
|
editor.show_completions(&ShowCompletions, cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2539,27 +2501,16 @@ impl ContextEditor {
|
|||||||
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
|
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||||
let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
|
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
||||||
let excerpt_id = *excerpt_id;
|
|
||||||
|
|
||||||
editor.remove_creases(
|
editor.remove_flaps(
|
||||||
removed
|
removed
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|range| self.pending_slash_command_creases.remove(range)),
|
.filter_map(|range| self.pending_slash_command_flaps.remove(range)),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
editor.remove_blocks(
|
let flap_ids = editor.insert_flaps(
|
||||||
HashSet::from_iter(
|
|
||||||
removed.iter().filter_map(|range| {
|
|
||||||
self.pending_slash_command_blocks.remove(range)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
let crease_ids = editor.insert_creases(
|
|
||||||
updated.iter().map(|command| {
|
updated.iter().map(|command| {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let confirm_command = Arc::new({
|
let confirm_command = Arc::new({
|
||||||
@@ -2591,28 +2542,13 @@ impl ContextEditor {
|
|||||||
move |row, _, _, _cx: &mut WindowContext| {
|
move |row, _, _, _cx: &mut WindowContext| {
|
||||||
render_pending_slash_command_gutter_decoration(
|
render_pending_slash_command_gutter_decoration(
|
||||||
row,
|
row,
|
||||||
&command.status,
|
command.status.clone(),
|
||||||
confirm_command.clone(),
|
confirm_command.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let render_trailer = {
|
let render_trailer =
|
||||||
let command = command.clone();
|
|_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||||
move |row, _unfold, cx: &mut WindowContext| {
|
|
||||||
// TODO: In the future we should investigate how we can expose
|
|
||||||
// this as a hook on the `SlashCommand` trait so that we don't
|
|
||||||
// need to special-case it here.
|
|
||||||
if command.name == "rustdoc" {
|
|
||||||
return render_rustdoc_slash_command_trailer(
|
|
||||||
row,
|
|
||||||
command.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Empty.into_any()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let start = buffer
|
let start = buffer
|
||||||
.anchor_in_excerpt(excerpt_id, command.source_range.start)
|
.anchor_in_excerpt(excerpt_id, command.source_range.start)
|
||||||
@@ -2620,47 +2556,16 @@ impl ContextEditor {
|
|||||||
let end = buffer
|
let end = buffer
|
||||||
.anchor_in_excerpt(excerpt_id, command.source_range.end)
|
.anchor_in_excerpt(excerpt_id, command.source_range.end)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Crease::new(start..end, placeholder, render_toggle, render_trailer)
|
Flap::new(start..end, placeholder, render_toggle, render_trailer)
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let block_ids = editor.insert_blocks(
|
self.pending_slash_command_flaps.extend(
|
||||||
updated
|
|
||||||
.iter()
|
|
||||||
.filter_map(|command| match &command.status {
|
|
||||||
PendingSlashCommandStatus::Error(error) => {
|
|
||||||
Some((command, error.clone()))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.map(|(command, error_message)| BlockProperties {
|
|
||||||
style: BlockStyle::Fixed,
|
|
||||||
position: Anchor {
|
|
||||||
buffer_id: Some(buffer_id),
|
|
||||||
excerpt_id,
|
|
||||||
text_anchor: command.source_range.start,
|
|
||||||
},
|
|
||||||
height: 1,
|
|
||||||
disposition: BlockDisposition::Below,
|
|
||||||
render: slash_command_error_block_renderer(error_message),
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.pending_slash_command_creases.extend(
|
|
||||||
updated
|
updated
|
||||||
.iter()
|
.iter()
|
||||||
.map(|command| command.source_range.clone())
|
.map(|command| command.source_range.clone())
|
||||||
.zip(crease_ids),
|
.zip(flap_ids),
|
||||||
);
|
|
||||||
|
|
||||||
self.pending_slash_command_blocks.extend(
|
|
||||||
updated
|
|
||||||
.iter()
|
|
||||||
.map(|command| command.source_range.clone())
|
|
||||||
.zip(block_ids),
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2703,7 +2608,7 @@ impl ContextEditor {
|
|||||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||||
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
||||||
let mut buffer_rows_to_fold = BTreeSet::new();
|
let mut buffer_rows_to_fold = BTreeSet::new();
|
||||||
let mut creases = Vec::new();
|
let mut flaps = Vec::new();
|
||||||
for section in sections {
|
for section in sections {
|
||||||
let start = buffer
|
let start = buffer
|
||||||
.anchor_in_excerpt(excerpt_id, section.range.start)
|
.anchor_in_excerpt(excerpt_id, section.range.start)
|
||||||
@@ -2713,32 +2618,26 @@ impl ContextEditor {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||||
buffer_rows_to_fold.insert(buffer_row);
|
buffer_rows_to_fold.insert(buffer_row);
|
||||||
creases.push(Crease::new(
|
flaps.push(Flap::new(
|
||||||
start..end,
|
start..end,
|
||||||
FoldPlaceholder {
|
FoldPlaceholder {
|
||||||
render: Arc::new({
|
render: Arc::new({
|
||||||
let editor = cx.view().downgrade();
|
let editor = cx.view().downgrade();
|
||||||
let icon = section.icon;
|
let render_placeholder = section.render_placeholder.clone();
|
||||||
let label = section.label.clone();
|
move |fold_id, fold_range, cx| {
|
||||||
move |fold_id, fold_range, _cx| {
|
|
||||||
let editor = editor.clone();
|
let editor = editor.clone();
|
||||||
ButtonLike::new(fold_id)
|
let unfold = Arc::new(move |cx: &mut WindowContext| {
|
||||||
.style(ButtonStyle::Filled)
|
editor
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
.update(cx, |editor, cx| {
|
||||||
.child(Icon::new(icon))
|
let buffer_start = fold_range
|
||||||
.child(Label::new(label.clone()).single_line())
|
.start
|
||||||
.on_click(move |_, cx| {
|
.to_point(&editor.buffer().read(cx).read(cx));
|
||||||
editor
|
let buffer_row = MultiBufferRow(buffer_start.row);
|
||||||
.update(cx, |editor, cx| {
|
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||||
let buffer_start = fold_range
|
})
|
||||||
.start
|
.ok();
|
||||||
.to_point(&editor.buffer().read(cx).read(cx));
|
});
|
||||||
let buffer_row = MultiBufferRow(buffer_start.row);
|
render_placeholder(fold_id.into(), unfold, cx)
|
||||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
.into_any_element()
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
constrain_width: false,
|
constrain_width: false,
|
||||||
@@ -2749,7 +2648,7 @@ impl ContextEditor {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.insert_creases(creases, cx);
|
editor.insert_flaps(flaps, cx);
|
||||||
|
|
||||||
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
|
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
|
||||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||||
@@ -3243,15 +3142,22 @@ fn render_slash_command_output_toggle(
|
|||||||
fold: ToggleFold,
|
fold: ToggleFold,
|
||||||
_cx: &mut WindowContext,
|
_cx: &mut WindowContext,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
Disclosure::new(("slash-command-output-fold-indicator", row.0), !is_folded)
|
IconButton::new(
|
||||||
.selected(is_folded)
|
("slash-command-output-fold-indicator", row.0),
|
||||||
.on_click(move |_e, cx| fold(!is_folded, cx))
|
ui::IconName::ChevronDown,
|
||||||
.into_any_element()
|
)
|
||||||
|
.on_click(move |_e, cx| fold(!is_folded, cx))
|
||||||
|
.icon_color(ui::Color::Muted)
|
||||||
|
.icon_size(ui::IconSize::Small)
|
||||||
|
.selected(is_folded)
|
||||||
|
.selected_icon(ui::IconName::ChevronRight)
|
||||||
|
.size(ui::ButtonSize::None)
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_pending_slash_command_gutter_decoration(
|
fn render_pending_slash_command_gutter_decoration(
|
||||||
row: MultiBufferRow,
|
row: MultiBufferRow,
|
||||||
status: &PendingSlashCommandStatus,
|
status: PendingSlashCommandStatus,
|
||||||
confirm_command: Arc<dyn Fn(&mut WindowContext)>,
|
confirm_command: Arc<dyn Fn(&mut WindowContext)>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let mut icon = IconButton::new(
|
let mut icon = IconButton::new(
|
||||||
@@ -3269,43 +3175,16 @@ fn render_pending_slash_command_gutter_decoration(
|
|||||||
PendingSlashCommandStatus::Running { .. } => {
|
PendingSlashCommandStatus::Running { .. } => {
|
||||||
icon = icon.selected(true);
|
icon = icon.selected(true);
|
||||||
}
|
}
|
||||||
PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
|
PendingSlashCommandStatus::Error(error) => {
|
||||||
|
icon = icon
|
||||||
|
.icon_color(Color::Error)
|
||||||
|
.tooltip(move |cx| Tooltip::text(format!("error: {error}"), cx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
icon.into_any_element()
|
icon.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_rustdoc_slash_command_trailer(
|
|
||||||
row: MultiBufferRow,
|
|
||||||
command: PendingSlashCommand,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> AnyElement {
|
|
||||||
let rustdoc_store = RustdocStore::global(cx);
|
|
||||||
|
|
||||||
let Some((crate_name, _)) = command
|
|
||||||
.argument
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|arg| arg.split_once(':'))
|
|
||||||
else {
|
|
||||||
return Empty.into_any();
|
|
||||||
};
|
|
||||||
|
|
||||||
let crate_name = CrateName::from(crate_name);
|
|
||||||
if !rustdoc_store.is_indexing(&crate_name) {
|
|
||||||
return Empty.into_any();
|
|
||||||
}
|
|
||||||
|
|
||||||
div()
|
|
||||||
.id(("crates-being-indexed", row.0))
|
|
||||||
.child(Icon::new(IconName::ArrowCircle).with_animation(
|
|
||||||
"arrow-circle",
|
|
||||||
Animation::new(Duration::from_secs(4)).repeat(),
|
|
||||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
|
||||||
))
|
|
||||||
.tooltip(move |cx| Tooltip::text(format!("Indexing {crate_name}…"), cx))
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_lsp_adapter_delegate(
|
fn make_lsp_adapter_delegate(
|
||||||
project: &Model<Project>,
|
project: &Model<Project>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
@@ -3320,19 +3199,6 @@ fn make_lsp_adapter_delegate(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn slash_command_error_block_renderer(message: String) -> RenderBlock {
|
|
||||||
Box::new(move |_| {
|
|
||||||
div()
|
|
||||||
.pl_6()
|
|
||||||
.child(
|
|
||||||
Label::new(format!("error: {}", message))
|
|
||||||
.single_line()
|
|
||||||
.color(Color::Error),
|
|
||||||
)
|
|
||||||
.into_any()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::fmt;
|
|||||||
|
|
||||||
pub use anthropic::Model as AnthropicModel;
|
pub use anthropic::Model as AnthropicModel;
|
||||||
use gpui::Pixels;
|
use gpui::Pixels;
|
||||||
pub use ollama::Model as OllamaModel;
|
|
||||||
pub use open_ai::Model as OpenAiModel;
|
pub use open_ai::Model as OpenAiModel;
|
||||||
use schemars::{
|
use schemars::{
|
||||||
schema::{InstanceType, Metadata, Schema, SchemaObject},
|
schema::{InstanceType, Metadata, Schema, SchemaObject},
|
||||||
@@ -24,7 +23,6 @@ pub enum CloudModel {
|
|||||||
Gpt4Turbo,
|
Gpt4Turbo,
|
||||||
#[default]
|
#[default]
|
||||||
Gpt4Omni,
|
Gpt4Omni,
|
||||||
Claude3_5Sonnet,
|
|
||||||
Claude3Opus,
|
Claude3Opus,
|
||||||
Claude3Sonnet,
|
Claude3Sonnet,
|
||||||
Claude3Haiku,
|
Claude3Haiku,
|
||||||
@@ -106,7 +104,6 @@ impl CloudModel {
|
|||||||
Self::Gpt4 => "gpt-4",
|
Self::Gpt4 => "gpt-4",
|
||||||
Self::Gpt4Turbo => "gpt-4-turbo-preview",
|
Self::Gpt4Turbo => "gpt-4-turbo-preview",
|
||||||
Self::Gpt4Omni => "gpt-4o",
|
Self::Gpt4Omni => "gpt-4o",
|
||||||
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
|
|
||||||
Self::Claude3Opus => "claude-3-opus",
|
Self::Claude3Opus => "claude-3-opus",
|
||||||
Self::Claude3Sonnet => "claude-3-sonnet",
|
Self::Claude3Sonnet => "claude-3-sonnet",
|
||||||
Self::Claude3Haiku => "claude-3-haiku",
|
Self::Claude3Haiku => "claude-3-haiku",
|
||||||
@@ -120,7 +117,6 @@ impl CloudModel {
|
|||||||
Self::Gpt4 => "GPT 4",
|
Self::Gpt4 => "GPT 4",
|
||||||
Self::Gpt4Turbo => "GPT 4 Turbo",
|
Self::Gpt4Turbo => "GPT 4 Turbo",
|
||||||
Self::Gpt4Omni => "GPT 4 Omni",
|
Self::Gpt4Omni => "GPT 4 Omni",
|
||||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
|
||||||
Self::Claude3Opus => "Claude 3 Opus",
|
Self::Claude3Opus => "Claude 3 Opus",
|
||||||
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
||||||
Self::Claude3Haiku => "Claude 3 Haiku",
|
Self::Claude3Haiku => "Claude 3 Haiku",
|
||||||
@@ -133,10 +129,7 @@ impl CloudModel {
|
|||||||
Self::Gpt3Point5Turbo => 2048,
|
Self::Gpt3Point5Turbo => 2048,
|
||||||
Self::Gpt4 => 4096,
|
Self::Gpt4 => 4096,
|
||||||
Self::Gpt4Turbo | Self::Gpt4Omni => 128000,
|
Self::Gpt4Turbo | Self::Gpt4Omni => 128000,
|
||||||
Self::Claude3_5Sonnet
|
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 200000,
|
||||||
| Self::Claude3Opus
|
|
||||||
| Self::Claude3Sonnet
|
|
||||||
| Self::Claude3Haiku => 200000,
|
|
||||||
Self::Custom(_) => 4096, // TODO: Make this configurable
|
Self::Custom(_) => 4096, // TODO: Make this configurable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,11 +168,6 @@ pub enum AssistantProvider {
|
|||||||
api_url: String,
|
api_url: String,
|
||||||
low_speed_timeout_in_seconds: Option<u64>,
|
low_speed_timeout_in_seconds: Option<u64>,
|
||||||
},
|
},
|
||||||
Ollama {
|
|
||||||
model: OllamaModel,
|
|
||||||
api_url: String,
|
|
||||||
low_speed_timeout_in_seconds: Option<u64>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AssistantProvider {
|
impl Default for AssistantProvider {
|
||||||
@@ -209,12 +197,6 @@ pub enum AssistantProviderContent {
|
|||||||
api_url: Option<String>,
|
api_url: Option<String>,
|
||||||
low_speed_timeout_in_seconds: Option<u64>,
|
low_speed_timeout_in_seconds: Option<u64>,
|
||||||
},
|
},
|
||||||
#[serde(rename = "ollama")]
|
|
||||||
Ollama {
|
|
||||||
default_model: Option<OllamaModel>,
|
|
||||||
api_url: Option<String>,
|
|
||||||
low_speed_timeout_in_seconds: Option<u64>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -346,13 +328,6 @@ impl AssistantSettingsContent {
|
|||||||
low_speed_timeout_in_seconds: None,
|
low_speed_timeout_in_seconds: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
LanguageModel::Ollama(model) => {
|
|
||||||
*provider = Some(AssistantProviderContent::Ollama {
|
|
||||||
default_model: Some(model),
|
|
||||||
api_url: None,
|
|
||||||
low_speed_timeout_in_seconds: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -497,27 +472,6 @@ impl Settings for AssistantSettings {
|
|||||||
Some(low_speed_timeout_in_seconds_override);
|
Some(low_speed_timeout_in_seconds_override);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(
|
|
||||||
AssistantProvider::Ollama {
|
|
||||||
model,
|
|
||||||
api_url,
|
|
||||||
low_speed_timeout_in_seconds,
|
|
||||||
},
|
|
||||||
AssistantProviderContent::Ollama {
|
|
||||||
default_model: model_override,
|
|
||||||
api_url: api_url_override,
|
|
||||||
low_speed_timeout_in_seconds: low_speed_timeout_in_seconds_override,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
merge(model, model_override);
|
|
||||||
merge(api_url, api_url_override);
|
|
||||||
if let Some(low_speed_timeout_in_seconds_override) =
|
|
||||||
low_speed_timeout_in_seconds_override
|
|
||||||
{
|
|
||||||
*low_speed_timeout_in_seconds =
|
|
||||||
Some(low_speed_timeout_in_seconds_override);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
(
|
||||||
AssistantProvider::Anthropic {
|
AssistantProvider::Anthropic {
|
||||||
model,
|
model,
|
||||||
@@ -565,15 +519,6 @@ impl Settings for AssistantSettings {
|
|||||||
.unwrap_or_else(|| anthropic::ANTHROPIC_API_URL.into()),
|
.unwrap_or_else(|| anthropic::ANTHROPIC_API_URL.into()),
|
||||||
low_speed_timeout_in_seconds,
|
low_speed_timeout_in_seconds,
|
||||||
},
|
},
|
||||||
AssistantProviderContent::Ollama {
|
|
||||||
default_model: model,
|
|
||||||
api_url,
|
|
||||||
low_speed_timeout_in_seconds,
|
|
||||||
} => AssistantProvider::Ollama {
|
|
||||||
model: model.unwrap_or_default(),
|
|
||||||
api_url: api_url.unwrap_or_else(|| ollama::OLLAMA_API_URL.into()),
|
|
||||||
low_speed_timeout_in_seconds,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ mod anthropic;
|
|||||||
mod cloud;
|
mod cloud;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod fake;
|
mod fake;
|
||||||
mod ollama;
|
|
||||||
mod open_ai;
|
mod open_ai;
|
||||||
|
|
||||||
pub use anthropic::*;
|
pub use anthropic::*;
|
||||||
pub use cloud::*;
|
pub use cloud::*;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use fake::*;
|
pub use fake::*;
|
||||||
pub use ollama::*;
|
|
||||||
pub use open_ai::*;
|
pub use open_ai::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -52,18 +50,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||||
settings_version,
|
settings_version,
|
||||||
)),
|
)),
|
||||||
AssistantProvider::Ollama {
|
|
||||||
model,
|
|
||||||
api_url,
|
|
||||||
low_speed_timeout_in_seconds,
|
|
||||||
} => CompletionProvider::Ollama(OllamaCompletionProvider::new(
|
|
||||||
model.clone(),
|
|
||||||
api_url.clone(),
|
|
||||||
client.http_client(),
|
|
||||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
|
||||||
settings_version,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
};
|
};
|
||||||
cx.set_global(provider);
|
cx.set_global(provider);
|
||||||
|
|
||||||
@@ -101,24 +87,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
settings_version,
|
settings_version,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
|
||||||
CompletionProvider::Ollama(provider),
|
|
||||||
AssistantProvider::Ollama {
|
|
||||||
model,
|
|
||||||
api_url,
|
|
||||||
low_speed_timeout_in_seconds,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
provider.update(
|
|
||||||
model.clone(),
|
|
||||||
api_url.clone(),
|
|
||||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
|
||||||
settings_version,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
(CompletionProvider::Cloud(provider), AssistantProvider::ZedDotDev { model }) => {
|
(CompletionProvider::Cloud(provider), AssistantProvider::ZedDotDev { model }) => {
|
||||||
provider.update(model.clone(), settings_version);
|
provider.update(model.clone(), settings_version);
|
||||||
}
|
}
|
||||||
@@ -162,23 +130,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
|||||||
settings_version,
|
settings_version,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
(
|
|
||||||
_,
|
|
||||||
AssistantProvider::Ollama {
|
|
||||||
model,
|
|
||||||
api_url,
|
|
||||||
low_speed_timeout_in_seconds,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
*provider = CompletionProvider::Ollama(OllamaCompletionProvider::new(
|
|
||||||
model.clone(),
|
|
||||||
api_url.clone(),
|
|
||||||
client.http_client(),
|
|
||||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
|
||||||
settings_version,
|
|
||||||
cx,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -191,7 +142,6 @@ pub enum CompletionProvider {
|
|||||||
Cloud(CloudCompletionProvider),
|
Cloud(CloudCompletionProvider),
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
Fake(FakeCompletionProvider),
|
Fake(FakeCompletionProvider),
|
||||||
Ollama(OllamaCompletionProvider),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl gpui::Global for CompletionProvider {}
|
impl gpui::Global for CompletionProvider {}
|
||||||
@@ -215,10 +165,6 @@ impl CompletionProvider {
|
|||||||
.available_models()
|
.available_models()
|
||||||
.map(LanguageModel::Cloud)
|
.map(LanguageModel::Cloud)
|
||||||
.collect(),
|
.collect(),
|
||||||
CompletionProvider::Ollama(provider) => provider
|
|
||||||
.available_models()
|
|
||||||
.map(|model| LanguageModel::Ollama(model.clone()))
|
|
||||||
.collect(),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(_) => unimplemented!(),
|
CompletionProvider::Fake(_) => unimplemented!(),
|
||||||
}
|
}
|
||||||
@@ -229,7 +175,6 @@ impl CompletionProvider {
|
|||||||
CompletionProvider::OpenAi(provider) => provider.settings_version(),
|
CompletionProvider::OpenAi(provider) => provider.settings_version(),
|
||||||
CompletionProvider::Anthropic(provider) => provider.settings_version(),
|
CompletionProvider::Anthropic(provider) => provider.settings_version(),
|
||||||
CompletionProvider::Cloud(provider) => provider.settings_version(),
|
CompletionProvider::Cloud(provider) => provider.settings_version(),
|
||||||
CompletionProvider::Ollama(provider) => provider.settings_version(),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(_) => unimplemented!(),
|
CompletionProvider::Fake(_) => unimplemented!(),
|
||||||
}
|
}
|
||||||
@@ -240,7 +185,6 @@ impl CompletionProvider {
|
|||||||
CompletionProvider::OpenAi(provider) => provider.is_authenticated(),
|
CompletionProvider::OpenAi(provider) => provider.is_authenticated(),
|
||||||
CompletionProvider::Anthropic(provider) => provider.is_authenticated(),
|
CompletionProvider::Anthropic(provider) => provider.is_authenticated(),
|
||||||
CompletionProvider::Cloud(provider) => provider.is_authenticated(),
|
CompletionProvider::Cloud(provider) => provider.is_authenticated(),
|
||||||
CompletionProvider::Ollama(provider) => provider.is_authenticated(),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(_) => true,
|
CompletionProvider::Fake(_) => true,
|
||||||
}
|
}
|
||||||
@@ -251,7 +195,6 @@ impl CompletionProvider {
|
|||||||
CompletionProvider::OpenAi(provider) => provider.authenticate(cx),
|
CompletionProvider::OpenAi(provider) => provider.authenticate(cx),
|
||||||
CompletionProvider::Anthropic(provider) => provider.authenticate(cx),
|
CompletionProvider::Anthropic(provider) => provider.authenticate(cx),
|
||||||
CompletionProvider::Cloud(provider) => provider.authenticate(cx),
|
CompletionProvider::Cloud(provider) => provider.authenticate(cx),
|
||||||
CompletionProvider::Ollama(provider) => provider.authenticate(cx),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(_) => Task::ready(Ok(())),
|
CompletionProvider::Fake(_) => Task::ready(Ok(())),
|
||||||
}
|
}
|
||||||
@@ -262,7 +205,6 @@ impl CompletionProvider {
|
|||||||
CompletionProvider::OpenAi(provider) => provider.authentication_prompt(cx),
|
CompletionProvider::OpenAi(provider) => provider.authentication_prompt(cx),
|
||||||
CompletionProvider::Anthropic(provider) => provider.authentication_prompt(cx),
|
CompletionProvider::Anthropic(provider) => provider.authentication_prompt(cx),
|
||||||
CompletionProvider::Cloud(provider) => provider.authentication_prompt(cx),
|
CompletionProvider::Cloud(provider) => provider.authentication_prompt(cx),
|
||||||
CompletionProvider::Ollama(provider) => provider.authentication_prompt(cx),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(_) => unimplemented!(),
|
CompletionProvider::Fake(_) => unimplemented!(),
|
||||||
}
|
}
|
||||||
@@ -273,7 +215,6 @@ impl CompletionProvider {
|
|||||||
CompletionProvider::OpenAi(provider) => provider.reset_credentials(cx),
|
CompletionProvider::OpenAi(provider) => provider.reset_credentials(cx),
|
||||||
CompletionProvider::Anthropic(provider) => provider.reset_credentials(cx),
|
CompletionProvider::Anthropic(provider) => provider.reset_credentials(cx),
|
||||||
CompletionProvider::Cloud(_) => Task::ready(Ok(())),
|
CompletionProvider::Cloud(_) => Task::ready(Ok(())),
|
||||||
CompletionProvider::Ollama(provider) => provider.reset_credentials(cx),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(_) => Task::ready(Ok(())),
|
CompletionProvider::Fake(_) => Task::ready(Ok(())),
|
||||||
}
|
}
|
||||||
@@ -284,7 +225,6 @@ impl CompletionProvider {
|
|||||||
CompletionProvider::OpenAi(provider) => LanguageModel::OpenAi(provider.model()),
|
CompletionProvider::OpenAi(provider) => LanguageModel::OpenAi(provider.model()),
|
||||||
CompletionProvider::Anthropic(provider) => LanguageModel::Anthropic(provider.model()),
|
CompletionProvider::Anthropic(provider) => LanguageModel::Anthropic(provider.model()),
|
||||||
CompletionProvider::Cloud(provider) => LanguageModel::Cloud(provider.model()),
|
CompletionProvider::Cloud(provider) => LanguageModel::Cloud(provider.model()),
|
||||||
CompletionProvider::Ollama(provider) => LanguageModel::Ollama(provider.model()),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(_) => LanguageModel::default(),
|
CompletionProvider::Fake(_) => LanguageModel::default(),
|
||||||
}
|
}
|
||||||
@@ -299,7 +239,6 @@ impl CompletionProvider {
|
|||||||
CompletionProvider::OpenAi(provider) => provider.count_tokens(request, cx),
|
CompletionProvider::OpenAi(provider) => provider.count_tokens(request, cx),
|
||||||
CompletionProvider::Anthropic(provider) => provider.count_tokens(request, cx),
|
CompletionProvider::Anthropic(provider) => provider.count_tokens(request, cx),
|
||||||
CompletionProvider::Cloud(provider) => provider.count_tokens(request, cx),
|
CompletionProvider::Cloud(provider) => provider.count_tokens(request, cx),
|
||||||
CompletionProvider::Ollama(provider) => provider.count_tokens(request, cx),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(_) => futures::FutureExt::boxed(futures::future::ready(Ok(0))),
|
CompletionProvider::Fake(_) => futures::FutureExt::boxed(futures::future::ready(Ok(0))),
|
||||||
}
|
}
|
||||||
@@ -313,7 +252,6 @@ impl CompletionProvider {
|
|||||||
CompletionProvider::OpenAi(provider) => provider.complete(request),
|
CompletionProvider::OpenAi(provider) => provider.complete(request),
|
||||||
CompletionProvider::Anthropic(provider) => provider.complete(request),
|
CompletionProvider::Anthropic(provider) => provider.complete(request),
|
||||||
CompletionProvider::Cloud(provider) => provider.complete(request),
|
CompletionProvider::Cloud(provider) => provider.complete(request),
|
||||||
CompletionProvider::Ollama(provider) => provider.complete(request),
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
CompletionProvider::Fake(provider) => provider.complete(),
|
CompletionProvider::Fake(provider) => provider.complete(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ impl Render for AuthenticationPrompt {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(Label::new("Click on").size(LabelSize::Small))
|
.child(Label::new("Click on").size(LabelSize::Small))
|
||||||
.child(Icon::new(IconName::ZedAssistant).size(IconSize::XSmall))
|
.child(Icon::new(IconName::Ai).size(IconSize::XSmall))
|
||||||
.child(
|
.child(
|
||||||
Label::new("in the status bar to close this panel.").size(LabelSize::Small),
|
Label::new("in the status bar to close this panel.").size(LabelSize::Small),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -101,10 +101,7 @@ impl CloudCompletionProvider {
|
|||||||
count_open_ai_tokens(request, cx.background_executor())
|
count_open_ai_tokens(request, cx.background_executor())
|
||||||
}
|
}
|
||||||
LanguageModel::Cloud(
|
LanguageModel::Cloud(
|
||||||
CloudModel::Claude3_5Sonnet
|
CloudModel::Claude3Opus | CloudModel::Claude3Sonnet | CloudModel::Claude3Haiku,
|
||||||
| CloudModel::Claude3Opus
|
|
||||||
| CloudModel::Claude3Sonnet
|
|
||||||
| CloudModel::Claude3Haiku,
|
|
||||||
) => {
|
) => {
|
||||||
// Can't find a tokenizer for Claude 3, so for now just use the same as OpenAI's as an approximation.
|
// Can't find a tokenizer for Claude 3, so for now just use the same as OpenAI's as an approximation.
|
||||||
count_open_ai_tokens(request, cx.background_executor())
|
count_open_ai_tokens(request, cx.background_executor())
|
||||||
|
|||||||
@@ -1,348 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
assistant_settings::OllamaModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
|
||||||
use futures::StreamExt as _;
|
|
||||||
use futures::{future::BoxFuture, stream::BoxStream, FutureExt};
|
|
||||||
use gpui::{AnyView, AppContext, Task};
|
|
||||||
use http::HttpClient;
|
|
||||||
use ollama::{
|
|
||||||
get_models, preload_model, stream_chat_completion, ChatMessage, ChatOptions, ChatRequest,
|
|
||||||
Role as OllamaRole,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
|
||||||
|
|
||||||
const OLLAMA_DOWNLOAD_URL: &str = "https://ollama.com/download";
|
|
||||||
const OLLAMA_LIBRARY_URL: &str = "https://ollama.com/library";
|
|
||||||
|
|
||||||
pub struct OllamaCompletionProvider {
|
|
||||||
api_url: String,
|
|
||||||
model: OllamaModel,
|
|
||||||
http_client: Arc<dyn HttpClient>,
|
|
||||||
low_speed_timeout: Option<Duration>,
|
|
||||||
settings_version: usize,
|
|
||||||
available_models: Vec<OllamaModel>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OllamaCompletionProvider {
|
|
||||||
pub fn new(
|
|
||||||
model: OllamaModel,
|
|
||||||
api_url: String,
|
|
||||||
http_client: Arc<dyn HttpClient>,
|
|
||||||
low_speed_timeout: Option<Duration>,
|
|
||||||
settings_version: usize,
|
|
||||||
cx: &AppContext,
|
|
||||||
) -> Self {
|
|
||||||
cx.spawn({
|
|
||||||
let api_url = api_url.clone();
|
|
||||||
let client = http_client.clone();
|
|
||||||
let model = model.name.clone();
|
|
||||||
|
|
||||||
|_| async move {
|
|
||||||
if model.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
preload_model(client.as_ref(), &api_url, &model).await
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
api_url,
|
|
||||||
model,
|
|
||||||
http_client,
|
|
||||||
low_speed_timeout,
|
|
||||||
settings_version,
|
|
||||||
available_models: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(
|
|
||||||
&mut self,
|
|
||||||
model: OllamaModel,
|
|
||||||
api_url: String,
|
|
||||||
low_speed_timeout: Option<Duration>,
|
|
||||||
settings_version: usize,
|
|
||||||
cx: &AppContext,
|
|
||||||
) {
|
|
||||||
cx.spawn({
|
|
||||||
let api_url = api_url.clone();
|
|
||||||
let client = self.http_client.clone();
|
|
||||||
let model = model.name.clone();
|
|
||||||
|
|
||||||
|_| async move { preload_model(client.as_ref(), &api_url, &model).await }
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
|
|
||||||
if model.name.is_empty() {
|
|
||||||
self.select_first_available_model()
|
|
||||||
} else {
|
|
||||||
self.model = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.api_url = api_url;
|
|
||||||
self.low_speed_timeout = low_speed_timeout;
|
|
||||||
self.settings_version = settings_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_models(&self) -> impl Iterator<Item = &OllamaModel> {
|
|
||||||
self.available_models.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select_first_available_model(&mut self) {
|
|
||||||
if let Some(model) = self.available_models.first() {
|
|
||||||
self.model = model.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn settings_version(&self) -> usize {
|
|
||||||
self.settings_version
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_authenticated(&self) -> bool {
|
|
||||||
!self.available_models.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
|
|
||||||
if self.is_authenticated() {
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
} else {
|
|
||||||
self.fetch_models(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
|
|
||||||
self.fetch_models(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch_models(&self, cx: &AppContext) -> Task<Result<()>> {
|
|
||||||
let http_client = self.http_client.clone();
|
|
||||||
let api_url = self.api_url.clone();
|
|
||||||
|
|
||||||
// As a proxy for the server being "authenticated", we'll check if its up by fetching the models
|
|
||||||
cx.spawn(|mut cx| async move {
|
|
||||||
let models = get_models(http_client.as_ref(), &api_url, None).await?;
|
|
||||||
|
|
||||||
let mut models: Vec<OllamaModel> = models
|
|
||||||
.into_iter()
|
|
||||||
// Since there is no metadata from the Ollama API
|
|
||||||
// indicating which models are embedding models,
|
|
||||||
// simply filter out models with "-embed" in their name
|
|
||||||
.filter(|model| !model.name.contains("-embed"))
|
|
||||||
.map(|model| OllamaModel::new(&model.name))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
models.sort_by(|a, b| a.name.cmp(&b.name));
|
|
||||||
|
|
||||||
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
|
|
||||||
if let CompletionProvider::Ollama(provider) = provider {
|
|
||||||
provider.available_models = models;
|
|
||||||
|
|
||||||
if !provider.available_models.is_empty() && provider.model.name.is_empty() {
|
|
||||||
provider.select_first_available_model()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
|
|
||||||
let fetch_models = Box::new(move |cx: &mut WindowContext| {
|
|
||||||
cx.update_global::<CompletionProvider, _>(|provider, cx| {
|
|
||||||
if let CompletionProvider::Ollama(provider) = provider {
|
|
||||||
provider.fetch_models(cx)
|
|
||||||
} else {
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.new_view(|cx| DownloadOllamaMessage::new(fetch_models, cx))
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn model(&self) -> OllamaModel {
|
|
||||||
self.model.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn count_tokens(
|
|
||||||
&self,
|
|
||||||
request: LanguageModelRequest,
|
|
||||||
_cx: &AppContext,
|
|
||||||
) -> BoxFuture<'static, Result<usize>> {
|
|
||||||
// There is no endpoint for this _yet_ in Ollama
|
|
||||||
// see: https://github.com/ollama/ollama/issues/1716 and https://github.com/ollama/ollama/issues/3582
|
|
||||||
let token_count = request
|
|
||||||
.messages
|
|
||||||
.iter()
|
|
||||||
.map(|msg| msg.content.chars().count())
|
|
||||||
.sum::<usize>()
|
|
||||||
/ 4;
|
|
||||||
|
|
||||||
async move { Ok(token_count) }.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn complete(
|
|
||||||
&self,
|
|
||||||
request: LanguageModelRequest,
|
|
||||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
|
||||||
let request = self.to_ollama_request(request);
|
|
||||||
|
|
||||||
let http_client = self.http_client.clone();
|
|
||||||
let api_url = self.api_url.clone();
|
|
||||||
let low_speed_timeout = self.low_speed_timeout;
|
|
||||||
async move {
|
|
||||||
let request =
|
|
||||||
stream_chat_completion(http_client.as_ref(), &api_url, request, low_speed_timeout);
|
|
||||||
let response = request.await?;
|
|
||||||
let stream = response
|
|
||||||
.filter_map(|response| async move {
|
|
||||||
match response {
|
|
||||||
Ok(delta) => {
|
|
||||||
let content = match delta.message {
|
|
||||||
ChatMessage::User { content } => content,
|
|
||||||
ChatMessage::Assistant { content } => content,
|
|
||||||
ChatMessage::System { content } => content,
|
|
||||||
};
|
|
||||||
Some(Ok(content))
|
|
||||||
}
|
|
||||||
Err(error) => Some(Err(error)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.boxed();
|
|
||||||
Ok(stream)
|
|
||||||
}
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_ollama_request(&self, request: LanguageModelRequest) -> ChatRequest {
|
|
||||||
let model = match request.model {
|
|
||||||
LanguageModel::Ollama(model) => model,
|
|
||||||
_ => self.model(),
|
|
||||||
};
|
|
||||||
|
|
||||||
ChatRequest {
|
|
||||||
model: model.name,
|
|
||||||
messages: request
|
|
||||||
.messages
|
|
||||||
.into_iter()
|
|
||||||
.map(|msg| match msg.role {
|
|
||||||
Role::User => ChatMessage::User {
|
|
||||||
content: msg.content,
|
|
||||||
},
|
|
||||||
Role::Assistant => ChatMessage::Assistant {
|
|
||||||
content: msg.content,
|
|
||||||
},
|
|
||||||
Role::System => ChatMessage::System {
|
|
||||||
content: msg.content,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
keep_alive: model.keep_alive.unwrap_or_default(),
|
|
||||||
stream: true,
|
|
||||||
options: Some(ChatOptions {
|
|
||||||
num_ctx: Some(model.max_tokens),
|
|
||||||
stop: Some(request.stop),
|
|
||||||
temperature: Some(request.temperature),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Role> for ollama::Role {
|
|
||||||
fn from(val: Role) -> Self {
|
|
||||||
match val {
|
|
||||||
Role::User => OllamaRole::User,
|
|
||||||
Role::Assistant => OllamaRole::Assistant,
|
|
||||||
Role::System => OllamaRole::System,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DownloadOllamaMessage {
|
|
||||||
retry_connection: Box<dyn Fn(&mut WindowContext) -> Task<Result<()>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DownloadOllamaMessage {
|
|
||||||
pub fn new(
|
|
||||||
retry_connection: Box<dyn Fn(&mut WindowContext) -> Task<Result<()>>>,
|
|
||||||
_cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
Self { retry_connection }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_download_button(&self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
ButtonLike::new("download_ollama_button")
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.size(ButtonSize::Large)
|
|
||||||
.layer(ElevationIndex::ModalSurface)
|
|
||||||
.child(Label::new("Get Ollama"))
|
|
||||||
.on_click(move |_, cx| cx.open_url(OLLAMA_DOWNLOAD_URL))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_retry_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
ButtonLike::new("retry_ollama_models")
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.size(ButtonSize::Large)
|
|
||||||
.layer(ElevationIndex::ModalSurface)
|
|
||||||
.child(Label::new("Retry"))
|
|
||||||
.on_click(cx.listener(move |this, _, cx| {
|
|
||||||
let connected = (this.retry_connection)(cx);
|
|
||||||
|
|
||||||
cx.spawn(|_this, _cx| async move {
|
|
||||||
connected.await?;
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_next_steps(&self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
v_flex()
|
|
||||||
.p_4()
|
|
||||||
.size_full()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Label::new("Once Ollama is on your machine, make sure to download a model or two.")
|
|
||||||
.size(LabelSize::Large),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex().w_full().p_4().justify_center().gap_2().child(
|
|
||||||
ButtonLike::new("view-models")
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.size(ButtonSize::Large)
|
|
||||||
.layer(ElevationIndex::ModalSurface)
|
|
||||||
.child(Label::new("View Available Models"))
|
|
||||||
.on_click(move |_, cx| cx.open_url(OLLAMA_LIBRARY_URL)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for DownloadOllamaMessage {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
v_flex()
|
|
||||||
.p_4()
|
|
||||||
.size_full()
|
|
||||||
.gap_2()
|
|
||||||
.child(Label::new("To use Ollama models via the assistant, Ollama must be running on your machine with at least one model downloaded.").size(LabelSize::Large))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.p_4()
|
|
||||||
.justify_center()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
self.render_download_button(cx)
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
self.render_retry_button(cx)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.child(self.render_next_steps(cx))
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -210,7 +210,6 @@ pub fn count_open_ai_tokens(
|
|||||||
|
|
||||||
match request.model {
|
match request.model {
|
||||||
LanguageModel::Anthropic(_)
|
LanguageModel::Anthropic(_)
|
||||||
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
|
|
||||||
| LanguageModel::Cloud(CloudModel::Claude3Opus)
|
| LanguageModel::Cloud(CloudModel::Claude3Opus)
|
||||||
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
|
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
|
||||||
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
|
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
|
||||||
@@ -337,7 +336,7 @@ impl Render for AuthenticationPrompt {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(Label::new("Click on").size(LabelSize::Small))
|
.child(Label::new("Click on").size(LabelSize::Small))
|
||||||
.child(Icon::new(IconName::ZedAssistant).size(IconSize::XSmall))
|
.child(Icon::new(IconName::Ai).size(IconSize::XSmall))
|
||||||
.child(
|
.child(
|
||||||
Label::new("in the status bar to close this panel.").size(LabelSize::Small),
|
Label::new("in the status bar to close this panel.").size(LabelSize::Small),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
|
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{AppContext, Model, ModelContext, Task};
|
use gpui::{AppContext, Model, ModelContext, Task};
|
||||||
use paths::contexts_dir;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
|
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
|
||||||
use ui::Context;
|
use ui::Context;
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{paths::CONTEXTS_DIR, ResultExt, TryFutureExt};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct SavedMessage {
|
pub struct SavedMessage {
|
||||||
@@ -28,22 +26,10 @@ pub struct SavedContext {
|
|||||||
pub messages: Vec<SavedMessage>,
|
pub messages: Vec<SavedMessage>,
|
||||||
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
pub slash_command_output_sections: Vec<SlashCommandOutputSection<usize>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SavedContext {
|
impl SavedContext {
|
||||||
pub const VERSION: &'static str = "0.3.0";
|
pub const VERSION: &'static str = "0.2.0";
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct SavedContextV0_2_0 {
|
|
||||||
pub id: Option<String>,
|
|
||||||
pub zed: String,
|
|
||||||
pub version: String,
|
|
||||||
pub text: String,
|
|
||||||
pub messages: Vec<SavedMessage>,
|
|
||||||
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
|
||||||
pub summary: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -76,7 +62,7 @@ impl ContextStore {
|
|||||||
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
|
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||||
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
|
let (mut events, _) = fs.watch(&CONTEXTS_DIR, CONTEXT_WATCH_DURATION).await;
|
||||||
|
|
||||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| Self {
|
let this = cx.new_model(|cx: &mut ModelContext<Self>| Self {
|
||||||
contexts_metadata: Vec::new(),
|
contexts_metadata: Vec::new(),
|
||||||
@@ -113,20 +99,6 @@ impl ContextStore {
|
|||||||
SavedContext::VERSION => {
|
SavedContext::VERSION => {
|
||||||
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
||||||
}
|
}
|
||||||
"0.2.0" => {
|
|
||||||
let saved_context =
|
|
||||||
serde_json::from_value::<SavedContextV0_2_0>(saved_context_json)?;
|
|
||||||
Ok(SavedContext {
|
|
||||||
id: saved_context.id,
|
|
||||||
zed: saved_context.zed,
|
|
||||||
version: saved_context.version,
|
|
||||||
text: saved_context.text,
|
|
||||||
messages: saved_context.messages,
|
|
||||||
message_metadata: saved_context.message_metadata,
|
|
||||||
summary: saved_context.summary,
|
|
||||||
slash_command_output_sections: Vec::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"0.1.0" => {
|
"0.1.0" => {
|
||||||
let saved_context =
|
let saved_context =
|
||||||
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
||||||
@@ -138,7 +110,6 @@ impl ContextStore {
|
|||||||
messages: saved_context.messages,
|
messages: saved_context.messages,
|
||||||
message_metadata: saved_context.message_metadata,
|
message_metadata: saved_context.message_metadata,
|
||||||
summary: saved_context.summary,
|
summary: saved_context.summary,
|
||||||
slash_command_output_sections: Vec::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
||||||
@@ -181,9 +152,9 @@ impl ContextStore {
|
|||||||
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
fs.create_dir(contexts_dir()).await?;
|
fs.create_dir(&CONTEXTS_DIR).await?;
|
||||||
|
|
||||||
let mut paths = fs.read_dir(contexts_dir()).await?;
|
let mut paths = fs.read_dir(&CONTEXTS_DIR).await?;
|
||||||
let mut contexts = Vec::<SavedContextMetadata>::new();
|
let mut contexts = Vec::<SavedContextMetadata>::new();
|
||||||
while let Some(path) = paths.next().await {
|
while let Some(path) = paths.next().await {
|
||||||
let path = path?;
|
let path = path?;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
use crate::{assistant_settings::AssistantSettings, CompletionProvider, ToggleModelSelector};
|
use crate::{assistant_settings::AssistantSettings, CompletionProvider, ToggleModelSelector};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
|
use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, PopoverMenuHandle, Tooltip};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ModelSelector {
|
pub struct ModelSelector {
|
||||||
@@ -19,7 +19,7 @@ impl ModelSelector {
|
|||||||
|
|
||||||
impl RenderOnce for ModelSelector {
|
impl RenderOnce for ModelSelector {
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
PopoverMenu::new("model-switcher")
|
popover_menu("model-switcher")
|
||||||
.with_handle(self.handle)
|
.with_handle(self.handle)
|
||||||
.menu(move |cx| {
|
.menu(move |cx| {
|
||||||
ContextMenu::build(cx, |mut menu, cx| {
|
ContextMenu::build(cx, |mut menu, cx| {
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ use futures::{
|
|||||||
};
|
};
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, percentage, point, size, Animation, AnimationExt, AppContext, BackgroundExecutor,
|
actions, percentage, point, size, Animation, AnimationExt, AnyElement, AppContext,
|
||||||
Bounds, EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions,
|
BackgroundExecutor, Bounds, DevicePixels, EventEmitter, Global, PromptLevel, ReadGlobal,
|
||||||
Transformation, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
Subscription, Task, TitlebarOptions, Transformation, UpdateGlobal, View, WindowBounds,
|
||||||
|
WindowHandle, WindowOptions,
|
||||||
};
|
};
|
||||||
use heed::{types::SerdeBincode, Database, RoTxn};
|
use heed::{types::SerdeBincode, Database, RoTxn};
|
||||||
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
|
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
|
||||||
@@ -25,7 +26,6 @@ use rope::Rope;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Reverse,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
@@ -33,10 +33,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
|
div, prelude::*, IconButtonShape, ListHeader, ListItem, ListItemSpacing, ListSubHeader,
|
||||||
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
ParentElement, Render, SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{paths::PROMPTS_DIR, ResultExt, TryFutureExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ actions!(
|
|||||||
/// Init starts loading the PromptStore in the background and assigns
|
/// Init starts loading the PromptStore in the background and assigns
|
||||||
/// a shared future to a global.
|
/// a shared future to a global.
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
let db_path = paths::prompts_dir().join("prompts-library-db.0.mdb");
|
let db_path = PROMPTS_DIR.join("prompts-library-db.0.mdb");
|
||||||
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
|
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
|
||||||
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
||||||
.boxed()
|
.boxed()
|
||||||
@@ -80,7 +80,11 @@ pub fn open_prompt_library(
|
|||||||
cx.spawn(|cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
let store = store.await?;
|
let store = store.await?;
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let bounds = Bounds::centered(None, size(px(1024.0), px(768.0)), cx);
|
let bounds = Bounds::centered(
|
||||||
|
None,
|
||||||
|
size(DevicePixels::from(1024), DevicePixels::from(768)),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
cx.open_window(
|
cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
titlebar: Some(TitlebarOptions {
|
titlebar: Some(TitlebarOptions {
|
||||||
@@ -93,7 +97,7 @@ pub fn open_prompt_library(
|
|||||||
},
|
},
|
||||||
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
|
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
|
||||||
)
|
)
|
||||||
})?
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,23 +124,41 @@ struct PromptEditor {
|
|||||||
struct PromptPickerDelegate {
|
struct PromptPickerDelegate {
|
||||||
store: Arc<PromptStore>,
|
store: Arc<PromptStore>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
matches: Vec<PromptMetadata>,
|
entries: Vec<PromptPickerEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PromptPickerEvent {
|
enum PromptPickerEvent {
|
||||||
Selected { prompt_id: PromptId },
|
Selected { prompt_id: Option<PromptId> },
|
||||||
Confirmed { prompt_id: PromptId },
|
Confirmed { prompt_id: PromptId },
|
||||||
Deleted { prompt_id: PromptId },
|
Deleted { prompt_id: PromptId },
|
||||||
ToggledDefault { prompt_id: PromptId },
|
ToggledDefault { prompt_id: PromptId },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PromptPickerEntry {
|
||||||
|
DefaultPromptsHeader,
|
||||||
|
DefaultPromptsEmpty,
|
||||||
|
AllPromptsHeader,
|
||||||
|
AllPromptsEmpty,
|
||||||
|
Prompt(PromptMetadata),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromptPickerEntry {
|
||||||
|
fn prompt_id(&self) -> Option<PromptId> {
|
||||||
|
match self {
|
||||||
|
PromptPickerEntry::Prompt(metadata) => Some(metadata.id),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EventEmitter<PromptPickerEvent> for Picker<PromptPickerDelegate> {}
|
impl EventEmitter<PromptPickerEvent> for Picker<PromptPickerDelegate> {}
|
||||||
|
|
||||||
impl PickerDelegate for PromptPickerDelegate {
|
impl PickerDelegate for PromptPickerDelegate {
|
||||||
type ListItem = ListItem;
|
type ListItem = AnyElement;
|
||||||
|
|
||||||
fn match_count(&self) -> usize {
|
fn match_count(&self) -> usize {
|
||||||
self.matches.len()
|
self.entries.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
fn selected_index(&self) -> usize {
|
||||||
@@ -145,11 +167,14 @@ impl PickerDelegate for PromptPickerDelegate {
|
|||||||
|
|
||||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
self.selected_index = ix;
|
self.selected_index = ix;
|
||||||
if let Some(prompt) = self.matches.get(self.selected_index) {
|
let prompt_id = if let Some(PromptPickerEntry::Prompt(prompt)) =
|
||||||
cx.emit(PromptPickerEvent::Selected {
|
self.entries.get(self.selected_index)
|
||||||
prompt_id: prompt.id,
|
{
|
||||||
});
|
Some(prompt.id)
|
||||||
}
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
cx.emit(PromptPickerEvent::Selected { prompt_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||||
@@ -158,24 +183,48 @@ impl PickerDelegate for PromptPickerDelegate {
|
|||||||
|
|
||||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||||
let search = self.store.search(query);
|
let search = self.store.search(query);
|
||||||
let prev_prompt_id = self.matches.get(self.selected_index).map(|mat| mat.id);
|
let prev_prompt_id = self
|
||||||
|
.entries
|
||||||
|
.get(self.selected_index)
|
||||||
|
.and_then(|mat| mat.prompt_id());
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let (matches, selected_index) = cx
|
let (entries, selected_index) = cx
|
||||||
.background_executor()
|
.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let matches = search.await;
|
let prompts = search.await;
|
||||||
|
let (default_prompts, prompts) = prompts
|
||||||
|
.into_iter()
|
||||||
|
.partition::<Vec<_>, _>(|prompt| prompt.default);
|
||||||
|
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
entries.push(PromptPickerEntry::DefaultPromptsHeader);
|
||||||
|
if default_prompts.is_empty() {
|
||||||
|
entries.push(PromptPickerEntry::DefaultPromptsEmpty);
|
||||||
|
} else {
|
||||||
|
entries.extend(default_prompts.into_iter().map(PromptPickerEntry::Prompt));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.push(PromptPickerEntry::AllPromptsHeader);
|
||||||
|
if prompts.is_empty() {
|
||||||
|
entries.push(PromptPickerEntry::AllPromptsEmpty);
|
||||||
|
} else {
|
||||||
|
entries.extend(prompts.into_iter().map(PromptPickerEntry::Prompt));
|
||||||
|
}
|
||||||
|
|
||||||
let selected_index = prev_prompt_id
|
let selected_index = prev_prompt_id
|
||||||
.and_then(|prev_prompt_id| {
|
.and_then(|prev_prompt_id| {
|
||||||
matches.iter().position(|entry| entry.id == prev_prompt_id)
|
entries
|
||||||
|
.iter()
|
||||||
|
.position(|entry| entry.prompt_id() == Some(prev_prompt_id))
|
||||||
})
|
})
|
||||||
|
.or_else(|| entries.iter().position(|entry| entry.prompt_id().is_some()))
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
(matches, selected_index)
|
(entries, selected_index)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.delegate.matches = matches;
|
this.delegate.entries = entries;
|
||||||
this.delegate.set_selected_index(selected_index, cx);
|
this.delegate.set_selected_index(selected_index, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
@@ -184,7 +233,7 @@ impl PickerDelegate for PromptPickerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
if let Some(prompt) = self.matches.get(self.selected_index) {
|
if let Some(PromptPickerEntry::Prompt(prompt)) = self.entries.get(self.selected_index) {
|
||||||
cx.emit(PromptPickerEvent::Confirmed {
|
cx.emit(PromptPickerEvent::Confirmed {
|
||||||
prompt_id: prompt.id,
|
prompt_id: prompt.id,
|
||||||
});
|
});
|
||||||
@@ -199,59 +248,82 @@ impl PickerDelegate for PromptPickerDelegate {
|
|||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let prompt = self.matches.get(ix)?;
|
let prompt = self.entries.get(ix)?;
|
||||||
let default = prompt.default;
|
let element = match prompt {
|
||||||
let prompt_id = prompt.id;
|
PromptPickerEntry::DefaultPromptsHeader => ListHeader::new("Default Prompts")
|
||||||
let element = ListItem::new(ix)
|
.inset(true)
|
||||||
.inset(true)
|
.start_slot(
|
||||||
.spacing(ListItemSpacing::Sparse)
|
Icon::new(IconName::Sparkle)
|
||||||
.selected(selected)
|
.color(Color::Muted)
|
||||||
.child(h_flex().h_5().line_height(relative(1.)).child(Label::new(
|
.size(IconSize::XSmall),
|
||||||
prompt.title.clone().unwrap_or("Untitled".into()),
|
)
|
||||||
)))
|
.selected(selected)
|
||||||
.end_slot::<IconButton>(default.then(|| {
|
.into_any_element(),
|
||||||
IconButton::new("toggle-default-prompt", IconName::SparkleFilled)
|
PromptPickerEntry::DefaultPromptsEmpty => {
|
||||||
.selected(true)
|
ListSubHeader::new("Star a prompt to add it to the default context")
|
||||||
.icon_color(Color::Accent)
|
.inset(true)
|
||||||
.shape(IconButtonShape::Square)
|
.selected(selected)
|
||||||
.tooltip(move |cx| Tooltip::text("Remove from Default Prompt", cx))
|
.into_any_element()
|
||||||
.on_click(cx.listener(move |_, _, cx| {
|
}
|
||||||
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
|
PromptPickerEntry::AllPromptsHeader => ListHeader::new("All Prompts")
|
||||||
}))
|
.inset(true)
|
||||||
}))
|
.start_slot(
|
||||||
.end_hover_slot(
|
Icon::new(IconName::Library)
|
||||||
h_flex()
|
.color(Color::Muted)
|
||||||
.gap_2()
|
.size(IconSize::XSmall),
|
||||||
.child(
|
)
|
||||||
IconButton::new("delete-prompt", IconName::Trash)
|
.selected(selected)
|
||||||
.icon_color(Color::Muted)
|
.into_any_element(),
|
||||||
.shape(IconButtonShape::Square)
|
PromptPickerEntry::AllPromptsEmpty => ListSubHeader::new("No prompts")
|
||||||
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
|
.inset(true)
|
||||||
.on_click(cx.listener(move |_, _, cx| {
|
.selected(selected)
|
||||||
cx.emit(PromptPickerEvent::Deleted { prompt_id })
|
.into_any_element(),
|
||||||
})),
|
PromptPickerEntry::Prompt(prompt) => {
|
||||||
|
let default = prompt.default;
|
||||||
|
let prompt_id = prompt.id;
|
||||||
|
ListItem::new(ix)
|
||||||
|
.inset(true)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.selected(selected)
|
||||||
|
.child(h_flex().h_5().line_height(relative(1.)).child(Label::new(
|
||||||
|
prompt.title.clone().unwrap_or("Untitled".into()),
|
||||||
|
)))
|
||||||
|
.end_hover_slot(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
IconButton::new("delete-prompt", IconName::Trash)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
|
||||||
|
.on_click(cx.listener(move |_, _, cx| {
|
||||||
|
cx.emit(PromptPickerEvent::Deleted { prompt_id })
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
||||||
|
.selected(default)
|
||||||
|
.selected_icon(IconName::SparkleFilled)
|
||||||
|
.icon_color(if default { Color::Accent } else { Color::Muted })
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.tooltip(move |cx| {
|
||||||
|
Tooltip::text(
|
||||||
|
if default {
|
||||||
|
"Remove from Default Prompt"
|
||||||
|
} else {
|
||||||
|
"Add to Default Prompt"
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(move |_, _, cx| {
|
||||||
|
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
|
||||||
|
})),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.into_any_element()
|
||||||
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
}
|
||||||
.selected(default)
|
};
|
||||||
.selected_icon(IconName::SparkleFilled)
|
|
||||||
.icon_color(if default { Color::Accent } else { Color::Muted })
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.tooltip(move |cx| {
|
|
||||||
Tooltip::text(
|
|
||||||
if default {
|
|
||||||
"Remove from Default Prompt"
|
|
||||||
} else {
|
|
||||||
"Add to Default Prompt"
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(move |_, _, cx| {
|
|
||||||
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Some(element)
|
Some(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,13 +349,11 @@ impl PromptLibrary {
|
|||||||
let delegate = PromptPickerDelegate {
|
let delegate = PromptPickerDelegate {
|
||||||
store: store.clone(),
|
store: store.clone(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
matches: Vec::new(),
|
entries: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let picker = cx.new_view(|cx| {
|
let picker = cx.new_view(|cx| {
|
||||||
let picker = Picker::uniform_list(delegate, cx)
|
let picker = Picker::list(delegate, cx).modal(false).max_height(None);
|
||||||
.modal(false)
|
|
||||||
.max_height(None);
|
|
||||||
picker.focus(cx);
|
picker.focus(cx);
|
||||||
picker
|
picker
|
||||||
});
|
});
|
||||||
@@ -306,7 +376,11 @@ impl PromptLibrary {
|
|||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
PromptPickerEvent::Selected { prompt_id } => {
|
PromptPickerEvent::Selected { prompt_id } => {
|
||||||
self.load_prompt(*prompt_id, false, cx);
|
if let Some(prompt_id) = *prompt_id {
|
||||||
|
self.load_prompt(prompt_id, false, cx);
|
||||||
|
} else {
|
||||||
|
self.focus_picker(&Default::default(), cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PromptPickerEvent::Confirmed { prompt_id } => {
|
PromptPickerEvent::Confirmed { prompt_id } => {
|
||||||
self.load_prompt(*prompt_id, true, cx);
|
self.load_prompt(*prompt_id, true, cx);
|
||||||
@@ -450,7 +524,6 @@ impl PromptLibrary {
|
|||||||
editor.set_show_gutter(false, cx);
|
editor.set_show_gutter(false, cx);
|
||||||
editor.set_show_wrap_guides(false, cx);
|
editor.set_show_wrap_guides(false, cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
editor.set_use_modal_editing(false);
|
|
||||||
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
|
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
|
||||||
editor.set_completion_provider(Box::new(
|
editor.set_completion_provider(Box::new(
|
||||||
SlashCommandCompletionProvider::new(commands, None, None),
|
SlashCommandCompletionProvider::new(commands, None, None),
|
||||||
@@ -494,23 +567,21 @@ impl PromptLibrary {
|
|||||||
if let Some(prompt_id) = prompt_id {
|
if let Some(prompt_id) = prompt_id {
|
||||||
if picker
|
if picker
|
||||||
.delegate
|
.delegate
|
||||||
.matches
|
.entries
|
||||||
.get(picker.delegate.selected_index())
|
.get(picker.delegate.selected_index())
|
||||||
.map_or(true, |old_selected_prompt| {
|
.map_or(true, |old_selected_prompt| {
|
||||||
old_selected_prompt.id != prompt_id
|
old_selected_prompt.prompt_id() != Some(prompt_id)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
if let Some(ix) = picker
|
if let Some(ix) = picker
|
||||||
.delegate
|
.delegate
|
||||||
.matches
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.position(|mat| mat.id == prompt_id)
|
.position(|mat| mat.prompt_id() == Some(prompt_id))
|
||||||
{
|
{
|
||||||
picker.set_selected_index(ix, true, cx);
|
picker.set_selected_index(ix, true, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
picker.focus(cx);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -589,6 +660,19 @@ impl PromptLibrary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cancel_last_inline_assist(
|
||||||
|
&mut self,
|
||||||
|
_: &editor::actions::Cancel,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
let canceled = InlineAssistant::update_global(cx, |assistant, cx| {
|
||||||
|
assistant.cancel_last_inline_assist(cx)
|
||||||
|
});
|
||||||
|
if !canceled {
|
||||||
|
cx.propagate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_prompt_editor_event(
|
fn handle_prompt_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt_id: PromptId,
|
prompt_id: PromptId,
|
||||||
@@ -727,6 +811,7 @@ impl PromptLibrary {
|
|||||||
div()
|
div()
|
||||||
.on_action(cx.listener(Self::focus_picker))
|
.on_action(cx.listener(Self::focus_picker))
|
||||||
.on_action(cx.listener(Self::inline_assist))
|
.on_action(cx.listener(Self::inline_assist))
|
||||||
|
.on_action(cx.listener(Self::cancel_last_inline_assist))
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.h_full()
|
.h_full()
|
||||||
.pt(Spacing::XXLarge.rems(cx))
|
.pt(Spacing::XXLarge.rems(cx))
|
||||||
@@ -832,8 +917,13 @@ impl PromptLibrary {
|
|||||||
|
|
||||||
impl Render for PromptLibrary {
|
impl Render for PromptLibrary {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let ui_font = theme::setup_ui_font(cx);
|
let (ui_font, ui_font_size) = {
|
||||||
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
|
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
|
||||||
|
};
|
||||||
|
|
||||||
let theme = cx.theme().clone();
|
let theme = cx.theme().clone();
|
||||||
|
cx.set_rem_size(ui_font_size);
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("prompt-manager")
|
.id("prompt-manager")
|
||||||
@@ -1015,7 +1105,7 @@ impl PromptStore {
|
|||||||
let cached_metadata = self.metadata_cache.read().metadata.clone();
|
let cached_metadata = self.metadata_cache.read().metadata.clone();
|
||||||
let executor = self.executor.clone();
|
let executor = self.executor.clone();
|
||||||
self.executor.spawn(async move {
|
self.executor.spawn(async move {
|
||||||
let mut matches = if query.is_empty() {
|
if query.is_empty() {
|
||||||
cached_metadata
|
cached_metadata
|
||||||
} else {
|
} else {
|
||||||
let candidates = cached_metadata
|
let candidates = cached_metadata
|
||||||
@@ -1041,9 +1131,7 @@ impl PromptStore {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mat| cached_metadata[mat.candidate_id].clone())
|
.map(|mat| cached_metadata[mat.candidate_id].clone())
|
||||||
.collect()
|
.collect()
|
||||||
};
|
}
|
||||||
matches.sort_by_key(|metadata| Reverse(metadata.default));
|
|
||||||
matches
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,32 +33,35 @@ pub fn generate_content_prompt(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(
|
// Include file content.
|
||||||
prompt,
|
for chunk in buffer.text_for_range(0..range.start) {
|
||||||
"The user has the following file open in the editor:"
|
prompt.push_str(chunk);
|
||||||
)?;
|
}
|
||||||
|
|
||||||
if range.is_empty() {
|
if range.is_empty() {
|
||||||
write!(prompt, "```")?;
|
prompt.push_str("<|START|>");
|
||||||
if let Some(language_name) = language_name {
|
} else {
|
||||||
write!(prompt, "{language_name}")?;
|
prompt.push_str("<|START|");
|
||||||
}
|
}
|
||||||
|
|
||||||
for chunk in buffer.as_rope().chunks_in_range(0..range.start) {
|
for chunk in buffer.text_for_range(range.clone()) {
|
||||||
prompt.push_str(chunk);
|
prompt.push_str(chunk);
|
||||||
}
|
}
|
||||||
prompt.push_str("<|CURSOR|>");
|
|
||||||
for chunk in buffer.as_rope().chunks_in_range(range.start..buffer.len()) {
|
|
||||||
prompt.push_str(chunk);
|
|
||||||
}
|
|
||||||
if !prompt.ends_with('\n') {
|
|
||||||
prompt.push('\n');
|
|
||||||
}
|
|
||||||
writeln!(prompt, "```")?;
|
|
||||||
prompt.push('\n');
|
|
||||||
|
|
||||||
|
if !range.is_empty() {
|
||||||
|
prompt.push_str("|END|>");
|
||||||
|
}
|
||||||
|
|
||||||
|
for chunk in buffer.text_for_range(range.end..buffer.len()) {
|
||||||
|
prompt.push_str(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt.push('\n');
|
||||||
|
|
||||||
|
if range.is_empty() {
|
||||||
writeln!(
|
writeln!(
|
||||||
prompt,
|
prompt,
|
||||||
"Assume the cursor is located where the `<|CURSOR|>` span is."
|
"Assume the cursor is located where the `<|START|>` span is."
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
writeln!(
|
writeln!(
|
||||||
@@ -72,42 +75,11 @@ pub fn generate_content_prompt(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
write!(prompt, "```")?;
|
writeln!(prompt, "Modify the user's selected {content_type} based upon the users prompt: '{user_prompt}'").unwrap();
|
||||||
for chunk in buffer.as_rope().chunks() {
|
writeln!(prompt, "You must reply with only the adjusted {content_type} (within the '<|START|' and '|END|>' spans) not the entire file.").unwrap();
|
||||||
prompt.push_str(chunk);
|
|
||||||
}
|
|
||||||
if !prompt.ends_with('\n') {
|
|
||||||
prompt.push('\n');
|
|
||||||
}
|
|
||||||
writeln!(prompt, "```")?;
|
|
||||||
prompt.push('\n');
|
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
prompt,
|
prompt,
|
||||||
"In particular, the following piece of text is selected:"
|
"Double check that you only return code and not the '<|START|' and '|END|'> spans"
|
||||||
)?;
|
|
||||||
write!(prompt, "```")?;
|
|
||||||
if let Some(language_name) = language_name {
|
|
||||||
write!(prompt, "{language_name}")?;
|
|
||||||
}
|
|
||||||
prompt.push('\n');
|
|
||||||
for chunk in buffer.text_for_range(range.clone()) {
|
|
||||||
prompt.push_str(chunk);
|
|
||||||
}
|
|
||||||
if !prompt.ends_with('\n') {
|
|
||||||
prompt.push('\n');
|
|
||||||
}
|
|
||||||
writeln!(prompt, "```")?;
|
|
||||||
prompt.push('\n');
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"Modify the user's selected {content_type} based upon the users prompt: {user_prompt}"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"You must reply with only the adjusted {content_type}, not the entire file."
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use anyhow::Result;
|
|||||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
|
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
|
||||||
use editor::{CompletionProvider, Editor};
|
use editor::{CompletionProvider, Editor};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
|
use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
|
||||||
use language::{Anchor, Buffer, CodeLabel, Documentation, HighlightId, LanguageServerId, ToPoint};
|
use language::{Anchor, Buffer, CodeLabel, Documentation, LanguageServerId, ToPoint};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -14,21 +14,17 @@ use std::{
|
|||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use ui::ActiveTheme;
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub mod active_command;
|
pub mod active_command;
|
||||||
pub mod default_command;
|
pub mod default_command;
|
||||||
pub mod diagnostics_command;
|
|
||||||
pub mod fetch_command;
|
pub mod fetch_command;
|
||||||
pub mod file_command;
|
pub mod file_command;
|
||||||
pub mod now_command;
|
|
||||||
pub mod project_command;
|
pub mod project_command;
|
||||||
pub mod prompt_command;
|
pub mod prompt_command;
|
||||||
pub mod rustdoc_command;
|
pub mod rustdoc_command;
|
||||||
pub mod search_command;
|
pub mod search_command;
|
||||||
pub mod tabs_command;
|
pub mod tabs_command;
|
||||||
pub mod term_command;
|
|
||||||
|
|
||||||
pub(crate) struct SlashCommandCompletionProvider {
|
pub(crate) struct SlashCommandCompletionProvider {
|
||||||
commands: Arc<SlashCommandRegistry>,
|
commands: Arc<SlashCommandRegistry>,
|
||||||
@@ -220,7 +216,6 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
|||||||
&self,
|
&self,
|
||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
buffer_position: Anchor,
|
buffer_position: Anchor,
|
||||||
_: editor::CompletionContext,
|
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> Task<Result<Vec<project::Completion>>> {
|
) -> Task<Result<Vec<project::Completion>>> {
|
||||||
let Some((name, argument, command_range, argument_range)) =
|
let Some((name, argument, command_range, argument_range)) =
|
||||||
@@ -349,19 +344,3 @@ impl SlashCommandLine {
|
|||||||
call
|
call
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_label_for_command(
|
|
||||||
command_name: &str,
|
|
||||||
arguments: &[&str],
|
|
||||||
cx: &AppContext,
|
|
||||||
) -> CodeLabel {
|
|
||||||
let mut label = CodeLabel::default();
|
|
||||||
label.push_str(command_name, None);
|
|
||||||
label.push_str(" ", None);
|
|
||||||
label.push_str(
|
|
||||||
&arguments.join(" "),
|
|
||||||
cx.theme().syntax().highlight_id("comment").map(HighlightId),
|
|
||||||
);
|
|
||||||
label.filter_range = 0..command_name.len();
|
|
||||||
label
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
use super::{
|
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
||||||
diagnostics_command::write_single_file_diagnostics,
|
|
||||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
|
||||||
SlashCommand, SlashCommandOutput,
|
|
||||||
};
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::{borrow::Cow, sync::Arc};
|
||||||
use std::sync::Arc;
|
use ui::{IntoElement, WindowContext};
|
||||||
use ui::WindowContext;
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct ActiveSlashCommand;
|
pub(crate) struct ActiveSlashCommand;
|
||||||
@@ -28,9 +24,9 @@ impl SlashCommand for ActiveSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
_cx: &mut AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
@@ -61,38 +57,46 @@ impl SlashCommand for ActiveSlashCommand {
|
|||||||
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
let path = snapshot.resolve_file_path(cx, true);
|
let path = snapshot.resolve_file_path(cx, true);
|
||||||
let task = cx.background_executor().spawn({
|
let text = cx.background_executor().spawn({
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
async move {
|
async move {
|
||||||
let mut output = String::new();
|
let path = path
|
||||||
output.push_str(&codeblock_fence_for_path(path.as_deref(), None));
|
.as_ref()
|
||||||
|
.map(|path| path.to_string_lossy())
|
||||||
|
.unwrap_or_else(|| Cow::Borrowed("untitled"));
|
||||||
|
|
||||||
|
let mut output = String::with_capacity(path.len() + snapshot.len() + 9);
|
||||||
|
output.push_str("```");
|
||||||
|
output.push_str(&path);
|
||||||
|
output.push('\n');
|
||||||
for chunk in snapshot.as_rope().chunks() {
|
for chunk in snapshot.as_rope().chunks() {
|
||||||
output.push_str(chunk);
|
output.push_str(chunk);
|
||||||
}
|
}
|
||||||
if !output.ends_with('\n') {
|
if !output.ends_with('\n') {
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
output.push_str("```\n");
|
output.push_str("```");
|
||||||
let has_diagnostics =
|
output
|
||||||
write_single_file_diagnostics(&mut output, path.as_deref(), &snapshot);
|
|
||||||
if output.ends_with('\n') {
|
|
||||||
output.pop();
|
|
||||||
}
|
|
||||||
(output, has_diagnostics)
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let (text, has_diagnostics) = task.await;
|
let text = text.await;
|
||||||
let range = 0..text.len();
|
let range = 0..text.len();
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: vec![build_entry_output_section(
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
path.as_deref(),
|
render_placeholder: Arc::new(move |id, unfold, _| {
|
||||||
false,
|
FilePlaceholder {
|
||||||
None,
|
id,
|
||||||
)],
|
path: path.clone(),
|
||||||
run_commands_in_text: has_diagnostics,
|
line_range: None,
|
||||||
|
unfold,
|
||||||
|
}
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
|
}],
|
||||||
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{SlashCommand, SlashCommandOutput};
|
use super::{prompt_command::PromptPlaceholder, SlashCommand, SlashCommandOutput};
|
||||||
use crate::prompt_library::PromptStore;
|
use crate::prompt_library::PromptStore;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
@@ -31,7 +31,7 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancellation_flag: Arc<AtomicBool>,
|
_cancellation_flag: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -53,7 +53,7 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
let prompts = store.default_prompt_metadata();
|
let prompts = store.default_prompt_metadata();
|
||||||
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
text.push('\n');
|
writeln!(text, "Default Prompt:").unwrap();
|
||||||
for prompt in prompts {
|
for prompt in prompts {
|
||||||
if let Some(title) = prompt.title {
|
if let Some(title) = prompt.title {
|
||||||
writeln!(text, "/prompt {}", title).unwrap();
|
writeln!(text, "/prompt {}", title).unwrap();
|
||||||
@@ -61,15 +61,17 @@ impl SlashCommand for DefaultSlashCommand {
|
|||||||
}
|
}
|
||||||
text.pop();
|
text.pop();
|
||||||
|
|
||||||
if text.is_empty() {
|
|
||||||
text.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range: 0..text.len(),
|
range: 0..text.len(),
|
||||||
icon: IconName::Library,
|
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||||
label: "Default".into(),
|
PromptPlaceholder {
|
||||||
|
title: "Default".into(),
|
||||||
|
id,
|
||||||
|
unfold,
|
||||||
|
}
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
}],
|
}],
|
||||||
text,
|
text,
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
|
|||||||
@@ -1,490 +0,0 @@
|
|||||||
use super::{create_label_for_command, SlashCommand, SlashCommandOutput};
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
|
||||||
use fuzzy::{PathMatch, StringMatchCandidate};
|
|
||||||
use gpui::{AppContext, Model, Task, View, WeakView};
|
|
||||||
use language::{
|
|
||||||
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
|
|
||||||
OffsetRangeExt, ToOffset,
|
|
||||||
};
|
|
||||||
use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
|
|
||||||
use rope::Point;
|
|
||||||
use std::fmt::Write;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::{
|
|
||||||
ops::Range,
|
|
||||||
sync::{atomic::AtomicBool, Arc},
|
|
||||||
};
|
|
||||||
use ui::prelude::*;
|
|
||||||
use util::paths::PathMatcher;
|
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
pub(crate) struct DiagnosticsCommand;
|
|
||||||
|
|
||||||
impl DiagnosticsCommand {
|
|
||||||
fn search_paths(
|
|
||||||
&self,
|
|
||||||
query: String,
|
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
|
||||||
workspace: &View<Workspace>,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Task<Vec<PathMatch>> {
|
|
||||||
if query.is_empty() {
|
|
||||||
let workspace = workspace.read(cx);
|
|
||||||
let entries = workspace.recent_navigation_history(Some(10), cx);
|
|
||||||
let path_prefix: Arc<str> = "".into();
|
|
||||||
Task::ready(
|
|
||||||
entries
|
|
||||||
.into_iter()
|
|
||||||
.map(|(entry, _)| PathMatch {
|
|
||||||
score: 0.,
|
|
||||||
positions: Vec::new(),
|
|
||||||
worktree_id: entry.worktree_id.to_usize(),
|
|
||||||
path: entry.path.clone(),
|
|
||||||
path_prefix: path_prefix.clone(),
|
|
||||||
distance_to_relative_ancestor: 0,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
|
||||||
let candidate_sets = worktrees
|
|
||||||
.into_iter()
|
|
||||||
.map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
PathMatchCandidateSet {
|
|
||||||
snapshot: worktree.snapshot(),
|
|
||||||
include_ignored: worktree
|
|
||||||
.root_entry()
|
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
|
||||||
include_root_name: true,
|
|
||||||
candidates: project::Candidates::Entries,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
|
||||||
cx.foreground_executor().spawn(async move {
|
|
||||||
fuzzy::match_path_sets(
|
|
||||||
candidate_sets.as_slice(),
|
|
||||||
query.as_str(),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
100,
|
|
||||||
&cancellation_flag,
|
|
||||||
executor,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SlashCommand for DiagnosticsCommand {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
"diagnostics".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn label(&self, cx: &AppContext) -> language::CodeLabel {
|
|
||||||
create_label_for_command("diagnostics", &[INCLUDE_WARNINGS_ARGUMENT], cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> String {
|
|
||||||
"Insert diagnostics".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
|
||||||
"Insert Diagnostics".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn requires_argument(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_argument(
|
|
||||||
self: Arc<Self>,
|
|
||||||
query: String,
|
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
|
||||||
workspace: Option<WeakView<Workspace>>,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Task<Result<Vec<String>>> {
|
|
||||||
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
|
|
||||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
|
||||||
};
|
|
||||||
let query = query.split_whitespace().last().unwrap_or("").to_string();
|
|
||||||
|
|
||||||
let paths = self.search_paths(query.clone(), cancellation_flag.clone(), &workspace, cx);
|
|
||||||
let executor = cx.background_executor().clone();
|
|
||||||
cx.background_executor().spawn(async move {
|
|
||||||
let mut matches: Vec<String> = paths
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(|path_match| {
|
|
||||||
format!(
|
|
||||||
"{}{}",
|
|
||||||
path_match.path_prefix,
|
|
||||||
path_match.path.to_string_lossy()
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
matches.extend(
|
|
||||||
fuzzy::match_strings(
|
|
||||||
&Options::match_candidates_for_args(),
|
|
||||||
&query,
|
|
||||||
false,
|
|
||||||
10,
|
|
||||||
&cancellation_flag,
|
|
||||||
executor,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(|candidate| candidate.string),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(matches)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
self: Arc<Self>,
|
|
||||||
argument: Option<&str>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
_delegate: Arc<dyn LspAdapterDelegate>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
|
||||||
return Task::ready(Err(anyhow!("workspace was dropped")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let options = Options::parse(argument);
|
|
||||||
|
|
||||||
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
|
|
||||||
cx.spawn(move |_| async move {
|
|
||||||
let Some((text, sections)) = task.await? else {
|
|
||||||
return Ok(SlashCommandOutput::default());
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(SlashCommandOutput {
|
|
||||||
text,
|
|
||||||
sections: sections
|
|
||||||
.into_iter()
|
|
||||||
.map(|(range, placeholder_type)| SlashCommandOutputSection {
|
|
||||||
range,
|
|
||||||
icon: match placeholder_type {
|
|
||||||
PlaceholderType::Root(_, _) => IconName::ExclamationTriangle,
|
|
||||||
PlaceholderType::File(_) => IconName::File,
|
|
||||||
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => {
|
|
||||||
IconName::XCircle
|
|
||||||
}
|
|
||||||
PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
|
|
||||||
IconName::ExclamationTriangle
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label: match placeholder_type {
|
|
||||||
PlaceholderType::Root(summary, source) => {
|
|
||||||
let mut label = String::new();
|
|
||||||
label.push_str("Diagnostics");
|
|
||||||
if let Some(source) = source {
|
|
||||||
write!(label, " ({})", source).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if summary.error_count > 0 || summary.warning_count > 0 {
|
|
||||||
label.push(':');
|
|
||||||
|
|
||||||
if summary.error_count > 0 {
|
|
||||||
write!(label, " {} errors", summary.error_count).unwrap();
|
|
||||||
if summary.warning_count > 0 {
|
|
||||||
label.push_str(",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if summary.warning_count > 0 {
|
|
||||||
write!(label, " {} warnings", summary.warning_count)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label.into()
|
|
||||||
}
|
|
||||||
PlaceholderType::File(file_path) => file_path.into(),
|
|
||||||
PlaceholderType::Diagnostic(_, message) => message.into(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
run_commands_in_text: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Options {
|
|
||||||
include_warnings: bool,
|
|
||||||
path_matcher: Option<PathMatcher>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
|
|
||||||
|
|
||||||
impl Options {
|
|
||||||
fn parse(arguments_line: Option<&str>) -> Self {
|
|
||||||
arguments_line
|
|
||||||
.map(|arguments_line| {
|
|
||||||
let args = arguments_line.split_whitespace().collect::<Vec<_>>();
|
|
||||||
let mut include_warnings = false;
|
|
||||||
let mut path_matcher = None;
|
|
||||||
for arg in args {
|
|
||||||
if arg == INCLUDE_WARNINGS_ARGUMENT {
|
|
||||||
include_warnings = true;
|
|
||||||
} else {
|
|
||||||
path_matcher = PathMatcher::new(&[arg.to_owned()]).log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
include_warnings,
|
|
||||||
path_matcher,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_candidates_for_args() -> [StringMatchCandidate; 1] {
|
|
||||||
[StringMatchCandidate::new(
|
|
||||||
0,
|
|
||||||
INCLUDE_WARNINGS_ARGUMENT.to_string(),
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_diagnostics(
|
|
||||||
project: Model<Project>,
|
|
||||||
options: Options,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Task<Result<Option<(String, Vec<(Range<usize>, PlaceholderType)>)>>> {
|
|
||||||
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
|
||||||
debug_assert_eq!(path_matcher.sources().len(), 1);
|
|
||||||
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let glob_is_exact_file_match = if let Some(path) = options
|
|
||||||
.path_matcher
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|pm| pm.sources().first())
|
|
||||||
{
|
|
||||||
PathBuf::try_from(path)
|
|
||||||
.ok()
|
|
||||||
.and_then(|path| {
|
|
||||||
project.read(cx).worktrees().find_map(|worktree| {
|
|
||||||
let worktree = worktree.read(cx);
|
|
||||||
let worktree_root_path = Path::new(worktree.root_name());
|
|
||||||
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
|
||||||
worktree.absolutize(&relative_path).ok()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.is_some()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let project_handle = project.downgrade();
|
|
||||||
let diagnostic_summaries: Vec<_> = project
|
|
||||||
.read(cx)
|
|
||||||
.diagnostic_summaries(false, cx)
|
|
||||||
.flat_map(|(path, _, summary)| {
|
|
||||||
let worktree = project.read(cx).worktree_for_id(path.worktree_id, cx)?;
|
|
||||||
let mut path_buf = PathBuf::from(worktree.read(cx).root_name());
|
|
||||||
path_buf.push(&path.path);
|
|
||||||
Some((path, path_buf, summary))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
|
||||||
let mut text = String::new();
|
|
||||||
if let Some(error_source) = error_source.as_ref() {
|
|
||||||
writeln!(text, "diagnostics: {}", error_source).unwrap();
|
|
||||||
} else {
|
|
||||||
writeln!(text, "diagnostics").unwrap();
|
|
||||||
}
|
|
||||||
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
|
|
||||||
|
|
||||||
let mut project_summary = DiagnosticSummary::default();
|
|
||||||
for (project_path, path, summary) in diagnostic_summaries {
|
|
||||||
if let Some(path_matcher) = &options.path_matcher {
|
|
||||||
if !path_matcher.is_match(&path) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
project_summary.error_count += summary.error_count;
|
|
||||||
if options.include_warnings {
|
|
||||||
project_summary.warning_count += summary.warning_count;
|
|
||||||
} else if summary.error_count == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let last_end = text.len();
|
|
||||||
let file_path = path.to_string_lossy().to_string();
|
|
||||||
if !glob_is_exact_file_match {
|
|
||||||
writeln!(&mut text, "{file_path}").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(buffer) = project_handle
|
|
||||||
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))?
|
|
||||||
.await
|
|
||||||
.log_err()
|
|
||||||
{
|
|
||||||
collect_buffer_diagnostics(
|
|
||||||
&mut text,
|
|
||||||
&mut sections,
|
|
||||||
cx.read_model(&buffer, |buffer, _| buffer.snapshot())?,
|
|
||||||
options.include_warnings,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !glob_is_exact_file_match {
|
|
||||||
sections.push((
|
|
||||||
last_end..text.len().saturating_sub(1),
|
|
||||||
PlaceholderType::File(file_path),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No diagnostics found
|
|
||||||
if sections.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
sections.push((
|
|
||||||
0..text.len(),
|
|
||||||
PlaceholderType::Root(project_summary, error_source),
|
|
||||||
));
|
|
||||||
Ok(Some((text, sections)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buffer_has_error_diagnostics(snapshot: &BufferSnapshot) -> bool {
|
|
||||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
|
||||||
let entry = &group.entries[group.primary_ix];
|
|
||||||
if entry.diagnostic.severity == DiagnosticSeverity::ERROR {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_single_file_diagnostics(
|
|
||||||
output: &mut String,
|
|
||||||
path: Option<&Path>,
|
|
||||||
snapshot: &BufferSnapshot,
|
|
||||||
) -> bool {
|
|
||||||
if let Some(path) = path {
|
|
||||||
if buffer_has_error_diagnostics(&snapshot) {
|
|
||||||
output.push_str("/diagnostics ");
|
|
||||||
output.push_str(&path.to_string_lossy());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_buffer_diagnostics(
|
|
||||||
text: &mut String,
|
|
||||||
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
|
|
||||||
snapshot: BufferSnapshot,
|
|
||||||
include_warnings: bool,
|
|
||||||
) {
|
|
||||||
for (_, group) in snapshot.diagnostic_groups(None) {
|
|
||||||
let entry = &group.entries[group.primary_ix];
|
|
||||||
collect_diagnostic(text, sections, entry, &snapshot, include_warnings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_diagnostic(
|
|
||||||
text: &mut String,
|
|
||||||
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
|
|
||||||
entry: &DiagnosticEntry<Anchor>,
|
|
||||||
snapshot: &BufferSnapshot,
|
|
||||||
include_warnings: bool,
|
|
||||||
) {
|
|
||||||
const EXCERPT_EXPANSION_SIZE: u32 = 2;
|
|
||||||
const MAX_MESSAGE_LENGTH: usize = 2000;
|
|
||||||
|
|
||||||
let ty = match entry.diagnostic.severity {
|
|
||||||
DiagnosticSeverity::WARNING => {
|
|
||||||
if !include_warnings {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DiagnosticType::Warning
|
|
||||||
}
|
|
||||||
DiagnosticSeverity::ERROR => DiagnosticType::Error,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
let prev_len = text.len();
|
|
||||||
|
|
||||||
let range = entry.range.to_point(snapshot);
|
|
||||||
let diagnostic_row_number = range.start.row + 1;
|
|
||||||
|
|
||||||
let start_row = range.start.row.saturating_sub(EXCERPT_EXPANSION_SIZE);
|
|
||||||
let end_row = (range.end.row + EXCERPT_EXPANSION_SIZE).min(snapshot.max_point().row) + 1;
|
|
||||||
let excerpt_range =
|
|
||||||
Point::new(start_row, 0).to_offset(&snapshot)..Point::new(end_row, 0).to_offset(&snapshot);
|
|
||||||
|
|
||||||
text.push_str("```");
|
|
||||||
if let Some(language_name) = snapshot.language().map(|l| l.code_fence_block_name()) {
|
|
||||||
text.push_str(&language_name);
|
|
||||||
}
|
|
||||||
text.push('\n');
|
|
||||||
|
|
||||||
let mut buffer_text = String::new();
|
|
||||||
for chunk in snapshot.text_for_range(excerpt_range) {
|
|
||||||
buffer_text.push_str(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, line) in buffer_text.lines().enumerate() {
|
|
||||||
let line_number = start_row + i as u32 + 1;
|
|
||||||
writeln!(text, "{}", line).unwrap();
|
|
||||||
|
|
||||||
if line_number == diagnostic_row_number {
|
|
||||||
text.push_str("//");
|
|
||||||
let prev_len = text.len();
|
|
||||||
write!(text, " {}: ", ty.as_str()).unwrap();
|
|
||||||
let padding = text.len() - prev_len;
|
|
||||||
|
|
||||||
let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH)
|
|
||||||
.replace('\n', format!("\n//{:padding$}", "").as_str());
|
|
||||||
|
|
||||||
writeln!(text, "{message}").unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(text, "```").unwrap();
|
|
||||||
sections.push((
|
|
||||||
prev_len..text.len().saturating_sub(1),
|
|
||||||
PlaceholderType::Diagnostic(ty, entry.diagnostic.message.clone()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum PlaceholderType {
|
|
||||||
Root(DiagnosticSummary, Option<String>),
|
|
||||||
File(String),
|
|
||||||
Diagnostic(DiagnosticType, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum DiagnosticType {
|
|
||||||
Warning,
|
|
||||||
Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiagnosticType {
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
DiagnosticType::Warning => "warning",
|
|
||||||
DiagnosticType::Error => "error",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -7,19 +5,12 @@ use anyhow::{anyhow, bail, Context, Result};
|
|||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
use html_to_markdown::convert_html_to_markdown;
|
||||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
|
||||||
enum ContentType {
|
|
||||||
Html,
|
|
||||||
Plaintext,
|
|
||||||
Json,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct FetchSlashCommand;
|
pub(crate) struct FetchSlashCommand;
|
||||||
|
|
||||||
impl FetchSlashCommand {
|
impl FetchSlashCommand {
|
||||||
@@ -46,53 +37,7 @@ impl FetchSlashCommand {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(content_type) = response.headers().get("content-type") else {
|
convert_html_to_markdown(&body[..])
|
||||||
bail!("missing Content-Type header");
|
|
||||||
};
|
|
||||||
let content_type = content_type
|
|
||||||
.to_str()
|
|
||||||
.context("invalid Content-Type header")?;
|
|
||||||
let content_type = match content_type {
|
|
||||||
"text/html" => ContentType::Html,
|
|
||||||
"text/plain" => ContentType::Plaintext,
|
|
||||||
"application/json" => ContentType::Json,
|
|
||||||
_ => ContentType::Html,
|
|
||||||
};
|
|
||||||
|
|
||||||
match content_type {
|
|
||||||
ContentType::Html => {
|
|
||||||
let mut handlers: Vec<TagHandler> = vec![
|
|
||||||
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
|
|
||||||
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::ListHandler)),
|
|
||||||
Rc::new(RefCell::new(markdown::TableHandler::new())),
|
|
||||||
Rc::new(RefCell::new(markdown::StyledTextHandler)),
|
|
||||||
];
|
|
||||||
if url.contains("wikipedia.org") {
|
|
||||||
use html_to_markdown::structure::wikipedia;
|
|
||||||
|
|
||||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaChromeRemover)));
|
|
||||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaInfoboxHandler)));
|
|
||||||
handlers.push(Rc::new(
|
|
||||||
RefCell::new(wikipedia::WikipediaCodeHandler::new()),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
handlers.push(Rc::new(RefCell::new(markdown::CodeHandler)));
|
|
||||||
}
|
|
||||||
|
|
||||||
convert_html_to_markdown(&body[..], &mut handlers)
|
|
||||||
}
|
|
||||||
ContentType::Plaintext => Ok(std::str::from_utf8(&body)?.to_owned()),
|
|
||||||
ContentType::Json => {
|
|
||||||
let json: serde_json::Value = serde_json::from_slice(&body)?;
|
|
||||||
|
|
||||||
Ok(format!(
|
|
||||||
"```json\n{}\n```",
|
|
||||||
serde_json::to_string_pretty(&json)?
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +59,7 @@ impl SlashCommand for FetchSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -153,11 +98,37 @@ impl SlashCommand for FetchSlashCommand {
|
|||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
icon: IconName::AtSign,
|
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||||
label: format!("fetch {}", url).into(),
|
FetchPlaceholder {
|
||||||
|
id,
|
||||||
|
unfold,
|
||||||
|
url: url.clone(),
|
||||||
|
}
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
struct FetchPlaceholder {
|
||||||
|
pub id: ElementId,
|
||||||
|
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||||
|
pub url: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for FetchPlaceholder {
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
let unfold = self.unfold;
|
||||||
|
|
||||||
|
ButtonLike::new(self.id)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
|
.child(Icon::new(IconName::AtSign))
|
||||||
|
.child(Label::new(format!("fetch {url}", url = self.url)))
|
||||||
|
.on_click(move |_, cx| unfold(cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
use super::{diagnostics_command::write_single_file_diagnostics, SlashCommand, SlashCommandOutput};
|
use super::{SlashCommand, SlashCommandOutput};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{AppContext, Model, Task, View, WeakView};
|
use gpui::{AppContext, RenderOnce, SharedString, Task, View, WeakView};
|
||||||
use language::{BufferSnapshot, LineEnding, LspAdapterDelegate};
|
use language::{LineEnding, LspAdapterDelegate};
|
||||||
use project::{PathMatchCandidateSet, Project};
|
use project::PathMatchCandidateSet;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||||
use util::{paths::PathMatcher, ResultExt};
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct FileSlashCommand;
|
pub(crate) struct FileSlashCommand;
|
||||||
@@ -60,7 +58,7 @@ impl FileSlashCommand {
|
|||||||
.root_entry()
|
.root_entry()
|
||||||
.map_or(false, |entry| entry.is_ignored),
|
.map_or(false, |entry| entry.is_ignored),
|
||||||
include_root_name: true,
|
include_root_name: true,
|
||||||
candidates: project::Candidates::Entries,
|
directories_only: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -100,7 +98,7 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakView<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -141,225 +139,88 @@ impl SlashCommand for FileSlashCommand {
|
|||||||
return Task::ready(Err(anyhow!("missing path")));
|
return Task::ready(Err(anyhow!("missing path")));
|
||||||
};
|
};
|
||||||
|
|
||||||
let task = collect_files(workspace.read(cx).project().clone(), argument, cx);
|
let path = PathBuf::from(argument);
|
||||||
|
let abs_path = workspace
|
||||||
|
.read(cx)
|
||||||
|
.visible_worktrees(cx)
|
||||||
|
.find_map(|worktree| {
|
||||||
|
let worktree = worktree.read(cx);
|
||||||
|
let worktree_root_path = Path::new(worktree.root_name());
|
||||||
|
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
|
||||||
|
worktree.absolutize(&relative_path).ok()
|
||||||
|
});
|
||||||
|
|
||||||
|
let Some(abs_path) = abs_path else {
|
||||||
|
return Task::ready(Err(anyhow!("missing path")));
|
||||||
|
};
|
||||||
|
|
||||||
|
let fs = workspace.read(cx).app_state().fs.clone();
|
||||||
|
let argument = argument.to_string();
|
||||||
|
let text = cx.background_executor().spawn(async move {
|
||||||
|
let mut content = fs.load(&abs_path).await?;
|
||||||
|
LineEnding::normalize(&mut content);
|
||||||
|
let mut output = String::with_capacity(argument.len() + content.len() + 9);
|
||||||
|
output.push_str("```");
|
||||||
|
output.push_str(&argument);
|
||||||
|
output.push('\n');
|
||||||
|
output.push_str(&content);
|
||||||
|
if !output.ends_with('\n') {
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
output.push_str("```");
|
||||||
|
anyhow::Ok(output)
|
||||||
|
});
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let (text, ranges) = task.await?;
|
let text = text.await?;
|
||||||
|
let range = 0..text.len();
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: ranges
|
sections: vec![SlashCommandOutputSection {
|
||||||
.into_iter()
|
range,
|
||||||
.map(|(range, path, entry_type)| {
|
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||||
build_entry_output_section(
|
FilePlaceholder {
|
||||||
range,
|
path: Some(path.clone()),
|
||||||
Some(&path),
|
line_range: None,
|
||||||
entry_type == EntryType::Directory,
|
id,
|
||||||
None,
|
unfold,
|
||||||
)
|
}
|
||||||
})
|
.into_any_element()
|
||||||
.collect(),
|
}),
|
||||||
run_commands_in_text: true,
|
}],
|
||||||
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(IntoElement)]
|
||||||
enum EntryType {
|
pub struct FilePlaceholder {
|
||||||
File,
|
pub path: Option<PathBuf>,
|
||||||
Directory,
|
pub line_range: Option<Range<u32>>,
|
||||||
|
pub id: ElementId,
|
||||||
|
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_files(
|
impl RenderOnce for FilePlaceholder {
|
||||||
project: Model<Project>,
|
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||||
glob_input: &str,
|
let unfold = self.unfold;
|
||||||
cx: &mut AppContext,
|
let title = if let Some(path) = self.path.as_ref() {
|
||||||
) -> Task<Result<(String, Vec<(Range<usize>, PathBuf, EntryType)>)>> {
|
SharedString::from(path.to_string_lossy().to_string())
|
||||||
let Ok(matcher) = PathMatcher::new(&[glob_input.to_owned()]) else {
|
} else {
|
||||||
return Task::ready(Err(anyhow!("invalid path")));
|
SharedString::from("untitled")
|
||||||
};
|
};
|
||||||
|
|
||||||
let project_handle = project.downgrade();
|
ButtonLike::new(self.id)
|
||||||
let snapshots = project
|
.style(ButtonStyle::Filled)
|
||||||
.read(cx)
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
.worktrees()
|
.child(Icon::new(IconName::File))
|
||||||
.map(|worktree| worktree.read(cx).snapshot())
|
.child(Label::new(title))
|
||||||
.collect::<Vec<_>>();
|
.when_some(self.line_range, |button, line_range| {
|
||||||
cx.spawn(|mut cx| async move {
|
button.child(Label::new(":")).child(Label::new(format!(
|
||||||
let mut text = String::new();
|
"{}-{}",
|
||||||
let mut ranges = Vec::new();
|
line_range.start, line_range.end
|
||||||
for snapshot in snapshots {
|
)))
|
||||||
let worktree_id = snapshot.id();
|
})
|
||||||
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
|
.on_click(move |_, cx| unfold(cx))
|
||||||
let mut folded_directory_names_stack = Vec::new();
|
|
||||||
let mut is_top_level_directory = true;
|
|
||||||
for entry in snapshot.entries(false, 0) {
|
|
||||||
let mut path_including_worktree_name = PathBuf::new();
|
|
||||||
path_including_worktree_name.push(snapshot.root_name());
|
|
||||||
path_including_worktree_name.push(&entry.path);
|
|
||||||
if !matcher.is_match(&path_including_worktree_name) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some((dir, _, _)) = directory_stack.last() {
|
|
||||||
if entry.path.starts_with(dir) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let (_, entry_name, start) = directory_stack.pop().unwrap();
|
|
||||||
ranges.push((
|
|
||||||
start..text.len().saturating_sub(1),
|
|
||||||
PathBuf::from(entry_name),
|
|
||||||
EntryType::Directory,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let filename = entry
|
|
||||||
.path
|
|
||||||
.file_name()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
if entry.is_dir() {
|
|
||||||
// Auto-fold directories that contain no files
|
|
||||||
let mut child_entries = snapshot.child_entries(&entry.path);
|
|
||||||
if let Some(child) = child_entries.next() {
|
|
||||||
if child_entries.next().is_none() && child.kind.is_dir() {
|
|
||||||
if is_top_level_directory {
|
|
||||||
is_top_level_directory = false;
|
|
||||||
folded_directory_names_stack.push(
|
|
||||||
path_including_worktree_name.to_string_lossy().to_string(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
folded_directory_names_stack.push(filename.to_string());
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Skip empty directories
|
|
||||||
folded_directory_names_stack.clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
|
|
||||||
let entry_start = text.len();
|
|
||||||
if prefix_paths.is_empty() {
|
|
||||||
if is_top_level_directory {
|
|
||||||
text.push_str(&path_including_worktree_name.to_string_lossy());
|
|
||||||
is_top_level_directory = false;
|
|
||||||
} else {
|
|
||||||
text.push_str(&filename);
|
|
||||||
}
|
|
||||||
directory_stack.push((entry.path.clone(), filename, entry_start));
|
|
||||||
} else {
|
|
||||||
let entry_name = format!("{}/{}", prefix_paths, &filename);
|
|
||||||
text.push_str(&entry_name);
|
|
||||||
directory_stack.push((entry.path.clone(), entry_name, entry_start));
|
|
||||||
}
|
|
||||||
text.push('\n');
|
|
||||||
} else if entry.is_file() {
|
|
||||||
let Some(open_buffer_task) = project_handle
|
|
||||||
.update(&mut cx, |project, cx| {
|
|
||||||
project.open_buffer((worktree_id, &entry.path), cx)
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if let Some(buffer) = open_buffer_task.await.log_err() {
|
|
||||||
let snapshot = cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
|
|
||||||
let prev_len = text.len();
|
|
||||||
collect_file_content(&mut text, &snapshot, filename.clone());
|
|
||||||
text.push('\n');
|
|
||||||
if !write_single_file_diagnostics(
|
|
||||||
&mut text,
|
|
||||||
Some(&path_including_worktree_name),
|
|
||||||
&snapshot,
|
|
||||||
) {
|
|
||||||
text.pop();
|
|
||||||
}
|
|
||||||
ranges.push((
|
|
||||||
prev_len..text.len(),
|
|
||||||
PathBuf::from(filename),
|
|
||||||
EntryType::File,
|
|
||||||
));
|
|
||||||
text.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some((dir, _, start)) = directory_stack.pop() {
|
|
||||||
let mut root_path = PathBuf::new();
|
|
||||||
root_path.push(snapshot.root_name());
|
|
||||||
root_path.push(&dir);
|
|
||||||
ranges.push((start..text.len(), root_path, EntryType::Directory));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((text, ranges))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_file_content(buffer: &mut String, snapshot: &BufferSnapshot, filename: String) {
|
|
||||||
let mut content = snapshot.text();
|
|
||||||
LineEnding::normalize(&mut content);
|
|
||||||
buffer.reserve(filename.len() + content.len() + 9);
|
|
||||||
buffer.push_str(&codeblock_fence_for_path(
|
|
||||||
Some(&PathBuf::from(filename)),
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
buffer.push_str(&content);
|
|
||||||
if !buffer.ends_with('\n') {
|
|
||||||
buffer.push('\n');
|
|
||||||
}
|
|
||||||
buffer.push_str("```");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
|
|
||||||
let mut text = String::new();
|
|
||||||
write!(text, "```").unwrap();
|
|
||||||
|
|
||||||
if let Some(path) = path {
|
|
||||||
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
|
||||||
write!(text, "{} ", extension).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(text, "{}", path.display()).unwrap();
|
|
||||||
} else {
|
|
||||||
write!(text, "untitled").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(row_range) = row_range {
|
|
||||||
write!(text, ":{}-{}", row_range.start + 1, row_range.end + 1).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
text.push('\n');
|
|
||||||
text
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_entry_output_section(
|
|
||||||
range: Range<usize>,
|
|
||||||
path: Option<&Path>,
|
|
||||||
is_directory: bool,
|
|
||||||
line_range: Option<Range<u32>>,
|
|
||||||
) -> SlashCommandOutputSection<usize> {
|
|
||||||
let mut label = if let Some(path) = path {
|
|
||||||
path.to_string_lossy().to_string()
|
|
||||||
} else {
|
|
||||||
"untitled".to_string()
|
|
||||||
};
|
|
||||||
if let Some(line_range) = line_range {
|
|
||||||
write!(label, ":{}-{}", line_range.start, line_range.end).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let icon = if is_directory {
|
|
||||||
IconName::Folder
|
|
||||||
} else {
|
|
||||||
IconName::File
|
|
||||||
};
|
|
||||||
|
|
||||||
SlashCommandOutputSection {
|
|
||||||
range,
|
|
||||||
icon,
|
|
||||||
label: label.into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
|
||||||
use chrono::{DateTime, Local};
|
|
||||||
use gpui::{AppContext, Task, WeakView};
|
|
||||||
use language::LspAdapterDelegate;
|
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
pub(crate) struct NowSlashCommand;
|
|
||||||
|
|
||||||
impl SlashCommand for NowSlashCommand {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
"now".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> String {
|
|
||||||
"insert the current date and time".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
|
||||||
"Insert current date and time".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn requires_argument(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_argument(
|
|
||||||
self: Arc<Self>,
|
|
||||||
_query: String,
|
|
||||||
_cancel: Arc<AtomicBool>,
|
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
|
||||||
_cx: &mut AppContext,
|
|
||||||
) -> Task<Result<Vec<String>>> {
|
|
||||||
Task::ready(Ok(Vec::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
self: Arc<Self>,
|
|
||||||
_argument: Option<&str>,
|
|
||||||
_workspace: WeakView<Workspace>,
|
|
||||||
_delegate: Arc<dyn LspAdapterDelegate>,
|
|
||||||
_cx: &mut WindowContext,
|
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
|
||||||
let now = Local::now();
|
|
||||||
let text = format!("Today is {now}.", now = now.to_rfc3339());
|
|
||||||
let range = 0..text.len();
|
|
||||||
|
|
||||||
Task::ready(Ok(SlashCommandOutput {
|
|
||||||
text,
|
|
||||||
sections: vec![SlashCommandOutputSection {
|
|
||||||
range,
|
|
||||||
icon: IconName::CountdownTimer,
|
|
||||||
label: now.to_rfc3339().into(),
|
|
||||||
}],
|
|
||||||
run_commands_in_text: false,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
struct NowPlaceholder {
|
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
pub now: DateTime<Local>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for NowPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::CountdownTimer))
|
|
||||||
.child(Label::new(self.now.to_rfc3339()))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ use std::{
|
|||||||
path::Path,
|
path::Path,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct ProjectSlashCommand;
|
pub(crate) struct ProjectSlashCommand;
|
||||||
@@ -102,7 +102,7 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -138,8 +138,15 @@ impl SlashCommand for ProjectSlashCommand {
|
|||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
icon: IconName::FileTree,
|
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||||
label: "Project".into(),
|
ButtonLike::new(id)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
|
.child(Icon::new(IconName::FileTree))
|
||||||
|
.child(Label::new("Project"))
|
||||||
|
.on_click(move |_, cx| unfold(cx))
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use assistant_slash_command::SlashCommandOutputSection;
|
|||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct PromptSlashCommand;
|
pub(crate) struct PromptSlashCommand;
|
||||||
@@ -28,7 +28,7 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
_cancellation_flag: Arc<AtomicBool>,
|
_cancellation_flag: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -69,20 +69,42 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let mut prompt = prompt.await?;
|
let prompt = prompt.await?;
|
||||||
if prompt.is_empty() {
|
|
||||||
prompt.push('\n');
|
|
||||||
}
|
|
||||||
let range = 0..prompt.len();
|
let range = 0..prompt.len();
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text: prompt,
|
text: prompt,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
icon: IconName::Library,
|
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||||
label: title,
|
PromptPlaceholder {
|
||||||
|
id,
|
||||||
|
unfold,
|
||||||
|
title: title.clone(),
|
||||||
|
}
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct PromptPlaceholder {
|
||||||
|
pub title: SharedString,
|
||||||
|
pub id: ElementId,
|
||||||
|
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for PromptPlaceholder {
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
let unfold = self.unfold;
|
||||||
|
ButtonLike::new(self.id)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
|
.child(Icon::new(IconName::Library))
|
||||||
|
.child(Label::new(self.title))
|
||||||
|
.on_click(move |_, cx| unfold(cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,39 +7,45 @@ use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutp
|
|||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
use gpui::{AppContext, Model, Task, WeakView};
|
use gpui::{AppContext, Model, Task, WeakView};
|
||||||
|
use html_to_markdown::convert_rustdoc_to_markdown;
|
||||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
|
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||||
use ui::prelude::*;
|
|
||||||
use util::{maybe, ResultExt};
|
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum RustdocSource {
|
||||||
|
/// The docs were sourced from local `cargo doc` output.
|
||||||
|
Local,
|
||||||
|
/// The docs were sourced from `docs.rs`.
|
||||||
|
DocsDotRs,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct RustdocSlashCommand;
|
pub(crate) struct RustdocSlashCommand;
|
||||||
|
|
||||||
impl RustdocSlashCommand {
|
impl RustdocSlashCommand {
|
||||||
async fn build_message(
|
async fn build_message(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
http_client: Arc<HttpClientWithUrl>,
|
http_client: Arc<HttpClientWithUrl>,
|
||||||
crate_name: CrateName,
|
crate_name: String,
|
||||||
module_path: Vec<String>,
|
module_path: Vec<String>,
|
||||||
path_to_cargo_toml: Option<&Path>,
|
path_to_cargo_toml: Option<&Path>,
|
||||||
) -> Result<(RustdocSource, String)> {
|
) -> Result<(RustdocSource, String)> {
|
||||||
let cargo_workspace_root = path_to_cargo_toml.and_then(|path| path.parent());
|
let cargo_workspace_root = path_to_cargo_toml.and_then(|path| path.parent());
|
||||||
if let Some(cargo_workspace_root) = cargo_workspace_root {
|
if let Some(cargo_workspace_root) = cargo_workspace_root {
|
||||||
let mut local_cargo_doc_path = cargo_workspace_root.join("target/doc");
|
let mut local_cargo_doc_path = cargo_workspace_root.join("target/doc");
|
||||||
local_cargo_doc_path.push(crate_name.as_ref());
|
local_cargo_doc_path.push(&crate_name);
|
||||||
if !module_path.is_empty() {
|
if !module_path.is_empty() {
|
||||||
local_cargo_doc_path.push(module_path.join("/"));
|
local_cargo_doc_path.push(module_path.join("/"));
|
||||||
}
|
}
|
||||||
local_cargo_doc_path.push("index.html");
|
local_cargo_doc_path.push("index.html");
|
||||||
|
|
||||||
if let Ok(contents) = fs.load(&local_cargo_doc_path).await {
|
if let Ok(contents) = fs.load(&local_cargo_doc_path).await {
|
||||||
let (markdown, _items) = convert_rustdoc_to_markdown(contents.as_bytes())?;
|
return Ok((
|
||||||
|
RustdocSource::Local,
|
||||||
dbg!(_items);
|
convert_rustdoc_to_markdown(contents.as_bytes())?,
|
||||||
|
));
|
||||||
return Ok((RustdocSource::Local, markdown));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +78,10 @@ impl RustdocSlashCommand {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (markdown, _items) = convert_rustdoc_to_markdown(&body[..])?;
|
Ok((
|
||||||
|
RustdocSource::DocsDotRs,
|
||||||
Ok((RustdocSource::DocsDotRs, markdown))
|
convert_rustdoc_to_markdown(&body[..])?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
||||||
@@ -109,42 +116,13 @@ impl SlashCommand for RustdocSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
cx: &mut AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> Task<Result<Vec<String>>> {
|
) -> Task<Result<Vec<String>>> {
|
||||||
let index_provider_deps = maybe!({
|
Task::ready(Ok(Vec::new()))
|
||||||
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
|
|
||||||
let workspace = workspace
|
|
||||||
.upgrade()
|
|
||||||
.ok_or_else(|| anyhow!("workspace was dropped"))?;
|
|
||||||
let project = workspace.read(cx).project().clone();
|
|
||||||
let fs = project.read(cx).fs().clone();
|
|
||||||
let cargo_workspace_root = Self::path_to_cargo_toml(project, cx)
|
|
||||||
.and_then(|path| path.parent().map(|path| path.to_path_buf()))
|
|
||||||
.ok_or_else(|| anyhow!("no Cargo workspace root found"))?;
|
|
||||||
|
|
||||||
anyhow::Ok((fs, cargo_workspace_root))
|
|
||||||
});
|
|
||||||
|
|
||||||
let store = RustdocStore::global(cx);
|
|
||||||
cx.background_executor().spawn(async move {
|
|
||||||
if let Some((crate_name, rest)) = query.split_once(':') {
|
|
||||||
if rest.is_empty() {
|
|
||||||
if let Some((fs, cargo_workspace_root)) = index_provider_deps.log_err() {
|
|
||||||
let provider = Box::new(LocalProvider::new(fs, cargo_workspace_root));
|
|
||||||
// We don't need to hold onto this task, as the `RustdocStore` will hold it
|
|
||||||
// until it completes.
|
|
||||||
let _ = store.clone().index(crate_name.into(), provider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let items = store.search(query).await;
|
|
||||||
Ok(items)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@@ -164,77 +142,91 @@ impl SlashCommand for RustdocSlashCommand {
|
|||||||
let project = workspace.read(cx).project().clone();
|
let project = workspace.read(cx).project().clone();
|
||||||
let fs = project.read(cx).fs().clone();
|
let fs = project.read(cx).fs().clone();
|
||||||
let http_client = workspace.read(cx).client().http_client();
|
let http_client = workspace.read(cx).client().http_client();
|
||||||
let path_to_cargo_toml = Self::path_to_cargo_toml(project, cx);
|
|
||||||
|
|
||||||
let mut path_components = argument.split("::");
|
let mut path_components = argument.split("::");
|
||||||
let crate_name = match path_components
|
let crate_name = match path_components
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| anyhow!("missing crate name"))
|
.ok_or_else(|| anyhow!("missing crate name"))
|
||||||
{
|
{
|
||||||
Ok(crate_name) => CrateName::from(crate_name),
|
Ok(crate_name) => crate_name.to_string(),
|
||||||
Err(err) => return Task::ready(Err(err)),
|
Err(err) => return Task::ready(Err(err)),
|
||||||
};
|
};
|
||||||
let item_path = path_components.map(ToString::to_string).collect::<Vec<_>>();
|
let module_path = path_components.map(ToString::to_string).collect::<Vec<_>>();
|
||||||
|
let path_to_cargo_toml = Self::path_to_cargo_toml(project, cx);
|
||||||
|
|
||||||
let text = cx.background_executor().spawn({
|
let text = cx.background_executor().spawn({
|
||||||
let rustdoc_store = RustdocStore::global(cx);
|
|
||||||
let crate_name = crate_name.clone();
|
let crate_name = crate_name.clone();
|
||||||
let item_path = item_path.clone();
|
let module_path = module_path.clone();
|
||||||
async move {
|
async move {
|
||||||
let item_docs = rustdoc_store
|
Self::build_message(
|
||||||
.load(
|
fs,
|
||||||
crate_name.clone(),
|
http_client,
|
||||||
if item_path.is_empty() {
|
crate_name,
|
||||||
None
|
module_path,
|
||||||
} else {
|
path_to_cargo_toml.as_deref(),
|
||||||
Some(item_path.join("::"))
|
)
|
||||||
},
|
.await
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Ok(item_docs) = item_docs {
|
|
||||||
anyhow::Ok((RustdocSource::Index, item_docs.docs().to_owned()))
|
|
||||||
} else {
|
|
||||||
Self::build_message(
|
|
||||||
fs,
|
|
||||||
http_client,
|
|
||||||
crate_name,
|
|
||||||
item_path,
|
|
||||||
path_to_cargo_toml.as_deref(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let module_path = if item_path.is_empty() {
|
let crate_name = SharedString::from(crate_name);
|
||||||
|
let module_path = if module_path.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(SharedString::from(item_path.join("::")))
|
Some(SharedString::from(module_path.join("::")))
|
||||||
};
|
};
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let (source, text) = text.await?;
|
let (source, text) = text.await?;
|
||||||
let range = 0..text.len();
|
let range = 0..text.len();
|
||||||
let crate_path = module_path
|
|
||||||
.map(|module_path| format!("{}::{}", crate_name, module_path))
|
|
||||||
.unwrap_or_else(|| crate_name.to_string());
|
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
icon: IconName::FileRust,
|
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||||
label: format!(
|
RustdocPlaceholder {
|
||||||
"rustdoc ({source}): {crate_path}",
|
id,
|
||||||
source = match source {
|
unfold,
|
||||||
RustdocSource::Index => "index",
|
source,
|
||||||
RustdocSource::Local => "local",
|
crate_name: crate_name.clone(),
|
||||||
RustdocSource::DocsDotRs => "docs.rs",
|
module_path: module_path.clone(),
|
||||||
}
|
}
|
||||||
)
|
.into_any_element()
|
||||||
.into(),
|
}),
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
struct RustdocPlaceholder {
|
||||||
|
pub id: ElementId,
|
||||||
|
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||||
|
pub source: RustdocSource,
|
||||||
|
pub crate_name: SharedString,
|
||||||
|
pub module_path: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for RustdocPlaceholder {
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
let unfold = self.unfold;
|
||||||
|
|
||||||
|
let crate_path = self
|
||||||
|
.module_path
|
||||||
|
.map(|module_path| format!("{crate_name}::{module_path}", crate_name = self.crate_name))
|
||||||
|
.unwrap_or(self.crate_name.to_string());
|
||||||
|
|
||||||
|
ButtonLike::new(self.id)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
|
.child(Icon::new(IconName::FileRust))
|
||||||
|
.child(Label::new(format!(
|
||||||
|
"rustdoc ({source}): {crate_path}",
|
||||||
|
source = match self.source {
|
||||||
|
RustdocSource::Local => "local",
|
||||||
|
RustdocSource::DocsDotRs => "docs.rs",
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
.on_click(move |_, cx| unfold(cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
use super::{
|
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
||||||
create_label_for_command,
|
|
||||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
|
||||||
SlashCommand, SlashCommandOutput,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
|
use language::{CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
|
||||||
use semantic_index::SemanticIndex;
|
use semantic_index::SemanticIndex;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, IconName};
|
use ui::{prelude::*, ButtonLike, ElevationIndex, Icon, IconName};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
@@ -25,7 +21,14 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn label(&self, cx: &AppContext) -> CodeLabel {
|
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||||
create_label_for_command("search", &["--n"], cx)
|
let mut label = CodeLabel::default();
|
||||||
|
label.push_str("search ", None);
|
||||||
|
label.push_str(
|
||||||
|
"--n",
|
||||||
|
cx.theme().syntax().highlight_id("comment").map(HighlightId),
|
||||||
|
);
|
||||||
|
label.filter_range = 0.."search".len();
|
||||||
|
label
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> String {
|
||||||
@@ -41,7 +44,7 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<AtomicBool>,
|
_cancel: Arc<AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -122,8 +125,9 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
let range_start = result.range.start.min(file_content.len());
|
let range_start = result.range.start.min(file_content.len());
|
||||||
let range_end = result.range.end.min(file_content.len());
|
let range_end = result.range.end.min(file_content.len());
|
||||||
|
|
||||||
let start_row = file_content[0..range_start].matches('\n').count() as u32;
|
let start_line =
|
||||||
let end_row = file_content[0..range_end].matches('\n').count() as u32;
|
file_content[0..range_start].matches('\n').count() as u32 + 1;
|
||||||
|
let end_line = file_content[0..range_end].matches('\n').count() as u32 + 1;
|
||||||
let start_line_byte_offset = file_content[0..range_start]
|
let start_line_byte_offset = file_content[0..range_start]
|
||||||
.rfind('\n')
|
.rfind('\n')
|
||||||
.map(|pos| pos + 1)
|
.map(|pos| pos + 1)
|
||||||
@@ -134,30 +138,47 @@ impl SlashCommand for SearchSlashCommand {
|
|||||||
.unwrap_or_else(|| file_content.len());
|
.unwrap_or_else(|| file_content.len());
|
||||||
|
|
||||||
let section_start_ix = text.len();
|
let section_start_ix = text.len();
|
||||||
text.push_str(&codeblock_fence_for_path(
|
writeln!(
|
||||||
Some(&result.path),
|
text,
|
||||||
Some(start_row..end_row),
|
"```{}:{}-{}",
|
||||||
));
|
result.path.display(),
|
||||||
|
start_line,
|
||||||
|
end_line,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let mut excerpt =
|
let mut excerpt =
|
||||||
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
||||||
LineEnding::normalize(&mut excerpt);
|
LineEnding::normalize(&mut excerpt);
|
||||||
text.push_str(&excerpt);
|
text.push_str(&excerpt);
|
||||||
writeln!(text, "\n```\n").unwrap();
|
writeln!(text, "\n```\n").unwrap();
|
||||||
let section_end_ix = text.len() - 1;
|
let section_end_ix = text.len() - 1;
|
||||||
sections.push(build_entry_output_section(
|
|
||||||
section_start_ix..section_end_ix,
|
sections.push(SlashCommandOutputSection {
|
||||||
Some(&full_path),
|
range: section_start_ix..section_end_ix,
|
||||||
false,
|
render_placeholder: Arc::new(move |id, unfold, _| {
|
||||||
Some(start_row + 1..end_row + 1),
|
FilePlaceholder {
|
||||||
));
|
id,
|
||||||
|
path: Some(full_path.clone()),
|
||||||
|
line_range: Some(start_line..end_line),
|
||||||
|
unfold,
|
||||||
|
}
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = SharedString::from(query);
|
let query = SharedString::from(query);
|
||||||
sections.push(SlashCommandOutputSection {
|
sections.push(SlashCommandOutputSection {
|
||||||
range: 0..text.len(),
|
range: 0..text.len(),
|
||||||
icon: IconName::MagnifyingGlass,
|
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||||
label: query,
|
ButtonLike::new(id)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
|
.child(Icon::new(IconName::MagnifyingGlass))
|
||||||
|
.child(Label::new(query.clone()))
|
||||||
|
.on_click(move |_, cx| unfold(cx))
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
SlashCommandOutput {
|
SlashCommandOutput {
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
use super::{
|
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
||||||
diagnostics_command::write_single_file_diagnostics,
|
|
||||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
|
||||||
SlashCommand, SlashCommandOutput,
|
|
||||||
};
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{AppContext, Entity, Task, WeakView};
|
use gpui::{AppContext, Entity, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::{fmt::Write, sync::Arc};
|
use std::{fmt::Write, path::Path, sync::Arc};
|
||||||
use ui::WindowContext;
|
use ui::{IntoElement, WindowContext};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct TabsSlashCommand;
|
pub(crate) struct TabsSlashCommand;
|
||||||
@@ -32,7 +29,7 @@ impl SlashCommand for TabsSlashCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
_query: String,
|
_query: String,
|
||||||
_cancel: Arc<std::sync::atomic::AtomicBool>,
|
_cancel: Arc<std::sync::atomic::AtomicBool>,
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -78,37 +75,44 @@ impl SlashCommand for TabsSlashCommand {
|
|||||||
|
|
||||||
let mut sections = Vec::new();
|
let mut sections = Vec::new();
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut has_diagnostics = false;
|
|
||||||
for (full_path, buffer, _) in open_buffers {
|
for (full_path, buffer, _) in open_buffers {
|
||||||
let section_start_ix = text.len();
|
let section_start_ix = text.len();
|
||||||
text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
|
writeln!(
|
||||||
|
text,
|
||||||
|
"```{}\n",
|
||||||
|
full_path
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(Path::new("untitled"))
|
||||||
|
.display()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
for chunk in buffer.as_rope().chunks() {
|
for chunk in buffer.as_rope().chunks() {
|
||||||
text.push_str(chunk);
|
text.push_str(chunk);
|
||||||
}
|
}
|
||||||
if !text.ends_with('\n') {
|
if !text.ends_with('\n') {
|
||||||
text.push('\n');
|
text.push('\n');
|
||||||
}
|
}
|
||||||
writeln!(text, "```").unwrap();
|
writeln!(text, "```\n").unwrap();
|
||||||
if write_single_file_diagnostics(&mut text, full_path.as_deref(), &buffer) {
|
|
||||||
has_diagnostics = true;
|
|
||||||
}
|
|
||||||
if !text.ends_with('\n') {
|
|
||||||
text.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
let section_end_ix = text.len() - 1;
|
let section_end_ix = text.len() - 1;
|
||||||
sections.push(build_entry_output_section(
|
|
||||||
section_start_ix..section_end_ix,
|
sections.push(SlashCommandOutputSection {
|
||||||
full_path.as_deref(),
|
range: section_start_ix..section_end_ix,
|
||||||
false,
|
render_placeholder: Arc::new(move |id, unfold, _| {
|
||||||
None,
|
FilePlaceholder {
|
||||||
));
|
id,
|
||||||
|
path: full_path.clone(),
|
||||||
|
line_range: None,
|
||||||
|
unfold,
|
||||||
|
}
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections,
|
sections,
|
||||||
run_commands_in_text: has_diagnostics,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Err(error) => Task::ready(Err(error)),
|
Err(error) => Task::ready(Err(error)),
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
|
||||||
use gpui::{AppContext, Task, WeakView};
|
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
|
||||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
|
||||||
use ui::prelude::*;
|
|
||||||
use workspace::Workspace;
|
|
||||||
|
|
||||||
use super::create_label_for_command;
|
|
||||||
|
|
||||||
pub(crate) struct TermSlashCommand;
|
|
||||||
|
|
||||||
const LINE_COUNT_ARG: &str = "--line-count";
|
|
||||||
|
|
||||||
impl SlashCommand for TermSlashCommand {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
"term".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn label(&self, cx: &AppContext) -> CodeLabel {
|
|
||||||
create_label_for_command("term", &[LINE_COUNT_ARG], cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> String {
|
|
||||||
"insert terminal output".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn menu_text(&self) -> String {
|
|
||||||
"Insert terminal output".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn requires_argument(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_argument(
|
|
||||||
self: Arc<Self>,
|
|
||||||
_query: String,
|
|
||||||
_cancel: Arc<AtomicBool>,
|
|
||||||
_workspace: Option<WeakView<Workspace>>,
|
|
||||||
_cx: &mut AppContext,
|
|
||||||
) -> Task<Result<Vec<String>>> {
|
|
||||||
Task::ready(Ok(vec![LINE_COUNT_ARG.to_string()]))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
self: Arc<Self>,
|
|
||||||
argument: Option<&str>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
_delegate: Arc<dyn LspAdapterDelegate>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
|
||||||
let Some(workspace) = workspace.upgrade() else {
|
|
||||||
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
|
||||||
};
|
|
||||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
|
||||||
return Task::ready(Err(anyhow::anyhow!("no terminal panel open")));
|
|
||||||
};
|
|
||||||
let Some(active_terminal) = terminal_panel
|
|
||||||
.read(cx)
|
|
||||||
.pane()
|
|
||||||
.read(cx)
|
|
||||||
.active_item()
|
|
||||||
.and_then(|t| t.downcast::<TerminalView>())
|
|
||||||
else {
|
|
||||||
return Task::ready(Err(anyhow::anyhow!("no active terminal")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let line_count = argument.and_then(|a| parse_argument(a)).unwrap_or(20);
|
|
||||||
|
|
||||||
let lines = active_terminal
|
|
||||||
.read(cx)
|
|
||||||
.model()
|
|
||||||
.read(cx)
|
|
||||||
.last_n_non_empty_lines(line_count);
|
|
||||||
|
|
||||||
let mut text = String::new();
|
|
||||||
text.push_str("Terminal output:\n");
|
|
||||||
text.push_str(&lines.join("\n"));
|
|
||||||
let range = 0..text.len();
|
|
||||||
|
|
||||||
Task::ready(Ok(SlashCommandOutput {
|
|
||||||
text,
|
|
||||||
sections: vec![SlashCommandOutputSection {
|
|
||||||
range,
|
|
||||||
icon: IconName::Terminal,
|
|
||||||
label: "Terminal".into(),
|
|
||||||
}],
|
|
||||||
run_commands_in_text: false,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_argument(argument: &str) -> Option<usize> {
|
|
||||||
let mut args = argument.split(' ');
|
|
||||||
if args.next() == Some(LINE_COUNT_ARG) {
|
|
||||||
if let Some(line_count) = args.next().and_then(|s| s.parse::<usize>().ok()) {
|
|
||||||
return Some(line_count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
@@ -18,5 +18,4 @@ derive_more.workspace = true
|
|||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
serde.workspace = true
|
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
mod slash_command_registry;
|
mod slash_command_registry;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
use gpui::{AnyElement, AppContext, ElementId, Task, WeakView, WindowContext};
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
pub use slash_command_registry::*;
|
pub use slash_command_registry::*;
|
||||||
use std::{
|
use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use workspace::{ui::IconName, Workspace};
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
SlashCommandRegistry::default_global(cx);
|
SlashCommandRegistry::default_global(cx);
|
||||||
@@ -23,7 +22,7 @@ pub trait SlashCommand: 'static + Send + Sync {
|
|||||||
fn description(&self) -> String;
|
fn description(&self) -> String;
|
||||||
fn menu_text(&self) -> String;
|
fn menu_text(&self) -> String;
|
||||||
fn complete_argument(
|
fn complete_argument(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
cancel: Arc<AtomicBool>,
|
cancel: Arc<AtomicBool>,
|
||||||
workspace: Option<WeakView<Workspace>>,
|
workspace: Option<WeakView<Workspace>>,
|
||||||
@@ -50,16 +49,14 @@ pub type RenderFoldPlaceholder = Arc<
|
|||||||
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct SlashCommandOutput {
|
pub struct SlashCommandOutput {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
pub sections: Vec<SlashCommandOutputSection<usize>>,
|
||||||
pub run_commands_in_text: bool,
|
pub run_commands_in_text: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone)]
|
||||||
pub struct SlashCommandOutputSection<T> {
|
pub struct SlashCommandOutputSection<T> {
|
||||||
pub range: Range<T>,
|
pub range: Range<T>,
|
||||||
pub icon: IconName,
|
pub render_placeholder: RenderFoldPlaceholder,
|
||||||
pub label: SharedString,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,13 +141,8 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
|||||||
let auto_updater = cx.new_model(|cx| {
|
let auto_updater = cx.new_model(|cx| {
|
||||||
let updater = AutoUpdater::new(version, http_client);
|
let updater = AutoUpdater::new(version, http_client);
|
||||||
|
|
||||||
let poll_for_updates = ReleaseChannel::try_global(cx)
|
|
||||||
.map(|channel| channel.poll_for_updates())
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if option_env!("ZED_UPDATE_EXPLANATION").is_none()
|
if option_env!("ZED_UPDATE_EXPLANATION").is_none()
|
||||||
&& env::var("ZED_UPDATE_EXPLANATION").is_err()
|
&& env::var("ZED_UPDATE_EXPLANATION").is_err()
|
||||||
&& poll_for_updates
|
|
||||||
{
|
{
|
||||||
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
||||||
.0
|
.0
|
||||||
@@ -191,13 +186,6 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ReleaseChannel::try_global(cx)
|
|
||||||
.map(|channel| channel.poll_for_updates())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(updater) = AutoUpdater::get(cx) {
|
if let Some(updater) = AutoUpdater::get(cx) {
|
||||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -86,16 +86,10 @@ impl Render for Breadcrumbs {
|
|||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.on_click(move |_, cx| {
|
.on_click(move |_, cx| {
|
||||||
if let Some(editor) = editor.upgrade() {
|
if let Some(editor) = editor.upgrade() {
|
||||||
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
outline::toggle(editor, &outline::Toggle, cx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.tooltip(|cx| {
|
.tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
|
||||||
Tooltip::for_action(
|
|
||||||
"Show symbol outline",
|
|
||||||
&editor::actions::ToggleOutline,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
None => element
|
None => element
|
||||||
// Match the height of the `ButtonLike` in the other arm.
|
// Match the height of the `ButtonLike` in the other arm.
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ path = "src/call.rs"
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
no-webrtc = ["live_kit_client/no-webrtc"]
|
|
||||||
test-support = [
|
test-support = [
|
||||||
"client/test-support",
|
"client/test-support",
|
||||||
"collections/test-support",
|
"collections/test-support",
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ impl ActiveCall {
|
|||||||
async fn handle_incoming_call(
|
async fn handle_incoming_call(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
@@ -141,6 +142,7 @@ impl ActiveCall {
|
|||||||
async fn handle_call_canceled(
|
async fn handle_call_canceled(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::CallCanceled>,
|
envelope: TypedEnvelope<proto::CallCanceled>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
|
|||||||
@@ -697,6 +697,7 @@ impl Room {
|
|||||||
async fn handle_room_updated(
|
async fn handle_room_updated(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::RoomUpdated>,
|
envelope: TypedEnvelope<proto::RoomUpdated>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let room = envelope
|
let room = envelope
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ impl ChannelBuffer {
|
|||||||
async fn handle_update_channel_buffer(
|
async fn handle_update_channel_buffer(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ops = update_channel_buffer
|
let ops = update_channel_buffer
|
||||||
@@ -159,6 +160,7 @@ impl ChannelBuffer {
|
|||||||
async fn handle_update_channel_buffer_collaborators(
|
async fn handle_update_channel_buffer_collaborators(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
|
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
|||||||
@@ -528,6 +528,7 @@ impl ChannelChat {
|
|||||||
async fn handle_message_sent(
|
async fn handle_message_sent(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::ChannelMessageSent>,
|
message: TypedEnvelope<proto::ChannelMessageSent>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
@@ -552,6 +553,7 @@ impl ChannelChat {
|
|||||||
async fn handle_message_removed(
|
async fn handle_message_removed(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::RemoveChannelMessage>,
|
message: TypedEnvelope<proto::RemoveChannelMessage>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
@@ -563,6 +565,7 @@ impl ChannelChat {
|
|||||||
async fn handle_message_updated(
|
async fn handle_message_updated(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::ChannelMessageUpdate>,
|
message: TypedEnvelope<proto::ChannelMessageUpdate>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
|
|||||||
@@ -888,6 +888,7 @@ impl ChannelStore {
|
|||||||
async fn handle_update_channels(
|
async fn handle_update_channels(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateChannels>,
|
message: TypedEnvelope<proto::UpdateChannels>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
@@ -901,6 +902,7 @@ impl ChannelStore {
|
|||||||
async fn handle_update_user_channels(
|
async fn handle_update_user_channels(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateUserChannels>,
|
message: TypedEnvelope<proto::UpdateUserChannels>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::channel_chat::ChannelChatEvent;
|
|||||||
use super::*;
|
use super::*;
|
||||||
use client::{test::FakeServer, Client, UserStore};
|
use client::{test::FakeServer, Client, UserStore};
|
||||||
use clock::FakeSystemClock;
|
use clock::FakeSystemClock;
|
||||||
use gpui::{AppContext, Context, Model, SemanticVersion, TestAppContext};
|
use gpui::{AppContext, Context, Model, TestAppContext};
|
||||||
use http::FakeHttpClient;
|
use http::FakeHttpClient;
|
||||||
use rpc::proto::{self};
|
use rpc::proto::{self};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
@@ -340,7 +340,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
|||||||
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
cx.set_global(settings_store);
|
cx.set_global(settings_store);
|
||||||
release_channel::init(SemanticVersion::default(), cx);
|
release_channel::init("0.0.0", cx);
|
||||||
client::init_settings(cx);
|
client::init_settings(cx);
|
||||||
|
|
||||||
let clock = Arc::new(FakeSystemClock::default());
|
let clock = Arc::new(FakeSystemClock::default());
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ path = "src/main.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
|
libc.workspace = true
|
||||||
ipc-channel = "0.18"
|
ipc-channel = "0.18"
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
parking_lot.workspace = true
|
|
||||||
paths.workspace = true
|
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
|||||||
@@ -3,12 +3,10 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fs, io,
|
env, fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::ExitStatus,
|
process::ExitStatus,
|
||||||
sync::Arc,
|
|
||||||
thread::{self, JoinHandle},
|
thread::{self, JoinHandle},
|
||||||
};
|
};
|
||||||
use util::paths::PathLikeWithPosition;
|
use util::paths::PathLikeWithPosition;
|
||||||
@@ -56,7 +54,7 @@ struct Args {
|
|||||||
fn parse_path_with_position(
|
fn parse_path_with_position(
|
||||||
argument_str: &str,
|
argument_str: &str,
|
||||||
) -> Result<PathLikeWithPosition<PathBuf>, std::convert::Infallible> {
|
) -> Result<PathLikeWithPosition<PathBuf>, std::convert::Infallible> {
|
||||||
PathLikeWithPosition::parse_str(argument_str, |_, path_str| {
|
PathLikeWithPosition::parse_str(argument_str, |path_str| {
|
||||||
Ok(Path::new(path_str).to_path_buf())
|
Ok(Path::new(path_str).to_path_buf())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -125,34 +123,26 @@ fn main() -> Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let exit_status = Arc::new(Mutex::new(None));
|
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn(move || {
|
||||||
|
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
||||||
|
let (tx, rx) = (handshake.requests, handshake.responses);
|
||||||
|
tx.send(CliRequest::Open {
|
||||||
|
paths,
|
||||||
|
wait: args.wait,
|
||||||
|
open_new_workspace,
|
||||||
|
dev_server_token: args.dev_server_token,
|
||||||
|
})?;
|
||||||
|
|
||||||
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
|
while let Ok(response) = rx.recv() {
|
||||||
let exit_status = exit_status.clone();
|
match response {
|
||||||
move || {
|
CliResponse::Ping => {}
|
||||||
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
CliResponse::Stdout { message } => println!("{message}"),
|
||||||
let (tx, rx) = (handshake.requests, handshake.responses);
|
CliResponse::Stderr { message } => eprintln!("{message}"),
|
||||||
tx.send(CliRequest::Open {
|
CliResponse::Exit { status } => std::process::exit(status),
|
||||||
paths,
|
|
||||||
wait: args.wait,
|
|
||||||
open_new_workspace,
|
|
||||||
dev_server_token: args.dev_server_token,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
while let Ok(response) = rx.recv() {
|
|
||||||
match response {
|
|
||||||
CliResponse::Ping => {}
|
|
||||||
CliResponse::Stdout { message } => println!("{message}"),
|
|
||||||
CliResponse::Stderr { message } => eprintln!("{message}"),
|
|
||||||
CliResponse::Exit { status } => {
|
|
||||||
exit_status.lock().replace(status);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
if args.foreground {
|
if args.foreground {
|
||||||
@@ -162,9 +152,6 @@ fn main() -> Result<()> {
|
|||||||
sender.join().unwrap()?;
|
sender.join().unwrap()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(exit_status) = exit_status.lock().take() {
|
|
||||||
std::process::exit(exit_status);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +161,10 @@ mod linux {
|
|||||||
env,
|
env,
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
io,
|
io,
|
||||||
os::unix::net::{SocketAddr, UnixDatagram},
|
os::{
|
||||||
|
linux::net::SocketAddrExt,
|
||||||
|
unix::net::{SocketAddr, UnixDatagram},
|
||||||
|
},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{self, ExitStatus},
|
process::{self, ExitStatus},
|
||||||
thread,
|
thread,
|
||||||
@@ -233,9 +223,12 @@ mod linux {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
||||||
let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL));
|
let uid: u32 = unsafe { libc::getuid() };
|
||||||
|
let sock_addr =
|
||||||
|
SocketAddr::from_abstract_name(format!("zed-{}-{}", *RELEASE_CHANNEL, uid))?;
|
||||||
|
|
||||||
let sock = UnixDatagram::unbound()?;
|
let sock = UnixDatagram::unbound()?;
|
||||||
if sock.connect(&sock_path).is_err() {
|
if sock.connect_addr(&sock_addr).is_err() {
|
||||||
self.boot_background(ipc_url)?;
|
self.boot_background(ipc_url)?;
|
||||||
} else {
|
} else {
|
||||||
sock.send(ipc_url.as_bytes())?;
|
sock.send(ipc_url.as_bytes())?;
|
||||||
|
|||||||
@@ -19,18 +19,17 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-recursion = "0.3"
|
async-recursion = "0.3"
|
||||||
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
|
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
|
||||||
|
async-native-tls = { version = "0.5.0", features = ["vendored"] }
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
feature_flags.workspace = true
|
feature_flags.workspace = true
|
||||||
fs.workspace = true
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
http.workspace = true
|
http.workspace = true
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
paths.workspace = true
|
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
@@ -51,7 +50,6 @@ time.workspace = true
|
|||||||
tiny_http = "0.8"
|
tiny_http = "0.8"
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
worktree.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clock = { workspace = true, features = ["test-support"] }
|
clock = { workspace = true, features = ["test-support"] }
|
||||||
@@ -62,10 +60,9 @@ settings = { workspace = true, features = ["test-support"] }
|
|||||||
util = { workspace = true, features = ["test-support"] }
|
util = { workspace = true, features = ["test-support"] }
|
||||||
http = { workspace = true, features = ["test-support"] }
|
http = { workspace = true, features = ["test-support"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
windows.workspace = true
|
async-native-tls = {"version" = "0.5.0", features = ["vendored"]}
|
||||||
|
# This is an indirect dependency of async-tungstenite that is included
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
# here so we can vendor libssl with the feature flag.
|
||||||
cocoa.workspace = true
|
[package.metadata.cargo-machete]
|
||||||
isahc = { workspace = true, features = ["static-curl"] }
|
ignored = ["async-native-tls"]
|
||||||
async-native-tls = { version = "0.5.0", features = ["vendored"] }
|
|
||||||
|
|||||||
@@ -509,7 +509,7 @@ impl Client {
|
|||||||
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
|
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
|
||||||
if use_zed_development_auth {
|
if use_zed_development_auth {
|
||||||
Arc::new(DevelopmentCredentialsProvider {
|
Arc::new(DevelopmentCredentialsProvider {
|
||||||
path: paths::config_dir().join("development_auth"),
|
path: util::paths::CONFIG_DIR.join("development_auth"),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Arc::new(KeychainCredentialsProvider)
|
Arc::new(KeychainCredentialsProvider)
|
||||||
@@ -689,22 +689,6 @@ impl Client {
|
|||||||
entity: WeakModel<E>,
|
entity: WeakModel<E>,
|
||||||
handler: H,
|
handler: H,
|
||||||
) -> Subscription
|
) -> Subscription
|
||||||
where
|
|
||||||
M: EnvelopedMessage,
|
|
||||||
E: 'static,
|
|
||||||
H: 'static + Sync + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
|
|
||||||
F: 'static + Future<Output = Result<()>>,
|
|
||||||
{
|
|
||||||
self.add_message_handler_impl(entity, move |model, message, _, cx| {
|
|
||||||
handler(model, message, cx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_message_handler_impl<M, E, H, F>(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
entity: WeakModel<E>,
|
|
||||||
handler: H,
|
|
||||||
) -> Subscription
|
|
||||||
where
|
where
|
||||||
M: EnvelopedMessage,
|
M: EnvelopedMessage,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
@@ -753,11 +737,19 @@ impl Client {
|
|||||||
where
|
where
|
||||||
M: RequestMessage,
|
M: RequestMessage,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
H: 'static + Sync + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
|
H: 'static
|
||||||
|
+ Sync
|
||||||
|
+ Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
F: 'static + Future<Output = Result<M::Response>>,
|
F: 'static + Future<Output = Result<M::Response>>,
|
||||||
{
|
{
|
||||||
self.add_message_handler_impl(model, move |handle, envelope, this, cx| {
|
self.add_message_handler(model, move |handle, envelope, this, cx| {
|
||||||
Self::respond_to_request(envelope.receipt(), handler(handle, envelope, cx), this)
|
Self::respond_to_request(
|
||||||
|
envelope.receipt(),
|
||||||
|
handler(handle, envelope, this.clone(), cx),
|
||||||
|
this,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,11 +757,11 @@ impl Client {
|
|||||||
where
|
where
|
||||||
M: EntityMessage,
|
M: EntityMessage,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
H: 'static + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
|
H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
|
||||||
F: 'static + Future<Output = Result<()>>,
|
F: 'static + Future<Output = Result<()>>,
|
||||||
{
|
{
|
||||||
self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, _, cx| {
|
self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
|
||||||
handler(subscriber.downcast::<E>().unwrap(), message, cx)
|
handler(subscriber.downcast::<E>().unwrap(), message, client, cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,13 +808,13 @@ impl Client {
|
|||||||
where
|
where
|
||||||
M: EntityMessage + RequestMessage,
|
M: EntityMessage + RequestMessage,
|
||||||
E: 'static,
|
E: 'static,
|
||||||
H: 'static + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
|
H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
|
||||||
F: 'static + Future<Output = Result<M::Response>>,
|
F: 'static + Future<Output = Result<M::Response>>,
|
||||||
{
|
{
|
||||||
self.add_entity_message_handler::<M, E, _, _>(move |entity, envelope, client, cx| {
|
self.add_model_message_handler(move |entity, envelope, client, cx| {
|
||||||
Self::respond_to_request::<M, _>(
|
Self::respond_to_request::<M, _>(
|
||||||
envelope.receipt(),
|
envelope.receipt(),
|
||||||
handler(entity.downcast::<E>().unwrap(), envelope, cx),
|
handler(entity, envelope, client.clone(), cx),
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -1437,31 +1429,6 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_dynamic(
|
|
||||||
&self,
|
|
||||||
envelope: proto::Envelope,
|
|
||||||
request_type: &'static str,
|
|
||||||
) -> impl Future<Output = Result<proto::Envelope>> {
|
|
||||||
let client_id = self.id();
|
|
||||||
log::debug!(
|
|
||||||
"rpc request start. client_id:{}. name:{}",
|
|
||||||
client_id,
|
|
||||||
request_type
|
|
||||||
);
|
|
||||||
let response = self
|
|
||||||
.connection_id()
|
|
||||||
.map(|conn_id| self.peer.request_dynamic(conn_id, envelope, request_type));
|
|
||||||
async move {
|
|
||||||
let response = response?.await;
|
|
||||||
log::debug!(
|
|
||||||
"rpc request finish. client_id:{}. name:{}",
|
|
||||||
client_id,
|
|
||||||
request_type
|
|
||||||
);
|
|
||||||
Ok(response?.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn respond<T: RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) -> Result<()> {
|
fn respond<T: RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) -> Result<()> {
|
||||||
log::debug!("rpc respond. client_id:{}. name:{}", self.id(), T::NAME);
|
log::debug!("rpc respond. client_id:{}. name:{}", self.id(), T::NAME);
|
||||||
self.peer.respond(receipt, response)
|
self.peer.respond(receipt, response)
|
||||||
@@ -1737,7 +1704,6 @@ mod tests {
|
|||||||
use gpui::{BackgroundExecutor, Context, TestAppContext};
|
use gpui::{BackgroundExecutor, Context, TestAppContext};
|
||||||
use http::FakeHttpClient;
|
use http::FakeHttpClient;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use proto::TypedEnvelope;
|
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::future;
|
use std::future;
|
||||||
|
|
||||||
@@ -1920,7 +1886,7 @@ mod tests {
|
|||||||
let (done_tx1, mut done_rx1) = smol::channel::unbounded();
|
let (done_tx1, mut done_rx1) = smol::channel::unbounded();
|
||||||
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
||||||
client.add_model_message_handler(
|
client.add_model_message_handler(
|
||||||
move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, mut cx| {
|
move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
|
||||||
match model.update(&mut cx, |model, _| model.id).unwrap() {
|
match model.update(&mut cx, |model, _| model.id).unwrap() {
|
||||||
1 => done_tx1.try_send(()).unwrap(),
|
1 => done_tx1.try_send(()).unwrap(),
|
||||||
2 => done_tx2.try_send(()).unwrap(),
|
2 => done_tx2.try_send(()).unwrap(),
|
||||||
@@ -1982,7 +1948,7 @@ mod tests {
|
|||||||
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
|
||||||
let subscription1 = client.add_message_handler(
|
let subscription1 = client.add_message_handler(
|
||||||
model.downgrade(),
|
model.downgrade(),
|
||||||
move |_, _: TypedEnvelope<proto::Ping>, _| {
|
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
|
||||||
done_tx1.try_send(()).unwrap();
|
done_tx1.try_send(()).unwrap();
|
||||||
async { Ok(()) }
|
async { Ok(()) }
|
||||||
},
|
},
|
||||||
@@ -1990,7 +1956,7 @@ mod tests {
|
|||||||
drop(subscription1);
|
drop(subscription1);
|
||||||
let _subscription2 = client.add_message_handler(
|
let _subscription2 = client.add_message_handler(
|
||||||
model.downgrade(),
|
model.downgrade(),
|
||||||
move |_, _: TypedEnvelope<proto::Ping>, _| {
|
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
|
||||||
done_tx2.try_send(()).unwrap();
|
done_tx2.try_send(()).unwrap();
|
||||||
async { Ok(()) }
|
async { Ok(()) }
|
||||||
},
|
},
|
||||||
@@ -2016,7 +1982,7 @@ mod tests {
|
|||||||
let (done_tx, mut done_rx) = smol::channel::unbounded();
|
let (done_tx, mut done_rx) = smol::channel::unbounded();
|
||||||
let subscription = client.add_message_handler(
|
let subscription = client.add_message_handler(
|
||||||
model.clone().downgrade(),
|
model.clone().downgrade(),
|
||||||
move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, mut cx| {
|
move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
|
||||||
model
|
model
|
||||||
.update(&mut cx, |model, _| model.subscription.take())
|
.update(&mut cx, |model, _| model.subscription.take())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ mod event_coalescer;
|
|||||||
use crate::{ChannelId, TelemetrySettings};
|
use crate::{ChannelId, TelemetrySettings};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clock::SystemClock;
|
use clock::SystemClock;
|
||||||
use collections::{HashMap, HashSet};
|
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
|
||||||
use http::{self, HttpClient, HttpClientWithUrl, Method};
|
use http::{self, HttpClient, HttpClientWithUrl, Method};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -24,7 +23,6 @@ use tempfile::NamedTempFile;
|
|||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
|
||||||
|
|
||||||
use self::event_coalescer::EventCoalescer;
|
use self::event_coalescer::EventCoalescer;
|
||||||
|
|
||||||
@@ -41,6 +39,7 @@ struct TelemetryState {
|
|||||||
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
||||||
session_id: Option<String>, // Per app launch
|
session_id: Option<String>, // Per app launch
|
||||||
release_channel: Option<&'static str>,
|
release_channel: Option<&'static str>,
|
||||||
|
app_metadata: AppMetadata,
|
||||||
architecture: &'static str,
|
architecture: &'static str,
|
||||||
events_queue: Vec<EventWrapper>,
|
events_queue: Vec<EventWrapper>,
|
||||||
flush_events_task: Option<Task<()>>,
|
flush_events_task: Option<Task<()>>,
|
||||||
@@ -49,29 +48,6 @@ struct TelemetryState {
|
|||||||
first_event_date_time: Option<DateTime<Utc>>,
|
first_event_date_time: Option<DateTime<Utc>>,
|
||||||
event_coalescer: EventCoalescer,
|
event_coalescer: EventCoalescer,
|
||||||
max_queue_size: usize,
|
max_queue_size: usize,
|
||||||
worktree_id_map: WorktreeIdMap,
|
|
||||||
|
|
||||||
os_name: String,
|
|
||||||
app_version: String,
|
|
||||||
os_version: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct WorktreeIdMap(HashMap<String, ProjectCache>);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ProjectCache {
|
|
||||||
name: String,
|
|
||||||
worktree_ids_reported: HashSet<WorktreeId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectCache {
|
|
||||||
fn new(name: String) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
worktree_ids_reported: HashSet::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
@@ -95,87 +71,6 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn os_name() -> String {
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
"macOS".to_string()
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
format!("Linux {}", gpui::guess_compositor())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
"Windows".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Note: This might do blocking IO! Only call from background threads
|
|
||||||
pub fn os_version() -> String {
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use cocoa::base::nil;
|
|
||||||
use cocoa::foundation::NSProcessInfo;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let process_info = cocoa::foundation::NSProcessInfo::processInfo(nil);
|
|
||||||
let version = process_info.operatingSystemVersion();
|
|
||||||
gpui::SemanticVersion::new(
|
|
||||||
version.majorVersion as usize,
|
|
||||||
version.minorVersion as usize,
|
|
||||||
version.patchVersion as usize,
|
|
||||||
)
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
let content = if let Ok(file) = std::fs::read_to_string(&Path::new("/etc/os-release")) {
|
|
||||||
file
|
|
||||||
} else if let Ok(file) = std::fs::read_to_string(&Path::new("/usr/lib/os-release")) {
|
|
||||||
file
|
|
||||||
} else {
|
|
||||||
log::error!("Failed to load /etc/os-release, /usr/lib/os-release");
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
let mut name = "unknown".to_string();
|
|
||||||
let mut version = "unknown".to_string();
|
|
||||||
|
|
||||||
for line in content.lines() {
|
|
||||||
if line.starts_with("ID=") {
|
|
||||||
name = line.trim_start_matches("ID=").trim_matches('"').to_string();
|
|
||||||
}
|
|
||||||
if line.starts_with("VERSION_ID=") {
|
|
||||||
version = line
|
|
||||||
.trim_start_matches("VERSION_ID=")
|
|
||||||
.trim_matches('"')
|
|
||||||
.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("{} {}", name, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
let mut info = unsafe { std::mem::zeroed() };
|
|
||||||
let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut info) };
|
|
||||||
if status.is_ok() {
|
|
||||||
gpui::SemanticVersion::new(
|
|
||||||
info.dwMajorVersion as _,
|
|
||||||
info.dwMinorVersion as _,
|
|
||||||
info.dwBuildNumber as _,
|
|
||||||
)
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
"unknown".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Telemetry {
|
impl Telemetry {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
clock: Arc<dyn SystemClock>,
|
clock: Arc<dyn SystemClock>,
|
||||||
@@ -189,6 +84,7 @@ impl Telemetry {
|
|||||||
|
|
||||||
let state = Arc::new(Mutex::new(TelemetryState {
|
let state = Arc::new(Mutex::new(TelemetryState {
|
||||||
settings: *TelemetrySettings::get_global(cx),
|
settings: *TelemetrySettings::get_global(cx),
|
||||||
|
app_metadata: cx.app_metadata(),
|
||||||
architecture: env::consts::ARCH,
|
architecture: env::consts::ARCH,
|
||||||
release_channel,
|
release_channel,
|
||||||
installation_id: None,
|
installation_id: None,
|
||||||
@@ -201,20 +97,6 @@ impl Telemetry {
|
|||||||
first_event_date_time: None,
|
first_event_date_time: None,
|
||||||
event_coalescer: EventCoalescer::new(clock.clone()),
|
event_coalescer: EventCoalescer::new(clock.clone()),
|
||||||
max_queue_size: MAX_QUEUE_LEN,
|
max_queue_size: MAX_QUEUE_LEN,
|
||||||
worktree_id_map: WorktreeIdMap(HashMap::from_iter([
|
|
||||||
(
|
|
||||||
"yarn.lock".to_string(),
|
|
||||||
ProjectCache::new("yarn".to_string()),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"package.json".to_string(),
|
|
||||||
ProjectCache::new("node".to_string()),
|
|
||||||
),
|
|
||||||
])),
|
|
||||||
|
|
||||||
os_version: None,
|
|
||||||
os_name: os_name(),
|
|
||||||
app_version: release_channel::AppVersion::global(cx).to_string(),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
@@ -223,7 +105,7 @@ impl Telemetry {
|
|||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(tempfile) =
|
if let Some(tempfile) =
|
||||||
NamedTempFile::new_in(paths::config_dir().as_path()).log_err()
|
NamedTempFile::new_in(util::paths::CONFIG_DIR.as_path()).log_err()
|
||||||
{
|
{
|
||||||
state.lock().log_file = Some(tempfile);
|
state.lock().log_file = Some(tempfile);
|
||||||
}
|
}
|
||||||
@@ -286,9 +168,6 @@ impl Telemetry {
|
|||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
state.installation_id = installation_id.map(|id| id.into());
|
state.installation_id = installation_id.map(|id| id.into());
|
||||||
state.session_id = Some(session_id);
|
state.session_id = Some(session_id);
|
||||||
state.app_version = release_channel::AppVersion::global(cx).to_string();
|
|
||||||
state.os_name = os_name();
|
|
||||||
|
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
@@ -481,52 +360,6 @@ impl Telemetry {
|
|||||||
self.report_event(event)
|
self.report_event(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_discovered_project_events(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
worktree_id: WorktreeId,
|
|
||||||
updated_entries_set: &UpdatedEntriesSet,
|
|
||||||
) {
|
|
||||||
let project_names: Vec<String> = {
|
|
||||||
let mut state = self.state.lock();
|
|
||||||
state
|
|
||||||
.worktree_id_map
|
|
||||||
.0
|
|
||||||
.iter_mut()
|
|
||||||
.filter_map(|(project_file_name, project_type_telemetry)| {
|
|
||||||
if project_type_telemetry
|
|
||||||
.worktree_ids_reported
|
|
||||||
.contains(&worktree_id)
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let project_file_found = updated_entries_set.iter().any(|(path, _, _)| {
|
|
||||||
path.as_ref()
|
|
||||||
.file_name()
|
|
||||||
.and_then(|name| name.to_str())
|
|
||||||
.map(|name_str| name_str == project_file_name)
|
|
||||||
.unwrap_or(false)
|
|
||||||
});
|
|
||||||
|
|
||||||
if !project_file_found {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
project_type_telemetry
|
|
||||||
.worktree_ids_reported
|
|
||||||
.insert(worktree_id);
|
|
||||||
|
|
||||||
Some(project_type_telemetry.name.clone())
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Done on purpose to avoid calling `self.state.lock()` multiple times
|
|
||||||
for project_name in project_names {
|
|
||||||
self.report_app_event(format!("open {} project", project_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report_event(self: &Arc<Self>, event: Event) {
|
fn report_event(self: &Arc<Self>, event: Event) {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
|
|
||||||
@@ -590,6 +423,10 @@ impl Telemetry {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ZED_CLIENT_CHECKSUM_SEED.is_none() {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
self.executor
|
self.executor
|
||||||
.spawn(
|
.spawn(
|
||||||
@@ -608,14 +445,20 @@ impl Telemetry {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let state = this.state.lock();
|
let state = this.state.lock();
|
||||||
|
|
||||||
let request_body = EventRequestBody {
|
let request_body = EventRequestBody {
|
||||||
installation_id: state.installation_id.as_deref().map(Into::into),
|
installation_id: state.installation_id.as_deref().map(Into::into),
|
||||||
session_id: state.session_id.clone(),
|
session_id: state.session_id.clone(),
|
||||||
is_staff: state.is_staff,
|
is_staff: state.is_staff,
|
||||||
app_version: state.app_version.clone(),
|
app_version: state
|
||||||
os_name: state.os_name.clone(),
|
.app_metadata
|
||||||
os_version: state.os_version.clone(),
|
.app_version
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string(),
|
||||||
|
os_name: state.app_metadata.os_name.to_string(),
|
||||||
|
os_version: state
|
||||||
|
.app_metadata
|
||||||
|
.os_version
|
||||||
|
.map(|version| version.to_string()),
|
||||||
architecture: state.architecture.to_string(),
|
architecture: state.architecture.to_string(),
|
||||||
|
|
||||||
release_channel: state.release_channel.map(Into::into),
|
release_channel: state.release_channel.map(Into::into),
|
||||||
@@ -625,7 +468,9 @@ impl Telemetry {
|
|||||||
serde_json::to_writer(&mut json_bytes, &request_body)?;
|
serde_json::to_writer(&mut json_bytes, &request_body)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let checksum = calculate_json_checksum(&json_bytes).unwrap_or("".to_string());
|
let Some(checksum) = calculate_json_checksum(&json_bytes) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
let request = http::Request::builder()
|
let request = http::Request::builder()
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ impl UserStore {
|
|||||||
async fn handle_update_invite_info(
|
async fn handle_update_invite_info(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
@@ -257,6 +258,7 @@ impl UserStore {
|
|||||||
async fn handle_show_contacts(
|
async fn handle_show_contacts(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
_: TypedEnvelope<proto::ShowContacts>,
|
_: TypedEnvelope<proto::ShowContacts>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
|
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
|
||||||
@@ -270,6 +272,7 @@ impl UserStore {
|
|||||||
async fn handle_update_contacts(
|
async fn handle_update_contacts(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateContacts>,
|
message: TypedEnvelope<proto::UpdateContacts>,
|
||||||
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ node_runtime.workspace = true
|
|||||||
notifications = { workspace = true, features = ["test-support"] }
|
notifications = { workspace = true, features = ["test-support"] }
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
project = { workspace = true, features = ["test-support"] }
|
project = { workspace = true, features = ["test-support"] }
|
||||||
recent_projects = { workspace = true }
|
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
dev_server_projects.workspace = true
|
dev_server_projects.workspace = true
|
||||||
rpc = { workspace = true, features = ["test-support"] }
|
rpc = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -122,11 +122,6 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: anthropic
|
name: anthropic
|
||||||
key: api_key
|
key: api_key
|
||||||
- name: GOOGLE_AI_API_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: google-ai
|
|
||||||
key: api_key
|
|
||||||
- name: BLOB_STORE_ACCESS_KEY
|
- name: BLOB_STORE_ACCESS_KEY
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
|
|||||||
@@ -308,13 +308,6 @@ pub async fn post_panic(
|
|||||||
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
|
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
|
||||||
let panic = report.panic;
|
let panic = report.panic;
|
||||||
|
|
||||||
if panic.os_name == "Linux" && panic.os_version == Some("1.0.0".to_string()) {
|
|
||||||
return Err(Error::Http(
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
"invalid os version".into(),
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
service = "client",
|
service = "client",
|
||||||
version = %panic.app_version,
|
version = %panic.app_version,
|
||||||
@@ -401,7 +394,12 @@ pub async fn post_events(
|
|||||||
))?;
|
))?;
|
||||||
};
|
};
|
||||||
|
|
||||||
let checksum_matched = checksum == expected;
|
if checksum != expected {
|
||||||
|
return Err(Error::Http(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"invalid checksum".into(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
let request_body: telemetry_events::EventRequestBody =
|
let request_body: telemetry_events::EventRequestBody =
|
||||||
serde_json::from_slice(&body).map_err(|err| {
|
serde_json::from_slice(&body).map_err(|err| {
|
||||||
@@ -426,7 +424,6 @@ pub async fn post_events(
|
|||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
country_code.clone(),
|
country_code.clone(),
|
||||||
checksum_matched,
|
|
||||||
)),
|
)),
|
||||||
// Needed for clients sending old copilot_event types
|
// Needed for clients sending old copilot_event types
|
||||||
Event::Copilot(_) => {}
|
Event::Copilot(_) => {}
|
||||||
@@ -439,7 +436,6 @@ pub async fn post_events(
|
|||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
country_code.clone(),
|
country_code.clone(),
|
||||||
checksum_matched,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
|
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
|
||||||
@@ -447,7 +443,6 @@ pub async fn post_events(
|
|||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
)),
|
)),
|
||||||
Event::Assistant(event) => {
|
Event::Assistant(event) => {
|
||||||
to_upload
|
to_upload
|
||||||
@@ -457,7 +452,6 @@ pub async fn post_events(
|
|||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
|
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
|
||||||
@@ -465,42 +459,36 @@ pub async fn post_events(
|
|||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
)),
|
)),
|
||||||
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
|
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
)),
|
)),
|
||||||
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
|
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
)),
|
)),
|
||||||
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
|
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
)),
|
)),
|
||||||
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
|
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
)),
|
)),
|
||||||
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
|
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
|
||||||
event.clone(),
|
event.clone(),
|
||||||
&wrapper,
|
&wrapper,
|
||||||
&request_body,
|
&request_body,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
)),
|
)),
|
||||||
Event::Extension(event) => {
|
Event::Extension(event) => {
|
||||||
let metadata = app
|
let metadata = app
|
||||||
@@ -515,7 +503,6 @@ pub async fn post_events(
|
|||||||
&request_body,
|
&request_body,
|
||||||
metadata,
|
metadata,
|
||||||
first_event_at,
|
first_event_at,
|
||||||
checksum_matched,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -663,30 +650,29 @@ where
|
|||||||
|
|
||||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
pub struct EditorEventRow {
|
pub struct EditorEventRow {
|
||||||
installation_id: String,
|
pub installation_id: String,
|
||||||
operation: String,
|
pub operation: String,
|
||||||
app_version: String,
|
pub app_version: String,
|
||||||
file_extension: String,
|
pub file_extension: String,
|
||||||
os_name: String,
|
pub os_name: String,
|
||||||
os_version: String,
|
pub os_version: String,
|
||||||
release_channel: String,
|
pub release_channel: String,
|
||||||
signed_in: bool,
|
pub signed_in: bool,
|
||||||
vim_mode: bool,
|
pub vim_mode: bool,
|
||||||
#[serde(serialize_with = "serialize_country_code")]
|
#[serde(serialize_with = "serialize_country_code")]
|
||||||
country_code: String,
|
pub country_code: String,
|
||||||
region_code: String,
|
pub region_code: String,
|
||||||
city: String,
|
pub city: String,
|
||||||
time: i64,
|
pub time: i64,
|
||||||
copilot_enabled: bool,
|
pub copilot_enabled: bool,
|
||||||
copilot_enabled_for_language: bool,
|
pub copilot_enabled_for_language: bool,
|
||||||
historical_event: bool,
|
pub historical_event: bool,
|
||||||
architecture: String,
|
pub architecture: String,
|
||||||
is_staff: Option<bool>,
|
pub is_staff: Option<bool>,
|
||||||
session_id: Option<String>,
|
pub session_id: Option<String>,
|
||||||
major: Option<i32>,
|
pub major: Option<i32>,
|
||||||
minor: Option<i32>,
|
pub minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
pub patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorEventRow {
|
impl EditorEventRow {
|
||||||
@@ -696,7 +682,6 @@ impl EditorEventRow {
|
|||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
country_code: Option<String>,
|
country_code: Option<String>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -707,7 +692,6 @@ impl EditorEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -732,26 +716,25 @@ impl EditorEventRow {
|
|||||||
|
|
||||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
pub struct InlineCompletionEventRow {
|
pub struct InlineCompletionEventRow {
|
||||||
installation_id: String,
|
pub installation_id: String,
|
||||||
provider: String,
|
pub provider: String,
|
||||||
suggestion_accepted: bool,
|
pub suggestion_accepted: bool,
|
||||||
app_version: String,
|
pub app_version: String,
|
||||||
file_extension: String,
|
pub file_extension: String,
|
||||||
os_name: String,
|
pub os_name: String,
|
||||||
os_version: String,
|
pub os_version: String,
|
||||||
release_channel: String,
|
pub release_channel: String,
|
||||||
signed_in: bool,
|
pub signed_in: bool,
|
||||||
#[serde(serialize_with = "serialize_country_code")]
|
#[serde(serialize_with = "serialize_country_code")]
|
||||||
country_code: String,
|
pub country_code: String,
|
||||||
region_code: String,
|
pub region_code: String,
|
||||||
city: String,
|
pub city: String,
|
||||||
time: i64,
|
pub time: i64,
|
||||||
is_staff: Option<bool>,
|
pub is_staff: Option<bool>,
|
||||||
session_id: Option<String>,
|
pub session_id: Option<String>,
|
||||||
major: Option<i32>,
|
pub major: Option<i32>,
|
||||||
minor: Option<i32>,
|
pub minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
pub patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlineCompletionEventRow {
|
impl InlineCompletionEventRow {
|
||||||
@@ -761,7 +744,6 @@ impl InlineCompletionEventRow {
|
|||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
country_code: Option<String>,
|
country_code: Option<String>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -772,7 +754,6 @@ impl InlineCompletionEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
@@ -799,9 +780,6 @@ pub struct CallEventRow {
|
|||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
|
||||||
os_version: String,
|
|
||||||
checksum_matched: bool,
|
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: String,
|
installation_id: String,
|
||||||
@@ -821,7 +799,6 @@ impl CallEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -832,10 +809,7 @@ impl CallEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone().unwrap_or_default(),
|
installation_id: body.installation_id.clone().unwrap_or_default(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -854,10 +828,7 @@ pub struct AssistantEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
|
||||||
os_version: String,
|
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
@@ -879,7 +850,6 @@ impl AssistantEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -890,10 +860,7 @@ impl AssistantEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -911,21 +878,18 @@ impl AssistantEventRow {
|
|||||||
|
|
||||||
#[derive(Debug, clickhouse::Row, Serialize)]
|
#[derive(Debug, clickhouse::Row, Serialize)]
|
||||||
pub struct CpuEventRow {
|
pub struct CpuEventRow {
|
||||||
installation_id: Option<String>,
|
pub installation_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
pub is_staff: Option<bool>,
|
||||||
usage_as_percentage: f32,
|
pub usage_as_percentage: f32,
|
||||||
core_count: u32,
|
pub core_count: u32,
|
||||||
app_version: String,
|
pub app_version: String,
|
||||||
release_channel: String,
|
pub release_channel: String,
|
||||||
os_name: String,
|
pub time: i64,
|
||||||
os_version: String,
|
pub session_id: Option<String>,
|
||||||
time: i64,
|
|
||||||
session_id: Option<String>,
|
|
||||||
// pub normalized_cpu_usage: f64, MATERIALIZED
|
// pub normalized_cpu_usage: f64, MATERIALIZED
|
||||||
major: Option<i32>,
|
pub major: Option<i32>,
|
||||||
minor: Option<i32>,
|
pub minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
pub patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CpuEventRow {
|
impl CpuEventRow {
|
||||||
@@ -934,7 +898,6 @@ impl CpuEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -945,10 +908,7 @@ impl CpuEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -966,10 +926,7 @@ pub struct MemoryEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
|
||||||
os_version: String,
|
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
@@ -988,7 +945,6 @@ impl MemoryEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -999,10 +955,7 @@ impl MemoryEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -1020,10 +973,7 @@ pub struct AppEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
|
||||||
os_version: String,
|
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
@@ -1041,7 +991,6 @@ impl AppEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1052,10 +1001,7 @@ impl AppEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -1072,10 +1018,7 @@ pub struct SettingEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
|
||||||
os_version: String,
|
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
@@ -1093,7 +1036,6 @@ impl SettingEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1103,11 +1045,8 @@ impl SettingEventRow {
|
|||||||
app_version: body.app_version.clone(),
|
app_version: body.app_version.clone(),
|
||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
checksum_matched,
|
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -1125,10 +1064,7 @@ pub struct ExtensionEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
|
||||||
os_version: String,
|
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
@@ -1151,7 +1087,6 @@ impl ExtensionEventRow {
|
|||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
extension_metadata: Option<ExtensionMetadata>,
|
extension_metadata: Option<ExtensionMetadata>,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1162,10 +1097,7 @@ impl ExtensionEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -1194,10 +1126,7 @@ pub struct EditEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
|
||||||
os_version: String,
|
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
@@ -1219,7 +1148,6 @@ impl EditEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1233,10 +1161,7 @@ impl EditEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
@@ -1255,10 +1180,7 @@ pub struct ActionEventRow {
|
|||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
checksum_matched: bool,
|
|
||||||
release_channel: String,
|
release_channel: String,
|
||||||
os_name: String,
|
|
||||||
os_version: String,
|
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
@@ -1278,7 +1200,6 @@ impl ActionEventRow {
|
|||||||
wrapper: &EventWrapper,
|
wrapper: &EventWrapper,
|
||||||
body: &EventRequestBody,
|
body: &EventRequestBody,
|
||||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||||
checksum_matched: bool,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let semver = body.semver();
|
let semver = body.semver();
|
||||||
let time =
|
let time =
|
||||||
@@ -1289,10 +1210,7 @@ impl ActionEventRow {
|
|||||||
major: semver.map(|v| v.major() as i32),
|
major: semver.map(|v| v.major() as i32),
|
||||||
minor: semver.map(|v| v.minor() as i32),
|
minor: semver.map(|v| v.minor() as i32),
|
||||||
patch: semver.map(|v| v.patch() as i32),
|
patch: semver.map(|v| v.patch() as i32),
|
||||||
checksum_matched,
|
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ mod test {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_verify_access_token(cx: &mut gpui::TestAppContext) {
|
async fn test_verify_access_token(cx: &mut gpui::TestAppContext) {
|
||||||
let test_db = crate::db::TestDb::sqlite(cx.executor().clone());
|
let test_db = crate::db::TestDb::postgres(cx.executor().clone());
|
||||||
let db = test_db.db();
|
let db = test_db.db();
|
||||||
|
|
||||||
let user = db
|
let user = db
|
||||||
|
|||||||
@@ -379,7 +379,6 @@ fn metadata_from_extension_and_version(
|
|||||||
|
|
||||||
pub fn convert_time_to_chrono(time: time::PrimitiveDateTime) -> chrono::DateTime<Utc> {
|
pub fn convert_time_to_chrono(time: time::PrimitiveDateTime) -> chrono::DateTime<Utc> {
|
||||||
chrono::DateTime::from_naive_utc_and_offset(
|
chrono::DateTime::from_naive_utc_and_offset(
|
||||||
#[allow(deprecated)]
|
|
||||||
chrono::NaiveDateTime::from_timestamp_opt(time.assume_utc().unix_timestamp(), 0).unwrap(),
|
chrono::NaiveDateTime::from_timestamp_opt(time.assume_utc().unix_timestamp(), 0).unwrap(),
|
||||||
Utc,
|
Utc,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ mod buffer_tests;
|
|||||||
mod channel_tests;
|
mod channel_tests;
|
||||||
mod contributor_tests;
|
mod contributor_tests;
|
||||||
mod db_tests;
|
mod db_tests;
|
||||||
// we only run postgres tests on macos right now
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
mod embedding_tests;
|
mod embedding_tests;
|
||||||
mod extension_tests;
|
mod extension_tests;
|
||||||
mod feature_flag_tests;
|
mod feature_flag_tests;
|
||||||
@@ -110,7 +108,6 @@ impl TestDb {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! test_both_dbs {
|
macro_rules! test_both_dbs {
|
||||||
($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => {
|
($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => {
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
|
async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
|
||||||
let test_db = $crate::db::TestDb::postgres(cx.executor().clone());
|
let test_db = $crate::db::TestDb::postgres(cx.executor().clone());
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::test_both_dbs;
|
use crate::test_both_dbs;
|
||||||
|
use gpui::TestAppContext;
|
||||||
use pretty_assertions::{assert_eq, assert_ne};
|
use pretty_assertions::{assert_eq, assert_ne};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use tests::TestDb;
|
||||||
|
|
||||||
test_both_dbs!(
|
test_both_dbs!(
|
||||||
test_get_users,
|
test_get_users,
|
||||||
@@ -562,10 +564,9 @@ fn test_fuzzy_like_string() {
|
|||||||
assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
|
assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target = "macos")]
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
|
async fn test_fuzzy_search_users(cx: &mut TestAppContext) {
|
||||||
let test_db = tests::TestDb::postgres(cx.executor());
|
let test_db = TestDb::postgres(cx.executor());
|
||||||
let db = test_db.db();
|
let db = test_db.db();
|
||||||
for (i, github_login) in [
|
for (i, github_login) in [
|
||||||
"California",
|
"California",
|
||||||
|
|||||||
@@ -548,9 +548,6 @@ impl Server {
|
|||||||
.add_request_handler(user_handler(
|
.add_request_handler(user_handler(
|
||||||
forward_mutating_project_request::<proto::RestartLanguageServers>,
|
forward_mutating_project_request::<proto::RestartLanguageServers>,
|
||||||
))
|
))
|
||||||
.add_request_handler(user_handler(
|
|
||||||
forward_mutating_project_request::<proto::LinkedEditingRange>,
|
|
||||||
))
|
|
||||||
.add_message_handler(create_buffer_for_peer)
|
.add_message_handler(create_buffer_for_peer)
|
||||||
.add_request_handler(update_buffer)
|
.add_request_handler(update_buffer)
|
||||||
.add_message_handler(broadcast_project_message_from_host::<proto::RefreshInlayHints>)
|
.add_message_handler(broadcast_project_message_from_host::<proto::RefreshInlayHints>)
|
||||||
@@ -4503,7 +4500,6 @@ async fn complete_with_google_ai(
|
|||||||
session.http_client.clone(),
|
session.http_client.clone(),
|
||||||
google_ai::API_URL,
|
google_ai::API_URL,
|
||||||
api_key.as_ref(),
|
api_key.as_ref(),
|
||||||
&request.model.clone(),
|
|
||||||
crate::ai::language_model_request_to_google_ai(request)?,
|
crate::ai::language_model_request_to_google_ai(request)?,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ async fn test_dev_server(cx: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppC
|
|||||||
assert_eq!(projects.len(), 1);
|
assert_eq!(projects.len(), 1);
|
||||||
assert_eq!(projects[0].path, "/remote");
|
assert_eq!(projects[0].path, "/remote");
|
||||||
workspace::join_dev_server_project(
|
workspace::join_dev_server_project(
|
||||||
projects[0].id,
|
|
||||||
projects[0].project_id.unwrap(),
|
projects[0].project_id.unwrap(),
|
||||||
client.app_state.clone(),
|
client.app_state.clone(),
|
||||||
None,
|
None,
|
||||||
@@ -208,7 +207,6 @@ async fn create_dev_server_project(
|
|||||||
assert_eq!(projects.len(), 1);
|
assert_eq!(projects.len(), 1);
|
||||||
assert_eq!(projects[0].path, "/remote");
|
assert_eq!(projects[0].path, "/remote");
|
||||||
workspace::join_dev_server_project(
|
workspace::join_dev_server_project(
|
||||||
projects[0].id,
|
|
||||||
projects[0].project_id.unwrap(),
|
projects[0].project_id.unwrap(),
|
||||||
client_app_state,
|
client_app_state,
|
||||||
None,
|
None,
|
||||||
@@ -493,7 +491,6 @@ async fn test_dev_server_reconnect(
|
|||||||
.update(cx2, |store, cx| {
|
.update(cx2, |store, cx| {
|
||||||
let projects = store.dev_server_projects();
|
let projects = store.dev_server_projects();
|
||||||
workspace::join_dev_server_project(
|
workspace::join_dev_server_project(
|
||||||
projects[0].id,
|
|
||||||
projects[0].project_id.unwrap(),
|
projects[0].project_id.unwrap(),
|
||||||
client2.app_state.clone(),
|
client2.app_state.clone(),
|
||||||
None,
|
None,
|
||||||
@@ -575,8 +572,7 @@ async fn test_save_as_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::Tes
|
|||||||
|
|
||||||
let title = remote_workspace
|
let title = remote_workspace
|
||||||
.update(&mut cx, |ws, cx| {
|
.update(&mut cx, |ws, cx| {
|
||||||
let active_item = ws.active_item(cx).unwrap();
|
ws.active_item(cx).unwrap().tab_description(0, &cx).unwrap()
|
||||||
active_item.tab_description(0, &cx).unwrap()
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,8 @@ use language::{
|
|||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use project::{
|
use project::{
|
||||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||||
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
|
||||||
};
|
};
|
||||||
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
|
||||||
use rpc::RECEIVE_TIMEOUT;
|
use rpc::RECEIVE_TIMEOUT;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
@@ -60,7 +59,6 @@ async fn test_host_disconnect(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
cx_b.update(editor::init);
|
cx_b.update(editor::init);
|
||||||
cx_b.update(recent_projects::init);
|
|
||||||
|
|
||||||
client_a
|
client_a
|
||||||
.fs()
|
.fs()
|
||||||
@@ -85,7 +83,10 @@ async fn test_host_disconnect(
|
|||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||||
cx_a.background_executor.run_until_parked();
|
cx_a.background_executor.run_until_parked();
|
||||||
|
|
||||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
assert!(worktree_a.read_with(cx_a, |tree, _| tree
|
||||||
|
.as_local()
|
||||||
|
.unwrap()
|
||||||
|
.has_update_observer()));
|
||||||
|
|
||||||
let workspace_b = cx_b
|
let workspace_b = cx_b
|
||||||
.add_window(|cx| Workspace::new(None, project_b.clone(), client_b.app_state.clone(), cx));
|
.add_window(|cx| Workspace::new(None, project_b.clone(), client_b.app_state.clone(), cx));
|
||||||
@@ -122,13 +123,17 @@ async fn test_host_disconnect(
|
|||||||
|
|
||||||
project_b.read_with(cx_b, |project, _| project.is_read_only());
|
project_b.read_with(cx_b, |project, _| project.is_read_only());
|
||||||
|
|
||||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
|
assert!(worktree_a.read_with(cx_a, |tree, _| !tree
|
||||||
|
.as_local()
|
||||||
|
.unwrap()
|
||||||
|
.has_update_observer()));
|
||||||
|
|
||||||
// Ensure client B's edited state is reset and that the whole window is blurred.
|
// Ensure client B's edited state is reset and that the whole window is blurred.
|
||||||
|
|
||||||
workspace_b
|
workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
|
assert_eq!(cx.focused(), None);
|
||||||
assert!(!workspace.is_edited());
|
assert!(!workspace.is_edited())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1006,8 +1011,6 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
|
|
||||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||||
fake_language_server.start_progress("the-token").await;
|
fake_language_server.start_progress("the-token").await;
|
||||||
|
|
||||||
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
|
|
||||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||||
token: lsp::NumberOrString::String("the-token".to_string()),
|
token: lsp::NumberOrString::String("the-token".to_string()),
|
||||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
||||||
@@ -1017,10 +1020,11 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
|
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap().1;
|
let status = project.language_server_statuses().next().unwrap();
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
assert_eq!(status.pending_work.len(), 1);
|
assert_eq!(status.pending_work.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1037,11 +1041,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||||
|
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap().1;
|
let status = project.language_server_statuses().next().unwrap();
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
});
|
});
|
||||||
|
|
||||||
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
|
|
||||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||||
token: lsp::NumberOrString::String("the-token".to_string()),
|
token: lsp::NumberOrString::String("the-token".to_string()),
|
||||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
||||||
@@ -1051,10 +1054,11 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
|
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
project_a.read_with(cx_a, |project, _| {
|
project_a.read_with(cx_a, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap().1;
|
let status = project.language_server_statuses().next().unwrap();
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
assert_eq!(status.pending_work.len(), 1);
|
assert_eq!(status.pending_work.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1064,7 +1068,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||||||
});
|
});
|
||||||
|
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap().1;
|
let status = project.language_server_statuses().next().unwrap();
|
||||||
assert_eq!(status.name, "the-language-server");
|
assert_eq!(status.name, "the-language-server");
|
||||||
assert_eq!(status.pending_work.len(), 1);
|
assert_eq!(status.pending_work.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1204,7 +1208,7 @@ async fn test_share_project(
|
|||||||
buffer_a.read_with(cx_a, |buffer, _| {
|
buffer_a.read_with(cx_a, |buffer, _| {
|
||||||
buffer
|
buffer
|
||||||
.snapshot()
|
.snapshot()
|
||||||
.selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
|
.remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX)
|
||||||
.count()
|
.count()
|
||||||
== 1
|
== 1
|
||||||
});
|
});
|
||||||
@@ -1245,7 +1249,7 @@ async fn test_share_project(
|
|||||||
buffer_a.read_with(cx_a, |buffer, _| {
|
buffer_a.read_with(cx_a, |buffer, _| {
|
||||||
buffer
|
buffer
|
||||||
.snapshot()
|
.snapshot()
|
||||||
.selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
|
.remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX)
|
||||||
.count()
|
.count()
|
||||||
== 0
|
== 0
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1378,7 +1378,10 @@ async fn test_unshare_project(
|
|||||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
assert!(worktree_a.read_with(cx_a, |tree, _| tree
|
||||||
|
.as_local()
|
||||||
|
.unwrap()
|
||||||
|
.has_update_observer()));
|
||||||
|
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||||
@@ -1403,7 +1406,10 @@ async fn test_unshare_project(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
|
assert!(worktree_a.read_with(cx_a, |tree, _| !tree
|
||||||
|
.as_local()
|
||||||
|
.unwrap()
|
||||||
|
.has_update_observer()));
|
||||||
|
|
||||||
assert!(project_c.read_with(cx_c, |project, _| project.is_disconnected()));
|
assert!(project_c.read_with(cx_c, |project, _| project.is_disconnected()));
|
||||||
|
|
||||||
@@ -1415,7 +1421,10 @@ async fn test_unshare_project(
|
|||||||
let project_c2 = client_c.build_dev_server_project(project_id, cx_c).await;
|
let project_c2 = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
assert!(worktree_a.read_with(cx_a, |tree, _| tree
|
||||||
|
.as_local()
|
||||||
|
.unwrap()
|
||||||
|
.has_update_observer()));
|
||||||
project_c2
|
project_c2
|
||||||
.update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
.update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||||
.await
|
.await
|
||||||
@@ -1522,7 +1531,7 @@ async fn test_project_reconnect(
|
|||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
|
let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
|
||||||
assert!(worktree.has_update_observer());
|
assert!(worktree.as_local().unwrap().has_update_observer());
|
||||||
worktree.id()
|
worktree.id()
|
||||||
});
|
});
|
||||||
let (worktree_a2, _) = project_a1
|
let (worktree_a2, _) = project_a1
|
||||||
@@ -1534,7 +1543,7 @@ async fn test_project_reconnect(
|
|||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
|
let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
|
||||||
assert!(tree.has_update_observer());
|
assert!(tree.as_local().unwrap().has_update_observer());
|
||||||
tree.id()
|
tree.id()
|
||||||
});
|
});
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
@@ -1567,7 +1576,9 @@ async fn test_project_reconnect(
|
|||||||
assert_eq!(project.collaborators().len(), 1);
|
assert_eq!(project.collaborators().len(), 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
worktree_a1.read_with(cx_a, |tree, _| assert!(tree.has_update_observer()));
|
worktree_a1.read_with(cx_a, |tree, _| {
|
||||||
|
assert!(tree.as_local().unwrap().has_update_observer())
|
||||||
|
});
|
||||||
|
|
||||||
// While client A is disconnected, add and remove files from client A's project.
|
// While client A is disconnected, add and remove files from client A's project.
|
||||||
client_a
|
client_a
|
||||||
@@ -1609,7 +1620,7 @@ async fn test_project_reconnect(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let worktree3_id = worktree_a3.read_with(cx_a, |tree, _| {
|
let worktree3_id = worktree_a3.read_with(cx_a, |tree, _| {
|
||||||
assert!(!tree.has_update_observer());
|
assert!(!tree.as_local().unwrap().has_update_observer());
|
||||||
tree.id()
|
tree.id()
|
||||||
});
|
});
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
@@ -1632,7 +1643,11 @@ async fn test_project_reconnect(
|
|||||||
|
|
||||||
project_a1.read_with(cx_a, |project, cx| {
|
project_a1.read_with(cx_a, |project, cx| {
|
||||||
assert!(project.is_shared());
|
assert!(project.is_shared());
|
||||||
assert!(worktree_a1.read(cx).has_update_observer());
|
assert!(worktree_a1
|
||||||
|
.read(cx)
|
||||||
|
.as_local()
|
||||||
|
.unwrap()
|
||||||
|
.has_update_observer());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
worktree_a1
|
worktree_a1
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@@ -1650,7 +1665,11 @@ async fn test_project_reconnect(
|
|||||||
"subdir2/i.txt"
|
"subdir2/i.txt"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert!(worktree_a3.read(cx).has_update_observer());
|
assert!(worktree_a3
|
||||||
|
.read(cx)
|
||||||
|
.as_local()
|
||||||
|
.unwrap()
|
||||||
|
.has_update_observer());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
worktree_a3
|
worktree_a3
|
||||||
.read(cx)
|
.read(cx)
|
||||||
@@ -1731,7 +1750,7 @@ async fn test_project_reconnect(
|
|||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
|
let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
|
||||||
assert!(tree.has_update_observer());
|
assert!(tree.as_local().unwrap().has_update_observer());
|
||||||
tree.id()
|
tree.id()
|
||||||
});
|
});
|
||||||
project_a1.update(cx_a, |project, cx| {
|
project_a1.update(cx_a, |project, cx| {
|
||||||
@@ -4772,7 +4791,7 @@ async fn test_references(
|
|||||||
// User is informed that a request is pending.
|
// User is informed that a request is pending.
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap().1;
|
let status = project.language_server_statuses().next().cloned().unwrap();
|
||||||
assert_eq!(status.name, "my-fake-lsp-adapter");
|
assert_eq!(status.name, "my-fake-lsp-adapter");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
status.pending_work.values().next().unwrap().message,
|
status.pending_work.values().next().unwrap().message,
|
||||||
@@ -4802,7 +4821,7 @@ async fn test_references(
|
|||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
project_b.read_with(cx_b, |project, cx| {
|
project_b.read_with(cx_b, |project, cx| {
|
||||||
// User is informed that a request is no longer pending.
|
// User is informed that a request is no longer pending.
|
||||||
let status = project.language_server_statuses().next().unwrap().1;
|
let status = project.language_server_statuses().next().unwrap();
|
||||||
assert!(status.pending_work.is_empty());
|
assert!(status.pending_work.is_empty());
|
||||||
|
|
||||||
assert_eq!(references.len(), 3);
|
assert_eq!(references.len(), 3);
|
||||||
@@ -4830,7 +4849,7 @@ async fn test_references(
|
|||||||
// User is informed that a request is pending.
|
// User is informed that a request is pending.
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap().1;
|
let status = project.language_server_statuses().next().cloned().unwrap();
|
||||||
assert_eq!(status.name, "my-fake-lsp-adapter");
|
assert_eq!(status.name, "my-fake-lsp-adapter");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
status.pending_work.values().next().unwrap().message,
|
status.pending_work.values().next().unwrap().message,
|
||||||
@@ -4847,7 +4866,7 @@ async fn test_references(
|
|||||||
// User is informed that the request is no longer pending.
|
// User is informed that the request is no longer pending.
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
project_b.read_with(cx_b, |project, _| {
|
project_b.read_with(cx_b, |project, _| {
|
||||||
let status = project.language_server_statuses().next().unwrap().1;
|
let status = project.language_server_statuses().next().unwrap();
|
||||||
assert!(status.pending_work.is_empty());
|
assert!(status.pending_work.is_empty());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4904,15 +4923,7 @@ async fn test_project_search(
|
|||||||
let mut results = HashMap::default();
|
let mut results = HashMap::default();
|
||||||
let mut search_rx = project_b.update(cx_b, |project, cx| {
|
let mut search_rx = project_b.update(cx_b, |project, cx| {
|
||||||
project.search(
|
project.search(
|
||||||
SearchQuery::text(
|
SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
|
||||||
"world",
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ use language::{
|
|||||||
};
|
};
|
||||||
use lsp::FakeLanguageServer;
|
use lsp::FakeLanguageServer;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use project::{
|
use project::{search::SearchQuery, Project, ProjectPath, SearchResult};
|
||||||
search::SearchQuery, Project, ProjectPath, SearchResult, DEFAULT_COMPLETION_CONTEXT,
|
|
||||||
};
|
|
||||||
use rand::{
|
use rand::{
|
||||||
distributions::{Alphanumeric, DistString},
|
distributions::{Alphanumeric, DistString},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -305,7 +303,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
.filter(|worktree| {
|
.filter(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
worktree.is_visible()
|
worktree.is_visible()
|
||||||
&& worktree.entries(false, 0).any(|e| e.is_file())
|
&& worktree.entries(false).any(|e| e.is_file())
|
||||||
&& worktree.root_entry().map_or(false, |e| e.is_dir())
|
&& worktree.root_entry().map_or(false, |e| e.is_dir())
|
||||||
})
|
})
|
||||||
.choose(rng)
|
.choose(rng)
|
||||||
@@ -427,14 +425,14 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
.filter(|worktree| {
|
.filter(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
worktree.is_visible()
|
worktree.is_visible()
|
||||||
&& worktree.entries(false, 0).any(|e| e.is_file())
|
&& worktree.entries(false).any(|e| e.is_file())
|
||||||
})
|
})
|
||||||
.choose(rng)
|
.choose(rng)
|
||||||
});
|
});
|
||||||
let Some(worktree) = worktree else { continue };
|
let Some(worktree) = worktree else { continue };
|
||||||
let full_path = worktree.read_with(cx, |worktree, _| {
|
let full_path = worktree.read_with(cx, |worktree, _| {
|
||||||
let entry = worktree
|
let entry = worktree
|
||||||
.entries(false, 0)
|
.entries(false)
|
||||||
.filter(|e| e.is_file())
|
.filter(|e| e.is_file())
|
||||||
.choose(rng)
|
.choose(rng)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -831,7 +829,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
.map_ok(|_| ())
|
.map_ok(|_| ())
|
||||||
.boxed(),
|
.boxed(),
|
||||||
LspRequestKind::Completion => project
|
LspRequestKind::Completion => project
|
||||||
.completions(&buffer, offset, DEFAULT_COMPLETION_CONTEXT, cx)
|
.completions(&buffer, offset, cx)
|
||||||
.map_ok(|_| ())
|
.map_ok(|_| ())
|
||||||
.boxed(),
|
.boxed(),
|
||||||
LspRequestKind::CodeAction => project
|
LspRequestKind::CodeAction => project
|
||||||
@@ -875,15 +873,8 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
|
|
||||||
let mut search = project.update(cx, |project, cx| {
|
let mut search = project.update(cx, |project, cx| {
|
||||||
project.search(
|
project.search(
|
||||||
SearchQuery::text(
|
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
|
||||||
query,
|
.unwrap(),
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -1213,8 +1204,8 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||||||
guest_project.remote_id(),
|
guest_project.remote_id(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
guest_snapshot.entries(false, 0).collect::<Vec<_>>(),
|
guest_snapshot.entries(false).collect::<Vec<_>>(),
|
||||||
host_snapshot.entries(false, 0).collect::<Vec<_>>(),
|
host_snapshot.entries(false).collect::<Vec<_>>(),
|
||||||
"{} has different snapshot than the host for worktree {:?} ({:?}) and project {:?}",
|
"{} has different snapshot than the host for worktree {:?} ({:?}) and project {:?}",
|
||||||
client.username,
|
client.username,
|
||||||
host_snapshot.abs_path(),
|
host_snapshot.abs_path(),
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ impl TestServer {
|
|||||||
}
|
}
|
||||||
let settings = SettingsStore::test(cx);
|
let settings = SettingsStore::test(cx);
|
||||||
cx.set_global(settings);
|
cx.set_global(settings);
|
||||||
release_channel::init(SemanticVersion::default(), cx);
|
release_channel::init("0.0.0", cx);
|
||||||
client::init_settings(cx);
|
client::init_settings(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -277,7 +277,11 @@ impl TestServer {
|
|||||||
node_runtime: FakeNodeRuntime::new(),
|
node_runtime: FakeNodeRuntime::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let os_keymap = "keymaps/default-macos.json";
|
let os_keymap = if cfg!(target_os = "linux") {
|
||||||
|
"keymaps/default-linux.json"
|
||||||
|
} else {
|
||||||
|
"keymaps/default-macos.json"
|
||||||
|
};
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
theme::init(theme::LoadThemes::JustBase, cx);
|
theme::init(theme::LoadThemes::JustBase, cx);
|
||||||
@@ -323,7 +327,7 @@ impl TestServer {
|
|||||||
}
|
}
|
||||||
let settings = SettingsStore::test(cx);
|
let settings = SettingsStore::test(cx);
|
||||||
cx.set_global(settings);
|
cx.set_global(settings);
|
||||||
release_channel::init(SemanticVersion::default(), cx);
|
release_channel::init("0.0.0", cx);
|
||||||
client::init_settings(cx);
|
client::init_settings(cx);
|
||||||
});
|
});
|
||||||
let (dev_server_id, _) = split_dev_server_token(&access_token).unwrap();
|
let (dev_server_id, _) = split_dev_server_token(&access_token).unwrap();
|
||||||
|
|||||||
@@ -35,12 +35,10 @@ call.workspace = true
|
|||||||
channel.workspace = true
|
channel.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
command_palette.workspace = true
|
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
emojis.workspace = true
|
emojis.workspace = true
|
||||||
extensions_ui.workspace = true
|
extensions_ui.workspace = true
|
||||||
feedback.workspace = true
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
|||||||
@@ -228,11 +228,11 @@ impl ChannelView {
|
|||||||
&self.editor,
|
&self.editor,
|
||||||
move |this, _, e: &EditorEvent, cx| {
|
move |this, _, e: &EditorEvent, cx| {
|
||||||
match e {
|
match e {
|
||||||
EditorEvent::Reparsed(_) => {
|
EditorEvent::Reparsed => {
|
||||||
this.focus_position_from_link(position.clone(), false, cx);
|
this.focus_position_from_link(position.clone(), false, cx);
|
||||||
this._reparse_subscription.take();
|
this._reparse_subscription.take();
|
||||||
}
|
}
|
||||||
EditorEvent::Edited { .. } | EditorEvent::SelectionsChanged { local: true } => {
|
EditorEvent::Edited | EditorEvent::SelectionsChanged { local: true } => {
|
||||||
this._reparse_subscription.take();
|
this._reparse_subscription.take();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use settings::Settings;
|
|||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, PopoverMenu,
|
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
|
||||||
TabBar, Tooltip,
|
TabBar, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
@@ -679,7 +679,7 @@ impl ChatPanel {
|
|||||||
cx,
|
cx,
|
||||||
div()
|
div()
|
||||||
.child(
|
.child(
|
||||||
PopoverMenu::new(("menu", message_id))
|
popover_menu(("menu", message_id))
|
||||||
.trigger(IconButton::new(
|
.trigger(IconButton::new(
|
||||||
("trigger", message_id),
|
("trigger", message_id),
|
||||||
IconName::Ellipsis,
|
IconName::Ellipsis,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use channel::{ChannelChat, ChannelStore, MessageParams};
|
use channel::{ChannelChat, ChannelStore, MessageParams};
|
||||||
use client::{UserId, UserStore};
|
use client::{UserId, UserStore};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
@@ -25,15 +25,8 @@ use crate::panel_settings::MessageEditorSettings;
|
|||||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref MENTIONS_SEARCH: SearchQuery = SearchQuery::regex(
|
static ref MENTIONS_SEARCH: SearchQuery =
|
||||||
"@[-_\\w]+",
|
SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
Default::default(),
|
|
||||||
Default::default()
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
@@ -53,7 +46,6 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
|||||||
&self,
|
&self,
|
||||||
buffer: &Model<Buffer>,
|
buffer: &Model<Buffer>,
|
||||||
buffer_position: language::Anchor,
|
buffer_position: language::Anchor,
|
||||||
_: editor::CompletionContext,
|
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> Task<anyhow::Result<Vec<Completion>>> {
|
) -> Task<anyhow::Result<Vec<Completion>>> {
|
||||||
let Some(handle) = self.0.upgrade() else {
|
let Some(handle) = self.0.upgrade() else {
|
||||||
@@ -140,7 +132,7 @@ impl MessageEditor {
|
|||||||
|
|
||||||
let markdown = language_registry.language_for_name("Markdown");
|
let markdown = language_registry.language_for_name("Markdown");
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
let markdown = markdown.await.context("failed to load Markdown language")?;
|
let markdown = markdown.await?;
|
||||||
buffer.update(&mut cx, |buffer, cx| {
|
buffer.update(&mut cx, |buffer, cx| {
|
||||||
buffer.set_language(Some(markdown), cx)
|
buffer.set_language(Some(markdown), cx)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ use gpui::{
|
|||||||
use project::{Project, RepositoryEntry};
|
use project::{Project, RepositoryEntry};
|
||||||
use recent_projects::RecentProjects;
|
use recent_projects::RecentProjects;
|
||||||
use rpc::proto::{self, DevServerStatus};
|
use rpc::proto::{self, DevServerStatus};
|
||||||
use settings::Settings;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::{ActiveTheme, ThemeSettings};
|
use theme::ActiveTheme;
|
||||||
use ui::{
|
use ui::{
|
||||||
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
|
h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike,
|
||||||
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
|
ButtonStyle, ContextMenu, Icon, IconButton, IconName, Indicator, TintColor, TitleBar, Tooltip,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
|
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
|
||||||
@@ -59,8 +58,6 @@ impl Render for CollabTitlebarItem {
|
|||||||
let project_id = self.project.read(cx).remote_id();
|
let project_id = self.project.read(cx).remote_id();
|
||||||
let workspace = self.workspace.upgrade();
|
let workspace = self.workspace.upgrade();
|
||||||
|
|
||||||
let platform_supported = cfg!(target_os = "macos");
|
|
||||||
|
|
||||||
TitleBar::new("collab-titlebar", Box::new(workspace::CloseWindow))
|
TitleBar::new("collab-titlebar", Box::new(workspace::CloseWindow))
|
||||||
// note: on windows titlebar behaviour is handled by the platform implementation
|
// note: on windows titlebar behaviour is handled by the platform implementation
|
||||||
.when(cfg!(not(windows)), |this| {
|
.when(cfg!(not(windows)), |this| {
|
||||||
@@ -74,7 +71,6 @@ impl Render for CollabTitlebarItem {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.children(self.render_application_menu(cx))
|
|
||||||
.children(self.render_project_host(cx))
|
.children(self.render_project_host(cx))
|
||||||
.child(self.render_project_name(cx))
|
.child(self.render_project_name(cx))
|
||||||
.children(self.render_project_branch(cx))
|
.children(self.render_project_branch(cx))
|
||||||
@@ -247,9 +243,7 @@ impl Render for CollabTitlebarItem {
|
|||||||
)
|
)
|
||||||
.tooltip(move |cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::text(
|
Tooltip::text(
|
||||||
if !platform_supported {
|
if is_muted {
|
||||||
"Cannot share microphone"
|
|
||||||
} else if is_muted {
|
|
||||||
"Unmute microphone"
|
"Unmute microphone"
|
||||||
} else {
|
} else {
|
||||||
"Mute microphone"
|
"Mute microphone"
|
||||||
@@ -259,8 +253,7 @@ impl Render for CollabTitlebarItem {
|
|||||||
})
|
})
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.selected(platform_supported && is_muted)
|
.selected(is_muted)
|
||||||
.disabled(!platform_supported)
|
|
||||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||||
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
|
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
|
||||||
)
|
)
|
||||||
@@ -278,11 +271,8 @@ impl Render for CollabTitlebarItem {
|
|||||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.selected(is_deafened)
|
.selected(is_deafened)
|
||||||
.disabled(!platform_supported)
|
|
||||||
.tooltip(move |cx| {
|
.tooltip(move |cx| {
|
||||||
if !platform_supported {
|
if can_use_microphone {
|
||||||
Tooltip::text("Cannot share microphone", cx)
|
|
||||||
} else if can_use_microphone {
|
|
||||||
Tooltip::with_meta(
|
Tooltip::with_meta(
|
||||||
"Deafen Audio",
|
"Deafen Audio",
|
||||||
None,
|
None,
|
||||||
@@ -301,13 +291,10 @@ impl Render for CollabTitlebarItem {
|
|||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.selected(is_screen_sharing)
|
.selected(is_screen_sharing)
|
||||||
.disabled(!platform_supported)
|
|
||||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
.tooltip(move |cx| {
|
.tooltip(move |cx| {
|
||||||
Tooltip::text(
|
Tooltip::text(
|
||||||
if !platform_supported {
|
if is_screen_sharing {
|
||||||
"Cannot share screen"
|
|
||||||
} else if is_screen_sharing {
|
|
||||||
"Stop Sharing Screen"
|
"Stop Sharing Screen"
|
||||||
} else {
|
} else {
|
||||||
"Share Screen"
|
"Share Screen"
|
||||||
@@ -388,173 +375,8 @@ impl CollabTitlebarItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
// resolve if you are in a room -> render_project_owner
|
||||||
cfg!(not(target_os = "macos")).then(|| {
|
// render_project_owner -> resolve if you are in a room -> Option<foo>
|
||||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
|
||||||
let font = cx.text_style().font();
|
|
||||||
let font_id = cx.text_system().resolve_font(&font);
|
|
||||||
let width = cx
|
|
||||||
.text_system()
|
|
||||||
.typographic_bounds(font_id, ui_font_size, 'm')
|
|
||||||
.unwrap()
|
|
||||||
.size
|
|
||||||
.width
|
|
||||||
* 3.0;
|
|
||||||
|
|
||||||
PopoverMenu::new("application-menu")
|
|
||||||
.menu(move |cx| {
|
|
||||||
let width = width;
|
|
||||||
ContextMenu::build(cx, move |menu, _cx| {
|
|
||||||
let width = width;
|
|
||||||
menu.header("Workspace")
|
|
||||||
.action("Open Command Palette", Box::new(command_palette::Toggle))
|
|
||||||
.custom_row(move |cx| {
|
|
||||||
div()
|
|
||||||
.w_full()
|
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.justify_between()
|
|
||||||
.cursor(gpui::CursorStyle::Arrow)
|
|
||||||
.child(Label::new("Buffer Font Size"))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.child(div().w(px(16.0)))
|
|
||||||
.child(
|
|
||||||
IconButton::new(
|
|
||||||
"reset-buffer-zoom",
|
|
||||||
IconName::RotateCcw,
|
|
||||||
)
|
|
||||||
.on_click(|_, cx| {
|
|
||||||
cx.dispatch_action(Box::new(
|
|
||||||
zed_actions::ResetBufferFontSize,
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("--buffer-zoom", IconName::Dash)
|
|
||||||
.on_click(|_, cx| {
|
|
||||||
cx.dispatch_action(Box::new(
|
|
||||||
zed_actions::DecreaseBufferFontSize,
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w(width)
|
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.justify_around()
|
|
||||||
.child(Label::new(
|
|
||||||
theme::get_buffer_font_size(cx).to_string(),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("+-buffer-zoom", IconName::Plus)
|
|
||||||
.on_click(|_, cx| {
|
|
||||||
cx.dispatch_action(Box::new(
|
|
||||||
zed_actions::IncreaseBufferFontSize,
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
.custom_row(move |cx| {
|
|
||||||
div()
|
|
||||||
.w_full()
|
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.justify_between()
|
|
||||||
.cursor(gpui::CursorStyle::Arrow)
|
|
||||||
.child(Label::new("UI Font Size"))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.child(
|
|
||||||
IconButton::new(
|
|
||||||
"reset-ui-zoom",
|
|
||||||
IconName::RotateCcw,
|
|
||||||
)
|
|
||||||
.on_click(|_, cx| {
|
|
||||||
cx.dispatch_action(Box::new(
|
|
||||||
zed_actions::ResetUiFontSize,
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("--ui-zoom", IconName::Dash)
|
|
||||||
.on_click(|_, cx| {
|
|
||||||
cx.dispatch_action(Box::new(
|
|
||||||
zed_actions::DecreaseUiFontSize,
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.w(width)
|
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.justify_around()
|
|
||||||
.child(Label::new(
|
|
||||||
theme::get_ui_font_size(cx).to_string(),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("+-ui-zoom", IconName::Plus)
|
|
||||||
.on_click(|_, cx| {
|
|
||||||
cx.dispatch_action(Box::new(
|
|
||||||
zed_actions::IncreaseUiFontSize,
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
.header("Project")
|
|
||||||
.action(
|
|
||||||
"Add Folder to Project...",
|
|
||||||
Box::new(workspace::AddFolderToProject),
|
|
||||||
)
|
|
||||||
.action("Open a new Project...", Box::new(workspace::Open))
|
|
||||||
.action(
|
|
||||||
"Open Recent Projects...",
|
|
||||||
Box::new(recent_projects::OpenRecent {
|
|
||||||
create_new_window: false,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.header("Help")
|
|
||||||
.action("About Zed", Box::new(zed_actions::About))
|
|
||||||
.action("Welcome", Box::new(workspace::Welcome))
|
|
||||||
.link(
|
|
||||||
"Documentation",
|
|
||||||
Box::new(zed_actions::OpenBrowser {
|
|
||||||
url: "https://zed.dev/docs".into(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.action("Give Feedback", Box::new(feedback::GiveFeedback))
|
|
||||||
.action("Check for Updates", Box::new(auto_update::Check))
|
|
||||||
.action("View Telemetry", Box::new(zed_actions::OpenTelemetryLog))
|
|
||||||
.action(
|
|
||||||
"View Dependency Licenses",
|
|
||||||
Box::new(zed_actions::OpenLicenses),
|
|
||||||
)
|
|
||||||
.separator()
|
|
||||||
.action("Quit", Box::new(zed_actions::Quit))
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.trigger(
|
|
||||||
IconButton::new("application-menu", ui::IconName::Menu)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.tooltip(|cx| Tooltip::text("Open Application Menu", cx))
|
|
||||||
.icon_size(IconSize::Small),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||||
if let Some(dev_server) =
|
if let Some(dev_server) =
|
||||||
@@ -591,17 +413,6 @@ impl CollabTitlebarItem {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.project.read(cx).is_disconnected() {
|
|
||||||
return Some(
|
|
||||||
Button::new("disconnected", "Disconnected")
|
|
||||||
.disabled(true)
|
|
||||||
.color(Color::Disabled)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.label_size(LabelSize::Small)
|
|
||||||
.into_any_element(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let host = self.project.read(cx).host()?;
|
let host = self.project.read(cx).host()?;
|
||||||
let host_user = self.user_store.read(cx).get_cached_user(host.user_id)?;
|
let host_user = self.user_store.read(cx).get_cached_user(host.user_id)?;
|
||||||
let participant_index = self
|
let participant_index = self
|
||||||
@@ -875,7 +686,7 @@ impl CollabTitlebarItem {
|
|||||||
.on_click(|_, cx| {
|
.on_click(|_, cx| {
|
||||||
if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
|
if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
|
||||||
if auto_updater.read(cx).status().is_updated() {
|
if auto_updater.read(cx).status().is_updated() {
|
||||||
workspace::reload(&Default::default(), cx);
|
workspace::restart(&Default::default(), cx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -906,13 +717,12 @@ impl CollabTitlebarItem {
|
|||||||
|
|
||||||
pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
|
pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||||
if let Some(user) = self.user_store.read(cx).current_user() {
|
if let Some(user) = self.user_store.read(cx).current_user() {
|
||||||
PopoverMenu::new("user-menu")
|
popover_menu("user-menu")
|
||||||
.menu(|cx| {
|
.menu(|cx| {
|
||||||
ContextMenu::build(cx, |menu, _| {
|
ContextMenu::build(cx, |menu, _| {
|
||||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||||
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
|
|
||||||
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
|
|
||||||
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
||||||
|
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
|
||||||
.separator()
|
.separator()
|
||||||
.action("Sign Out", client::SignOut.boxed_clone())
|
.action("Sign Out", client::SignOut.boxed_clone())
|
||||||
})
|
})
|
||||||
@@ -935,13 +745,12 @@ impl CollabTitlebarItem {
|
|||||||
)
|
)
|
||||||
.anchor(gpui::AnchorCorner::TopRight)
|
.anchor(gpui::AnchorCorner::TopRight)
|
||||||
} else {
|
} else {
|
||||||
PopoverMenu::new("user-menu")
|
popover_menu("user-menu")
|
||||||
.menu(|cx| {
|
.menu(|cx| {
|
||||||
ContextMenu::build(cx, |menu, _| {
|
ContextMenu::build(cx, |menu, _| {
|
||||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||||
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
|
|
||||||
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
|
|
||||||
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
.action("Extensions", extensions_ui::Extensions.boxed_clone())
|
||||||
|
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use call::{report_call_event_for_room, ActiveCall};
|
|||||||
pub use collab_panel::CollabPanel;
|
pub use collab_panel::CollabPanel;
|
||||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, AppContext, Pixels, PlatformDisplay, Size, Task, WindowBackgroundAppearance,
|
actions, point, AppContext, DevicePixels, Pixels, PlatformDisplay, Size, Task,
|
||||||
WindowBounds, WindowContext, WindowKind, WindowOptions,
|
WindowBackgroundAppearance, WindowBounds, WindowContext, WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
use panel_settings::MessageEditorSettings;
|
use panel_settings::MessageEditorSettings;
|
||||||
pub use panel_settings::{
|
pub use panel_settings::{
|
||||||
@@ -22,7 +22,6 @@ pub use panel_settings::{
|
|||||||
};
|
};
|
||||||
use release_channel::ReleaseChannel;
|
use release_channel::ReleaseChannel;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use ui::px;
|
|
||||||
use workspace::{notifications::DetachAndPromptErr, AppState};
|
use workspace::{notifications::DetachAndPromptErr, AppState};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
@@ -97,19 +96,22 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
|||||||
|
|
||||||
fn notification_window_options(
|
fn notification_window_options(
|
||||||
screen: Rc<dyn PlatformDisplay>,
|
screen: Rc<dyn PlatformDisplay>,
|
||||||
size: Size<Pixels>,
|
window_size: Size<Pixels>,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> WindowOptions {
|
) -> WindowOptions {
|
||||||
let notification_margin_width = px(16.);
|
let notification_margin_width = DevicePixels::from(16);
|
||||||
let notification_margin_height = px(-48.);
|
let notification_margin_height = DevicePixels::from(-0) - DevicePixels::from(48);
|
||||||
|
|
||||||
let bounds = gpui::Bounds::<Pixels> {
|
let screen_bounds = screen.bounds();
|
||||||
origin: screen.bounds().upper_right()
|
let size: Size<DevicePixels> = window_size.into();
|
||||||
|
|
||||||
|
let bounds = gpui::Bounds::<DevicePixels> {
|
||||||
|
origin: screen_bounds.upper_right()
|
||||||
- point(
|
- point(
|
||||||
size.width + notification_margin_width,
|
size.width + notification_margin_width,
|
||||||
notification_margin_height,
|
notification_margin_height,
|
||||||
),
|
),
|
||||||
size,
|
size: window_size.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let app_id = ReleaseChannel::global(cx).app_id();
|
let app_id = ReleaseChannel::global(cx).app_id();
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ use crate::notifications::collab_notification::CollabNotification;
|
|||||||
use call::{ActiveCall, IncomingCall};
|
use call::{ActiveCall, IncomingCall};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{prelude::*, AppContext, WindowHandle};
|
use gpui::{prelude::*, AppContext, WindowHandle};
|
||||||
|
use settings::Settings;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use theme::ThemeSettings;
|
||||||
use ui::{prelude::*, Button, Label};
|
use ui::{prelude::*, Button, Label};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
@@ -112,7 +113,13 @@ impl IncomingCallNotification {
|
|||||||
|
|
||||||
impl Render for IncomingCallNotification {
|
impl Render for IncomingCallNotification {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let ui_font = theme::setup_ui_font(cx);
|
// TODO: Is there a better place for us to initialize the font?
|
||||||
|
let (ui_font, ui_font_size) = {
|
||||||
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
|
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.set_rem_size(ui_font_size);
|
||||||
|
|
||||||
div().size_full().font(ui_font).child(
|
div().size_full().font(ui_font).child(
|
||||||
CollabNotification::new(
|
CollabNotification::new(
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use call::{room, ActiveCall};
|
|||||||
use client::User;
|
use client::User;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{AppContext, Size};
|
use gpui::{AppContext, Size};
|
||||||
|
use settings::Settings;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use theme::ThemeSettings;
|
||||||
use ui::{prelude::*, Button, Label};
|
use ui::{prelude::*, Button, Label};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
@@ -27,21 +27,16 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||||||
|
|
||||||
for screen in cx.displays() {
|
for screen in cx.displays() {
|
||||||
let options = notification_window_options(screen, window_size, cx);
|
let options = notification_window_options(screen, window_size, cx);
|
||||||
let Some(window) = cx
|
let window = cx.open_window(options, |cx| {
|
||||||
.open_window(options, |cx| {
|
cx.new_view(|_| {
|
||||||
cx.new_view(|_| {
|
ProjectSharedNotification::new(
|
||||||
ProjectSharedNotification::new(
|
owner.clone(),
|
||||||
owner.clone(),
|
*project_id,
|
||||||
*project_id,
|
worktree_root_names.clone(),
|
||||||
worktree_root_names.clone(),
|
app_state.clone(),
|
||||||
app_state.clone(),
|
)
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.log_err()
|
});
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
notification_windows
|
notification_windows
|
||||||
.entry(*project_id)
|
.entry(*project_id)
|
||||||
.or_insert(Vec::new())
|
.or_insert(Vec::new())
|
||||||
@@ -123,7 +118,13 @@ impl ProjectSharedNotification {
|
|||||||
|
|
||||||
impl Render for ProjectSharedNotification {
|
impl Render for ProjectSharedNotification {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let ui_font = theme::setup_ui_font(cx);
|
// TODO: Is there a better place for us to initialize the font?
|
||||||
|
let (ui_font, ui_font_size) = {
|
||||||
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
|
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.set_rem_size(ui_font_size);
|
||||||
|
|
||||||
div().size_full().font(ui_font).child(
|
div().size_full().font(ui_font).child(
|
||||||
CollabNotification::new(
|
CollabNotification::new(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "paths"
|
name = "color"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
@@ -8,9 +8,12 @@ license = "GPL-3.0-or-later"
|
|||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/paths.rs"
|
path = "src/color.rs"
|
||||||
|
doctest = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dirs.workspace = true
|
palette.workspace = true
|
||||||
util.workspace = true
|
|
||||||
227
crates/color/src/color.rs
Normal file
227
crates/color/src/color.rs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
//! # Color
|
||||||
|
//!
|
||||||
|
//! The `color` crate provides a set utilities for working with colors. It is a wrapper around the [`palette`](https://docs.rs/palette) crate with some additional functionality.
|
||||||
|
//!
|
||||||
|
//! It is used to create a manipulate colors when building themes.
|
||||||
|
//!
|
||||||
|
//! === In development note ===
|
||||||
|
//!
|
||||||
|
//! This crate is meant to sit between gpui and the theme/ui for all the color related stuff.
|
||||||
|
//!
|
||||||
|
//! It could be folded into gpui, ui or theme potentially but for now we'll continue
|
||||||
|
//! to develop it in isolation.
|
||||||
|
//!
|
||||||
|
//! Once we have a good idea of the needs of the theme system and color in gpui in general I see 3 paths:
|
||||||
|
//! 1. Use `palette` (or another color library) directly in gpui and everywhere else, rather than rolling our own color system.
|
||||||
|
//! 2. Keep this crate as a thin wrapper around `palette` and use it everywhere except gpui, and convert to gpui's color system when needed.
|
||||||
|
//! 3. Build the needed functionality into gpui and keep using its color system everywhere.
|
||||||
|
//!
|
||||||
|
//! I'm leaning towards 2 in the short term and 1 in the long term, but we'll need to discuss it more.
|
||||||
|
//!
|
||||||
|
//! === End development note ===
|
||||||
|
use palette::{
|
||||||
|
blend::Blend, convert::FromColorUnclamped, encoding, rgb::Rgb, Clamp, Mix, Srgb, WithAlpha,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The types of blend modes supported
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum BlendMode {
|
||||||
|
/// Multiplies the colors, resulting in a darker color. This mode is useful for creating shadows.
|
||||||
|
Multiply,
|
||||||
|
/// Lightens the color by adding the source and destination colors. It results in a lighter color.
|
||||||
|
Screen,
|
||||||
|
/// Combines Multiply and Screen blend modes. Parts of the image that are lighter than 50% gray are lightened, and parts that are darker are darkened.
|
||||||
|
Overlay,
|
||||||
|
/// Selects the darker of the base or blend color as the resulting color. Useful for darkening images without affecting the overall contrast.
|
||||||
|
Darken,
|
||||||
|
/// Selects the lighter of the base or blend color as the resulting color. Useful for lightening images without affecting the overall contrast.
|
||||||
|
Lighten,
|
||||||
|
/// Brightens the base color to reflect the blend color. The result is a lightened image.
|
||||||
|
Dodge,
|
||||||
|
/// Darkens the base color to reflect the blend color. The result is a darkened image.
|
||||||
|
Burn,
|
||||||
|
/// Similar to Overlay, but with a stronger effect. Hard Light can either multiply or screen colors, depending on the blend color.
|
||||||
|
HardLight,
|
||||||
|
/// A softer version of Hard Light. Soft Light either darkens or lightens colors, depending on the blend color.
|
||||||
|
SoftLight,
|
||||||
|
/// Subtracts the darker of the two constituent colors from the lighter color. Difference mode is useful for creating more vivid colors.
|
||||||
|
Difference,
|
||||||
|
/// Similar to Difference, but with a lower contrast. Exclusion mode produces an effect similar to Difference but with less intensity.
|
||||||
|
Exclusion,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a hexadecimal color string to a `palette::Hsla` color.
|
||||||
|
///
|
||||||
|
/// This function supports the following hex formats:
|
||||||
|
/// `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`.
|
||||||
|
pub fn hex_to_hsla(s: &str) -> Result<RGBAColor, String> {
|
||||||
|
let hex = s.trim_start_matches('#');
|
||||||
|
|
||||||
|
// Expand shorthand formats #RGB and #RGBA to #RRGGBB and #RRGGBBAA
|
||||||
|
let h = hex.as_bytes();
|
||||||
|
let arr: [u8; 8] = match h.len() {
|
||||||
|
// #RGB => #RRGGBBAA
|
||||||
|
3 => [h[0], h[0], h[1], h[1], h[2], h[2], b'f', b'f'],
|
||||||
|
// #RGBA => #RRGGBBAA
|
||||||
|
4 => [h[0], h[0], h[1], h[1], h[2], h[2], h[3], h[3]],
|
||||||
|
// #RRGGBB => #RRGGBBAA
|
||||||
|
6 => [h[0], h[1], h[2], h[3], h[4], h[5], b'f', b'f'],
|
||||||
|
// Already in #RRGGBBAA
|
||||||
|
8 => h.try_into().unwrap(),
|
||||||
|
_ => return Err("Invalid hexadecimal string length".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let hex =
|
||||||
|
std::str::from_utf8(&arr).map_err(|_| format!("Invalid hexadecimal string: {}", s))?;
|
||||||
|
let hex_val =
|
||||||
|
u32::from_str_radix(hex, 16).map_err(|_| format!("Invalid hexadecimal string: {}", s))?;
|
||||||
|
|
||||||
|
Ok(RGBAColor {
|
||||||
|
r: ((hex_val >> 24) & 0xFF) as f32 / 255.0,
|
||||||
|
g: ((hex_val >> 16) & 0xFF) as f32 / 255.0,
|
||||||
|
b: ((hex_val >> 8) & 0xFF) as f32 / 255.0,
|
||||||
|
a: (hex_val & 0xFF) as f32 / 255.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// These derives implement to and from palette's color types.
|
||||||
|
#[derive(FromColorUnclamped, WithAlpha, Debug, Clone)]
|
||||||
|
#[palette(skip_derives(Rgb), rgb_standard = "encoding::Srgb")]
|
||||||
|
pub struct RGBAColor {
|
||||||
|
r: f32,
|
||||||
|
g: f32,
|
||||||
|
b: f32,
|
||||||
|
// Let Palette know this is our alpha channel.
|
||||||
|
#[palette(alpha)]
|
||||||
|
a: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromColorUnclamped<RGBAColor> for RGBAColor {
|
||||||
|
fn from_color_unclamped(color: RGBAColor) -> RGBAColor {
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> FromColorUnclamped<Rgb<S, f32>> for RGBAColor
|
||||||
|
where
|
||||||
|
Srgb: FromColorUnclamped<Rgb<S, f32>>,
|
||||||
|
{
|
||||||
|
fn from_color_unclamped(color: Rgb<S, f32>) -> RGBAColor {
|
||||||
|
let srgb = Srgb::from_color_unclamped(color);
|
||||||
|
RGBAColor {
|
||||||
|
r: srgb.red,
|
||||||
|
g: srgb.green,
|
||||||
|
b: srgb.blue,
|
||||||
|
a: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> FromColorUnclamped<RGBAColor> for Rgb<S, f32>
|
||||||
|
where
|
||||||
|
Rgb<S, f32>: FromColorUnclamped<Srgb>,
|
||||||
|
{
|
||||||
|
fn from_color_unclamped(color: RGBAColor) -> Self {
|
||||||
|
Self::from_color_unclamped(Srgb::new(color.r, color.g, color.b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clamp for RGBAColor {
|
||||||
|
fn clamp(self) -> Self {
|
||||||
|
RGBAColor {
|
||||||
|
r: self.r.clamp(0., 1.),
|
||||||
|
g: self.g.clamp(0., 1.),
|
||||||
|
b: self.b.clamp(0., 1.),
|
||||||
|
a: self.a.clamp(0., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RGBAColor {
|
||||||
|
/// Creates a new color from the given RGBA values.
|
||||||
|
///
|
||||||
|
/// This color can be used to convert to any [`palette::Color`] type.
|
||||||
|
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||||
|
RGBAColor { r, g, b, a }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a set of states for this color.
|
||||||
|
pub fn states(self, is_light: bool) -> ColorStates {
|
||||||
|
states_for_color(self, is_light)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mixes this color with another [`palette::Hsl`] color at the given `mix_ratio`.
|
||||||
|
pub fn mixed(&self, other: RGBAColor, mix_ratio: f32) -> Self {
|
||||||
|
let srgb_self = Srgb::new(self.r, self.g, self.b);
|
||||||
|
let srgb_other = Srgb::new(other.r, other.g, other.b);
|
||||||
|
|
||||||
|
// Directly mix the colors as sRGB values
|
||||||
|
let mixed = srgb_self.mix(srgb_other, mix_ratio);
|
||||||
|
RGBAColor::from_color_unclamped(mixed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blend(&self, other: RGBAColor, blend_mode: BlendMode) -> Self {
|
||||||
|
let srgb_self = Srgb::new(self.r, self.g, self.b);
|
||||||
|
let srgb_other = Srgb::new(other.r, other.g, other.b);
|
||||||
|
|
||||||
|
let blended = match blend_mode {
|
||||||
|
// replace hsl methods with the respective sRGB methods
|
||||||
|
BlendMode::Multiply => srgb_self.multiply(srgb_other),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
r: blended.red,
|
||||||
|
g: blended.green,
|
||||||
|
b: blended.blue,
|
||||||
|
a: self.a,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of colors for different states of an element.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ColorStates {
|
||||||
|
/// The default color.
|
||||||
|
pub default: RGBAColor,
|
||||||
|
/// The color when the mouse is hovering over the element.
|
||||||
|
pub hover: RGBAColor,
|
||||||
|
/// The color when the mouse button is held down on the element.
|
||||||
|
pub active: RGBAColor,
|
||||||
|
/// The color when the element is focused with the keyboard.
|
||||||
|
pub focused: RGBAColor,
|
||||||
|
/// The color when the element is disabled.
|
||||||
|
pub disabled: RGBAColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a set of colors for different states of an element.
|
||||||
|
///
|
||||||
|
/// todo("This should take a theme and use appropriate colors from it")
|
||||||
|
pub fn states_for_color(color: RGBAColor, is_light: bool) -> ColorStates {
|
||||||
|
let adjustment_factor = if is_light { 0.1 } else { -0.1 };
|
||||||
|
let hover_adjustment = 1.0 - adjustment_factor;
|
||||||
|
let active_adjustment = 1.0 - 2.0 * adjustment_factor;
|
||||||
|
let focused_adjustment = 1.0 - 3.0 * adjustment_factor;
|
||||||
|
let disabled_adjustment = 1.0 - 4.0 * adjustment_factor;
|
||||||
|
|
||||||
|
let make_adjustment = |color: RGBAColor, adjustment: f32| -> RGBAColor {
|
||||||
|
// Adjust lightness for each state
|
||||||
|
// Note: Adjustment logic may differ; simplify as needed for sRGB
|
||||||
|
RGBAColor::new(
|
||||||
|
color.r * adjustment,
|
||||||
|
color.g * adjustment,
|
||||||
|
color.b * adjustment,
|
||||||
|
color.a,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let color = color.clamp();
|
||||||
|
|
||||||
|
ColorStates {
|
||||||
|
default: color.clone(),
|
||||||
|
hover: make_adjustment(color.clone(), hover_adjustment),
|
||||||
|
active: make_adjustment(color.clone(), active_adjustment),
|
||||||
|
focused: make_adjustment(color.clone(), focused_adjustment),
|
||||||
|
disabled: make_adjustment(color.clone(), disabled_adjustment),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,6 @@ lsp.workspace = true
|
|||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
node_runtime.workspace = true
|
node_runtime.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use util::{fs::remove_matching, maybe, ResultExt};
|
use util::{fs::remove_matching, maybe, paths, ResultExt};
|
||||||
|
|
||||||
pub use copilot_completion_provider::CopilotCompletionProvider;
|
pub use copilot_completion_provider::CopilotCompletionProvider;
|
||||||
pub use sign_in::CopilotCodeVerification;
|
pub use sign_in::CopilotCodeVerification;
|
||||||
@@ -968,7 +968,7 @@ fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::Url {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn clear_copilot_dir() {
|
async fn clear_copilot_dir() {
|
||||||
remove_matching(paths::copilot_dir(), |_| true).await
|
remove_matching(&paths::COPILOT_DIR, |_| true).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||||
@@ -979,7 +979,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||||||
let release =
|
let release =
|
||||||
latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
|
latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
|
||||||
|
|
||||||
let version_dir = &paths::copilot_dir().join(format!("copilot-{}", release.tag_name));
|
let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.tag_name));
|
||||||
|
|
||||||
fs::create_dir_all(version_dir).await?;
|
fs::create_dir_all(version_dir).await?;
|
||||||
let server_path = version_dir.join(SERVER_PATH);
|
let server_path = version_dir.join(SERVER_PATH);
|
||||||
@@ -1003,7 +1003,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||||||
let archive = Archive::new(decompressed_bytes);
|
let archive = Archive::new(decompressed_bytes);
|
||||||
archive.unpack(dist_dir).await?;
|
archive.unpack(dist_dir).await?;
|
||||||
|
|
||||||
remove_matching(paths::copilot_dir(), |entry| entry != version_dir).await;
|
remove_matching(&paths::COPILOT_DIR, |entry| entry != version_dir).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(server_path)
|
Ok(server_path)
|
||||||
@@ -1016,7 +1016,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||||||
// Fetch a cached binary, if it exists
|
// Fetch a cached binary, if it exists
|
||||||
maybe!(async {
|
maybe!(async {
|
||||||
let mut last_version_dir = None;
|
let mut last_version_dir = None;
|
||||||
let mut entries = fs::read_dir(paths::copilot_dir()).await?;
|
let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
|
||||||
while let Some(entry) = entries.next().await {
|
while let Some(entry) = entries.next().await {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
if entry.file_type().await?.is_dir() {
|
if entry.file_type().await?.is_dir() {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ gpui.workspace = true
|
|||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
paths.workspace = true
|
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
sqlez.workspace = true
|
sqlez.workspace = true
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ use anyhow::Context;
|
|||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
pub use indoc::indoc;
|
pub use indoc::indoc;
|
||||||
pub use lazy_static;
|
pub use lazy_static;
|
||||||
pub use paths::database_dir;
|
|
||||||
pub use smol;
|
pub use smol;
|
||||||
pub use sqlez;
|
pub use sqlez;
|
||||||
pub use sqlez_macros;
|
pub use sqlez_macros;
|
||||||
|
pub use util::paths::DB_DIR;
|
||||||
|
|
||||||
use release_channel::ReleaseChannel;
|
use release_channel::ReleaseChannel;
|
||||||
pub use release_channel::RELEASE_CHANNEL;
|
pub use release_channel::RELEASE_CHANNEL;
|
||||||
@@ -145,7 +145,7 @@ macro_rules! define_connection {
|
|||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
$crate::lazy_static::lazy_static! {
|
$crate::lazy_static::lazy_static! {
|
||||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
|
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
|
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
|
||||||
@@ -176,7 +176,7 @@ macro_rules! define_connection {
|
|||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
$crate::lazy_static::lazy_static! {
|
$crate::lazy_static::lazy_static! {
|
||||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
|
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user