Compare commits
86 Commits
dynamic-2
...
vim-comman
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbaf6fbb19 | ||
|
|
8c4fb34f6e | ||
|
|
d4891a62bb | ||
|
|
17bc0d1b17 | ||
|
|
db0d843fb1 | ||
|
|
ad4e52842c | ||
|
|
ca18549e02 | ||
|
|
5cbb360952 | ||
|
|
5ff7c893be | ||
|
|
99e4b3a4cf | ||
|
|
9af4b6bfc7 | ||
|
|
c84d432b5f | ||
|
|
6cea9813ad | ||
|
|
490a75aee6 | ||
|
|
4f364d6d09 | ||
|
|
89d2ace713 | ||
|
|
84a44bef8a | ||
|
|
6b9ddbfef2 | ||
|
|
8af8493da6 | ||
|
|
39edbe1c50 | ||
|
|
f6fa6600bc | ||
|
|
b55961b57a | ||
|
|
01b836a191 | ||
|
|
41180b8d81 | ||
|
|
81475ac4cd | ||
|
|
ba59e66314 | ||
|
|
5ede48337c | ||
|
|
3701e190ce | ||
|
|
5dc26c261d | ||
|
|
64d815a176 | ||
|
|
5dc54863a4 | ||
|
|
e4ba336971 | ||
|
|
195a270e18 | ||
|
|
3a26a4809d | ||
|
|
479c5df491 | ||
|
|
51404d4ea0 | ||
|
|
05c4c7872c | ||
|
|
0af6e442a7 | ||
|
|
7003b0f211 | ||
|
|
f489c8b79f | ||
|
|
be02b2faf4 | ||
|
|
258a8a37d8 | ||
|
|
78e0f71a28 | ||
|
|
59104a08fd | ||
|
|
7aa28c9b24 | ||
|
|
bb1d52b485 | ||
|
|
ca035dbdd8 | ||
|
|
71cc95d315 | ||
|
|
3707734f0a | ||
|
|
e19627d92f | ||
|
|
bb75d87285 | ||
|
|
eecbf203dc | ||
|
|
7fe5c27597 | ||
|
|
221edfc267 | ||
|
|
d95c424d18 | ||
|
|
d6d56191da | ||
|
|
2e87e1d26e | ||
|
|
e8862c45cc | ||
|
|
0c28b6a11a | ||
|
|
16fce64d3a | ||
|
|
b075ce8f04 | ||
|
|
54828ab836 | ||
|
|
6322351f00 | ||
|
|
78091fa91e | ||
|
|
d5735dab9a | ||
|
|
c793bbde84 | ||
|
|
03c54623d4 | ||
|
|
0afb3abfd2 | ||
|
|
2b46a4a0e9 | ||
|
|
bedf57db89 | ||
|
|
4855da53df | ||
|
|
15d3e54ae3 | ||
|
|
064bdab459 | ||
|
|
38cb95f427 | ||
|
|
7cc2538fe1 | ||
|
|
fc19cc0ddf | ||
|
|
e6def62c23 | ||
|
|
ff2347dff5 | ||
|
|
6319ae0b4a | ||
|
|
a8bd602334 | ||
|
|
af45db6d1e | ||
|
|
fab4b01655 | ||
|
|
2f6cb49d84 | ||
|
|
411ee7a47c | ||
|
|
831f7dbbc0 | ||
|
|
78fd378702 |
@@ -1,15 +0,0 @@
|
||||
# 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 --"
|
||||
5
.cargo/collab-config.toml
Normal file
5
.cargo/collab-config.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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"]
|
||||
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"
|
||||
labels: ["admin read", "triage", "enhancement"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
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.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
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.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
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
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
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,40 +1,45 @@
|
||||
name: Bug Report
|
||||
description: |
|
||||
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.
|
||||
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.
|
||||
labels: ["admin read", "triage", "defect"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
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.
|
||||
options:
|
||||
- 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:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
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.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||
description: Drag issues into the text input below
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
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.
|
||||
validations:
|
||||
required: false
|
||||
- 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
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||
description: Drag issues into the text input below
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
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,33 +1,38 @@
|
||||
name: Crash Report
|
||||
description: |
|
||||
Use this template for crash reports.
|
||||
Use this template for crash reports.
|
||||
labels: ["admin read", "triage", "defect", "panic / crash"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
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.
|
||||
options:
|
||||
- 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:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
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.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
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.
|
||||
validations:
|
||||
required: false
|
||||
- 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
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
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
|
||||
|
||||
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -38,9 +38,6 @@ jobs:
|
||||
- name: Remove untracked files
|
||||
run: git clean -df
|
||||
|
||||
- name: Set up default .cargo/config.toml
|
||||
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
||||
|
||||
- name: Check spelling
|
||||
run: |
|
||||
if ! which typos > /dev/null; then
|
||||
@@ -54,6 +51,9 @@ jobs:
|
||||
- name: Check unused dependencies
|
||||
uses: bnjbvr/cargo-machete@main
|
||||
|
||||
- name: Check licenses are present
|
||||
run: script/check-licenses
|
||||
|
||||
- name: Check license generation
|
||||
run: script/generate-licenses /tmp/zed_licenses_output
|
||||
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo xtask clippy
|
||||
run: ./script/clippy
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo xtask clippy
|
||||
run: ./script/clippy
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo xtask clippy
|
||||
run: ./script/clippy
|
||||
|
||||
- name: Build Zed
|
||||
run: cargo build -p zed
|
||||
@@ -353,7 +353,7 @@ jobs:
|
||||
|
||||
- uses: rui314/setup-mold@v1
|
||||
with:
|
||||
mold-version: 2.32.0
|
||||
mold_version: 2.32.0
|
||||
|
||||
- name: rustup
|
||||
run: |
|
||||
|
||||
5
.github/workflows/deploy_collab.yml
vendored
5
.github/workflows/deploy_collab.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo xtask clippy
|
||||
run: ./script/clippy
|
||||
|
||||
tests:
|
||||
name: Run tests
|
||||
@@ -75,6 +75,9 @@ jobs:
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Set up default .cargo/config.toml
|
||||
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
|
||||
|
||||
- name: Build docker image
|
||||
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
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo xtask clippy
|
||||
run: ./script/clippy
|
||||
tests:
|
||||
timeout-minutes: 60
|
||||
name: Run tests
|
||||
|
||||
17
.mailmap
17
.mailmap
@@ -9,12 +9,18 @@
|
||||
# Keep these entries sorted alphabetically.
|
||||
# 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@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> <magiclake@gmx.de>
|
||||
Conrad Irwin <conrad@zed.dev>
|
||||
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.gamail.com@gmail.com>
|
||||
Greg Morenz <greg-morenz@droid.cafe>
|
||||
@@ -48,12 +54,23 @@ Nate Butler <iamnbutler@gmail.com> <nate@zed.dev>
|
||||
Nathan Sobo <nathan@zed.dev>
|
||||
Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
|
||||
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@zed.dev>
|
||||
Piotr Osiewicz <piotr@zed.dev>
|
||||
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.gdn>
|
||||
Sergey Onufrienko <sergey@onufrienko.com>
|
||||
Thorsten Ball <thorsten@zed.dev>
|
||||
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.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",
|
||||
"command": "cargo",
|
||||
"args": ["xtask", "clippy"]
|
||||
"command": "./script/clippy",
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
|
||||
471
Cargo.lock
generated
471
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@@ -65,6 +65,7 @@ members = [
|
||||
"crates/open_ai",
|
||||
"crates/outline",
|
||||
"crates/outline_panel",
|
||||
"crates/paths",
|
||||
"crates/picker",
|
||||
"crates/prettier",
|
||||
"crates/project",
|
||||
@@ -77,6 +78,7 @@ members = [
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/release_channel",
|
||||
"crates/dev_server_projects",
|
||||
"crates/repl",
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
@@ -214,6 +216,7 @@ ollama = { path = "crates/ollama" }
|
||||
open_ai = { path = "crates/open_ai" }
|
||||
outline = { path = "crates/outline" }
|
||||
outline_panel = { path = "crates/outline_panel" }
|
||||
paths = { path = "crates/paths" }
|
||||
picker = { path = "crates/picker" }
|
||||
plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
@@ -227,6 +230,7 @@ quick_action_bar = { path = "crates/quick_action_bar" }
|
||||
recent_projects = { path = "crates/recent_projects" }
|
||||
release_channel = { path = "crates/release_channel" }
|
||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
||||
repl = { path = "crates/repl" }
|
||||
rich_text = { path = "crates/rich_text" }
|
||||
rope = { path = "crates/rope" }
|
||||
rpc = { path = "crates/rpc" }
|
||||
@@ -264,10 +268,12 @@ workspace = { path = "crates/workspace" }
|
||||
zed = { path = "crates/zed" }
|
||||
zed_actions = { path = "crates/zed_actions" }
|
||||
|
||||
alacritty_terminal = "0.23"
|
||||
anyhow = "1.0.57"
|
||||
any_vec = "0.13"
|
||||
ashpd = "0.8.0"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-dispatcher = { version = "0.1"}
|
||||
async-fs = "1.6"
|
||||
async-recursion = "1.0.0"
|
||||
async-tar = "0.4.2"
|
||||
@@ -284,10 +290,10 @@ clap = { version = "4.4", features = ["derive"] }
|
||||
clickhouse = { version = "0.11.6" }
|
||||
cocoa = "0.25"
|
||||
ctor = "0.2.6"
|
||||
signal-hook = "0.3.17"
|
||||
core-foundation = { version = "0.9.3" }
|
||||
core-foundation-sys = "0.8.6"
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
emojis = "0.6.1"
|
||||
env_logger = "0.9"
|
||||
exec = "0.3.1"
|
||||
@@ -295,16 +301,20 @@ fork = "0.1.23"
|
||||
futures = "0.3"
|
||||
futures-batch = "0.6.1"
|
||||
futures-lite = "1.13"
|
||||
git2 = { version = "0.18", default-features = false }
|
||||
git2 = { version = "0.19", default-features = false }
|
||||
globset = "0.4"
|
||||
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
||||
hex = "0.4.3"
|
||||
html5ever = "0.27.0"
|
||||
ignore = "0.4.22"
|
||||
image = "0.23"
|
||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
indoc = "1"
|
||||
# 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"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
@@ -330,6 +340,7 @@ rand = "0.8.5"
|
||||
refineable = { path = "./crates/refineable" }
|
||||
regex = "1.5"
|
||||
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"] }
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
schemars = "0.8"
|
||||
@@ -345,6 +356,7 @@ serde_repr = "0.1"
|
||||
sha2 = "0.10"
|
||||
shellexpand = "2.1.0"
|
||||
shlex = "1.3.0"
|
||||
signal-hook = "0.3.17"
|
||||
similar = "1.3"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "1.2"
|
||||
|
||||
1
assets/icons/rotate_ccw.svg
Normal file
1
assets/icons/rotate_ccw.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 302 B |
1
assets/icons/text-cursor.svg
Normal file
1
assets/icons/text-cursor.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -39,6 +39,7 @@
|
||||
"right": "vim::Right",
|
||||
"space": "vim::Space",
|
||||
"$": "vim::EndOfLine",
|
||||
"end": "vim::EndOfLine",
|
||||
"^": "vim::FirstNonWhitespace",
|
||||
"_": "vim::StartOfLineDownward",
|
||||
"g _": "vim::EndOfLineDownward",
|
||||
@@ -140,7 +141,8 @@
|
||||
"ctrl-q": "vim::ToggleVisualBlock",
|
||||
"shift-k": "editor::Hover",
|
||||
"shift-r": "vim::ToggleReplace",
|
||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||
"0": "vim::StartOfLine",
|
||||
"home": "vim::StartOfLine",
|
||||
"ctrl-f": "vim::PageDown",
|
||||
"pagedown": "vim::PageDown",
|
||||
"ctrl-b": "vim::PageUp",
|
||||
@@ -408,7 +410,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && VimCount",
|
||||
"context": "Editor && VimCount && vim_mode != insert",
|
||||
"bindings": {
|
||||
"0": ["vim::Number", 0]
|
||||
}
|
||||
@@ -634,8 +636,7 @@
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-t": "vim::Indent",
|
||||
"ctrl-d": "vim::Outdent",
|
||||
"ctrl-r \"": "editor::Paste",
|
||||
"ctrl-r +": "editor::Paste"
|
||||
"ctrl-r": ["vim::PushOperator", "Register"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -656,6 +657,13 @@
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == insert && VimWaiting",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace",
|
||||
"bindings": {
|
||||
@@ -664,7 +672,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen",
|
||||
"context": "EmptyPane || SharedScreen || Workspace",
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle"
|
||||
}
|
||||
|
||||
@@ -131,7 +131,14 @@
|
||||
// The default number of lines to expand excerpts in the multibuffer by.
|
||||
"expand_excerpt_lines": 3,
|
||||
// Globs to match against file paths to determine if a file is private.
|
||||
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
|
||||
"private_files": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
"**/*.key",
|
||||
"**/*.cert",
|
||||
"**/*.crt",
|
||||
"**/secrets.yml"
|
||||
],
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
@@ -145,9 +152,9 @@
|
||||
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
|
||||
// no matter how they were inserted.
|
||||
"always_treat_brackets_as_autoclosed": false,
|
||||
// Controls whether copilot provides suggestion immediately
|
||||
// or waits for a `copilot::Toggle`
|
||||
"show_copilot_suggestions": true,
|
||||
// Controls whether inline completions are shown immediately (true)
|
||||
// or manually by triggering `editor::ShowInlineCompletion` (false).
|
||||
"show_inline_completions": true,
|
||||
// Whether to show tabs and spaces in the editor.
|
||||
// This setting can take three values:
|
||||
//
|
||||
@@ -176,7 +183,9 @@
|
||||
// Whether to show breadcrumbs.
|
||||
"breadcrumbs": true,
|
||||
// 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": {
|
||||
@@ -516,9 +525,8 @@
|
||||
// "delay_ms": 600
|
||||
}
|
||||
},
|
||||
"copilot": {
|
||||
// The set of glob patterns for which copilot should be disabled
|
||||
// in any matching file.
|
||||
"inline_completions": {
|
||||
// A list of globs representing files that inline completions should be disabled for.
|
||||
"disabled_globs": [".env"]
|
||||
},
|
||||
// Settings specific to journaling
|
||||
@@ -723,7 +731,7 @@
|
||||
}
|
||||
},
|
||||
"JavaScript": {
|
||||
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
@@ -766,7 +774,7 @@
|
||||
}
|
||||
},
|
||||
"TSX": {
|
||||
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
@@ -777,7 +785,7 @@
|
||||
}
|
||||
},
|
||||
"TypeScript": {
|
||||
"language_servers": ["typescript-language-server", "!vtsls", "..."],
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ use editor::Editor;
|
||||
use extension::ExtensionStore;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
|
||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
|
||||
ViewContext, VisualContext as _,
|
||||
actions, anchored, deferred, percentage, Animation, AnimationExt as _, AppContext, CursorStyle,
|
||||
DismissEvent, EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render,
|
||||
SharedString, StatefulInteractiveElement, Styled, Transformation, View, ViewContext,
|
||||
VisualContext as _,
|
||||
};
|
||||
use language::{
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use project::{LanguageServerProgress, Project};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp::Reverse, fmt::Write, sync::Arc};
|
||||
use ui::prelude::*;
|
||||
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||
use ui::{prelude::*, ContextMenu};
|
||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||
|
||||
actions!(activity_indicator, [ShowErrorMessage]);
|
||||
|
||||
const DOWNLOAD_ICON: &str = "icons/download.svg";
|
||||
const WARNING_ICON: &str = "icons/warning.svg";
|
||||
|
||||
pub enum Event {
|
||||
ShowError { lsp_name: Arc<str>, error: String },
|
||||
}
|
||||
@@ -27,6 +27,7 @@ pub struct ActivityIndicator {
|
||||
statuses: Vec<LspStatus>,
|
||||
project: Model<Project>,
|
||||
auto_updater: Option<Model<AutoUpdater>>,
|
||||
context_menu: Option<View<ContextMenu>>,
|
||||
}
|
||||
|
||||
struct LspStatus {
|
||||
@@ -35,14 +36,14 @@ struct LspStatus {
|
||||
}
|
||||
|
||||
struct PendingWork<'a> {
|
||||
language_server_name: &'a str,
|
||||
language_server_id: LanguageServerId,
|
||||
progress_token: &'a str,
|
||||
progress: &'a LanguageServerProgress,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Content {
|
||||
icon: Option<&'static str>,
|
||||
icon: Option<gpui::AnyElement>,
|
||||
message: String,
|
||||
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
|
||||
}
|
||||
@@ -78,6 +79,7 @@ impl ActivityIndicator {
|
||||
statuses: Default::default(),
|
||||
project: project.clone(),
|
||||
auto_updater,
|
||||
context_menu: None,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -151,7 +153,7 @@ impl ActivityIndicator {
|
||||
.read(cx)
|
||||
.language_server_statuses()
|
||||
.rev()
|
||||
.filter_map(|status| {
|
||||
.filter_map(|(server_id, status)| {
|
||||
if status.pending_work.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -159,7 +161,7 @@ impl ActivityIndicator {
|
||||
.pending_work
|
||||
.iter()
|
||||
.map(|(token, progress)| PendingWork {
|
||||
language_server_name: status.name.as_str(),
|
||||
language_server_id: server_id,
|
||||
progress_token: token.as_str(),
|
||||
progress,
|
||||
})
|
||||
@@ -175,33 +177,44 @@ impl ActivityIndicator {
|
||||
// Show any language server has pending activity.
|
||||
let mut pending_work = self.pending_language_server_work(cx);
|
||||
if let Some(PendingWork {
|
||||
language_server_name,
|
||||
progress_token,
|
||||
progress,
|
||||
..
|
||||
}) = pending_work.next()
|
||||
{
|
||||
let mut message = language_server_name.to_string();
|
||||
|
||||
message.push_str(": ");
|
||||
if let Some(progress_message) = progress.message.as_ref() {
|
||||
message.push_str(progress_message);
|
||||
} else {
|
||||
message.push_str(progress_token);
|
||||
}
|
||||
let mut message = progress
|
||||
.title
|
||||
.as_deref()
|
||||
.unwrap_or(progress_token)
|
||||
.to_string();
|
||||
|
||||
if let Some(percentage) = progress.percentage {
|
||||
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();
|
||||
if additional_work_count > 0 {
|
||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||
}
|
||||
|
||||
return Content {
|
||||
icon: None,
|
||||
icon: Some(
|
||||
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,
|
||||
on_click: None,
|
||||
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -222,7 +235,11 @@ impl ActivityIndicator {
|
||||
|
||||
if !downloading.is_empty() {
|
||||
return Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!("Downloading {}...", downloading.join(", "),),
|
||||
on_click: None,
|
||||
};
|
||||
@@ -230,7 +247,11 @@ impl ActivityIndicator {
|
||||
|
||||
if !checking_for_update.is_empty() {
|
||||
return Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!(
|
||||
"Checking for updates to {}...",
|
||||
checking_for_update.join(", "),
|
||||
@@ -241,7 +262,11 @@ impl ActivityIndicator {
|
||||
|
||||
if !failed.is_empty() {
|
||||
return Content {
|
||||
icon: Some(WARNING_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!(
|
||||
"Failed to download {}. Click to show error.",
|
||||
failed.join(", "),
|
||||
@@ -255,7 +280,11 @@ impl ActivityIndicator {
|
||||
// Show any formatting failure
|
||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
||||
return Content {
|
||||
icon: Some(WARNING_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!("Formatting failed: {}. Click to see logs.", failure),
|
||||
on_click: Some(Arc::new(|_, cx| {
|
||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||
@@ -267,17 +296,29 @@ impl ActivityIndicator {
|
||||
if let Some(updater) = &self.auto_updater {
|
||||
return match &updater.read(cx).status() {
|
||||
AutoUpdateStatus::Checking => Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: "Checking for Zed updates…".to_string(),
|
||||
on_click: None,
|
||||
},
|
||||
AutoUpdateStatus::Downloading => Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: "Downloading Zed update…".to_string(),
|
||||
on_click: None,
|
||||
},
|
||||
AutoUpdateStatus::Installing => Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: "Installing Zed update…".to_string(),
|
||||
on_click: None,
|
||||
},
|
||||
@@ -292,7 +333,11 @@ impl ActivityIndicator {
|
||||
})),
|
||||
},
|
||||
AutoUpdateStatus::Errored => Content {
|
||||
icon: Some(WARNING_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: "Auto update failed".to_string(),
|
||||
on_click: Some(Arc::new(|this, cx| {
|
||||
this.dismiss_error_message(&Default::default(), cx)
|
||||
@@ -307,7 +352,11 @@ impl ActivityIndicator {
|
||||
{
|
||||
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
|
||||
return Content {
|
||||
icon: Some(DOWNLOAD_ICON),
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!("Updating {extension_id} extension…"),
|
||||
on_click: None,
|
||||
};
|
||||
@@ -316,6 +365,75 @@ impl ActivityIndicator {
|
||||
|
||||
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 {}
|
||||
@@ -338,8 +456,17 @@ impl Render for ActivityIndicator {
|
||||
}
|
||||
|
||||
result
|
||||
.children(content.icon.map(|icon| svg().path(icon)))
|
||||
.gap_2()
|
||||
.children(content.icon)
|
||||
.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)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ ollama = { workspace = true, features = ["schemars"] }
|
||||
open_ai = { workspace = true, features = ["schemars"] }
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
|
||||
@@ -34,7 +34,6 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
pub(crate) use streaming_diff::*;
|
||||
use util::paths::EMBEDDINGS_DIR;
|
||||
|
||||
actions!(
|
||||
assistant,
|
||||
@@ -271,7 +270,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
async move {
|
||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||
let semantic_index = SemanticIndex::new(
|
||||
EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
|
||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
&mut cx,
|
||||
)
|
||||
|
||||
@@ -15,32 +15,34 @@ use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use editor::actions::ShowCompletions;
|
||||
use editor::{
|
||||
actions::{FoldAt, MoveToEndOfLine, Newline, UnfoldAt},
|
||||
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, ToDisplayPoint},
|
||||
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
|
||||
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, ToDisplayPoint},
|
||||
scroll::{Autoscroll, AutoscrollStrategy},
|
||||
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
|
||||
};
|
||||
use editor::{display_map::FlapId, FoldPlaceholder};
|
||||
use editor::{display_map::CreaseId, FoldPlaceholder};
|
||||
use file_icons::FileIcons;
|
||||
use fs::Fs;
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
div, point, rems, Action, AnyElement, AnyView, AppContext, AsyncAppContext, AsyncWindowContext,
|
||||
ClipboardItem, Context as _, Empty, EventEmitter, FocusHandle, FocusableView,
|
||||
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
|
||||
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, UpdateGlobal, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
div, percentage, point, rems, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
|
||||
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context as _, Empty, EventEmitter,
|
||||
FocusHandle, FocusOutEvent, FocusableView, InteractiveElement, IntoElement, Model,
|
||||
ModelContext, ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled,
|
||||
Subscription, Task, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use language::{
|
||||
language_settings::SoftWrap, AnchorRangeExt, AutoindentMode, Buffer, LanguageRegistry,
|
||||
language_settings::SoftWrap, AnchorRangeExt as _, AutoindentMode, Buffer, LanguageRegistry,
|
||||
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
|
||||
};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use paths::contexts_dir;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
|
||||
use rustdoc::{CrateName, RustdocStore};
|
||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
@@ -54,10 +56,10 @@ use std::{
|
||||
};
|
||||
use telemetry_events::AssistantKind;
|
||||
use ui::{
|
||||
popover_menu, prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding,
|
||||
ListItem, ListItemSpacing, PopoverMenuHandle, Tab, TabBar, Tooltip,
|
||||
prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
|
||||
ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tab, TabBar, Tooltip,
|
||||
};
|
||||
use util::{paths::CONTEXTS_DIR, post_inc, ResultExt, TryFutureExt};
|
||||
use util::{post_inc, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
use workspace::NewFile;
|
||||
use workspace::{
|
||||
@@ -296,7 +298,7 @@ impl AssistantPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
|
||||
self.toolbar
|
||||
.update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
|
||||
cx.notify();
|
||||
@@ -576,7 +578,7 @@ impl AssistantPanel {
|
||||
fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let assistant = cx.view().clone();
|
||||
let zoomed = self.zoomed;
|
||||
popover_menu("assistant-popover")
|
||||
PopoverMenu::new("assistant-popover")
|
||||
.trigger(IconButton::new("trigger", IconName::Menu))
|
||||
.menu(move |cx| {
|
||||
let assistant = assistant.clone();
|
||||
@@ -618,7 +620,7 @@ impl AssistantPanel {
|
||||
)
|
||||
});
|
||||
|
||||
popover_menu("inject-context-menu")
|
||||
PopoverMenu::new("inject-context-menu")
|
||||
.trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
|
||||
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
|
||||
}))
|
||||
@@ -1010,6 +1012,7 @@ pub struct Context {
|
||||
edit_suggestions: Vec<EditSuggestion>,
|
||||
pending_slash_commands: Vec<PendingSlashCommand>,
|
||||
edits_since_last_slash_command_parse: language::Subscription,
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
next_message_id: MessageId,
|
||||
@@ -1051,6 +1054,7 @@ impl Context {
|
||||
next_message_id: Default::default(),
|
||||
edit_suggestions: Vec::new(),
|
||||
pending_slash_commands: Vec::new(),
|
||||
slash_command_output_sections: Vec::new(),
|
||||
edits_since_last_slash_command_parse,
|
||||
summary: None,
|
||||
pending_summary: Task::ready(None),
|
||||
@@ -1087,11 +1091,12 @@ impl Context {
|
||||
}
|
||||
|
||||
fn serialize(&self, cx: &AppContext) -> SavedContext {
|
||||
let buffer = self.buffer.read(cx);
|
||||
SavedContext {
|
||||
id: self.id.clone(),
|
||||
zed: "context".into(),
|
||||
version: SavedContext::VERSION.into(),
|
||||
text: self.buffer.read(cx).text(),
|
||||
text: buffer.text(),
|
||||
message_metadata: self.messages_metadata.clone(),
|
||||
messages: self
|
||||
.messages(cx)
|
||||
@@ -1105,6 +1110,22 @@ impl Context {
|
||||
.as_ref()
|
||||
.map(|summary| summary.text.clone())
|
||||
.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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,6 +1177,19 @@ impl Context {
|
||||
next_message_id,
|
||||
edit_suggestions: 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,
|
||||
summary: Some(Summary {
|
||||
text: saved_context.summary,
|
||||
@@ -1454,10 +1488,17 @@ impl Context {
|
||||
.map(|section| SlashCommandOutputSection {
|
||||
range: buffer.anchor_after(start + section.range.start)
|
||||
..buffer.anchor_before(start + section.range.end),
|
||||
render_placeholder: section.render_placeholder,
|
||||
icon: section.icon,
|
||||
label: section.label,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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 {
|
||||
output_range: buffer.anchor_after(start)
|
||||
..buffer.anchor_before(new_end),
|
||||
@@ -2001,7 +2042,7 @@ impl Context {
|
||||
let mut discriminant = 1;
|
||||
let mut new_path;
|
||||
loop {
|
||||
new_path = CONTEXTS_DIR.join(&format!(
|
||||
new_path = contexts_dir().join(&format!(
|
||||
"{} - {}.zed.json",
|
||||
summary.trim(),
|
||||
discriminant
|
||||
@@ -2015,7 +2056,7 @@ impl Context {
|
||||
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())
|
||||
.await?;
|
||||
this.update(&mut cx, |this, _| this.path = Some(path))?;
|
||||
@@ -2158,7 +2199,7 @@ pub struct ContextEditor {
|
||||
editor: View<Editor>,
|
||||
blocks: HashSet<BlockId>,
|
||||
scroll_position: Option<ScrollPosition>,
|
||||
pending_slash_command_flaps: HashMap<Range<language::Anchor>, FlapId>,
|
||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -2221,6 +2262,7 @@ impl ContextEditor {
|
||||
cx.subscribe(&editor, Self::handle_editor_event),
|
||||
];
|
||||
|
||||
let sections = context.read(cx).slash_command_output_sections.clone();
|
||||
let mut this = Self {
|
||||
context,
|
||||
editor,
|
||||
@@ -2230,10 +2272,11 @@ impl ContextEditor {
|
||||
scroll_position: None,
|
||||
fs,
|
||||
workspace: workspace.downgrade(),
|
||||
pending_slash_command_flaps: HashMap::default(),
|
||||
pending_slash_command_creases: HashMap::default(),
|
||||
_subscriptions,
|
||||
};
|
||||
this.update_message_headers(cx);
|
||||
this.insert_slash_command_output_sections(sections, cx);
|
||||
this
|
||||
}
|
||||
|
||||
@@ -2493,14 +2536,14 @@ impl ContextEditor {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
||||
|
||||
editor.remove_flaps(
|
||||
editor.remove_creases(
|
||||
removed
|
||||
.iter()
|
||||
.filter_map(|range| self.pending_slash_command_flaps.remove(range)),
|
||||
.filter_map(|range| self.pending_slash_command_creases.remove(range)),
|
||||
cx,
|
||||
);
|
||||
|
||||
let flap_ids = editor.insert_flaps(
|
||||
let crease_ids = editor.insert_creases(
|
||||
updated.iter().map(|command| {
|
||||
let workspace = self.workspace.clone();
|
||||
let confirm_command = Arc::new({
|
||||
@@ -2537,8 +2580,23 @@ impl ContextEditor {
|
||||
)
|
||||
}
|
||||
};
|
||||
let render_trailer =
|
||||
|_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||
let render_trailer = {
|
||||
let command = command.clone();
|
||||
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
|
||||
.anchor_in_excerpt(excerpt_id, command.source_range.start)
|
||||
@@ -2546,16 +2604,16 @@ impl ContextEditor {
|
||||
let end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, command.source_range.end)
|
||||
.unwrap();
|
||||
Flap::new(start..end, placeholder, render_toggle, render_trailer)
|
||||
Crease::new(start..end, placeholder, render_toggle, render_trailer)
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
|
||||
self.pending_slash_command_flaps.extend(
|
||||
self.pending_slash_command_creases.extend(
|
||||
updated
|
||||
.iter()
|
||||
.map(|command| command.source_range.clone())
|
||||
.zip(flap_ids),
|
||||
.zip(crease_ids),
|
||||
);
|
||||
})
|
||||
}
|
||||
@@ -2598,7 +2656,7 @@ impl ContextEditor {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
||||
let mut buffer_rows_to_fold = BTreeSet::new();
|
||||
let mut flaps = Vec::new();
|
||||
let mut creases = Vec::new();
|
||||
for section in sections {
|
||||
let start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, section.range.start)
|
||||
@@ -2608,26 +2666,32 @@ impl ContextEditor {
|
||||
.unwrap();
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
flaps.push(Flap::new(
|
||||
creases.push(Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
render: Arc::new({
|
||||
let editor = cx.view().downgrade();
|
||||
let render_placeholder = section.render_placeholder.clone();
|
||||
move |fold_id, fold_range, cx| {
|
||||
let icon = section.icon;
|
||||
let label = section.label.clone();
|
||||
move |fold_id, fold_range, _cx| {
|
||||
let editor = editor.clone();
|
||||
let unfold = Arc::new(move |cx: &mut WindowContext| {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let buffer_start = fold_range
|
||||
.start
|
||||
.to_point(&editor.buffer().read(cx).read(cx));
|
||||
let buffer_row = MultiBufferRow(buffer_start.row);
|
||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
render_placeholder(fold_id.into(), unfold, cx)
|
||||
ButtonLike::new(fold_id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(Icon::new(icon))
|
||||
.child(Label::new(label.clone()).single_line())
|
||||
.on_click(move |_, cx| {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let buffer_start = fold_range
|
||||
.start
|
||||
.to_point(&editor.buffer().read(cx).read(cx));
|
||||
let buffer_row = MultiBufferRow(buffer_start.row);
|
||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}),
|
||||
constrain_width: false,
|
||||
@@ -2638,7 +2702,7 @@ impl ContextEditor {
|
||||
));
|
||||
}
|
||||
|
||||
editor.insert_flaps(flaps, cx);
|
||||
editor.insert_creases(creases, cx);
|
||||
|
||||
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
|
||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||
@@ -3168,6 +3232,37 @@ fn render_pending_slash_command_gutter_decoration(
|
||||
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(
|
||||
project: &Model<Project>,
|
||||
cx: &mut AppContext,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandOutputSection;
|
||||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{AppContext, Model, ModelContext, Task};
|
||||
use paths::contexts_dir;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
|
||||
use ui::Context;
|
||||
use util::{paths::CONTEXTS_DIR, ResultExt, TryFutureExt};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedMessage {
|
||||
@@ -26,10 +28,22 @@ pub struct SavedContext {
|
||||
pub messages: Vec<SavedMessage>,
|
||||
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
pub summary: String,
|
||||
pub slash_command_output_sections: Vec<SlashCommandOutputSection<usize>>,
|
||||
}
|
||||
|
||||
impl SavedContext {
|
||||
pub const VERSION: &'static str = "0.2.0";
|
||||
pub const VERSION: &'static str = "0.3.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)]
|
||||
@@ -62,7 +76,7 @@ impl ContextStore {
|
||||
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
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 {
|
||||
contexts_metadata: Vec::new(),
|
||||
@@ -99,6 +113,20 @@ impl ContextStore {
|
||||
SavedContext::VERSION => {
|
||||
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" => {
|
||||
let saved_context =
|
||||
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
||||
@@ -110,6 +138,7 @@ impl ContextStore {
|
||||
messages: saved_context.messages,
|
||||
message_metadata: saved_context.message_metadata,
|
||||
summary: saved_context.summary,
|
||||
slash_command_output_sections: Vec::new(),
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
||||
@@ -152,9 +181,9 @@ impl ContextStore {
|
||||
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
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();
|
||||
while let Some(path) = paths.next().await {
|
||||
let path = path?;
|
||||
|
||||
@@ -6,7 +6,7 @@ use anyhow::Result;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
||||
use editor::{
|
||||
actions::{MoveDown, MoveUp},
|
||||
actions::{MoveDown, MoveUp, SelectAll},
|
||||
display_map::{
|
||||
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock,
|
||||
},
|
||||
@@ -186,7 +186,7 @@ impl InlineAssistant {
|
||||
editor.register_action(
|
||||
move |_: &editor::actions::Newline, cx: &mut WindowContext| {
|
||||
InlineAssistant::update_global(cx, |this, cx| {
|
||||
this.handle_editor_action(assist_id, false, cx)
|
||||
this.handle_editor_newline(assist_id, cx)
|
||||
})
|
||||
},
|
||||
)
|
||||
@@ -195,7 +195,7 @@ impl InlineAssistant {
|
||||
editor.register_action(
|
||||
move |_: &editor::actions::Cancel, cx: &mut WindowContext| {
|
||||
InlineAssistant::update_global(cx, |this, cx| {
|
||||
this.handle_editor_action(assist_id, true, cx)
|
||||
this.handle_editor_cancel(assist_id, cx)
|
||||
})
|
||||
},
|
||||
)
|
||||
@@ -277,19 +277,19 @@ impl InlineAssistant {
|
||||
) {
|
||||
let assist_id = inline_assist_editor.read(cx).id;
|
||||
match event {
|
||||
InlineAssistEditorEvent::Started => {
|
||||
InlineAssistEditorEvent::StartRequested => {
|
||||
self.start_inline_assist(assist_id, cx);
|
||||
}
|
||||
InlineAssistEditorEvent::Stopped => {
|
||||
InlineAssistEditorEvent::StopRequested => {
|
||||
self.stop_inline_assist(assist_id, cx);
|
||||
}
|
||||
InlineAssistEditorEvent::Confirmed => {
|
||||
InlineAssistEditorEvent::ConfirmRequested => {
|
||||
self.finish_inline_assist(assist_id, false, cx);
|
||||
}
|
||||
InlineAssistEditorEvent::Canceled => {
|
||||
InlineAssistEditorEvent::CancelRequested => {
|
||||
self.finish_inline_assist(assist_id, true, cx);
|
||||
}
|
||||
InlineAssistEditorEvent::Dismissed => {
|
||||
InlineAssistEditorEvent::DismissRequested => {
|
||||
self.dismiss_inline_assist(assist_id, cx);
|
||||
}
|
||||
InlineAssistEditorEvent::Resized { height_in_lines } => {
|
||||
@@ -298,12 +298,7 @@ impl InlineAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_editor_action(
|
||||
&mut self,
|
||||
assist_id: InlineAssistId,
|
||||
undo: bool,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
fn handle_editor_newline(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
||||
let Some(assist) = self.pending_assists.get(&assist_id) else {
|
||||
return;
|
||||
};
|
||||
@@ -317,9 +312,7 @@ impl InlineAssistant {
|
||||
if editor.selections.count() == 1 {
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
if assist_range.contains(&selection.start) && assist_range.contains(&selection.end) {
|
||||
if undo {
|
||||
self.finish_inline_assist(assist_id, true, cx);
|
||||
} else if matches!(assist.codegen.read(cx).status, CodegenStatus::Pending) {
|
||||
if matches!(assist.codegen.read(cx).status, CodegenStatus::Pending) {
|
||||
self.dismiss_inline_assist(assist_id, cx);
|
||||
} else {
|
||||
self.finish_inline_assist(assist_id, false, cx);
|
||||
@@ -332,6 +325,44 @@ impl InlineAssistant {
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn handle_editor_cancel(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
|
||||
let Some(assist) = self.pending_assists.get(&assist_id) else {
|
||||
return;
|
||||
};
|
||||
let Some(editor) = assist.editor.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
let assist_range = assist.codegen.read(cx).range().to_offset(&buffer);
|
||||
let propagate = editor.update(cx, |editor, cx| {
|
||||
if let Some(decorations) = assist.editor_decorations.as_ref() {
|
||||
if editor.selections.count() == 1 {
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
if assist_range.contains(&selection.start)
|
||||
&& assist_range.contains(&selection.end)
|
||||
{
|
||||
editor.change_selections(Some(Autoscroll::newest()), cx, |selections| {
|
||||
selections.select_ranges([assist_range.start..assist_range.start]);
|
||||
});
|
||||
decorations.prompt_editor.update(cx, |prompt_editor, cx| {
|
||||
prompt_editor.editor.update(cx, |prompt_editor, cx| {
|
||||
prompt_editor.select_all(&SelectAll, cx);
|
||||
prompt_editor.focus(cx);
|
||||
});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
if propagate {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_editor_event(
|
||||
&mut self,
|
||||
assist_id: InlineAssistId,
|
||||
@@ -345,14 +376,8 @@ impl InlineAssistant {
|
||||
|
||||
match event {
|
||||
EditorEvent::SelectionsChanged { local } if *local => {
|
||||
if let Some(decorations) = assist.editor_decorations.as_ref() {
|
||||
if decorations
|
||||
.prompt_editor
|
||||
.focus_handle(cx)
|
||||
.contains_focused(cx)
|
||||
{
|
||||
cx.focus_view(&editor);
|
||||
}
|
||||
if let CodegenStatus::Idle = &assist.codegen.read(cx).status {
|
||||
self.finish_inline_assist(assist_id, true, cx);
|
||||
}
|
||||
}
|
||||
EditorEvent::Saved => {
|
||||
@@ -813,18 +838,18 @@ impl InlineAssistId {
|
||||
}
|
||||
|
||||
enum InlineAssistEditorEvent {
|
||||
Started,
|
||||
Stopped,
|
||||
Confirmed,
|
||||
Canceled,
|
||||
Dismissed,
|
||||
StartRequested,
|
||||
StopRequested,
|
||||
ConfirmRequested,
|
||||
CancelRequested,
|
||||
DismissRequested,
|
||||
Resized { height_in_lines: u8 },
|
||||
}
|
||||
|
||||
struct InlineAssistEditor {
|
||||
id: InlineAssistId,
|
||||
height_in_lines: u8,
|
||||
prompt_editor: View<Editor>,
|
||||
editor: View<Editor>,
|
||||
edited_since_done: bool,
|
||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||
prompt_history: VecDeque<String>,
|
||||
@@ -844,25 +869,34 @@ impl Render for InlineAssistEditor {
|
||||
let buttons = match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => {
|
||||
vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(InlineAssistEditorEvent::CancelRequested)
|
||||
})),
|
||||
IconButton::new("start", IconName::Sparkle)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip(|cx| Tooltip::for_action("Transform", &menu::Confirm, cx))
|
||||
.on_click(
|
||||
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Started)),
|
||||
),
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(
|
||||
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
|
||||
cx.listener(|_, _, cx| {
|
||||
cx.emit(InlineAssistEditorEvent::StartRequested)
|
||||
}),
|
||||
),
|
||||
]
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(InlineAssistEditorEvent::CancelRequested)
|
||||
})),
|
||||
IconButton::new("stop", IconName::Stop)
|
||||
.icon_color(Color::Error)
|
||||
.size(ButtonSize::None)
|
||||
@@ -876,19 +910,19 @@ impl Render for InlineAssistEditor {
|
||||
)
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Stopped)),
|
||||
),
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
|
||||
.on_click(
|
||||
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
|
||||
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::StopRequested)),
|
||||
),
|
||||
]
|
||||
}
|
||||
CodegenStatus::Error(_) | CodegenStatus::Done => {
|
||||
vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(InlineAssistEditorEvent::CancelRequested)
|
||||
})),
|
||||
if self.edited_since_done {
|
||||
IconButton::new("restart", IconName::RotateCw)
|
||||
.icon_color(Color::Info)
|
||||
@@ -903,7 +937,7 @@ impl Render for InlineAssistEditor {
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(InlineAssistEditorEvent::Started);
|
||||
cx.emit(InlineAssistEditorEvent::StartRequested);
|
||||
}))
|
||||
} else {
|
||||
IconButton::new("confirm", IconName::Check)
|
||||
@@ -911,16 +945,9 @@ impl Render for InlineAssistEditor {
|
||||
.size(ButtonSize::None)
|
||||
.tooltip(|cx| Tooltip::for_action("Confirm Assist", &menu::Confirm, cx))
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(InlineAssistEditorEvent::Confirmed);
|
||||
cx.emit(InlineAssistEditorEvent::ConfirmRequested);
|
||||
}))
|
||||
},
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::None)
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(
|
||||
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
|
||||
),
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -1009,7 +1036,7 @@ impl Render for InlineAssistEditor {
|
||||
|
||||
impl FocusableView for InlineAssistEditor {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.prompt_editor.focus_handle(cx)
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1042,7 +1069,7 @@ impl InlineAssistEditor {
|
||||
let mut this = Self {
|
||||
id,
|
||||
height_in_lines: 1,
|
||||
prompt_editor,
|
||||
editor: prompt_editor,
|
||||
edited_since_done: false,
|
||||
gutter_dimensions,
|
||||
prompt_history,
|
||||
@@ -1057,14 +1084,14 @@ impl InlineAssistEditor {
|
||||
}
|
||||
|
||||
fn prompt(&self, cx: &AppContext) -> String {
|
||||
self.prompt_editor.read(cx).text(cx)
|
||||
self.editor.read(cx).text(cx)
|
||||
}
|
||||
|
||||
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let height_in_lines = cmp::max(
|
||||
2, // Make the editor at least two lines tall, to account for padding and buttons.
|
||||
cmp::min(
|
||||
self.prompt_editor
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1),
|
||||
Self::MAX_LINES as u32,
|
||||
),
|
||||
@@ -1088,7 +1115,7 @@ impl InlineAssistEditor {
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::Edited { .. } => {
|
||||
let prompt = self.prompt_editor.read(cx).text(cx);
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
if self
|
||||
.prompt_history_ix
|
||||
.map_or(true, |ix| self.prompt_history[ix] != prompt)
|
||||
@@ -1100,23 +1127,6 @@ impl InlineAssistEditor {
|
||||
self.edited_since_done = true;
|
||||
cx.notify();
|
||||
}
|
||||
EditorEvent::Blurred => {
|
||||
if let CodegenStatus::Idle = &self.codegen.read(cx).status {
|
||||
let assistant_panel_is_focused = self
|
||||
.workspace
|
||||
.as_ref()
|
||||
.and_then(|workspace| {
|
||||
let panel =
|
||||
workspace.upgrade()?.read(cx).panel::<AssistantPanel>(cx)?;
|
||||
Some(panel.focus_handle(cx).contains_focused(cx))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if !assistant_panel_is_focused {
|
||||
cx.emit(InlineAssistEditorEvent::Canceled);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1124,16 +1134,16 @@ impl InlineAssistEditor {
|
||||
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
|
||||
match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => {
|
||||
self.prompt_editor
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(false));
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
self.prompt_editor
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(true));
|
||||
}
|
||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
self.edited_since_done = false;
|
||||
self.prompt_editor
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(false));
|
||||
}
|
||||
}
|
||||
@@ -1142,10 +1152,10 @@ impl InlineAssistEditor {
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
cx.emit(InlineAssistEditorEvent::Canceled);
|
||||
cx.emit(InlineAssistEditorEvent::CancelRequested);
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
cx.emit(InlineAssistEditorEvent::Stopped);
|
||||
cx.emit(InlineAssistEditorEvent::StopRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1153,16 +1163,16 @@ impl InlineAssistEditor {
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
|
||||
match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => {
|
||||
cx.emit(InlineAssistEditorEvent::Started);
|
||||
cx.emit(InlineAssistEditorEvent::StartRequested);
|
||||
}
|
||||
CodegenStatus::Pending => {
|
||||
cx.emit(InlineAssistEditorEvent::Dismissed);
|
||||
cx.emit(InlineAssistEditorEvent::DismissRequested);
|
||||
}
|
||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
if self.edited_since_done {
|
||||
cx.emit(InlineAssistEditorEvent::Started);
|
||||
cx.emit(InlineAssistEditorEvent::StartRequested);
|
||||
} else {
|
||||
cx.emit(InlineAssistEditorEvent::Confirmed);
|
||||
cx.emit(InlineAssistEditorEvent::ConfirmRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1173,7 +1183,7 @@ impl InlineAssistEditor {
|
||||
if ix > 0 {
|
||||
self.prompt_history_ix = Some(ix - 1);
|
||||
let prompt = self.prompt_history[ix - 1].as_str();
|
||||
self.prompt_editor.update(cx, |editor, cx| {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_beginning(&Default::default(), cx);
|
||||
});
|
||||
@@ -1181,7 +1191,7 @@ impl InlineAssistEditor {
|
||||
} else if !self.prompt_history.is_empty() {
|
||||
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
|
||||
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
|
||||
self.prompt_editor.update(cx, |editor, cx| {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_beginning(&Default::default(), cx);
|
||||
});
|
||||
@@ -1193,14 +1203,14 @@ impl InlineAssistEditor {
|
||||
if ix < self.prompt_history.len() - 1 {
|
||||
self.prompt_history_ix = Some(ix + 1);
|
||||
let prompt = self.prompt_history[ix + 1].as_str();
|
||||
self.prompt_editor.update(cx, |editor, cx| {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_end(&Default::default(), cx)
|
||||
});
|
||||
} else {
|
||||
self.prompt_history_ix = None;
|
||||
let prompt = self.pending_prompt.as_str();
|
||||
self.prompt_editor.update(cx, |editor, cx| {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_end(&Default::default(), cx)
|
||||
});
|
||||
@@ -1211,7 +1221,7 @@ impl InlineAssistEditor {
|
||||
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: if self.prompt_editor.read(cx).read_only(cx) {
|
||||
color: if self.editor.read(cx).read_only(cx) {
|
||||
cx.theme().colors().text_disabled
|
||||
} else {
|
||||
cx.theme().colors().text
|
||||
@@ -1228,7 +1238,7 @@ impl InlineAssistEditor {
|
||||
white_space: WhiteSpace::Normal,
|
||||
};
|
||||
EditorElement::new(
|
||||
&self.prompt_editor,
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use crate::{assistant_settings::AssistantSettings, CompletionProvider, ToggleModelSelector};
|
||||
use fs::Fs;
|
||||
use settings::update_settings_file;
|
||||
use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, PopoverMenuHandle, Tooltip};
|
||||
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ModelSelector {
|
||||
@@ -19,7 +19,7 @@ impl ModelSelector {
|
||||
|
||||
impl RenderOnce for ModelSelector {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
popover_menu("model-switcher")
|
||||
PopoverMenu::new("model-switcher")
|
||||
.with_handle(self.handle)
|
||||
.menu(move |cx| {
|
||||
ContextMenu::build(cx, |mut menu, cx| {
|
||||
|
||||
@@ -36,7 +36,7 @@ use ui::{
|
||||
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
|
||||
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
||||
};
|
||||
use util::{paths::PROMPTS_DIR, ResultExt, TryFutureExt};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -48,7 +48,7 @@ actions!(
|
||||
/// Init starts loading the PromptStore in the background and assigns
|
||||
/// a shared future to a global.
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let db_path = PROMPTS_DIR.join("prompts-library-db.0.mdb");
|
||||
let db_path = paths::prompts_dir().join("prompts-library-db.0.mdb");
|
||||
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
|
||||
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
|
||||
.boxed()
|
||||
@@ -832,13 +832,8 @@ impl PromptLibrary {
|
||||
|
||||
impl Render for PromptLibrary {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let (ui_font, ui_font_size) = {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
|
||||
};
|
||||
|
||||
let ui_font = theme::setup_ui_font(cx);
|
||||
let theme = cx.theme().clone();
|
||||
cx.set_rem_size(ui_font_size);
|
||||
|
||||
h_flex()
|
||||
.id("prompt-manager")
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
||||
use super::{
|
||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||
SlashCommand, SlashCommandOutput,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandOutputSection;
|
||||
use editor::Editor;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use ui::{IntoElement, WindowContext};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use ui::WindowContext;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct ActiveSlashCommand;
|
||||
@@ -24,9 +27,9 @@ impl SlashCommand for ActiveSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
_query: String,
|
||||
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut AppContext,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
@@ -60,14 +63,8 @@ impl SlashCommand for ActiveSlashCommand {
|
||||
let text = cx.background_executor().spawn({
|
||||
let path = path.clone();
|
||||
async move {
|
||||
let path = path
|
||||
.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);
|
||||
let mut output = String::new();
|
||||
output.push_str(&codeblock_fence_for_path(path.as_deref(), None));
|
||||
output.push('\n');
|
||||
for chunk in snapshot.as_rope().chunks() {
|
||||
output.push_str(chunk);
|
||||
@@ -84,18 +81,12 @@ impl SlashCommand for ActiveSlashCommand {
|
||||
let range = 0..text.len();
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
sections: vec![build_entry_output_section(
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
||||
FilePlaceholder {
|
||||
id,
|
||||
path: path.clone(),
|
||||
line_range: None,
|
||||
unfold,
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
}],
|
||||
path.as_deref(),
|
||||
false,
|
||||
None,
|
||||
)],
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{prompt_command::PromptPlaceholder, SlashCommand, SlashCommandOutput};
|
||||
use super::{SlashCommand, SlashCommandOutput};
|
||||
use crate::prompt_library::PromptStore;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandOutputSection;
|
||||
@@ -31,7 +31,7 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
_query: String,
|
||||
_cancellation_flag: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
@@ -53,7 +53,7 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
let prompts = store.default_prompt_metadata();
|
||||
|
||||
let mut text = String::new();
|
||||
writeln!(text, "Default Prompt:").unwrap();
|
||||
text.push('\n');
|
||||
for prompt in prompts {
|
||||
if let Some(title) = prompt.title {
|
||||
writeln!(text, "/prompt {}", title).unwrap();
|
||||
@@ -61,17 +61,15 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
}
|
||||
text.pop();
|
||||
|
||||
if text.is_empty() {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
Ok(SlashCommandOutput {
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range: 0..text.len(),
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
PromptPlaceholder {
|
||||
title: "Default".into(),
|
||||
id,
|
||||
unfold,
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
icon: IconName::Library,
|
||||
label: "Default".into(),
|
||||
}],
|
||||
text,
|
||||
run_commands_in_text: true,
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{SlashCommand, SlashCommandOutput};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandOutputSection;
|
||||
use fuzzy::{PathMatch, StringMatchCandidate};
|
||||
use gpui::{svg, AppContext, Model, RenderOnce, Task, View, WeakView};
|
||||
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||
use language::{
|
||||
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
|
||||
OffsetRangeExt, ToOffset,
|
||||
@@ -14,7 +14,7 @@ use std::{
|
||||
ops::Range,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use ui::prelude::*;
|
||||
use util::paths::PathMatcher;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
@@ -98,7 +98,7 @@ impl SlashCommand for DiagnosticsCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
query: String,
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
@@ -164,14 +164,45 @@ impl SlashCommand for DiagnosticsCommand {
|
||||
.into_iter()
|
||||
.map(|(range, placeholder_type)| SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
DiagnosticsPlaceholder {
|
||||
id,
|
||||
unfold,
|
||||
placeholder_type: placeholder_type.clone(),
|
||||
icon: match placeholder_type {
|
||||
PlaceholderType::Root(_, _) => IconName::ExclamationTriangle,
|
||||
PlaceholderType::File(_) => IconName::File,
|
||||
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => {
|
||||
IconName::XCircle
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
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,
|
||||
@@ -189,7 +220,7 @@ struct Options {
|
||||
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
|
||||
|
||||
impl Options {
|
||||
pub fn parse(arguments_line: Option<&str>) -> Self {
|
||||
fn parse(arguments_line: Option<&str>) -> Self {
|
||||
arguments_line
|
||||
.map(|arguments_line| {
|
||||
let args = arguments_line.split_whitespace().collect::<Vec<_>>();
|
||||
@@ -199,7 +230,7 @@ impl Options {
|
||||
if arg == INCLUDE_WARNINGS_ARGUMENT {
|
||||
include_warnings = true;
|
||||
} else {
|
||||
path_matcher = PathMatcher::new(arg).log_err();
|
||||
path_matcher = PathMatcher::new(&[arg.to_owned()]).log_err();
|
||||
}
|
||||
}
|
||||
Self {
|
||||
@@ -223,10 +254,11 @@ fn collect_diagnostics(
|
||||
options: Options,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<(String, Vec<(Range<usize>, PlaceholderType)>)>> {
|
||||
let header = if let Some(path_matcher) = &options.path_matcher {
|
||||
format!("diagnostics: {}", path_matcher.source())
|
||||
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 {
|
||||
"diagnostics".to_string()
|
||||
None
|
||||
};
|
||||
|
||||
let project_handle = project.downgrade();
|
||||
@@ -234,7 +266,11 @@ fn collect_diagnostics(
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let mut text = String::new();
|
||||
writeln!(text, "{}", &header).unwrap();
|
||||
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();
|
||||
@@ -276,7 +312,7 @@ fn collect_diagnostics(
|
||||
}
|
||||
sections.push((
|
||||
0..text.len(),
|
||||
PlaceholderType::Root(project_summary, header),
|
||||
PlaceholderType::Root(project_summary, error_source),
|
||||
));
|
||||
|
||||
Ok((text, sections))
|
||||
@@ -362,12 +398,12 @@ fn collect_diagnostic(
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum PlaceholderType {
|
||||
Root(DiagnosticSummary, String),
|
||||
Root(DiagnosticSummary, Option<String>),
|
||||
File(String),
|
||||
Diagnostic(DiagnosticType, String),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, IntoElement)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum DiagnosticType {
|
||||
Warning,
|
||||
Error,
|
||||
@@ -381,64 +417,3 @@ impl DiagnosticType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct DiagnosticsPlaceholder {
|
||||
pub id: ElementId,
|
||||
pub placeholder_type: PlaceholderType,
|
||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||
}
|
||||
|
||||
impl RenderOnce for DiagnosticsPlaceholder {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
let unfold = self.unfold;
|
||||
let (icon, content) = match self.placeholder_type {
|
||||
PlaceholderType::Root(summary, title) => (
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_0p5()
|
||||
.when(summary.error_count > 0, |this| {
|
||||
this.child(DiagnosticType::Error)
|
||||
.child(Label::new(summary.error_count.to_string()))
|
||||
})
|
||||
.when(summary.warning_count > 0, |this| {
|
||||
this.child(DiagnosticType::Warning)
|
||||
.child(Label::new(summary.warning_count.to_string()))
|
||||
})
|
||||
.into_any_element(),
|
||||
Label::new(title),
|
||||
),
|
||||
PlaceholderType::File(file) => (
|
||||
Icon::new(IconName::File).into_any_element(),
|
||||
Label::new(file),
|
||||
),
|
||||
PlaceholderType::Diagnostic(diagnostic_type, message) => (
|
||||
diagnostic_type.into_any_element(),
|
||||
Label::new(message).single_line(),
|
||||
),
|
||||
};
|
||||
|
||||
ButtonLike::new(self.id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(icon)
|
||||
.child(content)
|
||||
.on_click(move |_, cx| unfold(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for DiagnosticType {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
svg()
|
||||
.size(cx.text_style().font_size)
|
||||
.flex_none()
|
||||
.map(|icon| match self {
|
||||
DiagnosticType::Error => icon
|
||||
.path(IconName::XCircle.path())
|
||||
.text_color(Color::Error.color(cx)),
|
||||
DiagnosticType::Warning => icon
|
||||
.path(IconName::ExclamationTriangle.path())
|
||||
.text_color(Color::Warning.color(cx)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use gpui::{AppContext, Task, WeakView};
|
||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use language::LspAdapterDelegate;
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
@@ -113,7 +113,7 @@ impl SlashCommand for FetchSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
_query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
@@ -152,37 +152,11 @@ impl SlashCommand for FetchSlashCommand {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
FetchPlaceholder {
|
||||
id,
|
||||
unfold,
|
||||
url: url.clone(),
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
icon: IconName::AtSign,
|
||||
label: format!("fetch {}", url).into(),
|
||||
}],
|
||||
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,16 +1,19 @@
|
||||
use super::{SlashCommand, SlashCommandOutput};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandOutputSection;
|
||||
use fs::Fs;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, RenderOnce, SharedString, Task, View, WeakView};
|
||||
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||
use language::{LineEnding, LspAdapterDelegate};
|
||||
use project::PathMatchCandidateSet;
|
||||
use project::{PathMatchCandidateSet, Worktree};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use ui::prelude::*;
|
||||
use util::{paths::PathMatcher, ResultExt};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct FileSlashCommand;
|
||||
@@ -58,7 +61,7 @@ impl FileSlashCommand {
|
||||
.root_entry()
|
||||
.map_or(false, |entry| entry.is_ignored),
|
||||
include_root_name: true,
|
||||
candidates: project::Candidates::Files,
|
||||
candidates: project::Candidates::Entries,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -98,7 +101,7 @@ impl SlashCommand for FileSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
query: String,
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
@@ -139,88 +142,254 @@ impl SlashCommand for FileSlashCommand {
|
||||
return Task::ready(Err(anyhow!("missing path")));
|
||||
};
|
||||
|
||||
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)
|
||||
});
|
||||
let task = collect_files(
|
||||
workspace.read(cx).visible_worktrees(cx).collect(),
|
||||
argument,
|
||||
fs,
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let text = text.await?;
|
||||
let range = 0..text.len();
|
||||
let (text, ranges) = task.await?;
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
FilePlaceholder {
|
||||
path: Some(path.clone()),
|
||||
line_range: None,
|
||||
id,
|
||||
unfold,
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
}],
|
||||
sections: ranges
|
||||
.into_iter()
|
||||
.map(|(range, path, entry_type)| {
|
||||
build_entry_output_section(
|
||||
range,
|
||||
Some(&path),
|
||||
entry_type == EntryType::Directory,
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct FilePlaceholder {
|
||||
pub path: Option<PathBuf>,
|
||||
pub line_range: Option<Range<u32>>,
|
||||
pub id: ElementId,
|
||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum EntryType {
|
||||
File,
|
||||
Directory,
|
||||
}
|
||||
|
||||
impl RenderOnce for FilePlaceholder {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
let unfold = self.unfold;
|
||||
let title = if let Some(path) = self.path.as_ref() {
|
||||
SharedString::from(path.to_string_lossy().to_string())
|
||||
} else {
|
||||
SharedString::from("untitled")
|
||||
};
|
||||
fn collect_files(
|
||||
worktrees: Vec<Model<Worktree>>,
|
||||
glob_input: &str,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<(String, Vec<(Range<usize>, PathBuf, EntryType)>)>> {
|
||||
let Ok(matcher) = PathMatcher::new(&[glob_input.to_owned()]) else {
|
||||
return Task::ready(Err(anyhow!("invalid path")));
|
||||
};
|
||||
|
||||
ButtonLike::new(self.id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(Icon::new(IconName::File))
|
||||
.child(Label::new(title))
|
||||
.when_some(self.line_range, |button, line_range| {
|
||||
button.child(Label::new(":")).child(Label::new(format!(
|
||||
"{}-{}",
|
||||
line_range.start, line_range.end
|
||||
)))
|
||||
})
|
||||
.on_click(move |_, cx| unfold(cx))
|
||||
let path = PathBuf::try_from(glob_input).ok();
|
||||
let file_path = if let Some(path) = &path {
|
||||
worktrees.iter().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()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(abs_path) = file_path {
|
||||
if abs_path.is_file() {
|
||||
let filename = path
|
||||
.as_ref()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
return cx.background_executor().spawn(async move {
|
||||
let mut text = String::new();
|
||||
collect_file_content(&mut text, fs, filename.clone(), abs_path.clone().into())
|
||||
.await?;
|
||||
let text_range = 0..text.len();
|
||||
Ok((
|
||||
text,
|
||||
vec![(text_range, path.unwrap_or_default(), EntryType::File)],
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let snapshots = worktrees
|
||||
.iter()
|
||||
.map(|worktree| worktree.read(cx).snapshot())
|
||||
.collect::<Vec<_>>();
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut text = String::new();
|
||||
let mut ranges = Vec::new();
|
||||
for snapshot in snapshots {
|
||||
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
|
||||
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_buf = PathBuf::new();
|
||||
path_buf.push(snapshot.root_name());
|
||||
path_buf.push(&entry.path);
|
||||
if !matcher.is_match(&path_buf) {
|
||||
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_buf.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_buf.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() {
|
||||
if let Some(abs_path) = snapshot.absolutize(&entry.path).log_err() {
|
||||
let prev_len = text.len();
|
||||
collect_file_content(
|
||||
&mut text,
|
||||
fs.clone(),
|
||||
filename.clone(),
|
||||
abs_path.into(),
|
||||
)
|
||||
.await?;
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
async fn collect_file_content(
|
||||
buffer: &mut String,
|
||||
fs: Arc<dyn Fs>,
|
||||
filename: String,
|
||||
abs_path: Arc<Path>,
|
||||
) -> Result<()> {
|
||||
let mut content = fs.load(&abs_path).await?;
|
||||
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("```");
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ impl SlashCommand for NowSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
_query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
@@ -53,9 +53,8 @@ impl SlashCommand for NowSlashCommand {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
NowPlaceholder { id, unfold, now }.into_any_element()
|
||||
}),
|
||||
icon: IconName::CountdownTimer,
|
||||
label: now.to_rfc3339().into(),
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
}))
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::{
|
||||
path::Path,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct ProjectSlashCommand;
|
||||
@@ -102,7 +102,7 @@ impl SlashCommand for ProjectSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
_query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
@@ -138,15 +138,8 @@ impl SlashCommand for ProjectSlashCommand {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
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()
|
||||
}),
|
||||
icon: IconName::FileTree,
|
||||
label: "Project".into(),
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ use assistant_slash_command::SlashCommandOutputSection;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct PromptSlashCommand;
|
||||
@@ -28,7 +28,7 @@ impl SlashCommand for PromptSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
query: String,
|
||||
_cancellation_flag: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
@@ -69,42 +69,20 @@ impl SlashCommand for PromptSlashCommand {
|
||||
}
|
||||
});
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let prompt = prompt.await?;
|
||||
let mut prompt = prompt.await?;
|
||||
if prompt.is_empty() {
|
||||
prompt.push('\n');
|
||||
}
|
||||
let range = 0..prompt.len();
|
||||
Ok(SlashCommandOutput {
|
||||
text: prompt,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
PromptPlaceholder {
|
||||
id,
|
||||
unfold,
|
||||
title: title.clone(),
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
icon: IconName::Library,
|
||||
label: title,
|
||||
}],
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,20 +10,11 @@ use gpui::{AppContext, Model, Task, WeakView};
|
||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use language::LspAdapterDelegate;
|
||||
use project::{Project, ProjectPath};
|
||||
use rustdoc::{convert_rustdoc_to_markdown, RustdocStore};
|
||||
use rustdoc::{CrateName, LocalProvider};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
|
||||
use ui::prelude::*;
|
||||
use util::{maybe, ResultExt};
|
||||
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;
|
||||
|
||||
impl RustdocSlashCommand {
|
||||
@@ -116,7 +107,7 @@ impl SlashCommand for RustdocSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
@@ -189,11 +180,18 @@ impl SlashCommand for RustdocSlashCommand {
|
||||
let item_path = item_path.clone();
|
||||
async move {
|
||||
let item_docs = rustdoc_store
|
||||
.load(crate_name.clone(), Some(item_path.join("::")))
|
||||
.load(
|
||||
crate_name.clone(),
|
||||
if item_path.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(item_path.join("::"))
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(item_docs) = item_docs {
|
||||
anyhow::Ok((RustdocSource::Local, item_docs.docs().to_owned()))
|
||||
anyhow::Ok((RustdocSource::Index, item_docs.docs().to_owned()))
|
||||
} else {
|
||||
Self::build_message(
|
||||
fs,
|
||||
@@ -215,56 +213,26 @@ impl SlashCommand for RustdocSlashCommand {
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let (source, text) = text.await?;
|
||||
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 {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
RustdocPlaceholder {
|
||||
id,
|
||||
unfold,
|
||||
source,
|
||||
crate_name: crate_name.clone(),
|
||||
module_path: module_path.clone(),
|
||||
icon: IconName::FileRust,
|
||||
label: format!(
|
||||
"rustdoc ({source}): {crate_path}",
|
||||
source = match source {
|
||||
RustdocSource::Index => "index",
|
||||
RustdocSource::Local => "local",
|
||||
RustdocSource::DocsDotRs => "docs.rs",
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
)
|
||||
.into(),
|
||||
}],
|
||||
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: CrateName,
|
||||
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,4 +1,7 @@
|
||||
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
||||
use super::{
|
||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||
SlashCommand, SlashCommandOutput,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::SlashCommandOutputSection;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
@@ -9,7 +12,7 @@ use std::{
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex, Icon, IconName};
|
||||
use ui::{prelude::*, IconName};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -44,7 +47,7 @@ impl SlashCommand for SearchSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
_query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
@@ -125,9 +128,8 @@ impl SlashCommand for SearchSlashCommand {
|
||||
let range_start = result.range.start.min(file_content.len());
|
||||
let range_end = result.range.end.min(file_content.len());
|
||||
|
||||
let start_line =
|
||||
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_row = file_content[0..range_start].matches('\n').count() as u32;
|
||||
let end_row = file_content[0..range_end].matches('\n').count() as u32;
|
||||
let start_line_byte_offset = file_content[0..range_start]
|
||||
.rfind('\n')
|
||||
.map(|pos| pos + 1)
|
||||
@@ -138,47 +140,30 @@ impl SlashCommand for SearchSlashCommand {
|
||||
.unwrap_or_else(|| file_content.len());
|
||||
|
||||
let section_start_ix = text.len();
|
||||
writeln!(
|
||||
text,
|
||||
"```{}:{}-{}",
|
||||
result.path.display(),
|
||||
start_line,
|
||||
end_line,
|
||||
)
|
||||
.unwrap();
|
||||
text.push_str(&codeblock_fence_for_path(
|
||||
Some(&result.path),
|
||||
Some(start_row..end_row),
|
||||
));
|
||||
|
||||
let mut excerpt =
|
||||
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
|
||||
LineEnding::normalize(&mut excerpt);
|
||||
text.push_str(&excerpt);
|
||||
writeln!(text, "\n```\n").unwrap();
|
||||
let section_end_ix = text.len() - 1;
|
||||
|
||||
sections.push(SlashCommandOutputSection {
|
||||
range: section_start_ix..section_end_ix,
|
||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
||||
FilePlaceholder {
|
||||
id,
|
||||
path: Some(full_path.clone()),
|
||||
line_range: Some(start_line..end_line),
|
||||
unfold,
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
});
|
||||
sections.push(build_entry_output_section(
|
||||
section_start_ix..section_end_ix,
|
||||
Some(&full_path),
|
||||
false,
|
||||
Some(start_row + 1..end_row + 1),
|
||||
));
|
||||
}
|
||||
|
||||
let query = SharedString::from(query);
|
||||
sections.push(SlashCommandOutputSection {
|
||||
range: 0..text.len(),
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
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()
|
||||
}),
|
||||
icon: IconName::MagnifyingGlass,
|
||||
label: query,
|
||||
});
|
||||
|
||||
SlashCommandOutput {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
||||
use super::{
|
||||
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||
SlashCommand, SlashCommandOutput,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandOutputSection;
|
||||
use collections::HashMap;
|
||||
use editor::Editor;
|
||||
use gpui::{AppContext, Entity, Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use ui::{IntoElement, WindowContext};
|
||||
use std::{fmt::Write, sync::Arc};
|
||||
use ui::WindowContext;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct TabsSlashCommand;
|
||||
@@ -29,7 +31,7 @@ impl SlashCommand for TabsSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
_query: String,
|
||||
_cancel: Arc<std::sync::atomic::AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
@@ -77,15 +79,7 @@ impl SlashCommand for TabsSlashCommand {
|
||||
let mut text = String::new();
|
||||
for (full_path, buffer, _) in open_buffers {
|
||||
let section_start_ix = text.len();
|
||||
writeln!(
|
||||
text,
|
||||
"```{}\n",
|
||||
full_path
|
||||
.as_deref()
|
||||
.unwrap_or(Path::new("untitled"))
|
||||
.display()
|
||||
)
|
||||
.unwrap();
|
||||
text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
|
||||
for chunk in buffer.as_rope().chunks() {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
@@ -94,19 +88,12 @@ impl SlashCommand for TabsSlashCommand {
|
||||
}
|
||||
writeln!(text, "```\n").unwrap();
|
||||
let section_end_ix = text.len() - 1;
|
||||
|
||||
sections.push(SlashCommandOutputSection {
|
||||
range: section_start_ix..section_end_ix,
|
||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
||||
FilePlaceholder {
|
||||
id,
|
||||
path: full_path.clone(),
|
||||
line_range: None,
|
||||
unfold,
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
});
|
||||
sections.push(build_entry_output_section(
|
||||
section_start_ix..section_end_ix,
|
||||
full_path.as_deref(),
|
||||
false,
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(SlashCommandOutput {
|
||||
|
||||
@@ -18,4 +18,5 @@ derive_more.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
mod slash_command_registry;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{AnyElement, AppContext, ElementId, Task, WeakView, WindowContext};
|
||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||
use language::{CodeLabel, LspAdapterDelegate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use slash_command_registry::*;
|
||||
use std::{
|
||||
ops::Range,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use workspace::Workspace;
|
||||
use workspace::{ui::IconName, Workspace};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
SlashCommandRegistry::default_global(cx);
|
||||
@@ -22,7 +23,7 @@ pub trait SlashCommand: 'static + Send + Sync {
|
||||
fn description(&self) -> String;
|
||||
fn menu_text(&self) -> String;
|
||||
fn complete_argument(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
query: String,
|
||||
cancel: Arc<AtomicBool>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
@@ -55,8 +56,9 @@ pub struct SlashCommandOutput {
|
||||
pub run_commands_in_text: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct SlashCommandOutputSection<T> {
|
||||
pub range: Range<T>,
|
||||
pub render_placeholder: RenderFoldPlaceholder,
|
||||
pub icon: IconName,
|
||||
pub label: SharedString,
|
||||
}
|
||||
|
||||
@@ -86,10 +86,16 @@ impl Render for Breadcrumbs {
|
||||
.style(ButtonStyle::Subtle)
|
||||
.on_click(move |_, cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
outline::toggle(editor, &outline::Toggle, cx)
|
||||
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
|
||||
}
|
||||
})
|
||||
.tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Show symbol outline",
|
||||
&editor::actions::ToggleOutline,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
),
|
||||
None => element
|
||||
// Match the height of the `ButtonLike` in the other arm.
|
||||
|
||||
@@ -13,6 +13,7 @@ path = "src/call.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
no-webrtc = ["live_kit_client/no-webrtc"]
|
||||
test-support = [
|
||||
"client/test-support",
|
||||
"collections/test-support",
|
||||
|
||||
@@ -21,6 +21,7 @@ anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
ipc-channel = "0.18"
|
||||
once_cell.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
serde.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -172,7 +172,6 @@ mod linux {
|
||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||
use fork::Fork;
|
||||
use once_cell::sync::Lazy;
|
||||
use util::paths;
|
||||
|
||||
use crate::{Detect, InstalledApp};
|
||||
|
||||
@@ -221,7 +220,7 @@ mod linux {
|
||||
}
|
||||
|
||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
||||
let sock_path = paths::SUPPORT_DIR.join(format!("zed-{}.sock", *RELEASE_CHANNEL));
|
||||
let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL));
|
||||
let sock = UnixDatagram::unbound()?;
|
||||
if sock.connect(&sock_path).is_err() {
|
||||
self.boot_background(ipc_url)?;
|
||||
|
||||
@@ -13,20 +13,13 @@ path = "src/client.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
default = ["static-libraries"]
|
||||
test-support = ["clock/test-support", "collections/test-support", "gpui/test-support", "rpc/test-support"]
|
||||
static-libraries = ["async-native-tls", "isahc"]
|
||||
dynamic-libraries = []
|
||||
|
||||
# Revert the changes
|
||||
# Reintroduce them, but with a feature flag.
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-recursion = "0.3"
|
||||
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
|
||||
async-native-tls = { version = "0.5.0", features = ["vendored"], optional = true}
|
||||
isahc = { workspace = true, features = ["static-curl"], optional = true}
|
||||
async-native-tls = { version = "0.5.0", features = ["vendored"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
@@ -38,6 +31,7 @@ http.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
paths.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
@@ -58,6 +52,7 @@ time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
worktree.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
@@ -73,3 +68,10 @@ windows.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
async-native-tls = {"version" = "0.5.0", features = ["vendored"]}
|
||||
# This is an indirect dependency of async-tungstenite that is included
|
||||
# here so we can vendor libssl with the feature flag.
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["async-native-tls"]
|
||||
|
||||
@@ -509,7 +509,7 @@ impl Client {
|
||||
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
|
||||
if use_zed_development_auth {
|
||||
Arc::new(DevelopmentCredentialsProvider {
|
||||
path: util::paths::CONFIG_DIR.join("development_auth"),
|
||||
path: paths::config_dir().join("development_auth"),
|
||||
})
|
||||
} else {
|
||||
Arc::new(KeychainCredentialsProvider)
|
||||
|
||||
@@ -3,6 +3,7 @@ mod event_coalescer;
|
||||
use crate::{ChannelId, TelemetrySettings};
|
||||
use chrono::{DateTime, Utc};
|
||||
use clock::SystemClock;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::Future;
|
||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||
use http::{self, HttpClient, HttpClientWithUrl, Method};
|
||||
@@ -23,6 +24,7 @@ use tempfile::NamedTempFile;
|
||||
#[cfg(not(debug_assertions))]
|
||||
use util::ResultExt;
|
||||
use util::TryFutureExt;
|
||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||
|
||||
use self::event_coalescer::EventCoalescer;
|
||||
|
||||
@@ -47,12 +49,31 @@ struct TelemetryState {
|
||||
first_event_date_time: Option<DateTime<Utc>>,
|
||||
event_coalescer: EventCoalescer,
|
||||
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)]
|
||||
const MAX_QUEUE_LEN: usize = 5;
|
||||
|
||||
@@ -180,6 +201,16 @@ impl Telemetry {
|
||||
first_event_date_time: None,
|
||||
event_coalescer: EventCoalescer::new(clock.clone()),
|
||||
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(),
|
||||
@@ -192,7 +223,7 @@ impl Telemetry {
|
||||
let state = state.clone();
|
||||
async move {
|
||||
if let Some(tempfile) =
|
||||
NamedTempFile::new_in(util::paths::CONFIG_DIR.as_path()).log_err()
|
||||
NamedTempFile::new_in(paths::config_dir().as_path()).log_err()
|
||||
{
|
||||
state.lock().log_file = Some(tempfile);
|
||||
}
|
||||
@@ -450,6 +481,52 @@ impl Telemetry {
|
||||
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) {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
@@ -513,10 +590,6 @@ impl Telemetry {
|
||||
return;
|
||||
}
|
||||
|
||||
if ZED_CLIENT_CHECKSUM_SEED.is_none() {
|
||||
return;
|
||||
};
|
||||
|
||||
let this = self.clone();
|
||||
self.executor
|
||||
.spawn(
|
||||
@@ -552,9 +625,7 @@ impl Telemetry {
|
||||
serde_json::to_writer(&mut json_bytes, &request_body)?;
|
||||
}
|
||||
|
||||
let Some(checksum) = calculate_json_checksum(&json_bytes) else {
|
||||
return Ok(());
|
||||
};
|
||||
let checksum = calculate_json_checksum(&json_bytes).unwrap_or("".to_string());
|
||||
|
||||
let request = http::Request::builder()
|
||||
.method(Method::POST)
|
||||
|
||||
@@ -308,13 +308,12 @@ pub async fn post_panic(
|
||||
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
|
||||
let panic = report.panic;
|
||||
|
||||
// better OS reporting for linux (because linux is hard):
|
||||
// - Remove os_version/app_version/os_name from the gpui platform trait
|
||||
// - Move platform processing data into client/telemetry
|
||||
// - Duplicate some small code in macOS platform for a version check
|
||||
// - Add GPUI API for reporting the selected platform integration
|
||||
// - macos-blade, macos-metal, linux-X11, linux-headless
|
||||
// if cfg(macos( { "Macos" } else { "Linux-{cx.compositor_name()"} ))
|
||||
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!(
|
||||
service = "client",
|
||||
@@ -402,12 +401,7 @@ pub async fn post_events(
|
||||
))?;
|
||||
};
|
||||
|
||||
if checksum != expected {
|
||||
return Err(Error::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"invalid checksum".into(),
|
||||
))?;
|
||||
}
|
||||
let checksum_matched = checksum == expected;
|
||||
|
||||
let request_body: telemetry_events::EventRequestBody =
|
||||
serde_json::from_slice(&body).map_err(|err| {
|
||||
@@ -432,6 +426,7 @@ pub async fn post_events(
|
||||
&request_body,
|
||||
first_event_at,
|
||||
country_code.clone(),
|
||||
checksum_matched,
|
||||
)),
|
||||
// Needed for clients sending old copilot_event types
|
||||
Event::Copilot(_) => {}
|
||||
@@ -444,6 +439,7 @@ pub async fn post_events(
|
||||
&request_body,
|
||||
first_event_at,
|
||||
country_code.clone(),
|
||||
checksum_matched,
|
||||
))
|
||||
}
|
||||
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
|
||||
@@ -451,6 +447,7 @@ pub async fn post_events(
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Assistant(event) => {
|
||||
to_upload
|
||||
@@ -460,6 +457,7 @@ pub async fn post_events(
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
))
|
||||
}
|
||||
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
|
||||
@@ -467,36 +465,42 @@ pub async fn post_events(
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
)),
|
||||
Event::Extension(event) => {
|
||||
let metadata = app
|
||||
@@ -511,6 +515,7 @@ pub async fn post_events(
|
||||
&request_body,
|
||||
metadata,
|
||||
first_event_at,
|
||||
checksum_matched,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -658,29 +663,30 @@ where
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct EditorEventRow {
|
||||
pub installation_id: String,
|
||||
pub operation: String,
|
||||
pub app_version: String,
|
||||
pub file_extension: String,
|
||||
pub os_name: String,
|
||||
pub os_version: String,
|
||||
pub release_channel: String,
|
||||
pub signed_in: bool,
|
||||
pub vim_mode: bool,
|
||||
installation_id: String,
|
||||
operation: String,
|
||||
app_version: String,
|
||||
file_extension: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
release_channel: String,
|
||||
signed_in: bool,
|
||||
vim_mode: bool,
|
||||
#[serde(serialize_with = "serialize_country_code")]
|
||||
pub country_code: String,
|
||||
pub region_code: String,
|
||||
pub city: String,
|
||||
pub time: i64,
|
||||
pub copilot_enabled: bool,
|
||||
pub copilot_enabled_for_language: bool,
|
||||
pub historical_event: bool,
|
||||
pub architecture: String,
|
||||
pub is_staff: Option<bool>,
|
||||
pub session_id: Option<String>,
|
||||
pub major: Option<i32>,
|
||||
pub minor: Option<i32>,
|
||||
pub patch: Option<i32>,
|
||||
country_code: String,
|
||||
region_code: String,
|
||||
city: String,
|
||||
time: i64,
|
||||
copilot_enabled: bool,
|
||||
copilot_enabled_for_language: bool,
|
||||
historical_event: bool,
|
||||
architecture: String,
|
||||
is_staff: Option<bool>,
|
||||
session_id: Option<String>,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
}
|
||||
|
||||
impl EditorEventRow {
|
||||
@@ -690,6 +696,7 @@ impl EditorEventRow {
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
country_code: Option<String>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -700,6 +707,7 @@ impl EditorEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -743,6 +751,7 @@ pub struct InlineCompletionEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
}
|
||||
|
||||
impl InlineCompletionEventRow {
|
||||
@@ -752,6 +761,7 @@ impl InlineCompletionEventRow {
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
country_code: Option<String>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -762,6 +772,7 @@ impl InlineCompletionEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -790,6 +801,7 @@ pub struct CallEventRow {
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
checksum_matched: bool,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: String,
|
||||
@@ -809,6 +821,7 @@ impl CallEventRow {
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -819,6 +832,7 @@ impl CallEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -840,6 +854,7 @@ pub struct AssistantEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
@@ -864,6 +879,7 @@ impl AssistantEventRow {
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -874,6 +890,7 @@ impl AssistantEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -908,6 +925,7 @@ pub struct CpuEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
}
|
||||
|
||||
impl CpuEventRow {
|
||||
@@ -916,6 +934,7 @@ impl CpuEventRow {
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -926,6 +945,7 @@ impl CpuEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -946,6 +966,7 @@ pub struct MemoryEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
@@ -967,6 +988,7 @@ impl MemoryEventRow {
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -977,6 +999,7 @@ impl MemoryEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -997,6 +1020,7 @@ pub struct AppEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
@@ -1017,6 +1041,7 @@ impl AppEventRow {
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -1027,6 +1052,7 @@ impl AppEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -1046,6 +1072,7 @@ pub struct SettingEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
@@ -1066,6 +1093,7 @@ impl SettingEventRow {
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -1075,6 +1103,7 @@ impl SettingEventRow {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
checksum_matched,
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
@@ -1096,6 +1125,7 @@ pub struct ExtensionEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
@@ -1121,6 +1151,7 @@ impl ExtensionEventRow {
|
||||
body: &EventRequestBody,
|
||||
extension_metadata: Option<ExtensionMetadata>,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -1131,6 +1162,7 @@ impl ExtensionEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -1162,6 +1194,7 @@ pub struct EditEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
@@ -1186,6 +1219,7 @@ impl EditEventRow {
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -1199,6 +1233,7 @@ impl EditEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
@@ -1220,6 +1255,7 @@ pub struct ActionEventRow {
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
checksum_matched: bool,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
@@ -1242,6 +1278,7 @@ impl ActionEventRow {
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
checksum_matched: bool,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
@@ -1252,6 +1289,7 @@ impl ActionEventRow {
|
||||
major: semver.map(|v| v.major() as i32),
|
||||
minor: semver.map(|v| v.minor() as i32),
|
||||
patch: semver.map(|v| v.patch() as i32),
|
||||
checksum_matched,
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
|
||||
@@ -379,6 +379,7 @@ fn metadata_from_extension_and_version(
|
||||
|
||||
pub fn convert_time_to_chrono(time: time::PrimitiveDateTime) -> chrono::DateTime<Utc> {
|
||||
chrono::DateTime::from_naive_utc_and_offset(
|
||||
#[allow(deprecated)]
|
||||
chrono::NaiveDateTime::from_timestamp_opt(time.assume_utc().unix_timestamp(), 0).unwrap(),
|
||||
Utc,
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ use language::{
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use project::{
|
||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
|
||||
SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
};
|
||||
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
||||
use rpc::RECEIVE_TIMEOUT;
|
||||
@@ -344,7 +344,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -461,7 +461,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -585,7 +585,7 @@ async fn test_collaborating_with_code_actions(
|
||||
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(params.range.start, lsp::Position::new(0, 0));
|
||||
assert_eq!(params.range.end, lsp::Position::new(0, 0));
|
||||
@@ -607,7 +607,7 @@ async fn test_collaborating_with_code_actions(
|
||||
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(params.range.start, lsp::Position::new(1, 31));
|
||||
assert_eq!(params.range.end, lsp::Position::new(1, 31));
|
||||
@@ -619,7 +619,7 @@ async fn test_collaborating_with_code_actions(
|
||||
changes: Some(
|
||||
[
|
||||
(
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(1, 22),
|
||||
@@ -629,7 +629,7 @@ async fn test_collaborating_with_code_actions(
|
||||
)],
|
||||
),
|
||||
(
|
||||
lsp::Uri::from_file_path("/a/other.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/other.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(0, 0),
|
||||
@@ -689,7 +689,7 @@ async fn test_collaborating_with_code_actions(
|
||||
changes: Some(
|
||||
[
|
||||
(
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(1, 22),
|
||||
@@ -699,7 +699,7 @@ async fn test_collaborating_with_code_actions(
|
||||
)],
|
||||
),
|
||||
(
|
||||
lsp::Uri::from_file_path("/a/other.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/other.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(0, 0),
|
||||
@@ -897,14 +897,14 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
changes: Some(
|
||||
[
|
||||
(
|
||||
lsp::Uri::from_file_path("/dir/one.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/dir/one.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
"THREE".to_string(),
|
||||
)],
|
||||
),
|
||||
(
|
||||
lsp::Uri::from_file_path("/dir/two.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/dir/two.rs").unwrap(),
|
||||
vec![
|
||||
lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
@@ -1006,6 +1006,8 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
fake_language_server.start_progress("the-token").await;
|
||||
|
||||
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
|
||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||
token: lsp::NumberOrString::String("the-token".to_string()),
|
||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
||||
@@ -1015,11 +1017,10 @@ 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();
|
||||
|
||||
project_a.read_with(cx_a, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "the-language-server");
|
||||
assert_eq!(status.pending_work.len(), 1);
|
||||
assert_eq!(
|
||||
@@ -1036,10 +1037,11 @@ 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;
|
||||
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "the-language-server");
|
||||
});
|
||||
|
||||
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
|
||||
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
|
||||
token: lsp::NumberOrString::String("the-token".to_string()),
|
||||
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
|
||||
@@ -1049,11 +1051,10 @@ 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();
|
||||
|
||||
project_a.read_with(cx_a, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "the-language-server");
|
||||
assert_eq!(status.pending_work.len(), 1);
|
||||
assert_eq!(
|
||||
@@ -1063,7 +1064,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
});
|
||||
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "the-language-server");
|
||||
assert_eq!(status.pending_work.len(), 1);
|
||||
assert_eq!(
|
||||
@@ -1313,7 +1314,7 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -1441,7 +1442,7 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -1610,7 +1611,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
@@ -1873,7 +1874,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
|
||||
let character = if other_hints { 0 } else { 2 };
|
||||
|
||||
@@ -3897,7 +3897,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
.await;
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
|
||||
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
@@ -3917,7 +3917,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
.unwrap();
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
|
||||
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
@@ -3991,7 +3991,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
// Simulate a language server reporting more errors for a file.
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
|
||||
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![
|
||||
lsp::Diagnostic {
|
||||
@@ -4085,7 +4085,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
// Simulate a language server reporting no errors for a file.
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
|
||||
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![],
|
||||
},
|
||||
@@ -4189,9 +4189,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
||||
for file_name in file_names {
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Uri::from_file_path(Path::new("/test").join(file_name))
|
||||
.unwrap()
|
||||
.into(),
|
||||
uri: lsp::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
@@ -4609,7 +4607,7 @@ async fn test_definition(
|
||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Uri::from_file_path("/root/dir-2/b.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
),
|
||||
)))
|
||||
@@ -4638,7 +4636,7 @@ async fn test_definition(
|
||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Uri::from_file_path("/root/dir-2/b.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
|
||||
),
|
||||
)))
|
||||
@@ -4674,7 +4672,7 @@ async fn test_definition(
|
||||
);
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Uri::from_file_path("/root/dir-2/c.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
|
||||
),
|
||||
)))
|
||||
@@ -4774,7 +4772,7 @@ async fn test_references(
|
||||
// User is informed that a request is pending.
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().cloned().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "my-fake-lsp-adapter");
|
||||
assert_eq!(
|
||||
status.pending_work.values().next().unwrap().message,
|
||||
@@ -4786,21 +4784,15 @@ async fn test_references(
|
||||
lsp_response_tx
|
||||
.unbounded_send(Ok(Some(vec![
|
||||
lsp::Location {
|
||||
uri: lsp::Uri::from_file_path("/root/dir-1/two.rs")
|
||||
.unwrap()
|
||||
.into(),
|
||||
uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
|
||||
},
|
||||
lsp::Location {
|
||||
uri: lsp::Uri::from_file_path("/root/dir-1/two.rs")
|
||||
.unwrap()
|
||||
.into(),
|
||||
uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
|
||||
},
|
||||
lsp::Location {
|
||||
uri: lsp::Uri::from_file_path("/root/dir-2/three.rs")
|
||||
.unwrap()
|
||||
.into(),
|
||||
uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
|
||||
},
|
||||
])))
|
||||
@@ -4810,7 +4802,7 @@ async fn test_references(
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, cx| {
|
||||
// User is informed that a request is no longer pending.
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert!(status.pending_work.is_empty());
|
||||
|
||||
assert_eq!(references.len(), 3);
|
||||
@@ -4838,7 +4830,7 @@ async fn test_references(
|
||||
// User is informed that a request is pending.
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().cloned().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert_eq!(status.name, "my-fake-lsp-adapter");
|
||||
assert_eq!(
|
||||
status.pending_work.values().next().unwrap().message,
|
||||
@@ -4855,7 +4847,7 @@ async fn test_references(
|
||||
// User is informed that the request is no longer pending.
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, _| {
|
||||
let status = project.language_server_statuses().next().unwrap();
|
||||
let status = project.language_server_statuses().next().unwrap().1;
|
||||
assert!(status.pending_work.is_empty());
|
||||
});
|
||||
}
|
||||
@@ -4912,7 +4904,15 @@ async fn test_project_search(
|
||||
let mut results = HashMap::default();
|
||||
let mut search_rx = project_b.update(cx_b, |project, cx| {
|
||||
project.search(
|
||||
SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
|
||||
SearchQuery::text(
|
||||
"world",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -5300,9 +5300,7 @@ async fn test_project_symbols(
|
||||
lsp::SymbolInformation {
|
||||
name: "TWO".into(),
|
||||
location: lsp::Location {
|
||||
uri: lsp::Uri::from_file_path("/code/crate-2/two.rs")
|
||||
.unwrap()
|
||||
.into(),
|
||||
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
},
|
||||
kind: lsp::SymbolKind::CONSTANT,
|
||||
@@ -5392,7 +5390,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Uri::from_file_path("/root/b.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/root/b.rs").unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
),
|
||||
)))
|
||||
|
||||
@@ -875,8 +875,15 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
|
||||
let mut search = project.update(cx, |project, cx| {
|
||||
project.search(
|
||||
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
|
||||
.unwrap(),
|
||||
SearchQuery::text(
|
||||
query,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1101,7 +1108,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
files
|
||||
.into_iter()
|
||||
.map(|file| lsp::Location {
|
||||
uri: lsp::Uri::from_file_path(file).unwrap().into(),
|
||||
uri: lsp::Url::from_file_path(file).unwrap(),
|
||||
range: Default::default(),
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -35,10 +35,12 @@ call.workspace = true
|
||||
channel.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
emojis.workspace = true
|
||||
extensions_ui.workspace = true
|
||||
feedback.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
|
||||
@@ -22,7 +22,7 @@ use settings::Settings;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{
|
||||
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
|
||||
prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, PopoverMenu,
|
||||
TabBar, Tooltip,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
@@ -679,7 +679,7 @@ impl ChatPanel {
|
||||
cx,
|
||||
div()
|
||||
.child(
|
||||
popover_menu(("menu", message_id))
|
||||
PopoverMenu::new(("menu", message_id))
|
||||
.trigger(IconButton::new(
|
||||
("trigger", message_id),
|
||||
IconName::Ellipsis,
|
||||
|
||||
@@ -25,8 +25,15 @@ use crate::panel_settings::MessageEditorSettings;
|
||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
lazy_static! {
|
||||
static ref MENTIONS_SEARCH: SearchQuery =
|
||||
SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
|
||||
static ref MENTIONS_SEARCH: SearchQuery = SearchQuery::regex(
|
||||
"@[-_\\w]+",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Default::default(),
|
||||
Default::default()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub struct MessageEditor {
|
||||
|
||||
@@ -10,11 +10,12 @@ use gpui::{
|
||||
use project::{Project, RepositoryEntry};
|
||||
use recent_projects::RecentProjects;
|
||||
use rpc::proto::{self, DevServerStatus};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use theme::ActiveTheme;
|
||||
use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{
|
||||
h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike,
|
||||
ButtonStyle, ContextMenu, Icon, IconButton, IconName, Indicator, TintColor, TitleBar, Tooltip,
|
||||
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
|
||||
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
|
||||
@@ -73,6 +74,7 @@ impl Render for CollabTitlebarItem {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.children(self.render_application_menu(cx))
|
||||
.children(self.render_project_host(cx))
|
||||
.child(self.render_project_name(cx))
|
||||
.children(self.render_project_branch(cx))
|
||||
@@ -386,8 +388,173 @@ impl CollabTitlebarItem {
|
||||
}
|
||||
}
|
||||
|
||||
// resolve if you are in a room -> render_project_owner
|
||||
// render_project_owner -> resolve if you are in a room -> Option<foo>
|
||||
pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
cfg!(not(target_os = "macos")).then(|| {
|
||||
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> {
|
||||
if let Some(dev_server) =
|
||||
@@ -739,12 +906,13 @@ impl CollabTitlebarItem {
|
||||
|
||||
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() {
|
||||
popover_menu("user-menu")
|
||||
PopoverMenu::new("user-menu")
|
||||
.menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||
.action("Extensions", extensions_ui::Extensions.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())
|
||||
.separator()
|
||||
.action("Sign Out", client::SignOut.boxed_clone())
|
||||
})
|
||||
@@ -767,12 +935,13 @@ impl CollabTitlebarItem {
|
||||
)
|
||||
.anchor(gpui::AnchorCorner::TopRight)
|
||||
} else {
|
||||
popover_menu("user-menu")
|
||||
PopoverMenu::new("user-menu")
|
||||
.menu(|cx| {
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||
.action("Extensions", extensions_ui::Extensions.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())
|
||||
})
|
||||
.into()
|
||||
})
|
||||
|
||||
@@ -3,9 +3,8 @@ use crate::notifications::collab_notification::CollabNotification;
|
||||
use call::{ActiveCall, IncomingCall};
|
||||
use futures::StreamExt;
|
||||
use gpui::{prelude::*, AppContext, WindowHandle};
|
||||
use settings::Settings;
|
||||
|
||||
use std::sync::{Arc, Weak};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, Button, Label};
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
@@ -113,13 +112,7 @@ impl IncomingCallNotification {
|
||||
|
||||
impl Render for IncomingCallNotification {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
// 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);
|
||||
let ui_font = theme::setup_ui_font(cx);
|
||||
|
||||
div().size_full().font(ui_font).child(
|
||||
CollabNotification::new(
|
||||
|
||||
@@ -4,9 +4,8 @@ use call::{room, ActiveCall};
|
||||
use client::User;
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, Size};
|
||||
use settings::Settings;
|
||||
use std::sync::{Arc, Weak};
|
||||
use theme::ThemeSettings;
|
||||
|
||||
use ui::{prelude::*, Button, Label};
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
@@ -124,13 +123,7 @@ impl ProjectSharedNotification {
|
||||
|
||||
impl Render for ProjectSharedNotification {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
// 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);
|
||||
let ui_font = theme::setup_ui_font(cx);
|
||||
|
||||
div().size_full().font(ui_font).child(
|
||||
CollabNotification::new(
|
||||
|
||||
@@ -38,6 +38,7 @@ lsp.workspace = true
|
||||
menu.workspace = true
|
||||
node_runtime.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
project.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
@@ -33,7 +33,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{fs::remove_matching, maybe, paths, ResultExt};
|
||||
use util::{fs::remove_matching, maybe, ResultExt};
|
||||
|
||||
pub use copilot_completion_provider::CopilotCompletionProvider;
|
||||
pub use sign_in::CopilotCodeVerification;
|
||||
@@ -188,7 +188,7 @@ impl Status {
|
||||
}
|
||||
|
||||
struct RegisteredBuffer {
|
||||
uri: lsp::RawUri,
|
||||
uri: lsp::Url,
|
||||
language_id: String,
|
||||
snapshot: BufferSnapshot,
|
||||
snapshot_version: i32,
|
||||
@@ -644,7 +644,7 @@ impl Copilot {
|
||||
registered_buffers
|
||||
.entry(buffer.entity_id())
|
||||
.or_insert_with(|| {
|
||||
let uri = uri_for_buffer(buffer, cx);
|
||||
let uri: lsp::Url = uri_for_buffer(buffer, cx);
|
||||
let language_id = id_for_language(buffer.read(cx).language());
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
server
|
||||
@@ -959,16 +959,16 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
|
||||
.unwrap_or_else(|| "plaintext".to_string())
|
||||
}
|
||||
|
||||
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::RawUri {
|
||||
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::Url {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
||||
lsp::Uri::from_file_path(file.abs_path(cx)).unwrap().into()
|
||||
lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
|
||||
} else {
|
||||
format!("buffer://{}", buffer.entity_id()).parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -979,7 +979,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
let release =
|
||||
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?;
|
||||
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);
|
||||
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)
|
||||
@@ -1016,7 +1016,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
// Fetch a cached binary, if it exists
|
||||
maybe!(async {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
|
||||
let mut entries = fs::read_dir(paths::copilot_dir()).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
@@ -1042,8 +1042,6 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
|
||||
@@ -1052,8 +1050,9 @@ mod tests {
|
||||
let (copilot, mut lsp) = Copilot::fake(cx);
|
||||
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local("Hello", cx));
|
||||
let buffer_1_uri =
|
||||
lsp::RawUri::from_str(&format!("buffer://{}", buffer_1.entity_id().as_u64())).unwrap();
|
||||
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
@@ -1069,8 +1068,9 @@ mod tests {
|
||||
);
|
||||
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local("Goodbye", cx));
|
||||
let buffer_2_uri =
|
||||
lsp::RawUri::from_str(&format!("buffer://{}", buffer_2.entity_id().as_u64())).unwrap();
|
||||
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
@@ -1119,9 +1119,7 @@ mod tests {
|
||||
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
|
||||
}
|
||||
);
|
||||
let buffer_1_uri: lsp::RawUri = lsp::Uri::from_file_path("/root/child/buffer-1")
|
||||
.unwrap()
|
||||
.into();
|
||||
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
|
||||
@@ -1121,10 +1121,7 @@ mod tests {
|
||||
cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
|
||||
let completions = completions.clone();
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
url.clone().into()
|
||||
);
|
||||
assert_eq!(params.text_document_position.text_document.uri, url.clone());
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
complete_from_position
|
||||
|
||||
@@ -102,7 +102,7 @@ pub struct GetCompletionsDocument {
|
||||
pub tab_size: u32,
|
||||
pub indent_size: u32,
|
||||
pub insert_spaces: bool,
|
||||
pub uri: lsp::RawUri,
|
||||
pub uri: lsp::Url,
|
||||
pub relative_path: String,
|
||||
pub position: lsp::Position,
|
||||
pub version: usize,
|
||||
|
||||
@@ -21,6 +21,7 @@ gpui.workspace = true
|
||||
indoc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
smol.workspace = true
|
||||
sqlez.workspace = true
|
||||
|
||||
@@ -7,10 +7,10 @@ use anyhow::Context;
|
||||
use gpui::AppContext;
|
||||
pub use indoc::indoc;
|
||||
pub use lazy_static;
|
||||
pub use paths::database_dir;
|
||||
pub use smol;
|
||||
pub use sqlez;
|
||||
pub use sqlez_macros;
|
||||
pub use util::paths::DB_DIR;
|
||||
|
||||
use release_channel::ReleaseChannel;
|
||||
pub use release_channel::RELEASE_CHANNEL;
|
||||
@@ -145,7 +145,7 @@ macro_rules! define_connection {
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
$crate::lazy_static::lazy_static! {
|
||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
|
||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
|
||||
}
|
||||
};
|
||||
(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")))]
|
||||
$crate::lazy_static::lazy_static! {
|
||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
|
||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ impl ProjectDiagnosticsEditor {
|
||||
let focus_handle = cx.focus_handle();
|
||||
cx.on_focus_in(&focus_handle, |this, cx| this.focus_in(cx))
|
||||
.detach();
|
||||
cx.on_focus_out(&focus_handle, |this, cx| this.focus_out(cx))
|
||||
cx.on_focus_out(&focus_handle, |this, _event, cx| this.focus_out(cx))
|
||||
.detach();
|
||||
|
||||
let excerpts = cx.new_model(|cx| {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
percentage, rems, Animation, AnimationExt, EventEmitter, IntoElement, ParentElement, Render,
|
||||
Styled, Subscription, Transformation, View, ViewContext, WeakView,
|
||||
rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
|
||||
ViewContext, WeakView,
|
||||
};
|
||||
use language::Diagnostic;
|
||||
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
||||
@@ -61,42 +59,7 @@ impl Render for DiagnosticIndicator {
|
||||
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
|
||||
};
|
||||
|
||||
let has_in_progress_checks = self
|
||||
.workspace
|
||||
.upgrade()
|
||||
.and_then(|workspace| {
|
||||
workspace
|
||||
.read(cx)
|
||||
.project()
|
||||
.read(cx)
|
||||
.language_servers_running_disk_based_diagnostics()
|
||||
.next()
|
||||
})
|
||||
.is_some();
|
||||
|
||||
let status = if has_in_progress_checks {
|
||||
Some(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
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)))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Checking…")
|
||||
.size(LabelSize::Small)
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
} else if let Some(diagnostic) = &self.current_diagnostic {
|
||||
let status = if let Some(diagnostic) = &self.current_diagnostic {
|
||||
let message = diagnostic.message.split('\n').next().unwrap().to_string();
|
||||
Some(
|
||||
Button::new("diagnostic_message", message)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! This module contains all actions supported by [`Editor`].
|
||||
use super::*;
|
||||
use gpui::action_as;
|
||||
use util::serde::default_true;
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
@@ -169,6 +170,7 @@ gpui::actions!(
|
||||
AddSelectionBelow,
|
||||
Backspace,
|
||||
Cancel,
|
||||
CancelLanguageServerWork,
|
||||
ConfirmRename,
|
||||
ContextMenuFirst,
|
||||
ContextMenuLast,
|
||||
@@ -289,6 +291,7 @@ gpui::actions!(
|
||||
TabPrev,
|
||||
ToggleGitBlame,
|
||||
ToggleGitBlameInline,
|
||||
ToggleSelectionMenu,
|
||||
ToggleHunkDiff,
|
||||
ToggleInlayHints,
|
||||
ToggleLineNumbers,
|
||||
@@ -303,3 +306,7 @@ gpui::actions!(
|
||||
UniqueLinesCaseSensitive,
|
||||
]
|
||||
);
|
||||
|
||||
action_as!(outline, ToggleOutline as Toggle);
|
||||
|
||||
action_as!(go_to_line, ToggleGoToLine as Toggle);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! [EditorElement]: crate::element::EditorElement
|
||||
|
||||
mod block_map;
|
||||
mod flap_map;
|
||||
mod crease_map;
|
||||
mod fold_map;
|
||||
mod inlay_map;
|
||||
mod tab_map;
|
||||
@@ -33,7 +33,7 @@ pub use block_map::{
|
||||
};
|
||||
use block_map::{BlockRow, BlockSnapshot};
|
||||
use collections::{HashMap, HashSet};
|
||||
pub use flap_map::*;
|
||||
pub use crease_map::*;
|
||||
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
|
||||
use fold_map::{FoldMap, FoldSnapshot};
|
||||
use gpui::{
|
||||
@@ -106,7 +106,7 @@ pub struct DisplayMap {
|
||||
/// Regions of inlays that should be highlighted.
|
||||
inlay_highlights: InlayHighlights,
|
||||
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
|
||||
flap_map: FlapMap,
|
||||
crease_map: CreaseMap,
|
||||
fold_placeholder: FoldPlaceholder,
|
||||
pub clip_at_line_ends: bool,
|
||||
}
|
||||
@@ -139,7 +139,7 @@ impl DisplayMap {
|
||||
excerpt_header_height,
|
||||
excerpt_footer_height,
|
||||
);
|
||||
let flap_map = FlapMap::default();
|
||||
let crease_map = CreaseMap::default();
|
||||
|
||||
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
||||
|
||||
@@ -151,7 +151,7 @@ impl DisplayMap {
|
||||
tab_map,
|
||||
wrap_map,
|
||||
block_map,
|
||||
flap_map,
|
||||
crease_map,
|
||||
fold_placeholder,
|
||||
text_highlights: Default::default(),
|
||||
inlay_highlights: Default::default(),
|
||||
@@ -178,7 +178,7 @@ impl DisplayMap {
|
||||
tab_snapshot,
|
||||
wrap_snapshot,
|
||||
block_snapshot,
|
||||
flap_snapshot: self.flap_map.snapshot(),
|
||||
crease_snapshot: self.crease_map.snapshot(),
|
||||
text_highlights: self.text_highlights.clone(),
|
||||
inlay_highlights: self.inlay_highlights.clone(),
|
||||
clip_at_line_ends: self.clip_at_line_ends,
|
||||
@@ -247,22 +247,22 @@ impl DisplayMap {
|
||||
self.block_map.read(snapshot, edits);
|
||||
}
|
||||
|
||||
pub fn insert_flaps(
|
||||
pub fn insert_creases(
|
||||
&mut self,
|
||||
flaps: impl IntoIterator<Item = Flap>,
|
||||
creases: impl IntoIterator<Item = Crease>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Vec<FlapId> {
|
||||
) -> Vec<CreaseId> {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
self.flap_map.insert(flaps, &snapshot)
|
||||
self.crease_map.insert(creases, &snapshot)
|
||||
}
|
||||
|
||||
pub fn remove_flaps(
|
||||
pub fn remove_creases(
|
||||
&mut self,
|
||||
flap_ids: impl IntoIterator<Item = FlapId>,
|
||||
crease_ids: impl IntoIterator<Item = CreaseId>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
self.flap_map.remove(flap_ids, &snapshot)
|
||||
self.crease_map.remove(crease_ids, &snapshot)
|
||||
}
|
||||
|
||||
pub fn insert_blocks(
|
||||
@@ -472,7 +472,7 @@ pub struct HighlightedChunk<'a> {
|
||||
pub struct DisplaySnapshot {
|
||||
pub buffer_snapshot: MultiBufferSnapshot,
|
||||
pub fold_snapshot: FoldSnapshot,
|
||||
pub flap_snapshot: FlapSnapshot,
|
||||
pub crease_snapshot: CreaseSnapshot,
|
||||
inlay_snapshot: InlaySnapshot,
|
||||
tab_snapshot: TabSnapshot,
|
||||
wrap_snapshot: WrapSnapshot,
|
||||
@@ -955,13 +955,13 @@ impl DisplaySnapshot {
|
||||
buffer_row: MultiBufferRow,
|
||||
) -> Option<(Range<Point>, FoldPlaceholder)> {
|
||||
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
|
||||
if let Some(flap) = self
|
||||
.flap_snapshot
|
||||
if let Some(crease) = self
|
||||
.crease_snapshot
|
||||
.query_row(buffer_row, &self.buffer_snapshot)
|
||||
{
|
||||
Some((
|
||||
flap.range.to_point(&self.buffer_snapshot),
|
||||
flap.placeholder.clone(),
|
||||
crease.range.to_point(&self.buffer_snapshot),
|
||||
crease.placeholder.clone(),
|
||||
))
|
||||
} else if self.starts_indent(MultiBufferRow(start.row))
|
||||
&& !self.is_line_folded(MultiBufferRow(start.row))
|
||||
@@ -1021,6 +1021,22 @@ impl Debug for DisplayPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for DisplayPoint {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
DisplayPoint(BlockPoint(self.0 .0 + other.0 .0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for DisplayPoint {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
DisplayPoint(BlockPoint(self.0 .0 - other.0 .0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
|
||||
#[serde(transparent)]
|
||||
pub struct DisplayRow(pub u32);
|
||||
@@ -1930,7 +1946,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_flaps(cx: &mut gpui::AppContext) {
|
||||
fn test_creases(cx: &mut gpui::AppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
|
||||
@@ -1953,8 +1969,8 @@ pub mod tests {
|
||||
let range =
|
||||
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
|
||||
|
||||
map.flap_map.insert(
|
||||
[Flap::new(
|
||||
map.crease_map.insert(
|
||||
[Crease::new(
|
||||
range,
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _status, _toggle, _cx| div(),
|
||||
|
||||
@@ -9,36 +9,36 @@ use ui::WindowContext;
|
||||
use crate::FoldPlaceholder;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
pub struct FlapId(usize);
|
||||
pub struct CreaseId(usize);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FlapMap {
|
||||
snapshot: FlapSnapshot,
|
||||
next_id: FlapId,
|
||||
id_to_range: HashMap<FlapId, Range<Anchor>>,
|
||||
pub struct CreaseMap {
|
||||
snapshot: CreaseSnapshot,
|
||||
next_id: CreaseId,
|
||||
id_to_range: HashMap<CreaseId, Range<Anchor>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FlapSnapshot {
|
||||
flaps: SumTree<FlapItem>,
|
||||
pub struct CreaseSnapshot {
|
||||
creases: SumTree<CreaseItem>,
|
||||
}
|
||||
|
||||
impl FlapSnapshot {
|
||||
/// Returns the first Flap starting on the specified buffer row.
|
||||
impl CreaseSnapshot {
|
||||
/// Returns the first Crease starting on the specified buffer row.
|
||||
pub fn query_row<'a>(
|
||||
&'a self,
|
||||
row: MultiBufferRow,
|
||||
snapshot: &'a MultiBufferSnapshot,
|
||||
) -> Option<&'a Flap> {
|
||||
) -> Option<&'a Crease> {
|
||||
let start = snapshot.anchor_before(Point::new(row.0, 0));
|
||||
let mut cursor = self.flaps.cursor::<ItemSummary>();
|
||||
let mut cursor = self.creases.cursor::<ItemSummary>();
|
||||
cursor.seek(&start, Bias::Left, snapshot);
|
||||
while let Some(item) = cursor.item() {
|
||||
match Ord::cmp(&item.flap.range.start.to_point(snapshot).row, &row.0) {
|
||||
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
|
||||
Ordering::Less => cursor.next(snapshot),
|
||||
Ordering::Equal => {
|
||||
if item.flap.range.start.is_valid(snapshot) {
|
||||
return Some(&item.flap);
|
||||
if item.crease.range.start.is_valid(snapshot) {
|
||||
return Some(&item.crease);
|
||||
} else {
|
||||
cursor.next(snapshot);
|
||||
}
|
||||
@@ -49,17 +49,17 @@ impl FlapSnapshot {
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn flap_items_with_offsets(
|
||||
pub fn crease_items_with_offsets(
|
||||
&self,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Vec<(FlapId, Range<Point>)> {
|
||||
let mut cursor = self.flaps.cursor::<ItemSummary>();
|
||||
) -> Vec<(CreaseId, Range<Point>)> {
|
||||
let mut cursor = self.creases.cursor::<ItemSummary>();
|
||||
let mut results = Vec::new();
|
||||
|
||||
cursor.next(snapshot);
|
||||
while let Some(item) = cursor.item() {
|
||||
let start_point = item.flap.range.start.to_point(snapshot);
|
||||
let end_point = item.flap.range.end.to_point(snapshot);
|
||||
let start_point = item.crease.range.start.to_point(snapshot);
|
||||
let end_point = item.crease.range.end.to_point(snapshot);
|
||||
results.push((item.id, start_point..end_point));
|
||||
cursor.next(snapshot);
|
||||
}
|
||||
@@ -82,14 +82,14 @@ type RenderTrailerFn =
|
||||
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Flap {
|
||||
pub struct Crease {
|
||||
pub range: Range<Anchor>,
|
||||
pub placeholder: FoldPlaceholder,
|
||||
pub render_toggle: RenderToggleFn,
|
||||
pub render_trailer: RenderTrailerFn,
|
||||
}
|
||||
|
||||
impl Flap {
|
||||
impl Crease {
|
||||
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
|
||||
range: Range<Anchor>,
|
||||
placeholder: FoldPlaceholder,
|
||||
@@ -115,7 +115,7 @@ impl Flap {
|
||||
+ 'static,
|
||||
TrailerElement: IntoElement,
|
||||
{
|
||||
Flap {
|
||||
Crease {
|
||||
range,
|
||||
placeholder,
|
||||
render_toggle: Arc::new(move |row, folded, toggle, cx| {
|
||||
@@ -128,50 +128,52 @@ impl Flap {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Flap {
|
||||
impl std::fmt::Debug for Crease {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Flap").field("range", &self.range).finish()
|
||||
f.debug_struct("Crease")
|
||||
.field("range", &self.range)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct FlapItem {
|
||||
id: FlapId,
|
||||
flap: Flap,
|
||||
struct CreaseItem {
|
||||
id: CreaseId,
|
||||
crease: Crease,
|
||||
}
|
||||
|
||||
impl FlapMap {
|
||||
pub fn snapshot(&self) -> FlapSnapshot {
|
||||
impl CreaseMap {
|
||||
pub fn snapshot(&self) -> CreaseSnapshot {
|
||||
self.snapshot.clone()
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
flaps: impl IntoIterator<Item = Flap>,
|
||||
creases: impl IntoIterator<Item = Crease>,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Vec<FlapId> {
|
||||
) -> Vec<CreaseId> {
|
||||
let mut new_ids = Vec::new();
|
||||
self.snapshot.flaps = {
|
||||
let mut new_flaps = SumTree::new();
|
||||
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
|
||||
for flap in flaps {
|
||||
new_flaps.append(cursor.slice(&flap.range, Bias::Left, snapshot), snapshot);
|
||||
self.snapshot.creases = {
|
||||
let mut new_creases = SumTree::new();
|
||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
|
||||
for crease in creases {
|
||||
new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id.0 += 1;
|
||||
self.id_to_range.insert(id, flap.range.clone());
|
||||
new_flaps.push(FlapItem { flap, id }, snapshot);
|
||||
self.id_to_range.insert(id, crease.range.clone());
|
||||
new_creases.push(CreaseItem { crease, id }, snapshot);
|
||||
new_ids.push(id);
|
||||
}
|
||||
new_flaps.append(cursor.suffix(snapshot), snapshot);
|
||||
new_flaps
|
||||
new_creases.append(cursor.suffix(snapshot), snapshot);
|
||||
new_creases
|
||||
};
|
||||
new_ids
|
||||
}
|
||||
|
||||
pub fn remove(
|
||||
&mut self,
|
||||
ids: impl IntoIterator<Item = FlapId>,
|
||||
ids: impl IntoIterator<Item = CreaseId>,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) {
|
||||
let mut removals = Vec::new();
|
||||
@@ -184,24 +186,24 @@ impl FlapMap {
|
||||
AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(&a_id))
|
||||
});
|
||||
|
||||
self.snapshot.flaps = {
|
||||
let mut new_flaps = SumTree::new();
|
||||
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
|
||||
self.snapshot.creases = {
|
||||
let mut new_creases = SumTree::new();
|
||||
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
|
||||
|
||||
for (id, range) in removals {
|
||||
new_flaps.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
|
||||
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
|
||||
while let Some(item) = cursor.item() {
|
||||
cursor.next(snapshot);
|
||||
if item.id == id {
|
||||
break;
|
||||
} else {
|
||||
new_flaps.push(item.clone(), snapshot);
|
||||
new_creases.push(item.clone(), snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_flaps.append(cursor.suffix(snapshot), snapshot);
|
||||
new_flaps
|
||||
new_creases.append(cursor.suffix(snapshot), snapshot);
|
||||
new_creases
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -227,17 +229,17 @@ impl sum_tree::Summary for ItemSummary {
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for FlapItem {
|
||||
impl sum_tree::Item for CreaseItem {
|
||||
type Summary = ItemSummary;
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
ItemSummary {
|
||||
range: self.flap.range.clone(),
|
||||
range: self.crease.range.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `FlapItem`s.
|
||||
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
|
||||
impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
|
||||
fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
|
||||
AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
|
||||
@@ -257,48 +259,48 @@ mod test {
|
||||
use multi_buffer::MultiBuffer;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_insert_and_remove_flaps(cx: &mut AppContext) {
|
||||
fn test_insert_and_remove_creases(cx: &mut AppContext) {
|
||||
let text = "line1\nline2\nline3\nline4\nline5";
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let mut flap_map = FlapMap::default();
|
||||
let mut crease_map = CreaseMap::default();
|
||||
|
||||
// Insert flaps
|
||||
let flaps = [
|
||||
Flap::new(
|
||||
// Insert creases
|
||||
let creases = [
|
||||
Crease::new(
|
||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
|_row, _folded, _cx| div(),
|
||||
),
|
||||
Flap::new(
|
||||
Crease::new(
|
||||
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
|_row, _folded, _cx| div(),
|
||||
),
|
||||
];
|
||||
let flap_ids = flap_map.insert(flaps, &snapshot);
|
||||
assert_eq!(flap_ids.len(), 2);
|
||||
let crease_ids = crease_map.insert(creases, &snapshot);
|
||||
assert_eq!(crease_ids.len(), 2);
|
||||
|
||||
// Verify flaps are inserted
|
||||
let flap_snapshot = flap_map.snapshot();
|
||||
assert!(flap_snapshot
|
||||
// Verify creases are inserted
|
||||
let crease_snapshot = crease_map.snapshot();
|
||||
assert!(crease_snapshot
|
||||
.query_row(MultiBufferRow(1), &snapshot)
|
||||
.is_some());
|
||||
assert!(flap_snapshot
|
||||
assert!(crease_snapshot
|
||||
.query_row(MultiBufferRow(3), &snapshot)
|
||||
.is_some());
|
||||
|
||||
// Remove flaps
|
||||
flap_map.remove(flap_ids, &snapshot);
|
||||
// Remove creases
|
||||
crease_map.remove(crease_ids, &snapshot);
|
||||
|
||||
// Verify flaps are removed
|
||||
let flap_snapshot = flap_map.snapshot();
|
||||
assert!(flap_snapshot
|
||||
// Verify creases are removed
|
||||
let crease_snapshot = crease_map.snapshot();
|
||||
assert!(crease_snapshot
|
||||
.query_row(MultiBufferRow(1), &snapshot)
|
||||
.is_none());
|
||||
assert!(flap_snapshot
|
||||
assert!(crease_snapshot
|
||||
.query_row(MultiBufferRow(3), &snapshot)
|
||||
.is_none());
|
||||
}
|
||||
@@ -66,11 +66,12 @@ use git::diff_hunk_to_display;
|
||||
use gpui::{
|
||||
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
|
||||
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
|
||||
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
|
||||
FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, ListSizingBehavior, Model,
|
||||
MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle,
|
||||
Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle,
|
||||
View, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
|
||||
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent, FocusableView,
|
||||
FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
|
||||
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
|
||||
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle,
|
||||
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle,
|
||||
WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
@@ -448,6 +449,7 @@ struct BufferOffset(usize);
|
||||
/// See the [module level documentation](self) for more information.
|
||||
pub struct Editor {
|
||||
focus_handle: FocusHandle,
|
||||
last_focused_descendant: Option<WeakFocusHandle>,
|
||||
/// The text buffer being edited
|
||||
buffer: Model<MultiBuffer>,
|
||||
/// Map of how text in the buffer should be displayed.
|
||||
@@ -535,6 +537,7 @@ pub struct Editor {
|
||||
show_git_blame_inline: bool,
|
||||
show_git_blame_inline_delay_task: Option<Task<()>>,
|
||||
git_blame_inline_enabled: bool,
|
||||
show_selection_menu: Option<bool>,
|
||||
blame: Option<Model<GitBlame>>,
|
||||
blame_subscription: Option<Subscription>,
|
||||
custom_context_menu: Option<
|
||||
@@ -549,6 +552,7 @@ pub struct Editor {
|
||||
tasks_update_task: Option<Task<()>>,
|
||||
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
|
||||
file_header_size: u8,
|
||||
breadcrumb_header: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -1735,6 +1739,8 @@ impl Editor {
|
||||
);
|
||||
let focus_handle = cx.focus_handle();
|
||||
cx.on_focus(&focus_handle, Self::handle_focus).detach();
|
||||
cx.on_focus_out(&focus_handle, Self::handle_focus_out)
|
||||
.detach();
|
||||
cx.on_blur(&focus_handle, Self::handle_blur).detach();
|
||||
|
||||
let show_indent_guides = if mode == EditorMode::SingleLine {
|
||||
@@ -1745,6 +1751,7 @@ impl Editor {
|
||||
|
||||
let mut this = Self {
|
||||
focus_handle,
|
||||
last_focused_descendant: None,
|
||||
buffer: buffer.clone(),
|
||||
display_map: display_map.clone(),
|
||||
selections,
|
||||
@@ -1828,6 +1835,7 @@ impl Editor {
|
||||
custom_context_menu: None,
|
||||
show_git_blame_gutter: false,
|
||||
show_git_blame_inline: false,
|
||||
show_selection_menu: None,
|
||||
show_git_blame_inline_delay_task: None,
|
||||
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
|
||||
blame: None,
|
||||
@@ -1856,6 +1864,7 @@ impl Editor {
|
||||
tasks_update_task: None,
|
||||
linked_edit_ranges: Default::default(),
|
||||
previous_search_ranges: None,
|
||||
breadcrumb_header: None,
|
||||
};
|
||||
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
||||
this._subscriptions.extend(project_subscriptions);
|
||||
@@ -2205,7 +2214,7 @@ impl Editor {
|
||||
// Copy selections to primary selection buffer
|
||||
#[cfg(target_os = "linux")]
|
||||
if local {
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let selections = &self.selections.disjoint;
|
||||
let buffer_handle = self.buffer.read(cx).read(cx);
|
||||
|
||||
let mut text = String::new();
|
||||
@@ -2492,6 +2501,7 @@ impl Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if !self.focus_handle.is_focused(cx) {
|
||||
self.last_focused_descendant = None;
|
||||
cx.focus(&self.focus_handle);
|
||||
}
|
||||
|
||||
@@ -2559,6 +2569,7 @@ impl Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if !self.focus_handle.is_focused(cx) {
|
||||
self.last_focused_descendant = None;
|
||||
cx.focus(&self.focus_handle);
|
||||
}
|
||||
|
||||
@@ -6413,82 +6424,96 @@ impl Editor {
|
||||
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
||||
}
|
||||
|
||||
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
pub fn do_paste(
|
||||
&mut self,
|
||||
text: &String,
|
||||
clipboard_selections: Option<Vec<ClipboardSelection>>,
|
||||
handle_entire_lines: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
let clipboard_text = Cow::Borrowed(text);
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
let clipboard_text = Cow::Borrowed(item.text());
|
||||
if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
|
||||
let old_selections = this.selections.all::<usize>(cx);
|
||||
let all_selections_were_entire_line =
|
||||
clipboard_selections.iter().all(|s| s.is_entire_line);
|
||||
let first_selection_indent_column =
|
||||
clipboard_selections.first().map(|s| s.first_line_indent);
|
||||
if clipboard_selections.len() != old_selections.len() {
|
||||
clipboard_selections.drain(..);
|
||||
}
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.read(cx);
|
||||
let mut start_offset = 0;
|
||||
let mut edits = Vec::new();
|
||||
let mut original_indent_columns = Vec::new();
|
||||
let line_mode = this.selections.line_mode;
|
||||
for (ix, selection) in old_selections.iter().enumerate() {
|
||||
let to_insert;
|
||||
let entire_line;
|
||||
let original_indent_column;
|
||||
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
|
||||
let end_offset = start_offset + clipboard_selection.len;
|
||||
to_insert = &clipboard_text[start_offset..end_offset];
|
||||
entire_line = clipboard_selection.is_entire_line;
|
||||
start_offset = end_offset + 1;
|
||||
original_indent_column =
|
||||
Some(clipboard_selection.first_line_indent);
|
||||
} else {
|
||||
to_insert = clipboard_text.as_str();
|
||||
entire_line = all_selections_were_entire_line;
|
||||
original_indent_column = first_selection_indent_column
|
||||
}
|
||||
|
||||
// If the corresponding selection was empty when this slice of the
|
||||
// clipboard text was written, then the entire line containing the
|
||||
// selection was copied. If this selection is also currently empty,
|
||||
// then paste the line before the current line of the buffer.
|
||||
let range = if selection.is_empty() && !line_mode && entire_line {
|
||||
let column = selection.start.to_point(&snapshot).column as usize;
|
||||
let line_start = selection.start - column;
|
||||
line_start..line_start
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
|
||||
edits.push((range, to_insert));
|
||||
original_indent_columns.extend(original_indent_column);
|
||||
}
|
||||
drop(snapshot);
|
||||
|
||||
buffer.edit(
|
||||
edits,
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
} else {
|
||||
this.insert(&clipboard_text, cx);
|
||||
if let Some(mut clipboard_selections) = clipboard_selections {
|
||||
let old_selections = this.selections.all::<usize>(cx);
|
||||
let all_selections_were_entire_line =
|
||||
clipboard_selections.iter().all(|s| s.is_entire_line);
|
||||
let first_selection_indent_column =
|
||||
clipboard_selections.first().map(|s| s.first_line_indent);
|
||||
if clipboard_selections.len() != old_selections.len() {
|
||||
clipboard_selections.drain(..);
|
||||
}
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.read(cx);
|
||||
let mut start_offset = 0;
|
||||
let mut edits = Vec::new();
|
||||
let mut original_indent_columns = Vec::new();
|
||||
for (ix, selection) in old_selections.iter().enumerate() {
|
||||
let to_insert;
|
||||
let entire_line;
|
||||
let original_indent_column;
|
||||
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
|
||||
let end_offset = start_offset + clipboard_selection.len;
|
||||
to_insert = &clipboard_text[start_offset..end_offset];
|
||||
entire_line = clipboard_selection.is_entire_line;
|
||||
start_offset = end_offset + 1;
|
||||
original_indent_column = Some(clipboard_selection.first_line_indent);
|
||||
} else {
|
||||
to_insert = clipboard_text.as_str();
|
||||
entire_line = all_selections_were_entire_line;
|
||||
original_indent_column = first_selection_indent_column
|
||||
}
|
||||
|
||||
// If the corresponding selection was empty when this slice of the
|
||||
// clipboard text was written, then the entire line containing the
|
||||
// selection was copied. If this selection is also currently empty,
|
||||
// then paste the line before the current line of the buffer.
|
||||
let range = if selection.is_empty() && handle_entire_lines && entire_line {
|
||||
let column = selection.start.to_point(&snapshot).column as usize;
|
||||
let line_start = selection.start - column;
|
||||
line_start..line_start
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
|
||||
edits.push((range, to_insert));
|
||||
original_indent_columns.extend(original_indent_column);
|
||||
}
|
||||
drop(snapshot);
|
||||
|
||||
buffer.edit(
|
||||
edits,
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
} else {
|
||||
this.insert(&clipboard_text, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
self.do_paste(
|
||||
item.text(),
|
||||
item.metadata::<Vec<ClipboardSelection>>(),
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
@@ -8950,7 +8975,7 @@ impl Editor {
|
||||
});
|
||||
language_server_name.map(|language_server_name| {
|
||||
project.open_local_buffer_via_lsp(
|
||||
lsp::Uri::from(lsp_location.uri.clone()),
|
||||
lsp_location.uri.clone(),
|
||||
server_id,
|
||||
language_server_name,
|
||||
cx,
|
||||
@@ -9473,6 +9498,20 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_language_server_work(
|
||||
&mut self,
|
||||
_: &CancelLanguageServerWork,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(project) = self.project.clone() {
|
||||
self.buffer.update(cx, |multi_buffer, cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
project.cancel_language_server_work_for_buffers(multi_buffer.all_buffers(), cx);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||
cx.show_character_palette();
|
||||
}
|
||||
@@ -9888,22 +9927,22 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_flaps(
|
||||
pub fn insert_creases(
|
||||
&mut self,
|
||||
flaps: impl IntoIterator<Item = Flap>,
|
||||
creases: impl IntoIterator<Item = Crease>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Vec<FlapId> {
|
||||
) -> Vec<CreaseId> {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.insert_flaps(flaps, cx))
|
||||
.update(cx, |map, cx| map.insert_creases(creases, cx))
|
||||
}
|
||||
|
||||
pub fn remove_flaps(
|
||||
pub fn remove_creases(
|
||||
&mut self,
|
||||
ids: impl IntoIterator<Item = FlapId>,
|
||||
ids: impl IntoIterator<Item = CreaseId>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.remove_flaps(ids, cx));
|
||||
.update(cx, |map, cx| map.remove_creases(ids, cx));
|
||||
}
|
||||
|
||||
pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
|
||||
@@ -10147,6 +10186,20 @@ impl Editor {
|
||||
self.git_blame_inline_enabled
|
||||
}
|
||||
|
||||
pub fn toggle_selection_menu(&mut self, _: &ToggleSelectionMenu, cx: &mut ViewContext<Self>) {
|
||||
self.show_selection_menu = self
|
||||
.show_selection_menu
|
||||
.map(|show_selections_menu| !show_selections_menu)
|
||||
.or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn selection_menu_enabled(&self, cx: &AppContext) -> bool {
|
||||
self.show_selection_menu
|
||||
.unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
|
||||
}
|
||||
|
||||
fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
|
||||
if let Some(project) = self.project.as_ref() {
|
||||
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
|
||||
@@ -10454,6 +10507,10 @@ impl Editor {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_breadcrumb_header(&mut self, new_header: String) {
|
||||
self.breadcrumb_header = Some(new_header);
|
||||
}
|
||||
|
||||
pub fn clear_search_within_ranges(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.clear_background_highlights::<SearchWithinRange>(cx);
|
||||
}
|
||||
@@ -11301,9 +11358,13 @@ impl Editor {
|
||||
|
||||
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(EditorEvent::Focused);
|
||||
if let Some(rename) = self.pending_rename.as_ref() {
|
||||
let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
|
||||
cx.focus(&rename_editor_focus_handle);
|
||||
|
||||
if let Some(descendant) = self
|
||||
.last_focused_descendant
|
||||
.take()
|
||||
.and_then(|descendant| descendant.upgrade())
|
||||
{
|
||||
cx.focus(&descendant);
|
||||
} else {
|
||||
if let Some(blame) = self.blame.as_ref() {
|
||||
blame.update(cx, GitBlame::focus)
|
||||
@@ -11325,6 +11386,12 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_focus_out(&mut self, event: FocusOutEvent, _cx: &mut ViewContext<Self>) {
|
||||
if event.blurred != self.focus_handle {
|
||||
self.last_focused_descendant = Some(event.blurred);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.blink_manager.update(cx, BlinkManager::disable);
|
||||
self.buffer
|
||||
@@ -11728,8 +11795,8 @@ impl EditorSnapshot {
|
||||
) -> Option<AnyElement> {
|
||||
let folded = self.is_line_folded(buffer_row);
|
||||
|
||||
if let Some(flap) = self
|
||||
.flap_snapshot
|
||||
if let Some(crease) = self
|
||||
.crease_snapshot
|
||||
.query_row(buffer_row, &self.buffer_snapshot)
|
||||
{
|
||||
let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
|
||||
@@ -11744,7 +11811,7 @@ impl EditorSnapshot {
|
||||
}
|
||||
});
|
||||
|
||||
Some((flap.render_toggle)(
|
||||
Some((crease.render_toggle)(
|
||||
buffer_row,
|
||||
folded,
|
||||
toggle_callback,
|
||||
@@ -11770,16 +11837,16 @@ impl EditorSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_flap_trailer(
|
||||
pub fn render_crease_trailer(
|
||||
&self,
|
||||
buffer_row: MultiBufferRow,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<AnyElement> {
|
||||
let folded = self.is_line_folded(buffer_row);
|
||||
let flap = self
|
||||
.flap_snapshot
|
||||
let crease = self
|
||||
.crease_snapshot
|
||||
.query_row(buffer_row, &self.buffer_snapshot)?;
|
||||
Some((flap.render_trailer)(buffer_row, folded, cx))
|
||||
Some((crease.render_trailer)(buffer_row, folded, cx))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ pub enum DoubleClickInMultibuffer {
|
||||
pub struct Toolbar {
|
||||
pub breadcrumbs: bool,
|
||||
pub quick_actions: bool,
|
||||
pub selections_menu: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
@@ -129,6 +130,7 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub hover_popover_enabled: Option<bool>,
|
||||
|
||||
/// Whether to pop the completions menu while typing in an editor without
|
||||
/// explicitly requesting it.
|
||||
///
|
||||
@@ -202,10 +204,15 @@ pub struct ToolbarContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub breadcrumbs: Option<bool>,
|
||||
/// Whether to display quik action buttons in the editor toolbar.
|
||||
/// Whether to display quick action buttons in the editor toolbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub quick_actions: Option<bool>,
|
||||
|
||||
/// Whether to show the selections menu in the editor toolbar
|
||||
///
|
||||
/// Default: true
|
||||
pub selections_menu: Option<bool>,
|
||||
}
|
||||
|
||||
/// Scrollbar related settings
|
||||
|
||||
@@ -5861,7 +5861,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||
);
|
||||
assert_eq!(params.options.tab_size, 4);
|
||||
Ok(Some(vec![lsp::TextEdit::new(
|
||||
@@ -5887,7 +5887,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||
);
|
||||
futures::future::pending::<()>().await;
|
||||
unreachable!()
|
||||
@@ -5936,7 +5936,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||
);
|
||||
assert_eq!(params.options.tab_size, 8);
|
||||
Ok(Some(vec![]))
|
||||
@@ -6139,7 +6139,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
.on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||
Ok(Some(vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
|
||||
format!("[{} formatted]", params.text_document.uri.as_str()),
|
||||
format!("[{} formatted]", params.text_document.uri),
|
||||
)]))
|
||||
})
|
||||
.detach();
|
||||
@@ -6213,7 +6213,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||
);
|
||||
assert_eq!(params.options.tab_size, 4);
|
||||
Ok(Some(vec![lsp::TextEdit::new(
|
||||
@@ -6239,7 +6239,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||
);
|
||||
futures::future::pending::<()>().await;
|
||||
unreachable!()
|
||||
@@ -6289,7 +6289,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||
);
|
||||
assert_eq!(params.options.tab_size, 8);
|
||||
Ok(Some(vec![]))
|
||||
@@ -6363,7 +6363,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||
);
|
||||
assert_eq!(params.options.tab_size, 4);
|
||||
Ok(Some(vec![lsp::TextEdit::new(
|
||||
@@ -6385,7 +6385,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
||||
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/file.rs").unwrap().into()
|
||||
lsp::Url::from_file_path("/file.rs").unwrap()
|
||||
);
|
||||
futures::future::pending::<()>().await;
|
||||
unreachable!()
|
||||
@@ -8028,7 +8028,7 @@ async fn go_to_prev_overlapping_diagnostic(
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Uri::from_file_path("/root/file").unwrap().into(),
|
||||
uri: lsp::Url::from_file_path("/root/file").unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![
|
||||
lsp::Diagnostic {
|
||||
@@ -8400,7 +8400,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
|
||||
fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -12049,7 +12049,7 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppCont
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let editor = cx.add_window(|cx| {
|
||||
@@ -12070,7 +12070,7 @@ fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
||||
}
|
||||
|
||||
let flap = Flap::new(
|
||||
let crease = Crease::new(
|
||||
range,
|
||||
FoldPlaceholder::test(),
|
||||
{
|
||||
@@ -12087,7 +12087,7 @@ fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
|_row, _folded, _cx| div(),
|
||||
);
|
||||
|
||||
editor.insert_flaps(Some(flap), cx);
|
||||
editor.insert_creases(Some(crease), cx);
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
|
||||
snapshot
|
||||
@@ -12151,10 +12151,7 @@ pub fn handle_completion_request(
|
||||
let completions = completions.clone();
|
||||
counter.fetch_add(1, atomic::Ordering::Release);
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
url.clone().into()
|
||||
);
|
||||
assert_eq!(params.text_document_position.text_document.uri, url.clone());
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
complete_from_position
|
||||
|
||||
@@ -341,6 +341,7 @@ impl EditorElement {
|
||||
}
|
||||
});
|
||||
register_action(view, cx, Editor::restart_language_server);
|
||||
register_action(view, cx, Editor::cancel_language_server_work);
|
||||
register_action(view, cx, Editor::show_character_palette);
|
||||
register_action(view, cx, |editor, action, cx| {
|
||||
if let Some(task) = editor.confirm_completion(action, cx) {
|
||||
@@ -1136,7 +1137,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn prepaint_flap_trailers(
|
||||
fn prepaint_crease_trailers(
|
||||
&self,
|
||||
trailers: Vec<Option<AnyElement>>,
|
||||
lines: &[LineWithInvisibles],
|
||||
@@ -1145,7 +1146,7 @@ impl EditorElement {
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
em_width: Pixels,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<Option<FlapTrailerLayout>> {
|
||||
) -> Vec<Option<CreaseTrailerLayout>> {
|
||||
trailers
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
@@ -1170,7 +1171,7 @@ impl EditorElement {
|
||||
let centering_offset = point(px(0.), (line_height - size.height) / 2.);
|
||||
let origin = content_origin + position + centering_offset;
|
||||
element.prepaint_as_root(origin, available_space, cx);
|
||||
Some(FlapTrailerLayout {
|
||||
Some(CreaseTrailerLayout {
|
||||
element,
|
||||
bounds: Bounds::new(origin, size),
|
||||
})
|
||||
@@ -1266,7 +1267,7 @@ impl EditorElement {
|
||||
display_row: DisplayRow,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
line_layout: &LineWithInvisibles,
|
||||
flap_trailer: Option<&FlapTrailerLayout>,
|
||||
crease_trailer: Option<&CreaseTrailerLayout>,
|
||||
em_width: Pixels,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
@@ -1306,8 +1307,8 @@ impl EditorElement {
|
||||
let start_x = {
|
||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
||||
|
||||
let line_end = if let Some(flap_trailer) = flap_trailer {
|
||||
flap_trailer.bounds.right()
|
||||
let line_end = if let Some(crease_trailer) = crease_trailer {
|
||||
crease_trailer.bounds.right()
|
||||
} else {
|
||||
content_origin.x - scroll_pixel_position.x + line_layout.width
|
||||
};
|
||||
@@ -1779,7 +1780,7 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_flap_trailers(
|
||||
fn layout_crease_trailers(
|
||||
&self,
|
||||
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
|
||||
snapshot: &EditorSnapshot,
|
||||
@@ -1789,7 +1790,7 @@ impl EditorElement {
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
if let Some(multibuffer_row) = row {
|
||||
snapshot.render_flap_trailer(multibuffer_row, cx)
|
||||
snapshot.render_crease_trailer(multibuffer_row, cx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -2810,38 +2811,12 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
fn paint_line_numbers(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
let scroll_top = scroll_position.y * line_height;
|
||||
|
||||
cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
|
||||
for (_, hunk_hitbox) in &layout.display_hunks {
|
||||
if let Some(hunk_hitbox) = hunk_hitbox {
|
||||
cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
|
||||
}
|
||||
}
|
||||
|
||||
let show_git_gutter = layout
|
||||
.position_map
|
||||
.snapshot
|
||||
.show_git_diff_gutter
|
||||
.unwrap_or_else(|| {
|
||||
matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
)
|
||||
});
|
||||
if show_git_gutter {
|
||||
Self::paint_diff_hunks(layout.gutter_hitbox.bounds, layout, cx)
|
||||
}
|
||||
|
||||
self.paint_gutter_highlights(layout, cx);
|
||||
|
||||
if layout.blamed_display_rows.is_some() {
|
||||
self.paint_blamed_display_rows(layout, cx);
|
||||
}
|
||||
|
||||
for (ix, line) in layout.line_numbers.iter().enumerate() {
|
||||
if let Some(line) = line {
|
||||
@@ -2856,29 +2831,9 @@ impl EditorElement {
|
||||
line.paint(line_origin, line_height, cx).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
||||
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||
for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
|
||||
fold_indicator.paint(cx);
|
||||
}
|
||||
});
|
||||
|
||||
for test_indicators in layout.test_indicators.iter_mut() {
|
||||
test_indicators.paint(cx);
|
||||
}
|
||||
|
||||
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
|
||||
indicator.paint(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn paint_diff_hunks(
|
||||
gutter_bounds: Bounds<Pixels>,
|
||||
layout: &EditorLayout,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
fn paint_diff_hunks(layout: &EditorLayout, cx: &mut WindowContext) {
|
||||
if layout.display_hunks.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -2891,7 +2846,7 @@ impl EditorElement {
|
||||
let hunk_bounds = Self::diff_hunk_bounds(
|
||||
&layout.position_map.snapshot,
|
||||
line_height,
|
||||
gutter_bounds,
|
||||
layout.gutter_hitbox.bounds,
|
||||
&hunk,
|
||||
);
|
||||
Some((
|
||||
@@ -3008,7 +2963,45 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_gutter_indicators(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
||||
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||
for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
|
||||
fold_indicator.paint(cx);
|
||||
}
|
||||
});
|
||||
|
||||
for test_indicators in layout.test_indicators.iter_mut() {
|
||||
test_indicators.paint(cx);
|
||||
}
|
||||
|
||||
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
|
||||
indicator.paint(cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn paint_gutter_highlights(&self, layout: &EditorLayout, cx: &mut WindowContext) {
|
||||
for (_, hunk_hitbox) in &layout.display_hunks {
|
||||
if let Some(hunk_hitbox) = hunk_hitbox {
|
||||
cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
|
||||
}
|
||||
}
|
||||
|
||||
let show_git_gutter = layout
|
||||
.position_map
|
||||
.snapshot
|
||||
.show_git_diff_gutter
|
||||
.unwrap_or_else(|| {
|
||||
matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
)
|
||||
});
|
||||
if show_git_gutter {
|
||||
Self::paint_diff_hunks(layout, cx)
|
||||
}
|
||||
|
||||
let highlight_width = 0.275 * layout.position_map.line_height;
|
||||
let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
|
||||
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
||||
@@ -3075,8 +3068,8 @@ impl EditorElement {
|
||||
self.paint_redactions(layout, cx);
|
||||
self.paint_cursors(layout, cx);
|
||||
self.paint_inline_blame(layout, cx);
|
||||
cx.with_element_namespace("flap_trailers", |cx| {
|
||||
for trailer in layout.flap_trailers.iter_mut().flatten() {
|
||||
cx.with_element_namespace("crease_trailers", |cx| {
|
||||
for trailer in layout.crease_trailers.iter_mut().flatten() {
|
||||
trailer.element.paint(cx);
|
||||
}
|
||||
});
|
||||
@@ -4705,8 +4698,8 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
|
||||
self.layout_flap_trailers(buffer_rows.iter().copied(), &snapshot, cx)
|
||||
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
|
||||
self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
|
||||
});
|
||||
|
||||
let display_hunks = self.layout_git_gutters(
|
||||
@@ -4767,9 +4760,9 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
);
|
||||
|
||||
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
|
||||
self.prepaint_flap_trailers(
|
||||
flap_trailers,
|
||||
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
|
||||
self.prepaint_crease_trailers(
|
||||
crease_trailers,
|
||||
&line_layouts,
|
||||
line_height,
|
||||
content_origin,
|
||||
@@ -4785,12 +4778,12 @@ impl Element for EditorElement {
|
||||
if (start_row..end_row).contains(&display_row) {
|
||||
let line_ix = display_row.minus(start_row) as usize;
|
||||
let line_layout = &line_layouts[line_ix];
|
||||
let flap_trailer_layout = flap_trailers[line_ix].as_ref();
|
||||
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
|
||||
inline_blame = self.layout_inline_blame(
|
||||
display_row,
|
||||
&snapshot.display_snapshot,
|
||||
line_layout,
|
||||
flap_trailer_layout,
|
||||
crease_trailer_layout,
|
||||
em_width,
|
||||
content_origin,
|
||||
scroll_pixel_position,
|
||||
@@ -5045,7 +5038,7 @@ impl Element for EditorElement {
|
||||
test_indicators,
|
||||
code_actions_indicator,
|
||||
gutter_fold_toggles,
|
||||
flap_trailers,
|
||||
crease_trailers,
|
||||
tab_invisible,
|
||||
space_invisible,
|
||||
}
|
||||
@@ -5112,8 +5105,10 @@ impl Element for EditorElement {
|
||||
self.paint_mouse_listeners(layout, hovered_hunk, cx);
|
||||
self.paint_background(layout, cx);
|
||||
self.paint_indent_guides(layout, cx);
|
||||
|
||||
if layout.gutter_hitbox.size.width > Pixels::ZERO {
|
||||
self.paint_gutter(layout, cx)
|
||||
self.paint_blamed_display_rows(layout, cx);
|
||||
self.paint_line_numbers(layout, cx);
|
||||
}
|
||||
|
||||
self.paint_text(layout, cx);
|
||||
@@ -5124,6 +5119,11 @@ impl Element for EditorElement {
|
||||
});
|
||||
}
|
||||
|
||||
if layout.gutter_hitbox.size.width > Pixels::ZERO {
|
||||
self.paint_gutter_highlights(layout, cx);
|
||||
self.paint_gutter_indicators(layout, cx);
|
||||
}
|
||||
|
||||
self.paint_scrollbar(layout, cx);
|
||||
self.paint_mouse_context_menu(layout, cx);
|
||||
});
|
||||
@@ -5169,7 +5169,7 @@ pub struct EditorLayout {
|
||||
code_actions_indicator: Option<AnyElement>,
|
||||
test_indicators: Vec<AnyElement>,
|
||||
gutter_fold_toggles: Vec<Option<AnyElement>>,
|
||||
flap_trailers: Vec<Option<FlapTrailerLayout>>,
|
||||
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
|
||||
mouse_context_menu: Option<AnyElement>,
|
||||
tab_invisible: ShapedLine,
|
||||
space_invisible: ShapedLine,
|
||||
@@ -5293,7 +5293,7 @@ impl ScrollbarLayout {
|
||||
}
|
||||
}
|
||||
|
||||
struct FlapTrailerLayout {
|
||||
struct CreaseTrailerLayout {
|
||||
element: AnyElement,
|
||||
bounds: Bounds<Pixels>,
|
||||
}
|
||||
|
||||
@@ -741,7 +741,7 @@ mod tests {
|
||||
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url.clone().into(),
|
||||
target_uri: url.clone(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
@@ -815,7 +815,7 @@ mod tests {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url.clone().into(),
|
||||
target_uri: url.clone(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
@@ -841,7 +841,7 @@ mod tests {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url.clone().into(),
|
||||
target_uri: url.clone(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
@@ -904,7 +904,7 @@ mod tests {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url.into(),
|
||||
target_uri: url,
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
@@ -980,7 +980,7 @@ mod tests {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: None,
|
||||
target_uri: url.into(),
|
||||
target_uri: url,
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
@@ -1008,7 +1008,7 @@ mod tests {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: None,
|
||||
target_uri: url.into(),
|
||||
target_uri: url,
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
@@ -1088,7 +1088,7 @@ mod tests {
|
||||
let hint_label = ": TestStruct";
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let expected_uri = expected_uri.clone().into();
|
||||
let expected_uri = expected_uri.clone();
|
||||
async move {
|
||||
assert_eq!(params.text_document.uri, expected_uri);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
|
||||
@@ -1376,7 +1376,7 @@ mod tests {
|
||||
let closure_uri = uri.clone();
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let task_uri = closure_uri.clone().into();
|
||||
let task_uri = closure_uri.clone();
|
||||
async move {
|
||||
assert_eq!(params.text_document.uri, task_uri);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
@@ -1467,7 +1467,7 @@ mod tests {
|
||||
lsp::InlayHintLabelPart {
|
||||
value: new_type_label.to_string(),
|
||||
location: Some(lsp::Location {
|
||||
uri: task_uri.clone().into(),
|
||||
uri: task_uri.clone(),
|
||||
range: new_type_target_range,
|
||||
}),
|
||||
tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
|
||||
@@ -1482,7 +1482,7 @@ mod tests {
|
||||
lsp::InlayHintLabelPart {
|
||||
value: struct_label.to_string(),
|
||||
location: Some(lsp::Location {
|
||||
uri: task_uri.into(),
|
||||
uri: task_uri,
|
||||
range: struct_target_range,
|
||||
}),
|
||||
tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
|
||||
|
||||
@@ -1307,7 +1307,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
let current_call_id =
|
||||
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
|
||||
@@ -1439,7 +1439,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
let current_call_id =
|
||||
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
|
||||
@@ -1613,7 +1613,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
@@ -1666,7 +1666,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/other.md").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/other.md").unwrap(),
|
||||
);
|
||||
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
@@ -1790,7 +1790,7 @@ pub mod tests {
|
||||
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
Ok(Some(vec![
|
||||
lsp::InlayHint {
|
||||
@@ -2136,7 +2136,7 @@ pub mod tests {
|
||||
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: lsp::Position::new(0, i),
|
||||
@@ -2305,7 +2305,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
|
||||
task_lsp_request_ranges.lock().push(params.range);
|
||||
@@ -2673,11 +2673,11 @@ pub mod tests {
|
||||
let task_editor_edited = Arc::clone(&closure_editor_edited);
|
||||
async move {
|
||||
let hint_text = if params.text_document.uri
|
||||
== lsp::Uri::from_file_path("/a/main.rs").unwrap().into()
|
||||
== lsp::Url::from_file_path("/a/main.rs").unwrap()
|
||||
{
|
||||
"main hint"
|
||||
} else if params.text_document.uri
|
||||
== lsp::Uri::from_file_path("/a/other.rs").unwrap().into()
|
||||
== lsp::Url::from_file_path("/a/other.rs").unwrap()
|
||||
{
|
||||
"other hint"
|
||||
} else {
|
||||
@@ -2981,11 +2981,11 @@ pub mod tests {
|
||||
let task_editor_edited = Arc::clone(&closure_editor_edited);
|
||||
async move {
|
||||
let hint_text = if params.text_document.uri
|
||||
== lsp::Uri::from_file_path("/a/main.rs").unwrap().into()
|
||||
== lsp::Url::from_file_path("/a/main.rs").unwrap()
|
||||
{
|
||||
"main hint"
|
||||
} else if params.text_document.uri
|
||||
== lsp::Uri::from_file_path("/a/other.rs").unwrap().into()
|
||||
== lsp::Url::from_file_path("/a/other.rs").unwrap()
|
||||
{
|
||||
"other hint"
|
||||
} else {
|
||||
@@ -3177,7 +3177,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
let query_start = params.range.start;
|
||||
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
|
||||
@@ -3244,7 +3244,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
|
||||
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
|
||||
|
||||
@@ -816,22 +816,24 @@ impl Item for Editor {
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
let filename = buffer
|
||||
.snapshot()
|
||||
.resolve_file_path(
|
||||
cx,
|
||||
self.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "untitled".to_string());
|
||||
let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
|
||||
buffer
|
||||
.snapshot()
|
||||
.resolve_file_path(
|
||||
cx,
|
||||
self.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "untitled".to_string())
|
||||
});
|
||||
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
|
||||
let mut breadcrumbs = vec![BreadcrumbText {
|
||||
text: filename,
|
||||
text,
|
||||
highlights: None,
|
||||
font: Some(settings.buffer_font.clone()),
|
||||
}];
|
||||
|
||||
@@ -27,7 +27,7 @@ pub struct EditorLspTestContext {
|
||||
pub cx: EditorTestContext,
|
||||
pub lsp: lsp::FakeLanguageServer,
|
||||
pub workspace: View<Workspace>,
|
||||
pub buffer_lsp_url: lsp::Uri,
|
||||
pub buffer_lsp_url: lsp::Url,
|
||||
}
|
||||
|
||||
impl EditorLspTestContext {
|
||||
@@ -113,7 +113,7 @@ impl EditorLspTestContext {
|
||||
},
|
||||
lsp,
|
||||
workspace,
|
||||
buffer_lsp_url: lsp::Uri::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
|
||||
buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ impl EditorLspTestContext {
|
||||
where
|
||||
T: 'static + request::Request,
|
||||
T::Params: 'static + Send,
|
||||
F: 'static + Send + FnMut(lsp::Uri, T::Params, gpui::AsyncAppContext) -> Fut,
|
||||
F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
|
||||
Fut: 'static + Send + Future<Output = Result<T::Result>>,
|
||||
{
|
||||
let url = self.buffer_lsp_url.clone();
|
||||
|
||||
@@ -12,6 +12,9 @@ workspace = true
|
||||
path = "src/extension_store.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
no-webrtc = ["workspace/no-webrtc"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
@@ -30,6 +33,7 @@ language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
node_runtime.workspace = true
|
||||
paths.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::sync::{atomic::AtomicBool, Arc};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||
use futures::FutureExt;
|
||||
use gpui::{AppContext, IntoElement, Task, WeakView, WindowContext};
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use language::LspAdapterDelegate;
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use ui::prelude::*;
|
||||
use wasmtime_wasi::WasiView;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -36,13 +36,34 @@ impl SlashCommand for ExtensionSlashCommand {
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
_query: String,
|
||||
self: Arc<Self>,
|
||||
query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut AppContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
cx.background_executor().spawn(async move {
|
||||
self.extension
|
||||
.call({
|
||||
let this = self.clone();
|
||||
move |extension, store| {
|
||||
async move {
|
||||
let completions = extension
|
||||
.call_complete_slash_command_argument(
|
||||
store,
|
||||
&this.command,
|
||||
query.as_ref(),
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| anyhow!("{}", e))?;
|
||||
|
||||
anyhow::Ok(completions)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
fn run(
|
||||
@@ -52,11 +73,9 @@ impl SlashCommand for ExtensionSlashCommand {
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let command_name = SharedString::from(self.command.name.clone());
|
||||
let argument = argument.map(|arg| arg.to_string());
|
||||
let text = cx.background_executor().spawn(async move {
|
||||
let output = self
|
||||
.extension
|
||||
let output = cx.background_executor().spawn(async move {
|
||||
self.extension
|
||||
.call({
|
||||
let this = self.clone();
|
||||
move |extension, store| {
|
||||
@@ -77,29 +96,21 @@ impl SlashCommand for ExtensionSlashCommand {
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
output.ok_or_else(|| anyhow!("no output from command: {}", self.command.name))
|
||||
.await
|
||||
});
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let text = text.await?;
|
||||
let range = 0..text.len();
|
||||
let output = output.await?;
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new({
|
||||
let command_name = command_name.clone();
|
||||
move |id, unfold, _cx| {
|
||||
ButtonLike::new(id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(Icon::new(IconName::Code))
|
||||
.child(Label::new(command_name.clone()))
|
||||
.on_click(move |_event, cx| unfold(cx))
|
||||
.into_any_element()
|
||||
}
|
||||
}),
|
||||
}],
|
||||
text: output.text,
|
||||
sections: output
|
||||
.sections
|
||||
.into_iter()
|
||||
.map(|section| SlashCommandOutputSection {
|
||||
range: section.range.into(),
|
||||
icon: IconName::Code,
|
||||
label: section.label.into(),
|
||||
})
|
||||
.collect(),
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ use std::{
|
||||
};
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use url::Url;
|
||||
use util::{maybe, paths::EXTENSIONS_DIR, ResultExt};
|
||||
use util::{maybe, ResultExt};
|
||||
use wasm_host::{
|
||||
wit::{is_supported_wasm_api_version, wasm_api_version_range},
|
||||
WasmExtension, WasmHost,
|
||||
@@ -179,7 +179,7 @@ pub fn init(
|
||||
|
||||
let store = cx.new_model(move |cx| {
|
||||
ExtensionStore::new(
|
||||
EXTENSIONS_DIR.clone(),
|
||||
paths::extensions_dir().clone(),
|
||||
None,
|
||||
fs,
|
||||
client.http_client().clone(),
|
||||
|
||||
@@ -6,7 +6,7 @@ use release_channel::ReleaseChannel;
|
||||
use since_v0_0_7 as latest;
|
||||
|
||||
use super::{wasm_engine, WasmState};
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use language::{LanguageServerName, LspAdapterDelegate};
|
||||
use semantic_version::SemanticVersion;
|
||||
use std::{ops::RangeInclusive, sync::Arc};
|
||||
@@ -19,6 +19,7 @@ use wasmtime::{
|
||||
pub use latest::CodeLabelSpanLiteral;
|
||||
pub use latest::{
|
||||
zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind},
|
||||
zed::extension::slash_command::SlashCommandOutput,
|
||||
CodeLabel, CodeLabelSpan, Command, Range, SlashCommand,
|
||||
};
|
||||
pub use since_v0_0_4::LanguageServerConfig;
|
||||
@@ -256,19 +257,36 @@ impl Extension {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_complete_slash_command_argument(
|
||||
&self,
|
||||
store: &mut Store<WasmState>,
|
||||
command: &SlashCommand,
|
||||
query: &str,
|
||||
) -> Result<Result<Vec<String>, String>> {
|
||||
match self {
|
||||
Extension::V007(ext) => {
|
||||
ext.call_complete_slash_command_argument(store, command, query)
|
||||
.await
|
||||
}
|
||||
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_run_slash_command(
|
||||
&self,
|
||||
store: &mut Store<WasmState>,
|
||||
command: &SlashCommand,
|
||||
argument: Option<&str>,
|
||||
resource: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
) -> Result<Result<SlashCommandOutput, String>> {
|
||||
match self {
|
||||
Extension::V007(ext) => {
|
||||
ext.call_run_slash_command(store, command, argument, resource)
|
||||
.await
|
||||
}
|
||||
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(None)),
|
||||
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
|
||||
Err(anyhow!("`run_slash_command` not available prior to v0.0.7"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,9 @@ impl HostWorktree for WasmState {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl common::Host for WasmState {}
|
||||
|
||||
#[async_trait]
|
||||
impl nodejs::Host for WasmState {
|
||||
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
|
||||
|
||||
@@ -24,7 +24,7 @@ pub use wit::{
|
||||
npm_package_latest_version,
|
||||
},
|
||||
zed::extension::platform::{current_platform, Architecture, Os},
|
||||
zed::extension::slash_command::SlashCommand,
|
||||
zed::extension::slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection},
|
||||
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
||||
LanguageServerInstallationStatus, Range, Worktree,
|
||||
};
|
||||
@@ -66,9 +66,11 @@ pub trait Extension: Send + Sync {
|
||||
/// language.
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
language_server_id: &LanguageServerId,
|
||||
worktree: &Worktree,
|
||||
) -> Result<Command>;
|
||||
_language_server_id: &LanguageServerId,
|
||||
_worktree: &Worktree,
|
||||
) -> Result<Command> {
|
||||
Err("`language_server_command` not implemented".to_string())
|
||||
}
|
||||
|
||||
/// Returns the initialization options to pass to the specified language server.
|
||||
fn language_server_initialization_options(
|
||||
@@ -106,14 +108,23 @@ pub trait Extension: Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
/// Runs the given slash command.
|
||||
/// Returns the completions that should be shown when completing the provided slash command with the given query.
|
||||
fn complete_slash_command_argument(
|
||||
&self,
|
||||
_command: SlashCommand,
|
||||
_query: String,
|
||||
) -> Result<Vec<String>, String> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Returns the output from running the provided slash command.
|
||||
fn run_slash_command(
|
||||
&self,
|
||||
_command: SlashCommand,
|
||||
_argument: Option<String>,
|
||||
_worktree: &Worktree,
|
||||
) -> Result<Option<String>, String> {
|
||||
Ok(None)
|
||||
) -> Result<SlashCommandOutput, String> {
|
||||
Err("`run_slash_command` not implemented".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,11 +234,18 @@ impl wit::Guest for Component {
|
||||
Ok(labels)
|
||||
}
|
||||
|
||||
fn complete_slash_command_argument(
|
||||
command: SlashCommand,
|
||||
query: String,
|
||||
) -> Result<Vec<String>, String> {
|
||||
extension().complete_slash_command_argument(command, query)
|
||||
}
|
||||
|
||||
fn run_slash_command(
|
||||
command: SlashCommand,
|
||||
argument: Option<String>,
|
||||
worktree: &Worktree,
|
||||
) -> Result<Option<String>, String> {
|
||||
) -> Result<SlashCommandOutput, String> {
|
||||
extension().run_slash_command(command, argument, worktree)
|
||||
}
|
||||
}
|
||||
|
||||
9
crates/extension_api/wit/since_v0.0.7/common.wit
Normal file
9
crates/extension_api/wit/since_v0.0.7/common.wit
Normal file
@@ -0,0 +1,9 @@
|
||||
interface common {
|
||||
/// A (half-open) range (`[start, end)`).
|
||||
record range {
|
||||
/// The start of the range (inclusive).
|
||||
start: u32,
|
||||
/// The end of the range (exclusive).
|
||||
end: u32,
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@ world extension {
|
||||
import platform;
|
||||
import nodejs;
|
||||
|
||||
use common.{range};
|
||||
use lsp.{completion, symbol};
|
||||
use slash-command.{slash-command};
|
||||
use slash-command.{slash-command, slash-command-output};
|
||||
|
||||
/// Initializes the extension.
|
||||
export init-extension: func();
|
||||
@@ -118,17 +119,12 @@ world extension {
|
||||
highlight-name: option<string>,
|
||||
}
|
||||
|
||||
/// A (half-open) range (`[start, end)`).
|
||||
record range {
|
||||
/// The start of the range (inclusive).
|
||||
start: u32,
|
||||
/// The end of the range (exclusive).
|
||||
end: u32,
|
||||
}
|
||||
|
||||
export labels-for-completions: func(language-server-id: string, completions: list<completion>) -> result<list<option<code-label>>, string>;
|
||||
export labels-for-symbols: func(language-server-id: string, symbols: list<symbol>) -> result<list<option<code-label>>, string>;
|
||||
|
||||
/// Runs the provided slash command.
|
||||
export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
/// Returns the completions that should be shown when completing the provided slash command with the given query.
|
||||
export complete-slash-command-argument: func(command: slash-command, query: string) -> result<list<string>, string>;
|
||||
|
||||
/// Returns the output from running the provided slash command.
|
||||
export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<slash-command-output, string>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
interface slash-command {
|
||||
use common.{range};
|
||||
|
||||
/// A slash command for use in the Assistant.
|
||||
record slash-command {
|
||||
/// The name of the slash command.
|
||||
@@ -10,4 +12,20 @@ interface slash-command {
|
||||
/// Whether this slash command requires an argument.
|
||||
requires-argument: bool,
|
||||
}
|
||||
|
||||
/// The output of a slash command.
|
||||
record slash-command-output {
|
||||
/// The text produced by the slash command.
|
||||
text: string,
|
||||
/// The list of sections to show in the slash command placeholder.
|
||||
sections: list<slash-command-output-section>,
|
||||
}
|
||||
|
||||
/// A section in the slash command output.
|
||||
record slash-command-output-section {
|
||||
/// The range this section occupies.
|
||||
range: range,
|
||||
/// The label to display in the placeholder for this section.
|
||||
label: string,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ anyhow.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
env_logger.workspace = true
|
||||
fs.workspace = true
|
||||
extension.workspace = true
|
||||
extension = { workspace = true, features = ["no-webrtc"] }
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
rpc.workspace = true
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::ops::DerefMut;
|
||||
use std::time::Duration;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{popover_menu, prelude::*, ContextMenu, ToggleButton, Tooltip};
|
||||
use ui::{prelude::*, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
|
||||
use util::ResultExt as _;
|
||||
use workspace::item::TabContentParams;
|
||||
use workspace::{
|
||||
@@ -526,23 +526,26 @@ impl ExtensionsPage {
|
||||
.tooltip(move |cx| Tooltip::text(repository_url.clone(), cx)),
|
||||
)
|
||||
.child(
|
||||
popover_menu(SharedString::from(format!("more-{}", extension.id)))
|
||||
.trigger(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("more-{}", extension.id)),
|
||||
IconName::Ellipsis,
|
||||
)
|
||||
.icon_color(Color::Accent)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Filled),
|
||||
PopoverMenu::new(SharedString::from(format!(
|
||||
"more-{}",
|
||||
extension.id
|
||||
)))
|
||||
.trigger(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("more-{}", extension.id)),
|
||||
IconName::Ellipsis,
|
||||
)
|
||||
.menu(move |cx| {
|
||||
Some(Self::render_remote_extension_context_menu(
|
||||
&this,
|
||||
extension_id.clone(),
|
||||
cx,
|
||||
))
|
||||
}),
|
||||
.icon_color(Color::Accent)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Filled),
|
||||
)
|
||||
.menu(move |cx| {
|
||||
Some(Self::render_remote_extension_context_menu(
|
||||
&this,
|
||||
extension_id.clone(),
|
||||
cx,
|
||||
))
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -7,9 +7,9 @@ use collections::{BTreeSet, HashMap};
|
||||
use editor::{scroll::Autoscroll, Bias, Editor};
|
||||
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
|
||||
use gpui::{
|
||||
actions, impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter,
|
||||
FocusHandle, FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render,
|
||||
Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||
actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
|
||||
FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task,
|
||||
View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use new_path_prompt::NewPathPrompt;
|
||||
@@ -30,13 +30,6 @@ use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
||||
use workspace::{item::PreviewTabsSettings, ModalView, Workspace};
|
||||
|
||||
actions!(file_finder, [SelectPrev]);
|
||||
impl_actions!(file_finder, [Toggle]);
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)]
|
||||
pub struct Toggle {
|
||||
#[serde(default)]
|
||||
pub separate_history: bool,
|
||||
}
|
||||
|
||||
impl ModalView for FileFinder {}
|
||||
|
||||
@@ -52,7 +45,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
|
||||
impl FileFinder {
|
||||
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||
workspace.register_action(|workspace, action: &Toggle, cx| {
|
||||
workspace.register_action(|workspace, action: &workspace::ToggleFileFinder, cx| {
|
||||
let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
|
||||
Self::open(workspace, action.separate_history, cx);
|
||||
return;
|
||||
|
||||
@@ -6,7 +6,7 @@ use gpui::{Entity, TestAppContext, VisualTestContext};
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::FS_WATCH_LATENCY;
|
||||
use serde_json::json;
|
||||
use workspace::{AppState, Workspace};
|
||||
use workspace::{AppState, ToggleFileFinder, Workspace};
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
@@ -872,7 +872,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
|
||||
let current_history = open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
|
||||
|
||||
for expected_selected_index in 0..current_history.len() {
|
||||
cx.dispatch_action(Toggle::default());
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
let picker = active_file_picker(&workspace, cx);
|
||||
let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index());
|
||||
assert_eq!(
|
||||
@@ -881,7 +881,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
cx.dispatch_action(Toggle::default());
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
let selected_index = workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_modal::<FileFinder>(cx)
|
||||
@@ -1201,7 +1201,7 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
|
||||
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
|
||||
open_queried_buffer("main", 1, "main.rs", &workspace, cx).await;
|
||||
|
||||
cx.dispatch_action(Toggle::default());
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
let picker = active_file_picker(&workspace, cx);
|
||||
// main.rs is on top, previously used is selected
|
||||
picker.update(cx, |finder, _| {
|
||||
@@ -1653,7 +1653,7 @@ async fn test_switches_between_release_norelease_modes_on_forward_nav(
|
||||
// Back to navigation with initial shortcut
|
||||
// Open file on modifiers release
|
||||
cx.simulate_modifiers_change(Modifiers::secondary_key());
|
||||
cx.dispatch_action(Toggle::default());
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
cx.simulate_modifiers_change(Modifiers::none());
|
||||
cx.read(|cx| {
|
||||
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
|
||||
@@ -1769,7 +1769,7 @@ async fn test_repeat_toggle_action(cx: &mut gpui::TestAppContext) {
|
||||
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
|
||||
|
||||
cx.dispatch_action(Toggle::default());
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
let picker = active_file_picker(&workspace, cx);
|
||||
picker.update(cx, |picker, _| {
|
||||
assert_eq!(picker.delegate.selected_index, 0);
|
||||
@@ -1777,9 +1777,9 @@ async fn test_repeat_toggle_action(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
|
||||
// When toggling repeatedly, the picker scrolls to reveal the selected item.
|
||||
cx.dispatch_action(Toggle::default());
|
||||
cx.dispatch_action(Toggle::default());
|
||||
cx.dispatch_action(Toggle::default());
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
cx.dispatch_action(ToggleFileFinder::default());
|
||||
picker.update(cx, |picker, _| {
|
||||
assert_eq!(picker.delegate.selected_index, 3);
|
||||
assert_eq!(picker.logical_scroll_top_index(), 3);
|
||||
@@ -1886,7 +1886,7 @@ fn open_file_picker(
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> View<Picker<FileFinderDelegate>> {
|
||||
cx.dispatch_action(Toggle {
|
||||
cx.dispatch_action(ToggleFileFinder {
|
||||
separate_history: true,
|
||||
});
|
||||
active_file_picker(workspace, cx)
|
||||
|
||||
@@ -12,34 +12,32 @@ workspace = true
|
||||
path = "src/fs.rs"
|
||||
|
||||
[dependencies]
|
||||
collections.workspace = true
|
||||
rope.workspace = true
|
||||
text.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
tempfile.workspace = true
|
||||
lazy_static.workspace = true
|
||||
parking_lot.workspace = true
|
||||
smol.workspace = true
|
||||
git.workspace = true
|
||||
git2.workspace = true
|
||||
gpui = { workspace = true, optional = true }
|
||||
lazy_static.workspace = true
|
||||
libc.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
rope.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
libc.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
|
||||
gpui = { workspace = true, optional = true }
|
||||
util.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
fsevent.workspace = true
|
||||
objc = "0.2"
|
||||
cocoa = "0.25"
|
||||
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
notify = "6.1.1"
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::{
|
||||
};
|
||||
use tempfile::{NamedTempFile, TempDir};
|
||||
use text::LineEnding;
|
||||
use util::{paths, ResultExt};
|
||||
use util::ResultExt;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use collections::{btree_map, BTreeMap};
|
||||
@@ -135,8 +135,6 @@ pub struct RealFs {
|
||||
}
|
||||
|
||||
pub struct RealWatcher {
|
||||
#[cfg(target_os = "linux")]
|
||||
root_path: PathBuf,
|
||||
#[cfg(target_os = "linux")]
|
||||
fs_watcher: parking_lot::Mutex<notify::INotifyWatcher>,
|
||||
}
|
||||
@@ -327,7 +325,7 @@ impl Fs for RealFs {
|
||||
// Use the directory of the destination as temp dir to avoid
|
||||
// invalid cross-device link error, and XDG_CACHE_DIR for fallback.
|
||||
// See https://github.com/zed-industries/zed/pull/8437 for more details.
|
||||
NamedTempFile::new_in(path.parent().unwrap_or(&paths::TEMP_DIR))
|
||||
NamedTempFile::new_in(path.parent().unwrap_or(&paths::temp_dir()))
|
||||
} else {
|
||||
NamedTempFile::new()
|
||||
}?;
|
||||
@@ -452,25 +450,38 @@ impl Fs for RealFs {
|
||||
async fn watch(
|
||||
&self,
|
||||
path: &Path,
|
||||
_latency: Duration,
|
||||
latency: Duration,
|
||||
) -> (
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use parking_lot::Mutex;
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
let pending_paths: Arc<Mutex<Vec<PathBuf>>> = Default::default();
|
||||
let root_path = path.to_path_buf();
|
||||
|
||||
let file_watcher = notify::recommended_watcher({
|
||||
let tx = tx.clone();
|
||||
let pending_paths = pending_paths.clone();
|
||||
move |event: Result<notify::Event, _>| {
|
||||
if let Some(event) = event.log_err() {
|
||||
tx.try_send(event.paths).ok();
|
||||
let mut paths = event.paths;
|
||||
paths.retain(|path| path.starts_with(&root_path));
|
||||
if !paths.is_empty() {
|
||||
paths.sort();
|
||||
let mut pending_paths = pending_paths.lock();
|
||||
if pending_paths.is_empty() {
|
||||
tx.try_send(()).ok();
|
||||
}
|
||||
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, PathBuf::cmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("Could not start file watcher");
|
||||
|
||||
let watcher = Arc::new(RealWatcher {
|
||||
root_path: path.to_path_buf(),
|
||||
fs_watcher: parking_lot::Mutex::new(file_watcher),
|
||||
});
|
||||
|
||||
@@ -484,14 +495,13 @@ impl Fs for RealFs {
|
||||
(
|
||||
Box::pin(rx.filter_map({
|
||||
let watcher = watcher.clone();
|
||||
move |mut paths| {
|
||||
paths.retain(|path| path.starts_with(&watcher.root_path));
|
||||
move |_| {
|
||||
let _ = watcher.clone();
|
||||
let pending_paths = pending_paths.clone();
|
||||
async move {
|
||||
if paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(paths)
|
||||
}
|
||||
smol::Timer::after(latency).await;
|
||||
let paths = std::mem::take(&mut *pending_paths.lock());
|
||||
(!paths.is_empty()).then_some(paths)
|
||||
}
|
||||
}
|
||||
})),
|
||||
|
||||
@@ -134,7 +134,13 @@ impl Render for CursorPosition {
|
||||
});
|
||||
}
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::for_action("Go to Line/Column", &crate::Toggle, cx)),
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Go to Line/Column",
|
||||
&editor::actions::ToggleGoToLine,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ pub mod cursor_position;
|
||||
use cursor_position::LineIndicatorFormat;
|
||||
use editor::{scroll::Autoscroll, Editor};
|
||||
use gpui::{
|
||||
actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
|
||||
div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
|
||||
FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
@@ -13,8 +13,6 @@ use ui::{h_flex, prelude::*, v_flex, Label};
|
||||
use util::paths::FILE_ROW_COLUMN_DELIMITER;
|
||||
use workspace::ModalView;
|
||||
|
||||
actions!(go_to_line, [Toggle]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
LineIndicatorFormat::register(cx);
|
||||
cx.observe_new_views(GoToLine::register).detach();
|
||||
@@ -43,7 +41,7 @@ impl GoToLine {
|
||||
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||
let handle = cx.view().downgrade();
|
||||
editor
|
||||
.register_action(move |_: &Toggle, cx| {
|
||||
.register_action(move |_: &editor::actions::ToggleGoToLine, cx| {
|
||||
let Some(editor) = handle.upgrade() else {
|
||||
return;
|
||||
};
|
||||
@@ -341,7 +339,7 @@ mod tests {
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> View<GoToLine> {
|
||||
cx.dispatch_action(Toggle);
|
||||
cx.dispatch_action(editor::actions::ToggleGoToLine);
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.active_modal::<GoToLine>(cx).unwrap().clone()
|
||||
})
|
||||
|
||||
@@ -103,15 +103,14 @@ blade-graphics.workspace = true
|
||||
blade-macros.workspace = true
|
||||
blade-util.workspace = true
|
||||
bytemuck = "1"
|
||||
cosmic-text = "0.11.2"
|
||||
copypasta = "0.10.1"
|
||||
cosmic-text = { git = "https://github.com/pop-os/cosmic-text", rev = "542b20c" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
as-raw-xcb-connection = "1"
|
||||
ashpd.workspace = true
|
||||
calloop = "0.12.4"
|
||||
calloop-wayland-source = "0.2.0"
|
||||
wayland-backend = { version = "0.3.3", features = ["client_system"] }
|
||||
calloop = "0.13.0"
|
||||
calloop-wayland-source = "0.3.0"
|
||||
wayland-backend = { version = "0.3.3", features = ["client_system", "dlopen"] }
|
||||
wayland-client = { version = "0.31.2" }
|
||||
wayland-cursor = "0.31.1"
|
||||
wayland-protocols = { version = "0.31.2", features = [
|
||||
@@ -136,11 +135,12 @@ xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca
|
||||
"x11rb-xcb",
|
||||
"x11rb-client",
|
||||
] }
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
|
||||
x11-clipboard = "0.9.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
windows-core = "0.57"
|
||||
clipboard-win = "3.1.1"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
embed-resource = "2.4"
|
||||
|
||||
@@ -189,7 +189,7 @@ macro_rules! actions {
|
||||
#[serde(crate = "gpui::private::serde")]
|
||||
pub struct $name;
|
||||
|
||||
gpui::__impl_action!($namespace, $name,
|
||||
gpui::__impl_action!($namespace, $name, $name,
|
||||
fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||
Ok(Box::new(Self))
|
||||
}
|
||||
@@ -200,12 +200,48 @@ macro_rules! actions {
|
||||
};
|
||||
}
|
||||
|
||||
/// Defines a unit struct that can be used as an actions, with a name
|
||||
/// that differs from it's type name.
|
||||
///
|
||||
/// To use more complex data types as actions, and rename them use
|
||||
/// `impl_action_as!`
|
||||
#[macro_export]
|
||||
macro_rules! action_as {
|
||||
($namespace:path, $name:ident as $visual_name:tt) => {
|
||||
#[doc = "The `"]
|
||||
#[doc = stringify!($name)]
|
||||
#[doc = "` action, see [`gpui::actions!`]"]
|
||||
#[derive(
|
||||
::std::cmp::PartialEq,
|
||||
::std::clone::Clone,
|
||||
::std::default::Default,
|
||||
::std::fmt::Debug,
|
||||
gpui::private::serde_derive::Deserialize,
|
||||
)]
|
||||
#[serde(crate = "gpui::private::serde")]
|
||||
pub struct $name;
|
||||
|
||||
gpui::__impl_action!(
|
||||
$namespace,
|
||||
$name,
|
||||
$visual_name,
|
||||
fn build(
|
||||
_: gpui::private::serde_json::Value,
|
||||
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||
Ok(Box::new(Self))
|
||||
}
|
||||
);
|
||||
|
||||
gpui::register_action!($name);
|
||||
};
|
||||
}
|
||||
|
||||
/// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
|
||||
#[macro_export]
|
||||
macro_rules! impl_actions {
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
$(
|
||||
gpui::__impl_action!($namespace, $name,
|
||||
gpui::__impl_action!($namespace, $name, $name,
|
||||
fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||
Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?))
|
||||
}
|
||||
@@ -216,17 +252,39 @@ macro_rules! impl_actions {
|
||||
};
|
||||
}
|
||||
|
||||
/// Implements the Action trait for a struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
|
||||
/// Allows you to rename the action visually, without changing the struct's name
|
||||
#[macro_export]
|
||||
macro_rules! impl_action_as {
|
||||
($namespace:path, $name:ident as $visual_name:tt ) => {
|
||||
gpui::__impl_action!(
|
||||
$namespace,
|
||||
$name,
|
||||
$visual_name,
|
||||
fn build(
|
||||
value: gpui::private::serde_json::Value,
|
||||
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||
Ok(std::boxed::Box::new(
|
||||
gpui::private::serde_json::from_value::<Self>(value)?,
|
||||
))
|
||||
}
|
||||
);
|
||||
|
||||
gpui::register_action!($name);
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __impl_action {
|
||||
($namespace:path, $name:ident, $build:item) => {
|
||||
($namespace:path, $name:ident, $visual_name:tt, $build:item) => {
|
||||
impl gpui::Action for $name {
|
||||
fn name(&self) -> &'static str
|
||||
{
|
||||
concat!(
|
||||
stringify!($namespace),
|
||||
"::",
|
||||
stringify!($name),
|
||||
stringify!($visual_name),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -237,7 +295,7 @@ macro_rules! __impl_action {
|
||||
concat!(
|
||||
stringify!($namespace),
|
||||
"::",
|
||||
stringify!($name),
|
||||
stringify!($visual_name),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -479,6 +479,7 @@ impl AppContext {
|
||||
Ok(mut window) => {
|
||||
let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
|
||||
window.root_view.replace(root_view.into());
|
||||
WindowContext::new(cx, &mut window).defer(|cx| cx.appearance_changed());
|
||||
cx.window_handles.insert(id, window.handle);
|
||||
cx.windows.get_mut(id).unwrap().replace(window);
|
||||
Ok(handle)
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use util::TryFutureExt;
|
||||
use waker_fn::waker_fn;
|
||||
@@ -25,14 +25,16 @@ use rand::rngs::StdRng;
|
||||
/// for spawning background tasks.
|
||||
#[derive(Clone)]
|
||||
pub struct BackgroundExecutor {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
#[doc(hidden)]
|
||||
pub dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
}
|
||||
|
||||
/// A pointer to the executor that is currently running,
|
||||
/// for spawning tasks on the main thread.
|
||||
#[derive(Clone)]
|
||||
pub struct ForegroundExecutor {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
#[doc(hidden)]
|
||||
pub dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
not_send: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
@@ -314,6 +316,14 @@ impl BackgroundExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current time.
|
||||
///
|
||||
/// Calling this instead of `std::time::Instant::now` allows the use
|
||||
/// of fake timers in tests.
|
||||
pub fn now(&self) -> Instant {
|
||||
self.dispatcher.now()
|
||||
}
|
||||
|
||||
/// Returns a task that will complete after the given duration.
|
||||
/// Depending on other concurrent tasks the elapsed duration may be longer
|
||||
/// than requested.
|
||||
|
||||
@@ -2157,6 +2157,12 @@ impl From<Percentage> for Radians {
|
||||
#[repr(transparent)]
|
||||
pub struct Pixels(pub f32);
|
||||
|
||||
impl std::fmt::Display for Pixels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_fmt(format_args!("{}px", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div for Pixels {
|
||||
type Output = f32;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ use seahash::SeaHasher;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
ops::Range,
|
||||
@@ -275,6 +275,9 @@ pub trait PlatformDispatcher: Send + Sync {
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
|
||||
fn park(&self, timeout: Option<Duration>) -> bool;
|
||||
fn unparker(&self) -> Unparker;
|
||||
fn now(&self) -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_test(&self) -> Option<&TestDispatcher> {
|
||||
|
||||
@@ -6,7 +6,8 @@ use crate::{
|
||||
use anyhow::{anyhow, Context, Ok, Result};
|
||||
use collections::HashMap;
|
||||
use cosmic_text::{
|
||||
Attrs, AttrsList, BufferLine, CacheKey, Family, Font as CosmicTextFont, FontSystem, SwashCache,
|
||||
Attrs, AttrsList, CacheKey, Family, Font as CosmicTextFont, FontSystem, ShapeBuffer, ShapeLine,
|
||||
SwashCache,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
@@ -23,6 +24,7 @@ pub(crate) struct CosmicTextSystem(RwLock<CosmicTextSystemState>);
|
||||
struct CosmicTextSystemState {
|
||||
swash_cache: SwashCache,
|
||||
font_system: FontSystem,
|
||||
scratch: ShapeBuffer,
|
||||
/// Contains all already loaded fonts, including all faces. Indexed by `FontId`.
|
||||
loaded_fonts_store: Vec<Arc<CosmicTextFont>>,
|
||||
/// Caches the `FontId`s associated with a specific family to avoid iterating the font database
|
||||
@@ -42,6 +44,7 @@ impl CosmicTextSystem {
|
||||
Self(RwLock::new(CosmicTextSystemState {
|
||||
font_system,
|
||||
swash_cache: SwashCache::new(),
|
||||
scratch: ShapeBuffer::default(),
|
||||
loaded_fonts_store: Vec::new(),
|
||||
font_ids_by_family_cache: HashMap::default(),
|
||||
postscript_names: HashMap::default(),
|
||||
@@ -60,8 +63,6 @@ impl PlatformTextSystem for CosmicTextSystem {
|
||||
self.0.write().add_fonts(fonts)
|
||||
}
|
||||
|
||||
// todo(linux) ensure that this integrates with platform font loading
|
||||
// do we need to do more than call load_system_fonts()?
|
||||
fn all_font_names(&self) -> Vec<String> {
|
||||
self.0
|
||||
.read()
|
||||
@@ -367,13 +368,11 @@ impl CosmicTextSystemState {
|
||||
}
|
||||
}
|
||||
|
||||
// todo(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer
|
||||
#[profiling::function]
|
||||
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
|
||||
let mut attrs_list = AttrsList::new(Attrs::new());
|
||||
let mut offs = 0;
|
||||
for run in font_runs {
|
||||
// todo(linux) We need to check we are doing utf properly
|
||||
let font = &self.loaded_fonts_store[run.font_id.0];
|
||||
let font = self.font_system.db().face(font.id()).unwrap();
|
||||
attrs_list.add_span(
|
||||
@@ -386,17 +385,27 @@ impl CosmicTextSystemState {
|
||||
);
|
||||
offs += run.len;
|
||||
}
|
||||
let mut line = BufferLine::new(text, attrs_list, cosmic_text::Shaping::Advanced);
|
||||
|
||||
let layout = line.layout(
|
||||
let mut line = ShapeLine::new_in_buffer(
|
||||
&mut self.scratch,
|
||||
&mut self.font_system,
|
||||
text,
|
||||
&attrs_list,
|
||||
cosmic_text::Shaping::Advanced,
|
||||
4,
|
||||
);
|
||||
|
||||
let mut layout = Vec::with_capacity(1);
|
||||
line.layout_to_buffer(
|
||||
&mut self.scratch,
|
||||
font_size.0,
|
||||
f32::MAX, // We do our own wrapping
|
||||
None, // We do our own wrapping
|
||||
cosmic_text::Wrap::None,
|
||||
None,
|
||||
&mut layout,
|
||||
None,
|
||||
);
|
||||
let mut runs = Vec::new();
|
||||
|
||||
let mut runs = Vec::new();
|
||||
let layout = layout.first().unwrap();
|
||||
for glyph in &layout.glyphs {
|
||||
let font_id = glyph.font_id;
|
||||
@@ -412,7 +421,7 @@ impl CosmicTextSystemState {
|
||||
// todo(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction
|
||||
glyphs.push(ShapedGlyph {
|
||||
id: GlyphId(glyph.glyph_id as u32),
|
||||
position: point((glyph.x).into(), glyph.y.into()),
|
||||
position: point(glyph.x.into(), glyph.y.into()),
|
||||
index: glyph.start,
|
||||
is_emoji,
|
||||
});
|
||||
|
||||
@@ -109,6 +109,17 @@ impl Keystroke {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if this keystroke left
|
||||
/// the ime system in an incomplete state.
|
||||
pub fn is_ime_in_progress(&self) -> bool {
|
||||
self.ime_key.is_none()
|
||||
&& (is_printable_key(&self.key) || self.key.is_empty())
|
||||
&& !(self.modifiers.platform
|
||||
|| self.modifiers.control
|
||||
|| self.modifiers.function
|
||||
|| self.modifiers.alt)
|
||||
}
|
||||
|
||||
/// Returns a new keystroke with the ime_key filled.
|
||||
/// This is used for dispatch_keystroke where we want users to
|
||||
/// be able to simulate typing "space", etc.
|
||||
@@ -123,9 +134,7 @@ impl Keystroke {
|
||||
"space" => Some(" ".into()),
|
||||
"tab" => Some("\t".into()),
|
||||
"enter" => Some("\n".into()),
|
||||
"up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end"
|
||||
| "delete" | "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6"
|
||||
| "f7" | "f8" | "f9" | "f10" | "f11" | "f12" => None,
|
||||
key if !is_printable_key(key) => None,
|
||||
key => {
|
||||
if self.modifiers.shift {
|
||||
Some(key.to_uppercase())
|
||||
@@ -139,6 +148,15 @@ impl Keystroke {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_printable_key(key: &str) -> bool {
|
||||
match key {
|
||||
"up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end" | "delete"
|
||||
| "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9"
|
||||
| "f10" | "f11" | "f12" => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Keystroke {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.modifiers.control {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user