Compare commits

..

10 Commits

Author SHA1 Message Date
Mikayla Maki
99105ea26e Attempt a feature based way of switching to dynamic libraries 2024-06-18 22:00:58 -07:00
Mikayla Maki
72f54f1c44 Switch to feature based library linking 2024-06-18 21:21:57 -07:00
Mikayla
66c667b6c6 fix typo in arm builders 2024-06-14 17:26:48 -07:00
Mikayla
85613af919 Disable usage of sqlite3 features that are too new for Ubuntu 2024-06-14 17:00:10 -07:00
Mikayla
e1c0d10f76 Add sqlite3 to arm runners 2024-06-14 15:48:36 -07:00
Mikayla Maki
81c12961db Fix windows bundles 2024-06-14 15:13:23 -07:00
Mikayla Maki
3ae9d61791 Ensure we only dynamically link fontconfig on linux 2024-06-14 14:48:22 -07:00
Mikayla Maki
6d0042b502 Remove async-native-tls 2024-06-14 14:39:28 -07:00
Mikayla Maki
27f0665246 Make macOS use statically linked libraries 2024-06-14 14:37:28 -07:00
Conrad Irwin
6aa78ff095 Dynamicer builds
Co-Authored-By: Mikayla <mikayla@zed.dev>
2024-06-14 15:14:20 -06:00
312 changed files with 5008 additions and 12673 deletions

15
.cargo/ci-config.toml Normal file
View File

@@ -0,0 +1,15 @@
# This config is different from config.toml in this directory, as the latter is recognized by Cargo.
# This file is placed in $HOME/.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with $HOME/.cargo/config.toml
# with preference for settings from Zeds config.toml.
# TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence.
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
# would be incovenient.
# We *could* override things like RUSTFLAGS manually by setting them as environment variables, but that is less DRY; worse yet, if you forget to set proper environment variables
# in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it.
[build]
rustflags = ["-D", "warnings"]
[alias]
xtask = "run --package xtask --"

View File

@@ -1,5 +0,0 @@
# This file is used to build collab in a Docker image.
# In particular, we don't use clang.
[build]
# v0 mangling scheme provides more detailed backtraces around closures
rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]

View File

@@ -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
- 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:
required: true
- 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
- 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

View File

@@ -1,45 +1,40 @@
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
- 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:
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
required: true
- 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
- 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

View File

@@ -1,38 +1,33 @@
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
- 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:
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
required: true
- 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
- 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

View File

@@ -38,6 +38,9 @@ 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
@@ -51,9 +54,6 @@ 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: ./script/clippy
run: cargo xtask clippy
- name: Run tests
uses: ./.github/actions/run_tests
@@ -117,7 +117,7 @@ jobs:
clean: false
- name: cargo clippy
run: ./script/clippy
run: cargo xtask 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: ./script/clippy
run: cargo xtask clippy
- name: Build Zed
run: cargo build -p zed
@@ -307,7 +307,7 @@ jobs:
exit 1
fi
- name: Create Linux .tar.gz bundle
- name: Create and upload Linux .tar.gz bundle
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
@@ -315,7 +315,7 @@ jobs:
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
path: target/release/zed-*.tar.gz
path: zed-*.tar.gz
- name: Upload app bundle to release
uses: softprops/action-gh-release@v1
@@ -348,7 +348,7 @@ jobs:
- name: Set up Clang
run: |
sudo apt-get update
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libzstd-dev libvulkan1 libgit2-dev
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
- uses: rui314/setup-mold@v1

View File

@@ -27,7 +27,7 @@ jobs:
uses: ./.github/actions/check_style
- name: Run clippy
run: ./script/clippy
run: cargo xtask clippy
tests:
name: Run tests
@@ -75,9 +75,6 @@ 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

View File

@@ -32,7 +32,7 @@ jobs:
uses: ./.github/actions/check_style
- name: Run clippy
run: ./script/clippy
run: cargo xtask clippy
tests:
timeout-minutes: 60
name: Run tests

View File

@@ -9,18 +9,12 @@
# 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>
@@ -54,23 +48,12 @@ 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>

View File

@@ -1,7 +1,7 @@
[
{
"label": "clippy",
"command": "./script/clippy",
"args": []
"command": "cargo",
"args": ["xtask", "clippy"]
}
]

904
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -65,7 +65,6 @@ members = [
"crates/open_ai",
"crates/outline",
"crates/outline_panel",
"crates/paths",
"crates/picker",
"crates/prettier",
"crates/project",
@@ -78,7 +77,6 @@ members = [
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/dev_server_projects",
"crates/repl",
"crates/rich_text",
"crates/rope",
"crates/rpc",
@@ -136,7 +134,6 @@ members = [
"extensions/prisma",
"extensions/purescript",
"extensions/ruby",
"extensions/snippets",
"extensions/svelte",
"extensions/terraform",
"extensions/toml",
@@ -217,7 +214,6 @@ 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" }
@@ -231,7 +227,6 @@ 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" }
@@ -269,21 +264,19 @@ 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"
async-trait = "0.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
blade-macros = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
blade-util = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
cap-std = "3.0"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }
@@ -291,10 +284,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"
@@ -302,13 +295,12 @@ fork = "0.1.23"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.19", default-features = false }
git2 = { version = "0.18", 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.25.1"
indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "1"
# We explicitly disable http2 support in isahc.
@@ -338,7 +330,6 @@ 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"
@@ -354,7 +345,6 @@ 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"
@@ -459,12 +449,11 @@ features = [
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7b4894ba2ae81b988846676f54c0988d4027ef4f" }
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "4968e819c0d9b015437ffc694511e175801a17c7" }
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }
[profile.dev]
split-debuginfo = "unpacked"
debug = "limited"
codegen-units = 16
[profile.dev.package]
taffy = { opt-level = 3 }
@@ -483,11 +472,6 @@ codegen-units = 1
[profile.release.package]
zed = { codegen-units = 16 }
[profile.release-fast]
inherits = "release"
lto = false
codegen-units = 16
[workspace.lints.clippy]
dbg_macro = "deny"
todo = "deny"

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>

Before

Width:  |  Height:  |  Size: 302 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>

Before

Width:  |  Height:  |  Size: 345 B

View File

@@ -25,8 +25,7 @@
],
"ctrl-shift-down": "editor::AddSelectionBelow",
"ctrl-shift-up": "editor::AddSelectionAbove",
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
"ctrl-shift-m": "markdown::OpenPreviewToTheSide"
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine"
}
},
{

View File

@@ -1,4 +1,5 @@
[
// todo(linux): Review the editor bindings
// Standard Linux bindings
{
"bindings": {
@@ -42,8 +43,13 @@
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-t": "editor::Transpose",
// "ctrl-backspace": "editor::DeleteToBeginningOfLine",
// "ctrl-delete": "editor::DeleteToEndOfLine",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
// "ctrl-w": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd",
// "alt-h": "editor::DeleteToPreviousWordStart",
// "alt-d": "editor::DeleteToNextWordEnd",
"ctrl-x": "editor::Cut",
"ctrl-insert": "editor::Copy",
"ctrl-c": "editor::Copy",
@@ -53,19 +59,25 @@
"ctrl-z": "editor::Undo",
"ctrl-shift-z": "editor::Redo",
"up": "editor::MoveUp",
"ctrl-up": "editor::LineUp",
"ctrl-down": "editor::LineDown",
// "ctrl-up": "editor::MoveToStartOfParagraph", todo(linux) Should be "scroll down by 1 line"
"pageup": "editor::PageUp",
"shift-pageup": "editor::SelectPageUp",
// "shift-pageup": "editor::MovePageUp", todo(linux) should be 'select page up'
"home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown",
// "ctrl-down": "editor::MoveToEndOfParagraph", todo(linux) should be "scroll up by 1 line"
"pagedown": "editor::PageDown",
"shift-pagedown": "editor::SelectPageDown",
// "shift-pagedown": "editor::MovePageDown", todo(linux) should be 'select page down'
"end": "editor::MoveToEndOfLine",
"left": "editor::MoveLeft",
"right": "editor::MoveRight",
"ctrl-left": "editor::MoveToPreviousWordStart",
// "alt-b": "editor::MoveToPreviousWordStart",
"ctrl-right": "editor::MoveToNextWordEnd",
// "alt-f": "editor::MoveToNextWordEnd",
// "cmd-left": "editor::MoveToBeginningOfLine",
// "ctrl-a": "editor::MoveToBeginningOfLine",
// "cmd-right": "editor::MoveToEndOfLine",
// "ctrl-e": "editor::MoveToEndOfLine",
"ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
@@ -76,6 +88,8 @@
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::AddSelectionAbove",
"ctrl-shift-down": "editor::AddSelectionBelow",
// "ctrl-shift-up": "editor::SelectToStartOfParagraph",
// "ctrl-shift-down": "editor::SelectToEndOfParagraph",
"ctrl-shift-home": "editor::SelectToBeginning",
"ctrl-shift-end": "editor::SelectToEnd",
"ctrl-a": "editor::SelectAll",
@@ -152,8 +166,7 @@
// "focus": false
// }
// ],
"ctrl->": "assistant::QuoteSelection",
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
"ctrl->": "assistant::QuoteSelection"
}
},
{
@@ -283,13 +296,6 @@
"ctrl-alt-shift-x": "search::ToggleRegex"
}
},
{
"context": "Terminal",
"bindings": {
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"]
}
},
// Bindings from VS Code
{
"context": "Editor",

View File

@@ -188,8 +188,7 @@
"focus": false
}
],
"cmd->": "assistant::QuoteSelection",
"cmd-alt-e": "editor::SelectEnclosingSymbol"
"cmd->": "assistant::QuoteSelection"
}
},
{

View File

@@ -39,7 +39,6 @@
"right": "vim::Right",
"space": "vim::Space",
"$": "vim::EndOfLine",
"end": "vim::EndOfLine",
"^": "vim::FirstNonWhitespace",
"_": "vim::StartOfLineDownward",
"g _": "vim::EndOfLineDownward",
@@ -141,8 +140,7 @@
"ctrl-q": "vim::ToggleVisualBlock",
"shift-k": "editor::Hover",
"shift-r": "vim::ToggleReplace",
"0": "vim::StartOfLine",
"home": "vim::StartOfLine",
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
"ctrl-f": "vim::PageDown",
"pagedown": "vim::PageDown",
"ctrl-b": "vim::PageUp",
@@ -410,7 +408,7 @@
}
},
{
"context": "Editor && VimCount && vim_mode != insert",
"context": "Editor && VimCount",
"bindings": {
"0": ["vim::Number", 0]
}
@@ -612,13 +610,13 @@
{
"context": "Editor && vim_mode == normal && !VimWaiting",
"bindings": {
"g c c": "vim::ToggleComments"
"g c c": "editor::ToggleComments"
}
},
{
"context": "Editor && vim_mode == visual",
"bindings": {
"g c": "vim::ToggleComments"
"g c": "editor::ToggleComments"
}
},
{
@@ -636,7 +634,8 @@
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent",
"ctrl-r": ["vim::PushOperator", "Register"]
"ctrl-r \"": "editor::Paste",
"ctrl-r +": "editor::Paste"
}
},
{
@@ -645,13 +644,11 @@
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"tab": "vim::Tab",
"enter": "vim::Enter",
"backspace": "vim::UndoReplace"
}
},
{
"context": "Editor && vim_mode != replace && VimWaiting",
"context": "Editor && VimWaiting",
"bindings": {
"tab": "vim::Tab",
"enter": "vim::Enter",
@@ -659,13 +656,6 @@
"ctrl-[": ["vim::SwitchMode", "Normal"]
}
},
{
"context": "Editor && vim_mode == insert && VimWaiting",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore"
}
},
{
"context": "BufferSearchBar && !in_replace",
"bindings": {

View File

@@ -131,14 +131,7 @@
// 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,
@@ -146,19 +139,15 @@
// opening parenthesis, bracket, brace, single or double quote characters.
// For example, when you type (, Zed will add a closing ) at the correct position.
"use_autoclose": true,
// Whether to automatically surround selected text when typing opening parenthesis,
// bracket, brace, single or double quote characters.
// For example, when you select text and type (, Zed will surround the text with ().
"use_auto_surround": true,
// Controls how the editor handles the autoclosed characters.
// When set to `false`(default), skipping over and auto-removing of the closing characters
// happen only for auto-inserted characters.
// 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 inline completions are shown immediately (true)
// or manually by triggering `editor::ShowInlineCompletion` (false).
"show_inline_completions": true,
// Controls whether copilot provides suggestion immediately
// or waits for a `copilot::Toggle`
"show_copilot_suggestions": true,
// Whether to show tabs and spaces in the editor.
// This setting can take three values:
//
@@ -187,9 +176,7 @@
// Whether to show breadcrumbs.
"breadcrumbs": true,
// Whether to show quick action buttons.
"quick_actions": true,
// Whether to show the Selections menu in the editor toolbar
"selections_menu": true
"quick_actions": true
},
// Scrollbar related settings
"scrollbar": {
@@ -231,8 +218,6 @@
"line_numbers": true,
// Whether to show code action buttons in the gutter.
"code_actions": true,
// Whether to show runnables buttons in the gutter.
"runnables": true,
// Whether to show fold buttons in the gutter.
"folds": true
},
@@ -241,8 +226,6 @@
"enabled": true,
/// The width of the indent guides in pixels, between 1 and 10.
"line_width": 1,
/// The width of the active indent guide in pixels, between 1 and 10.
"active_line_width": 1,
/// Determines how indent guides are colored.
/// This setting can take the following three values:
///
@@ -257,8 +240,6 @@
/// 2. "indent_aware"
"background_coloring": "disabled"
},
// Whether the editor will scroll beyond the last line.
"scroll_beyond_last_line": "one_page",
// The number of lines to keep above/below the cursor when scrolling.
"vertical_scroll_margin": 3,
// Scroll sensitivity multiplier. This multiplier is applied
@@ -312,14 +293,7 @@
"auto_reveal_entries": true,
/// Whether to fold directories automatically
/// when a directory has only one directory inside.
"auto_fold_dirs": false,
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
///
/// Default: always
"show": "always"
}
"auto_fold_dirs": false
},
"outline_panel": {
// Whether to show the outline panel button in the status bar
@@ -477,16 +451,16 @@
// or falling back to formatting via language server:
// "formatter": "auto"
"formatter": "auto",
// How to soft-wrap long lines of text.
// Possible values:
// How to soft-wrap long lines of text. This setting can take
// three values:
//
// 1. Do not soft wrap.
// "soft_wrap": "none",
// 2. Prefer a single line generally, unless an overly long line is encountered.
// "soft_wrap": "prefer_line",
// 3. Soft wrap lines that overflow the editor.
// 3. Soft wrap lines that overflow the editor:
// "soft_wrap": "editor_width",
// 4. Soft wrap lines at the preferred line length.
// 4. Soft wrap lines at the preferred line length
// "soft_wrap": "preferred_line_length",
"soft_wrap": "prefer_line",
// The column at which to soft-wrap lines, for buffers where soft-wrap
@@ -542,8 +516,9 @@
// "delay_ms": 600
}
},
"inline_completions": {
// A list of globs representing files that inline completions should be disabled for.
"copilot": {
// The set of glob patterns for which copilot should be disabled
// in any matching file.
"disabled_globs": [".env"]
},
// Settings specific to journaling
@@ -748,7 +723,7 @@
}
},
"JavaScript": {
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"language_servers": ["typescript-language-server", "!vtsls", "..."],
"prettier": {
"allowed": true
}
@@ -791,7 +766,7 @@
}
},
"TSX": {
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"language_servers": ["typescript-language-server", "!vtsls", "..."],
"prettier": {
"allowed": true
}
@@ -802,7 +777,7 @@
}
},
"TypeScript": {
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"language_servers": ["typescript-language-server", "!vtsls", "..."],
"prettier": {
"allowed": true
}

View File

@@ -3,22 +3,22 @@ use editor::Editor;
use extension::ExtensionStore;
use futures::StreamExt;
use gpui::{
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,
actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
ViewContext, VisualContext as _,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
use project::{LanguageServerProgress, Project};
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
use ui::{prelude::*, ContextMenu};
use std::{cmp::Reverse, fmt::Write, sync::Arc};
use ui::prelude::*;
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,7 +27,6 @@ pub struct ActivityIndicator {
statuses: Vec<LspStatus>,
project: Model<Project>,
auto_updater: Option<Model<AutoUpdater>>,
context_menu: Option<View<ContextMenu>>,
}
struct LspStatus {
@@ -36,14 +35,14 @@ struct LspStatus {
}
struct PendingWork<'a> {
language_server_id: LanguageServerId,
language_server_name: &'a str,
progress_token: &'a str,
progress: &'a LanguageServerProgress,
}
#[derive(Default)]
struct Content {
icon: Option<gpui::AnyElement>,
icon: Option<&'static str>,
message: String,
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
}
@@ -79,7 +78,6 @@ impl ActivityIndicator {
statuses: Default::default(),
project: project.clone(),
auto_updater,
context_menu: None,
}
});
@@ -153,7 +151,7 @@ impl ActivityIndicator {
.read(cx)
.language_server_statuses()
.rev()
.filter_map(|(server_id, status)| {
.filter_map(|status| {
if status.pending_work.is_empty() {
None
} else {
@@ -161,7 +159,7 @@ impl ActivityIndicator {
.pending_work
.iter()
.map(|(token, progress)| PendingWork {
language_server_id: server_id,
language_server_name: status.name.as_str(),
progress_token: token.as_str(),
progress,
})
@@ -177,44 +175,33 @@ 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 = progress
.title
.as_deref()
.unwrap_or(progress_token)
.to_string();
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);
}
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: 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(),
),
icon: None,
message,
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
on_click: None,
};
}
@@ -235,11 +222,7 @@ impl ActivityIndicator {
if !downloading.is_empty() {
return Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(DOWNLOAD_ICON),
message: format!("Downloading {}...", downloading.join(", "),),
on_click: None,
};
@@ -247,11 +230,7 @@ impl ActivityIndicator {
if !checking_for_update.is_empty() {
return Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(DOWNLOAD_ICON),
message: format!(
"Checking for updates to {}...",
checking_for_update.join(", "),
@@ -262,11 +241,7 @@ impl ActivityIndicator {
if !failed.is_empty() {
return Content {
icon: Some(
Icon::new(IconName::ExclamationTriangle)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(WARNING_ICON),
message: format!(
"Failed to download {}. Click to show error.",
failed.join(", "),
@@ -280,11 +255,7 @@ impl ActivityIndicator {
// Show any formatting failure
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
return Content {
icon: Some(
Icon::new(IconName::ExclamationTriangle)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(WARNING_ICON),
message: format!("Formatting failed: {}. Click to see logs.", failure),
on_click: Some(Arc::new(|_, cx| {
cx.dispatch_action(Box::new(workspace::OpenLog));
@@ -296,29 +267,17 @@ impl ActivityIndicator {
if let Some(updater) = &self.auto_updater {
return match &updater.read(cx).status() {
AutoUpdateStatus::Checking => Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(DOWNLOAD_ICON),
message: "Checking for Zed updates…".to_string(),
on_click: None,
},
AutoUpdateStatus::Downloading => Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(DOWNLOAD_ICON),
message: "Downloading Zed update…".to_string(),
on_click: None,
},
AutoUpdateStatus::Installing => Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(DOWNLOAD_ICON),
message: "Installing Zed update…".to_string(),
on_click: None,
},
@@ -333,11 +292,7 @@ impl ActivityIndicator {
})),
},
AutoUpdateStatus::Errored => Content {
icon: Some(
Icon::new(IconName::ExclamationTriangle)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(WARNING_ICON),
message: "Auto update failed".to_string(),
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&Default::default(), cx)
@@ -352,11 +307,7 @@ impl ActivityIndicator {
{
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
return Content {
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
icon: Some(DOWNLOAD_ICON),
message: format!("Updating {extension_id} extension…"),
on_click: None,
};
@@ -365,75 +316,6 @@ 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 {}
@@ -456,17 +338,8 @@ impl Render for ActivityIndicator {
}
result
.gap_2()
.children(content.icon)
.children(content.icon.map(|icon| svg().path(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)
}))
}
}

View File

@@ -12,8 +12,6 @@ pub const ANTHROPIC_API_URL: &'static str = "https://api.anthropic.com";
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
#[default]
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3-5-sonnet-20240620")]
Claude3_5Sonnet,
#[serde(alias = "claude-3-opus", rename = "claude-3-opus-20240229")]
Claude3Opus,
#[serde(alias = "claude-3-sonnet", rename = "claude-3-sonnet-20240229")]
@@ -24,9 +22,7 @@ pub enum Model {
impl Model {
pub fn from_id(id: &str) -> Result<Self> {
if id.starts_with("claude-3-5-sonnet") {
Ok(Self::Claude3_5Sonnet)
} else if id.starts_with("claude-3-opus") {
if id.starts_with("claude-3-opus") {
Ok(Self::Claude3Opus)
} else if id.starts_with("claude-3-sonnet") {
Ok(Self::Claude3Sonnet)
@@ -39,7 +35,6 @@ impl Model {
pub fn id(&self) -> &'static str {
match self {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-20240620",
Model::Claude3Opus => "claude-3-opus-20240229",
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
Model::Claude3Haiku => "claude-3-opus-20240307",
@@ -48,7 +43,6 @@ impl Model {
pub fn display_name(&self) -> &'static str {
match self {
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",

View File

@@ -16,7 +16,6 @@ doctest = false
anyhow.workspace = true
anthropic = { workspace = true, features = ["schemars"] }
assistant_slash_command.workspace = true
async-watch.workspace = true
cargo_toml.workspace = true
chrono.workspace = true
client.workspace = true
@@ -40,7 +39,6 @@ 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
@@ -56,7 +54,6 @@ smol.workspace = true
strsim = "0.11"
strum.workspace = true
telemetry_events.workspace = true
terminal_view.workspace = true
theme.workspace = true
tiktoken-rs.workspace = true
toml.workspace = true

View File

@@ -10,14 +10,14 @@ mod search;
mod slash_command;
mod streaming_diff;
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
pub use assistant_panel::AssistantPanel;
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaModel, OpenAiModel};
use assistant_slash_command::SlashCommandRegistry;
use client::{proto, Client};
use command_palette_hooks::CommandPaletteFilter;
pub(crate) use completion_provider::*;
pub(crate) use context_store::*;
use fs::Fs;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
pub(crate) use inline_assistant::*;
pub(crate) use model_selector::*;
@@ -26,15 +26,15 @@ use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use slash_command::{
active_command, auto_command, default_command, diagnostics_command, fetch_command,
file_command, now_command, project_command, prompt_command, rustdoc_command, search_command,
tabs_command, term_command,
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
project_command, prompt_command, rustdoc_command, search_command, tabs_command,
};
use std::{
fmt::{self, Display},
sync::Arc,
};
pub(crate) use streaming_diff::*;
use util::paths::EMBEDDINGS_DIR;
actions!(
assistant,
@@ -187,10 +187,7 @@ impl LanguageModelRequest {
LanguageModel::Anthropic(_) => {}
LanguageModel::Ollama(_) => {}
LanguageModel::Cloud(model) => match model {
CloudModel::Claude3Opus
| CloudModel::Claude3Sonnet
| CloudModel::Claude3Haiku
| CloudModel::Claude3_5Sonnet => {
CloudModel::Claude3Opus | CloudModel::Claude3Sonnet | CloudModel::Claude3Haiku => {
preprocess_anthropic_request(self);
}
_ => {}
@@ -265,7 +262,7 @@ impl Assistant {
}
}
pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
cx.set_global(Assistant::default());
AssistantSettings::register(cx);
@@ -274,7 +271,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
async move {
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
let semantic_index = SemanticIndex::new(
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
Arc::new(embedding_provider),
&mut cx,
)
@@ -289,7 +286,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
assistant_slash_command::init(cx);
register_slash_commands(cx);
assistant_panel::init(cx);
inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
inline_assistant::init(client.telemetry().clone(), cx);
RustdocStore::init_global(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
@@ -311,7 +308,6 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
fn register_slash_commands(cx: &mut AppContext) {
let slash_command_registry = SlashCommandRegistry::global(cx);
slash_command_registry.register_command(auto_command::AutoCommand, true);
slash_command_registry.register_command(file_command::FileSlashCommand, true);
slash_command_registry.register_command(active_command::ActiveSlashCommand, true);
slash_command_registry.register_command(tabs_command::TabsSlashCommand, true);
@@ -319,31 +315,12 @@ fn register_slash_commands(cx: &mut AppContext) {
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
slash_command_registry.register_command(term_command::TermSlashCommand, true);
slash_command_registry.register_command(now_command::NowSlashCommand, true);
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
}
pub fn humanize_token_count(count: usize) -> String {
match count {
0..=999 => count.to_string(),
1000..=9999 => {
let thousands = count / 1000;
let hundreds = (count % 1000 + 50) / 100;
if hundreds == 0 {
format!("{}k", thousands)
} else if hundreds == 10 {
format!("{}k", thousands + 1)
} else {
format!("{}.{}k", thousands, hundreds)
}
}
_ => format!("{}k", (count + 500) / 1000),
}
}
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {

View File

@@ -1,6 +1,5 @@
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings},
humanize_token_count,
prompt_library::open_prompt_library,
search::*,
slash_command::{
@@ -16,36 +15,32 @@ 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, ShowCompletions, UnfoldAt},
display_map::{
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, RenderBlock, ToDisplayPoint,
},
actions::{FoldAt, MoveToEndOfLine, Newline, UnfoldAt},
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, ToDisplayPoint},
scroll::{Autoscroll, AutoscrollStrategy},
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
};
use editor::{display_map::CreaseId, FoldPlaceholder};
use editor::{display_map::FlapId, FoldPlaceholder};
use file_icons::FileIcons;
use fs::Fs;
use futures::future::Shared;
use futures::{FutureExt, StreamExt};
use gpui::{
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,
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,
};
use language::{
language_settings::SoftWrap, AnchorRangeExt as _, AutoindentMode, Buffer, LanguageRegistry,
language_settings::SoftWrap, AnchorRangeExt, 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::{
@@ -59,10 +54,10 @@ use std::{
};
use telemetry_events::AssistantKind;
use ui::{
prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tab, TabBar, Tooltip,
popover_menu, prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding,
ListItem, ListItemSpacing, PopoverMenuHandle, Tab, TabBar, Tooltip,
};
use util::{post_inc, ResultExt, TryFutureExt};
use util::{paths::CONTEXTS_DIR, post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
use workspace::NewFile;
use workspace::{
@@ -90,10 +85,6 @@ pub fn init(cx: &mut AppContext) {
.detach();
}
pub enum AssistantPanelEvent {
ContextEdited,
}
pub struct AssistantPanel {
workspace: WeakView<Workspace>,
width: Option<Pixels>,
@@ -305,7 +296,7 @@ impl AssistantPanel {
}
}
fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
self.toolbar
.update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
cx.notify();
@@ -365,11 +356,11 @@ impl AssistantPanel {
return;
}
let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
return;
};
let context_editor = assistant_panel
let context_editor = assistant
.read(cx)
.active_context_editor()
.and_then(|editor| {
@@ -396,37 +387,25 @@ impl AssistantPanel {
return;
};
if assistant_panel.update(cx, |panel, cx| panel.is_authenticated(cx)) {
if assistant.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(
&active_editor,
Some(cx.view().downgrade()),
include_context.then_some(&assistant_panel),
include_context,
cx,
)
})
} else {
let assistant_panel = assistant_panel.downgrade();
let assistant = assistant.downgrade();
cx.spawn(|workspace, mut cx| async move {
assistant_panel
assistant
.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
.await?;
if assistant_panel
.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))?
{
if assistant.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))? {
cx.update(|cx| {
let assistant_panel = if include_context {
assistant_panel.upgrade()
} else {
None
};
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(
&active_editor,
Some(workspace),
assistant_panel.as_ref(),
cx,
)
assistant.assist(&active_editor, Some(workspace), include_context, cx)
})
})?
} else {
@@ -477,7 +456,7 @@ impl AssistantPanel {
_subscriptions: subscriptions,
});
self.show_saved_contexts = false;
cx.emit(AssistantPanelEvent::ContextEdited);
cx.notify();
}
@@ -489,7 +468,6 @@ impl AssistantPanel {
) {
match event {
ContextEditorEvent::TabContentChanged => cx.notify(),
ContextEditorEvent::Edited => cx.emit(AssistantPanelEvent::ContextEdited),
}
}
@@ -598,7 +576,7 @@ impl AssistantPanel {
fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let assistant = cx.view().clone();
let zoomed = self.zoomed;
PopoverMenu::new("assistant-popover")
popover_menu("assistant-popover")
.trigger(IconButton::new("trigger", IconName::Menu))
.menu(move |cx| {
let assistant = assistant.clone();
@@ -640,7 +618,7 @@ impl AssistantPanel {
)
});
PopoverMenu::new("inject-context-menu")
popover_menu("inject-context-menu")
.trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
}))
@@ -881,33 +859,18 @@ impl AssistantPanel {
context: &Model<Context>,
cx: &mut ViewContext<Self>,
) -> Option<impl IntoElement> {
let model = CompletionProvider::global(cx).model();
let token_count = context.read(cx).token_count()?;
let max_token_count = model.max_token_count();
let remaining_tokens = max_token_count as isize - token_count as isize;
let token_count_color = if remaining_tokens <= 0 {
let remaining_tokens = context.read(cx).remaining_tokens(cx)?;
let remaining_tokens_color = if remaining_tokens <= 0 {
Color::Error
} else if token_count as f32 / max_token_count as f32 >= 0.8 {
} else if remaining_tokens <= 500 {
Color::Warning
} else {
Color::Muted
};
Some(
h_flex()
.gap_0p5()
.child(
Label::new(humanize_token_count(token_count))
.size(LabelSize::Small)
.color(token_count_color),
)
.child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
.child(
Label::new(humanize_token_count(max_token_count))
.size(LabelSize::Small)
.color(Color::Muted),
),
Label::new(remaining_tokens.to_string())
.size(LabelSize::Small)
.color(remaining_tokens_color),
)
}
}
@@ -1011,7 +974,6 @@ impl Panel for AssistantPanel {
}
impl EventEmitter<PanelEvent> for AssistantPanel {}
impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
impl FocusableView for AssistantPanel {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
@@ -1048,7 +1010,6 @@ 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,
@@ -1090,7 +1051,6 @@ 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),
@@ -1127,12 +1087,11 @@ 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: buffer.text(),
text: self.buffer.read(cx).text(),
message_metadata: self.messages_metadata.clone(),
messages: self
.messages(cx)
@@ -1146,22 +1105,6 @@ 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(),
}
}
@@ -1213,19 +1156,6 @@ 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,
@@ -1524,17 +1454,10 @@ impl Context {
.map(|section| SlashCommandOutputSection {
range: buffer.anchor_after(start + section.range.start)
..buffer.anchor_before(start + section.range.end),
icon: section.icon,
label: section.label,
render_placeholder: section.render_placeholder,
})
.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),
@@ -1572,6 +1495,11 @@ impl Context {
}
}
fn remaining_tokens(&self, cx: &AppContext) -> Option<isize> {
let model = CompletionProvider::global(cx).model();
Some(model.max_token_count() as isize - self.token_count? as isize)
}
fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
self.count_remaining_tokens(cx);
}
@@ -2073,7 +2001,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
@@ -2087,7 +2015,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))?;
@@ -2212,7 +2140,6 @@ struct PendingCompletion {
}
enum ContextEditorEvent {
Edited,
TabContentChanged,
}
@@ -2231,8 +2158,7 @@ pub struct ContextEditor {
editor: View<Editor>,
blocks: HashSet<BlockId>,
scroll_position: Option<ScrollPosition>,
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
pending_slash_command_blocks: HashMap<Range<language::Anchor>, BlockId>,
pending_slash_command_flaps: HashMap<Range<language::Anchor>, FlapId>,
_subscriptions: Vec<Subscription>,
}
@@ -2283,7 +2209,6 @@ impl ContextEditor {
editor.set_show_line_numbers(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_completion_provider(Box::new(completion_provider));
@@ -2296,7 +2221,6 @@ 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,
@@ -2306,12 +2230,10 @@ impl ContextEditor {
scroll_position: None,
fs,
workspace: workspace.downgrade(),
pending_slash_command_creases: HashMap::default(),
pending_slash_command_blocks: HashMap::default(),
pending_slash_command_flaps: HashMap::default(),
_subscriptions,
};
this.update_message_headers(cx);
this.insert_slash_command_output_sections(sections, cx);
this
}
@@ -2569,27 +2491,16 @@ impl ContextEditor {
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
let excerpt_id = *excerpt_id;
let excerpt_id = *buffer.as_singleton().unwrap().0;
editor.remove_creases(
editor.remove_flaps(
removed
.iter()
.filter_map(|range| self.pending_slash_command_creases.remove(range)),
.filter_map(|range| self.pending_slash_command_flaps.remove(range)),
cx,
);
editor.remove_blocks(
HashSet::from_iter(
removed.iter().filter_map(|range| {
self.pending_slash_command_blocks.remove(range)
}),
),
None,
cx,
);
let crease_ids = editor.insert_creases(
let flap_ids = editor.insert_flaps(
updated.iter().map(|command| {
let workspace = self.workspace.clone();
let confirm_command = Arc::new({
@@ -2621,28 +2532,13 @@ impl ContextEditor {
move |row, _, _, _cx: &mut WindowContext| {
render_pending_slash_command_gutter_decoration(
row,
&command.status,
command.status.clone(),
confirm_command.clone(),
)
}
};
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 render_trailer =
|_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
let start = buffer
.anchor_in_excerpt(excerpt_id, command.source_range.start)
@@ -2650,47 +2546,16 @@ impl ContextEditor {
let end = buffer
.anchor_in_excerpt(excerpt_id, command.source_range.end)
.unwrap();
Crease::new(start..end, placeholder, render_toggle, render_trailer)
Flap::new(start..end, placeholder, render_toggle, render_trailer)
}),
cx,
);
let block_ids = editor.insert_blocks(
updated
.iter()
.filter_map(|command| match &command.status {
PendingSlashCommandStatus::Error(error) => {
Some((command, error.clone()))
}
_ => None,
})
.map(|(command, error_message)| BlockProperties {
style: BlockStyle::Fixed,
position: Anchor {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: command.source_range.start,
},
height: 1,
disposition: BlockDisposition::Below,
render: slash_command_error_block_renderer(error_message),
}),
None,
cx,
);
self.pending_slash_command_creases.extend(
self.pending_slash_command_flaps.extend(
updated
.iter()
.map(|command| command.source_range.clone())
.zip(crease_ids),
);
self.pending_slash_command_blocks.extend(
updated
.iter()
.map(|command| command.source_range.clone())
.zip(block_ids),
.zip(flap_ids),
);
})
}
@@ -2733,7 +2598,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 creases = Vec::new();
let mut flaps = Vec::new();
for section in sections {
let start = buffer
.anchor_in_excerpt(excerpt_id, section.range.start)
@@ -2743,32 +2608,26 @@ impl ContextEditor {
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
creases.push(Crease::new(
flaps.push(Flap::new(
start..end,
FoldPlaceholder {
render: Arc::new({
let editor = cx.view().downgrade();
let icon = section.icon;
let label = section.label.clone();
move |fold_id, fold_range, _cx| {
let render_placeholder = section.render_placeholder.clone();
move |fold_id, fold_range, cx| {
let editor = editor.clone();
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()
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)
}
}),
constrain_width: false,
@@ -2779,7 +2638,7 @@ impl ContextEditor {
));
}
editor.insert_creases(creases, cx);
editor.insert_flaps(flaps, cx);
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
editor.fold_at(&FoldAt { buffer_row }, cx);
@@ -2805,7 +2664,6 @@ impl ContextEditor {
EditorEvent::SelectionsChanged { .. } => {
self.scroll_position = self.cursor_scroll_position(cx);
}
EditorEvent::BufferEdited => cx.emit(ContextEditorEvent::Edited),
_ => {}
}
}
@@ -3282,7 +3140,7 @@ fn render_slash_command_output_toggle(
fn render_pending_slash_command_gutter_decoration(
row: MultiBufferRow,
status: &PendingSlashCommandStatus,
status: PendingSlashCommandStatus,
confirm_command: Arc<dyn Fn(&mut WindowContext)>,
) -> AnyElement {
let mut icon = IconButton::new(
@@ -3300,43 +3158,16 @@ fn render_pending_slash_command_gutter_decoration(
PendingSlashCommandStatus::Running { .. } => {
icon = icon.selected(true);
}
PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
PendingSlashCommandStatus::Error(error) => {
icon = icon
.icon_color(Color::Error)
.tooltip(move |cx| Tooltip::text(format!("error: {error}"), cx));
}
}
icon.into_any_element()
}
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,
@@ -3351,19 +3182,6 @@ fn make_lsp_adapter_delegate(
})
}
fn slash_command_error_block_renderer(message: String) -> RenderBlock {
Box::new(move |_| {
div()
.pl_6()
.child(
Label::new(format!("error: {}", message))
.single_line()
.color(Color::Error),
)
.into_any()
})
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -24,7 +24,6 @@ pub enum CloudModel {
Gpt4Turbo,
#[default]
Gpt4Omni,
Claude3_5Sonnet,
Claude3Opus,
Claude3Sonnet,
Claude3Haiku,
@@ -106,7 +105,6 @@ impl CloudModel {
Self::Gpt4 => "gpt-4",
Self::Gpt4Turbo => "gpt-4-turbo-preview",
Self::Gpt4Omni => "gpt-4o",
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
Self::Claude3Opus => "claude-3-opus",
Self::Claude3Sonnet => "claude-3-sonnet",
Self::Claude3Haiku => "claude-3-haiku",
@@ -120,7 +118,6 @@ impl CloudModel {
Self::Gpt4 => "GPT 4",
Self::Gpt4Turbo => "GPT 4 Turbo",
Self::Gpt4Omni => "GPT 4 Omni",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
@@ -133,10 +130,7 @@ impl CloudModel {
Self::Gpt3Point5Turbo => 2048,
Self::Gpt4 => 4096,
Self::Gpt4Turbo | Self::Gpt4Omni => 128000,
Self::Claude3_5Sonnet
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 200000,
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 200000,
Self::Custom(_) => 4096, // TODO: Make this configurable
}
}

View File

@@ -101,10 +101,7 @@ impl CloudCompletionProvider {
count_open_ai_tokens(request, cx.background_executor())
}
LanguageModel::Cloud(
CloudModel::Claude3_5Sonnet
| CloudModel::Claude3Opus
| CloudModel::Claude3Sonnet
| CloudModel::Claude3Haiku,
CloudModel::Claude3Opus | CloudModel::Claude3Sonnet | CloudModel::Claude3Haiku,
) => {
// Can't find a tokenizer for Claude 3, so for now just use the same as OpenAI's as an approximation.
count_open_ai_tokens(request, cx.background_executor())

View File

@@ -210,7 +210,6 @@ pub fn count_open_ai_tokens(
match request.model {
LanguageModel::Anthropic(_)
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Opus)
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {

View File

@@ -1,17 +1,15 @@
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::{ResultExt, TryFutureExt};
use util::{paths::CONTEXTS_DIR, ResultExt, TryFutureExt};
#[derive(Serialize, Deserialize)]
pub struct SavedMessage {
@@ -28,22 +26,10 @@ 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.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,
pub const VERSION: &'static str = "0.2.0";
}
#[derive(Serialize, Deserialize)]
@@ -76,7 +62,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(),
@@ -113,20 +99,6 @@ 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)?;
@@ -138,7 +110,6 @@ 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)),
@@ -181,9 +152,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?;

File diff suppressed because it is too large Load Diff

View File

@@ -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::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, 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 {
PopoverMenu::new("model-switcher")
popover_menu("model-switcher")
.with_handle(self.handle)
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {

View File

@@ -36,7 +36,7 @@ use ui::{
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
};
use util::{ResultExt, TryFutureExt};
use util::{paths::PROMPTS_DIR, 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 = paths::prompts_dir().join("prompts-library-db.0.mdb");
let db_path = PROMPTS_DIR.join("prompts-library-db.0.mdb");
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
.boxed()
@@ -569,7 +569,7 @@ impl PromptLibrary {
let provider = CompletionProvider::global(cx);
if provider.is_authenticated() {
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(&prompt_editor, None, None, cx)
assistant.assist(&prompt_editor, None, false, cx)
})
} else {
for window in cx.windows() {
@@ -832,8 +832,13 @@ impl PromptLibrary {
impl Render for PromptLibrary {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
let (ui_font, ui_font_size) = {
let theme_settings = ThemeSettings::get_global(cx);
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
};
let theme = cx.theme().clone();
cx.set_rem_size(ui_font_size);
h_flex()
.id("prompt-manager")

View File

@@ -33,32 +33,35 @@ pub fn generate_content_prompt(
)?;
}
writeln!(
prompt,
"The user has the following file open in the editor:"
)?;
// Include file content.
for chunk in buffer.text_for_range(0..range.start) {
prompt.push_str(chunk);
}
if range.is_empty() {
write!(prompt, "```")?;
if let Some(language_name) = language_name {
write!(prompt, "{language_name}")?;
}
prompt.push_str("<|START|>");
} else {
prompt.push_str("<|START|");
}
for chunk in buffer.as_rope().chunks_in_range(0..range.start) {
prompt.push_str(chunk);
}
prompt.push_str("<|CURSOR|>");
for chunk in buffer.as_rope().chunks_in_range(range.start..buffer.len()) {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
for chunk in buffer.text_for_range(range.clone()) {
prompt.push_str(chunk);
}
if !range.is_empty() {
prompt.push_str("|END|>");
}
for chunk in buffer.text_for_range(range.end..buffer.len()) {
prompt.push_str(chunk);
}
prompt.push('\n');
if range.is_empty() {
writeln!(
prompt,
"Assume the cursor is located where the `<|CURSOR|>` span is."
"Assume the cursor is located where the `<|START|>` span is."
)
.unwrap();
writeln!(
@@ -72,42 +75,11 @@ pub fn generate_content_prompt(
)
.unwrap();
} else {
write!(prompt, "```")?;
for chunk in buffer.as_rope().chunks() {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(prompt, "Modify the user's selected {content_type} based upon the users prompt: '{user_prompt}'").unwrap();
writeln!(prompt, "You must reply with only the adjusted {content_type} (within the '<|START|' and '|END|>' spans) not the entire file.").unwrap();
writeln!(
prompt,
"In particular, the following piece of text is selected:"
)?;
write!(prompt, "```")?;
if let Some(language_name) = language_name {
write!(prompt, "{language_name}")?;
}
prompt.push('\n');
for chunk in buffer.text_for_range(range.clone()) {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"Modify the user's selected {content_type} based upon the users prompt: {user_prompt}"
)
.unwrap();
writeln!(
prompt,
"You must reply with only the adjusted {content_type}, not the entire file."
"Double check that you only return code and not the '<|START|' and '|END|'> spans"
)
.unwrap();
}

View File

@@ -3,8 +3,8 @@ use anyhow::Result;
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
use language::{Anchor, Buffer, CodeLabel, Documentation, HighlightId, LanguageServerId, ToPoint};
use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
use language::{Anchor, Buffer, CodeLabel, Documentation, LanguageServerId, ToPoint};
use parking_lot::{Mutex, RwLock};
use rope::Point;
use std::{
@@ -14,11 +14,9 @@ use std::{
Arc,
},
};
use ui::ActiveTheme;
use workspace::Workspace;
pub mod active_command;
pub mod auto_command;
pub mod default_command;
pub mod diagnostics_command;
pub mod fetch_command;
@@ -29,7 +27,6 @@ pub mod prompt_command;
pub mod rustdoc_command;
pub mod search_command;
pub mod tabs_command;
pub mod term_command;
pub(crate) struct SlashCommandCompletionProvider {
commands: Arc<SlashCommandRegistry>,
@@ -350,19 +347,3 @@ impl SlashCommandLine {
call
}
}
pub fn create_label_for_command(
command_name: &str,
arguments: &[&str],
cx: &AppContext,
) -> CodeLabel {
let mut label = CodeLabel::default();
label.push_str(command_name, None);
label.push_str(" ", None);
label.push_str(
&arguments.join(" "),
cx.theme().syntax().highlight_id("comment").map(HighlightId),
);
label.filter_range = 0..command_name.len();
label
}

View File

@@ -1,15 +1,11 @@
use super::{
diagnostics_command::write_single_file_diagnostics,
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use editor::Editor;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use ui::WindowContext;
use std::{borrow::Cow, sync::Arc};
use ui::{IntoElement, WindowContext};
use workspace::Workspace;
pub(crate) struct ActiveSlashCommand;
@@ -28,9 +24,9 @@ impl SlashCommand for ActiveSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
_query: String,
_cancel: Arc<AtomicBool>,
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
@@ -61,38 +57,46 @@ impl SlashCommand for ActiveSlashCommand {
let snapshot = buffer.read(cx).snapshot();
let path = snapshot.resolve_file_path(cx, true);
let task = cx.background_executor().spawn({
let text = cx.background_executor().spawn({
let path = path.clone();
async move {
let mut output = String::new();
output.push_str(&codeblock_fence_for_path(path.as_deref(), None));
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);
output.push('\n');
for chunk in snapshot.as_rope().chunks() {
output.push_str(chunk);
}
if !output.ends_with('\n') {
output.push('\n');
}
output.push_str("```\n");
let has_diagnostics =
write_single_file_diagnostics(&mut output, path.as_deref(), &snapshot);
if output.ends_with('\n') {
output.pop();
}
(output, has_diagnostics)
output.push_str("```");
output
}
});
cx.foreground_executor().spawn(async move {
let (text, has_diagnostics) = task.await;
let text = text.await;
let range = 0..text.len();
Ok(SlashCommandOutput {
text,
sections: vec![build_entry_output_section(
sections: vec![SlashCommandOutputSection {
range,
path.as_deref(),
false,
None,
)],
run_commands_in_text: has_diagnostics,
render_placeholder: Arc::new(move |id, unfold, _| {
FilePlaceholder {
id,
path: path.clone(),
line_range: None,
unfold,
}
.into_any_element()
}),
}],
run_commands_in_text: false,
})
})
});

View File

@@ -1,193 +0,0 @@
use super::create_label_for_command;
use super::{SlashCommand, SlashCommandOutput};
use crate::{CompletionProvider, LanguageModelRequest, LanguageModelRequestMessage, Role};
use anyhow::{anyhow, Result};
use futures::StreamExt;
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use ui::WindowContext;
use workspace::Workspace;
pub(crate) struct AutoCommand;
impl SlashCommand for AutoCommand {
fn name(&self) -> String {
"auto".into()
}
fn description(&self) -> String {
"Automatically infer what context to add, based on your prompt".into()
}
fn menu_text(&self) -> String {
"Automatically Infer Context".into()
}
fn label(&self, cx: &AppContext) -> CodeLabel {
create_label_for_command("auto", &["--prompt"], cx)
}
fn complete_argument(
self: Arc<Self>,
_query: String,
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
// There's no autocomplete for a prompt, since it's arbitrary text.
Task::ready(Ok(Vec::new()))
}
fn requires_argument(&self) -> bool {
true
}
fn run(
self: Arc<Self>,
argument: Option<&str>,
_workspace: WeakView<Workspace>,
_delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let Some(argument) = argument else {
return Task::ready(Err(anyhow!("missing prompt")));
};
let prompt = format!("{PROMPT_INSTRUCTIONS_BEFORE_SUMMARY}\n{SUMMARY}\n{PROMPT_INSTRUCTIONS_AFTER_SUMMARY}\n{argument}");
let request = LanguageModelRequest {
model: CompletionProvider::global(cx).model(),
messages: vec![LanguageModelRequestMessage {
role: Role::User,
content: prompt,
}],
stop: vec![],
temperature: 1.0,
};
let stream = CompletionProvider::global(cx).complete(request);
let mut wip_action: String = String::new();
let task: Task<Result<String>> = cx.spawn(|_cx| async move {
let mut actions_text = String::new();
let stream_completion = async {
let mut messages = stream.await?;
while let Some(message) = messages.next().await {
let text = message?;
chunked_line(&mut wip_action, &text, |line| {
actions_text.push('/');
actions_text.push_str(line);
actions_text.push('\n');
});
smol::future::yield_now().await;
}
anyhow::Ok(())
};
stream_completion.await?;
Ok(actions_text)
});
// As a convenience, append /auto's argument to the end of the prompt so you don't have to write it again.
let argument = argument.to_string();
cx.background_executor().spawn(async move {
let mut text = task.await?;
text.push_str(&argument);
Ok(SlashCommandOutput {
text,
sections: Vec::new(),
run_commands_in_text: true,
})
})
}
}
const PROMPT_INSTRUCTIONS_BEFORE_SUMMARY: &str = r#"
I'm going to give you a prompt. I don't want you to respond
to the prompt itself. I want you to figure out which of the following
actions on my project, if any, would help you answer the prompt.
Here are the actions:
## file
This action's parameter is a file path to one of the files
in the project. If you ask for this action, I will tell you
the full contents of the file, so you can learn all the
details of the file.
## search
This action's parameter is a string to search for across
the project. It will tell you which files this string
(or similar strings; it is a semantic search) appear in,
as well as some context of the lines surrounding each result.
---
That was the end of the list of actions.
Here is an XML summary of each of the files in my project:
"#;
const PROMPT_INSTRUCTIONS_AFTER_SUMMARY: &str = r#"
Actions have a cost, so only include actions that you think
will be helpful to you in doing a great job answering the
prompt in the future.
You must respond ONLY with a list of actions you would like to
perform. Each action should be on its own line, and followed by a space and then its parameter.
Actions can be performed more than once with different parameters.
Here is an example valid response:
```
file path/to/my/file.txt
file path/to/another/file.txt
search something to search for
search something else to search for
```
Once again, do not forget: you must respond ONLY in the format of
one action per line, and the action name should be followed by
its parameter. Your response must not include anything other
than a list of actions, with one action per line, in this format.
It is extremely important that you do not deviate from this format even slightly!
This is the end of my instructions for how to respond. The rest is the prompt:
"#;
const SUMMARY: &str = "";
fn chunked_line(wip: &mut String, chunk: &str, mut on_line_end: impl FnMut(&str)) {
// The first iteration of the loop should just push to wip
// and nothing else. We only push what we encountered in
// previous iterations of the loop.
//
// This correctly handles both the scenario where no
// newlines are encountered (the loop will only run once,
// and so will only push to wip), as well as the scenario
// where the chunk contains at least one newline but
// does not end in a newline (the last iteration of the
// loop will update wip but will not run anything).
let mut is_first_iteration = true;
for line in chunk.split('\n') {
if is_first_iteration {
is_first_iteration = false;
} else {
// Since this isn't the first iteration of the loop, we definitely hit a newline
// at the end of the previous iteration! Run the function on whatever wip we have.
on_line_end(wip);
wip.clear();
}
wip.push_str(line);
}
}

View File

@@ -1,4 +1,4 @@
use super::{SlashCommand, SlashCommandOutput};
use super::{prompt_command::PromptPlaceholder, 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: Arc<Self>,
&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();
text.push('\n');
writeln!(text, "Default Prompt:").unwrap();
for prompt in prompts {
if let Some(title) = prompt.title {
writeln!(text, "/prompt {}", title).unwrap();
@@ -61,15 +61,17 @@ impl SlashCommand for DefaultSlashCommand {
}
text.pop();
if text.is_empty() {
text.push('\n');
}
Ok(SlashCommandOutput {
sections: vec![SlashCommandOutputSection {
range: 0..text.len(),
icon: IconName::Library,
label: "Default".into(),
render_placeholder: Arc::new(move |id, unfold, _cx| {
PromptPlaceholder {
title: "Default".into(),
id,
unfold,
}
.into_any_element()
}),
}],
text,
run_commands_in_text: true,

View File

@@ -1,8 +1,8 @@
use super::{create_label_for_command, SlashCommand, SlashCommandOutput};
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use fuzzy::{PathMatch, StringMatchCandidate};
use gpui::{AppContext, Model, Task, View, WeakView};
use gpui::{svg, AppContext, Model, RenderOnce, Task, View, WeakView};
use language::{
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
OffsetRangeExt, ToOffset,
@@ -10,12 +10,11 @@ use language::{
use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
use rope::Point;
use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::{
ops::Range,
sync::{atomic::AtomicBool, Arc},
};
use ui::prelude::*;
use ui::{prelude::*, ButtonLike, ElevationIndex};
use util::paths::PathMatcher;
use util::ResultExt;
use workspace::Workspace;
@@ -58,7 +57,7 @@ impl DiagnosticsCommand {
include_ignored: worktree
.root_entry()
.map_or(false, |entry| entry.is_ignored),
include_root_name: true,
include_root_name: false,
candidates: project::Candidates::Entries,
}
})
@@ -86,10 +85,6 @@ impl SlashCommand for DiagnosticsCommand {
"diagnostics".into()
}
fn label(&self, cx: &AppContext) -> language::CodeLabel {
create_label_for_command("diagnostics", &[INCLUDE_WARNINGS_ARGUMENT], cx)
}
fn description(&self) -> String {
"Insert diagnostics".into()
}
@@ -103,7 +98,7 @@ impl SlashCommand for DiagnosticsCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
@@ -162,55 +157,21 @@ impl SlashCommand for DiagnosticsCommand {
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
cx.spawn(move |_| async move {
let Some((text, sections)) = task.await? else {
return Ok(SlashCommandOutput::default());
};
let (text, sections) = task.await?;
Ok(SlashCommandOutput {
text,
sections: sections
.into_iter()
.map(|(range, placeholder_type)| SlashCommandOutputSection {
range,
icon: match placeholder_type {
PlaceholderType::Root(_, _) => IconName::ExclamationTriangle,
PlaceholderType::File(_) => IconName::File,
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => {
IconName::XCircle
render_placeholder: Arc::new(move |id, unfold, _cx| {
DiagnosticsPlaceholder {
id,
unfold,
placeholder_type: placeholder_type.clone(),
}
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(),
},
.into_any_element()
}),
})
.collect(),
run_commands_in_text: false,
@@ -228,7 +189,7 @@ struct Options {
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
impl Options {
fn parse(arguments_line: Option<&str>) -> Self {
pub fn parse(arguments_line: Option<&str>) -> Self {
arguments_line
.map(|arguments_line| {
let args = arguments_line.split_whitespace().collect::<Vec<_>>();
@@ -238,7 +199,7 @@ impl Options {
if arg == INCLUDE_WARNINGS_ARGUMENT {
include_warnings = true;
} else {
path_matcher = PathMatcher::new(&[arg.to_owned()]).log_err();
path_matcher = PathMatcher::new(arg).log_err();
}
}
Self {
@@ -261,59 +222,25 @@ fn collect_diagnostics(
project: Model<Project>,
options: Options,
cx: &mut AppContext,
) -> Task<Result<Option<(String, Vec<(Range<usize>, PlaceholderType)>)>>> {
let error_source = if let Some(path_matcher) = &options.path_matcher {
debug_assert_eq!(path_matcher.sources().len(), 1);
Some(path_matcher.sources().first().cloned().unwrap_or_default())
) -> Task<Result<(String, Vec<(Range<usize>, PlaceholderType)>)>> {
let header = if let Some(path_matcher) = &options.path_matcher {
format!("diagnostics: {}", path_matcher.source())
} else {
None
};
let glob_is_exact_file_match = if let Some(path) = options
.path_matcher
.as_ref()
.and_then(|pm| pm.sources().first())
{
PathBuf::try_from(path)
.ok()
.and_then(|path| {
project.read(cx).worktrees().find_map(|worktree| {
let worktree = worktree.read(cx);
let worktree_root_path = Path::new(worktree.root_name());
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
worktree.absolutize(&relative_path).ok()
})
})
.is_some()
} else {
false
"diagnostics".to_string()
};
let project_handle = project.downgrade();
let diagnostic_summaries: Vec<_> = project
.read(cx)
.diagnostic_summaries(false, cx)
.flat_map(|(path, _, summary)| {
let worktree = project.read(cx).worktree_for_id(path.worktree_id, cx)?;
let mut path_buf = PathBuf::from(worktree.read(cx).root_name());
path_buf.push(&path.path);
Some((path, path_buf, summary))
})
.collect();
let diagnostic_summaries: Vec<_> = project.read(cx).diagnostic_summaries(false, cx).collect();
cx.spawn(|mut cx| async move {
let mut text = String::new();
if let Some(error_source) = error_source.as_ref() {
writeln!(text, "diagnostics: {}", error_source).unwrap();
} else {
writeln!(text, "diagnostics").unwrap();
}
writeln!(text, "{}", &header).unwrap();
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
let mut project_summary = DiagnosticSummary::default();
for (project_path, path, summary) in diagnostic_summaries {
for (project_path, _, summary) in diagnostic_summaries {
if let Some(path_matcher) = &options.path_matcher {
if !path_matcher.is_match(&path) {
if !path_matcher.is_match(&project_path.path) {
continue;
}
}
@@ -326,10 +253,8 @@ fn collect_diagnostics(
}
let last_end = text.len();
let file_path = path.to_string_lossy().to_string();
if !glob_is_exact_file_match {
writeln!(&mut text, "{file_path}").unwrap();
}
let file_path = project_path.path.to_string_lossy().to_string();
writeln!(&mut text, "{file_path}").unwrap();
if let Some(buffer) = project_handle
.update(&mut cx, |project, cx| project.open_buffer(project_path, cx))?
@@ -344,52 +269,20 @@ fn collect_diagnostics(
);
}
if !glob_is_exact_file_match {
sections.push((
last_end..text.len().saturating_sub(1),
PlaceholderType::File(file_path),
))
}
sections.push((
last_end..text.len().saturating_sub(1),
PlaceholderType::File(file_path),
))
}
// No diagnostics found
if sections.is_empty() {
return Ok(None);
}
sections.push((
0..text.len(),
PlaceholderType::Root(project_summary, error_source),
PlaceholderType::Root(project_summary, header),
));
Ok(Some((text, sections)))
Ok((text, sections))
})
}
pub fn buffer_has_error_diagnostics(snapshot: &BufferSnapshot) -> bool {
for (_, group) in snapshot.diagnostic_groups(None) {
let entry = &group.entries[group.primary_ix];
if entry.diagnostic.severity == DiagnosticSeverity::ERROR {
return true;
}
}
false
}
pub fn write_single_file_diagnostics(
output: &mut String,
path: Option<&Path>,
snapshot: &BufferSnapshot,
) -> bool {
if let Some(path) = path {
if buffer_has_error_diagnostics(&snapshot) {
output.push_str("/diagnostics ");
output.push_str(&path.to_string_lossy());
return true;
}
}
false
}
fn collect_buffer_diagnostics(
text: &mut String,
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
@@ -469,12 +362,12 @@ fn collect_diagnostic(
#[derive(Clone)]
pub enum PlaceholderType {
Root(DiagnosticSummary, Option<String>),
Root(DiagnosticSummary, String),
File(String),
Diagnostic(DiagnosticType, String),
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, IntoElement)]
pub enum DiagnosticType {
Warning,
Error,
@@ -488,3 +381,64 @@ 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)),
})
}
}

View File

@@ -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::*;
use ui::{prelude::*, ButtonLike, ElevationIndex};
use workspace::Workspace;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
@@ -62,7 +62,6 @@ impl FetchSlashCommand {
match content_type {
ContentType::Html => {
let mut handlers: Vec<TagHandler> = vec![
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
Rc::new(RefCell::new(markdown::ParagraphHandler)),
Rc::new(RefCell::new(markdown::HeadingHandler)),
Rc::new(RefCell::new(markdown::ListHandler)),
@@ -114,7 +113,7 @@ impl SlashCommand for FetchSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -153,11 +152,37 @@ impl SlashCommand for FetchSlashCommand {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::AtSign,
label: format!("fetch {}", url).into(),
render_placeholder: Arc::new(move |id, unfold, _cx| {
FetchPlaceholder {
id,
unfold,
url: url.clone(),
}
.into_any_element()
}),
}],
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))
}
}

View File

@@ -1,18 +1,16 @@
use super::{diagnostics_command::write_single_file_diagnostics, SlashCommand, SlashCommandOutput};
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{BufferSnapshot, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project};
use gpui::{AppContext, RenderOnce, SharedString, Task, View, WeakView};
use language::{LineEnding, LspAdapterDelegate};
use project::PathMatchCandidateSet;
use std::{
fmt::Write,
ops::Range,
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};
use ui::prelude::*;
use util::{paths::PathMatcher, ResultExt};
use ui::{prelude::*, ButtonLike, ElevationIndex};
use workspace::Workspace;
pub(crate) struct FileSlashCommand;
@@ -60,7 +58,7 @@ impl FileSlashCommand {
.root_entry()
.map_or(false, |entry| entry.is_ignored),
include_root_name: true,
candidates: project::Candidates::Entries,
candidates: project::Candidates::Files,
}
})
.collect::<Vec<_>>();
@@ -100,7 +98,7 @@ impl SlashCommand for FileSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
@@ -141,225 +139,88 @@ impl SlashCommand for FileSlashCommand {
return Task::ready(Err(anyhow!("missing path")));
};
let task = collect_files(workspace.read(cx).project().clone(), argument, cx);
let path = PathBuf::from(argument);
let abs_path = workspace
.read(cx)
.visible_worktrees(cx)
.find_map(|worktree| {
let worktree = worktree.read(cx);
let worktree_root_path = Path::new(worktree.root_name());
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
worktree.absolutize(&relative_path).ok()
});
let Some(abs_path) = abs_path else {
return Task::ready(Err(anyhow!("missing path")));
};
let fs = workspace.read(cx).app_state().fs.clone();
let argument = argument.to_string();
let text = cx.background_executor().spawn(async move {
let mut content = fs.load(&abs_path).await?;
LineEnding::normalize(&mut content);
let mut output = String::with_capacity(argument.len() + content.len() + 9);
output.push_str("```");
output.push_str(&argument);
output.push('\n');
output.push_str(&content);
if !output.ends_with('\n') {
output.push('\n');
}
output.push_str("```");
anyhow::Ok(output)
});
cx.foreground_executor().spawn(async move {
let (text, ranges) = task.await?;
let text = text.await?;
let range = 0..text.len();
Ok(SlashCommandOutput {
text,
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: true,
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()
}),
}],
run_commands_in_text: false,
})
})
}
}
#[derive(Clone, Copy, PartialEq)]
enum EntryType {
File,
Directory,
#[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)>,
}
fn collect_files(
project: Model<Project>,
glob_input: &str,
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")));
};
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")
};
let project_handle = project.downgrade();
let snapshots = project
.read(cx)
.worktrees()
.map(|worktree| worktree.read(cx).snapshot())
.collect::<Vec<_>>();
cx.spawn(|mut cx| async move {
let mut text = String::new();
let mut ranges = Vec::new();
for snapshot in snapshots {
let worktree_id = snapshot.id();
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_including_worktree_name = PathBuf::new();
path_including_worktree_name.push(snapshot.root_name());
path_including_worktree_name.push(&entry.path);
if !matcher.is_match(&path_including_worktree_name) {
continue;
}
while let Some((dir, _, _)) = directory_stack.last() {
if entry.path.starts_with(dir) {
break;
}
let (_, entry_name, start) = directory_stack.pop().unwrap();
ranges.push((
start..text.len().saturating_sub(1),
PathBuf::from(entry_name),
EntryType::Directory,
));
}
let filename = entry
.path
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_string();
if entry.is_dir() {
// Auto-fold directories that contain no files
let mut child_entries = snapshot.child_entries(&entry.path);
if let Some(child) = child_entries.next() {
if child_entries.next().is_none() && child.kind.is_dir() {
if is_top_level_directory {
is_top_level_directory = false;
folded_directory_names_stack.push(
path_including_worktree_name.to_string_lossy().to_string(),
);
} else {
folded_directory_names_stack.push(filename.to_string());
}
continue;
}
} else {
// Skip empty directories
folded_directory_names_stack.clear();
continue;
}
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
let entry_start = text.len();
if prefix_paths.is_empty() {
if is_top_level_directory {
text.push_str(&path_including_worktree_name.to_string_lossy());
is_top_level_directory = false;
} else {
text.push_str(&filename);
}
directory_stack.push((entry.path.clone(), filename, entry_start));
} else {
let entry_name = format!("{}/{}", prefix_paths, &filename);
text.push_str(&entry_name);
directory_stack.push((entry.path.clone(), entry_name, entry_start));
}
text.push('\n');
} else if entry.is_file() {
let Some(open_buffer_task) = project_handle
.update(&mut cx, |project, cx| {
project.open_buffer((worktree_id, &entry.path), cx)
})
.ok()
else {
continue;
};
if let Some(buffer) = open_buffer_task.await.log_err() {
let snapshot = cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
let prev_len = text.len();
collect_file_content(&mut text, &snapshot, filename.clone());
text.push('\n');
if !write_single_file_diagnostics(
&mut text,
Some(&path_including_worktree_name),
&snapshot,
) {
text.pop();
}
ranges.push((
prev_len..text.len(),
PathBuf::from(filename),
EntryType::File,
));
text.push('\n');
}
}
}
while let Some((dir, _, start)) = directory_stack.pop() {
let mut root_path = PathBuf::new();
root_path.push(snapshot.root_name());
root_path.push(&dir);
ranges.push((start..text.len(), root_path, EntryType::Directory));
}
}
Ok((text, ranges))
})
}
fn collect_file_content(buffer: &mut String, snapshot: &BufferSnapshot, filename: String) {
let mut content = snapshot.text();
LineEnding::normalize(&mut content);
buffer.reserve(filename.len() + content.len() + 9);
buffer.push_str(&codeblock_fence_for_path(
Some(&PathBuf::from(filename)),
None,
));
buffer.push_str(&content);
if !buffer.ends_with('\n') {
buffer.push('\n');
}
buffer.push_str("```");
}
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
let mut text = String::new();
write!(text, "```").unwrap();
if let Some(path) = path {
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
write!(text, "{} ", extension).unwrap();
}
write!(text, "{}", path.display()).unwrap();
} else {
write!(text, "untitled").unwrap();
}
if let Some(row_range) = row_range {
write!(text, ":{}-{}", row_range.start + 1, row_range.end + 1).unwrap();
}
text.push('\n');
text
}
pub fn build_entry_output_section(
range: Range<usize>,
path: Option<&Path>,
is_directory: bool,
line_range: Option<Range<u32>>,
) -> SlashCommandOutputSection<usize> {
let mut label = if let Some(path) = path {
path.to_string_lossy().to_string()
} else {
"untitled".to_string()
};
if let Some(line_range) = line_range {
write!(label, ":{}-{}", line_range.start, line_range.end).unwrap();
}
let icon = if is_directory {
IconName::Folder
} else {
IconName::File
};
SlashCommandOutputSection {
range,
icon,
label: label.into(),
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))
}
}

View File

@@ -29,7 +29,7 @@ impl SlashCommand for NowSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -53,8 +53,9 @@ impl SlashCommand for NowSlashCommand {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::CountdownTimer,
label: now.to_rfc3339().into(),
render_placeholder: Arc::new(move |id, unfold, _cx| {
NowPlaceholder { id, unfold, now }.into_any_element()
}),
}],
run_commands_in_text: false,
}))

View File

@@ -10,7 +10,7 @@ use std::{
path::Path,
sync::{atomic::AtomicBool, Arc},
};
use ui::prelude::*;
use ui::{prelude::*, ButtonLike, ElevationIndex};
use workspace::Workspace;
pub(crate) struct ProjectSlashCommand;
@@ -102,7 +102,7 @@ impl SlashCommand for ProjectSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -138,8 +138,15 @@ impl SlashCommand for ProjectSlashCommand {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::FileTree,
label: "Project".into(),
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()
}),
}],
run_commands_in_text: false,
})

View File

@@ -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::*;
use ui::{prelude::*, ButtonLike, ElevationIndex};
use workspace::Workspace;
pub(crate) struct PromptSlashCommand;
@@ -28,7 +28,7 @@ impl SlashCommand for PromptSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
query: String,
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -69,20 +69,42 @@ impl SlashCommand for PromptSlashCommand {
}
});
cx.foreground_executor().spawn(async move {
let mut prompt = prompt.await?;
if prompt.is_empty() {
prompt.push('\n');
}
let prompt = prompt.await?;
let range = 0..prompt.len();
Ok(SlashCommandOutput {
text: prompt,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::Library,
label: title,
render_placeholder: Arc::new(move |id, unfold, _cx| {
PromptPlaceholder {
id,
unfold,
title: title.clone(),
}
.into_any_element()
}),
}],
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))
}
}

View File

@@ -10,11 +10,20 @@ use gpui::{AppContext, Model, Task, WeakView};
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::LspAdapterDelegate;
use project::{Project, ProjectPath};
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
use ui::prelude::*;
use rustdoc::{convert_rustdoc_to_markdown, RustdocStore};
use rustdoc::{CrateName, LocalProvider};
use ui::{prelude::*, ButtonLike, ElevationIndex};
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 {
@@ -107,7 +116,7 @@ impl SlashCommand for RustdocSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
query: String,
_cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
@@ -180,18 +189,11 @@ impl SlashCommand for RustdocSlashCommand {
let item_path = item_path.clone();
async move {
let item_docs = rustdoc_store
.load(
crate_name.clone(),
if item_path.is_empty() {
None
} else {
Some(item_path.join("::"))
},
)
.load(crate_name.clone(), Some(item_path.join("::")))
.await;
if let Ok(item_docs) = item_docs {
anyhow::Ok((RustdocSource::Index, item_docs.docs().to_owned()))
anyhow::Ok((RustdocSource::Local, item_docs.docs().to_owned()))
} else {
Self::build_message(
fs,
@@ -213,26 +215,56 @@ 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,
icon: IconName::FileRust,
label: format!(
"rustdoc ({source}): {crate_path}",
source = match source {
RustdocSource::Index => "index",
RustdocSource::Local => "local",
RustdocSource::DocsDotRs => "docs.rs",
render_placeholder: Arc::new(move |id, unfold, _cx| {
RustdocPlaceholder {
id,
unfold,
source,
crate_name: crate_name.clone(),
module_path: module_path.clone(),
}
)
.into(),
.into_any_element()
}),
}],
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))
}
}

View File

@@ -1,19 +1,15 @@
use super::{
create_label_for_command,
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
use anyhow::Result;
use assistant_slash_command::SlashCommandOutputSection;
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
use language::{CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use semantic_index::SemanticIndex;
use std::{
fmt::Write,
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{prelude::*, IconName};
use ui::{prelude::*, ButtonLike, ElevationIndex, Icon, IconName};
use util::ResultExt;
use workspace::Workspace;
@@ -25,7 +21,14 @@ impl SlashCommand for SearchSlashCommand {
}
fn label(&self, cx: &AppContext) -> CodeLabel {
create_label_for_command("search", &["--n"], cx)
let mut label = CodeLabel::default();
label.push_str("search ", None);
label.push_str(
"--n",
cx.theme().syntax().highlight_id("comment").map(HighlightId),
);
label.filter_range = 0.."search".len();
label
}
fn description(&self) -> String {
@@ -41,7 +44,7 @@ impl SlashCommand for SearchSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -122,8 +125,9 @@ 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_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 =
file_content[0..range_start].matches('\n').count() as u32 + 1;
let end_line = file_content[0..range_end].matches('\n').count() as u32 + 1;
let start_line_byte_offset = file_content[0..range_start]
.rfind('\n')
.map(|pos| pos + 1)
@@ -134,30 +138,47 @@ impl SlashCommand for SearchSlashCommand {
.unwrap_or_else(|| file_content.len());
let section_start_ix = text.len();
text.push_str(&codeblock_fence_for_path(
Some(&result.path),
Some(start_row..end_row),
));
writeln!(
text,
"```{}:{}-{}",
result.path.display(),
start_line,
end_line,
)
.unwrap();
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(build_entry_output_section(
section_start_ix..section_end_ix,
Some(&full_path),
false,
Some(start_row + 1..end_row + 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()
}),
});
}
let query = SharedString::from(query);
sections.push(SlashCommandOutputSection {
range: 0..text.len(),
icon: IconName::MagnifyingGlass,
label: query,
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()
}),
});
SlashCommandOutput {

View File

@@ -1,15 +1,12 @@
use super::{
diagnostics_command::write_single_file_diagnostics,
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use super::{file_command::FilePlaceholder, 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, sync::Arc};
use ui::WindowContext;
use std::{fmt::Write, path::Path, sync::Arc};
use ui::{IntoElement, WindowContext};
use workspace::Workspace;
pub(crate) struct TabsSlashCommand;
@@ -32,7 +29,7 @@ impl SlashCommand for TabsSlashCommand {
}
fn complete_argument(
self: Arc<Self>,
&self,
_query: String,
_cancel: Arc<std::sync::atomic::AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -78,37 +75,44 @@ impl SlashCommand for TabsSlashCommand {
let mut sections = Vec::new();
let mut text = String::new();
let mut has_diagnostics = false;
for (full_path, buffer, _) in open_buffers {
let section_start_ix = text.len();
text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
writeln!(
text,
"```{}\n",
full_path
.as_deref()
.unwrap_or(Path::new("untitled"))
.display()
)
.unwrap();
for chunk in buffer.as_rope().chunks() {
text.push_str(chunk);
}
if !text.ends_with('\n') {
text.push('\n');
}
writeln!(text, "```").unwrap();
if write_single_file_diagnostics(&mut text, full_path.as_deref(), &buffer) {
has_diagnostics = true;
}
if !text.ends_with('\n') {
text.push('\n');
}
writeln!(text, "```\n").unwrap();
let section_end_ix = text.len() - 1;
sections.push(build_entry_output_section(
section_start_ix..section_end_ix,
full_path.as_deref(),
false,
None,
));
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()
}),
});
}
Ok(SlashCommandOutput {
text,
sections,
run_commands_in_text: has_diagnostics,
run_commands_in_text: false,
})
}),
Err(error) => Task::ready(Err(error)),

View File

@@ -1,105 +0,0 @@
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::Result;
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use ui::prelude::*;
use workspace::Workspace;
use super::create_label_for_command;
pub(crate) struct TermSlashCommand;
const LINE_COUNT_ARG: &str = "--line-count";
impl SlashCommand for TermSlashCommand {
fn name(&self) -> String {
"term".into()
}
fn label(&self, cx: &AppContext) -> CodeLabel {
create_label_for_command("term", &[LINE_COUNT_ARG], cx)
}
fn description(&self) -> String {
"insert terminal output".into()
}
fn menu_text(&self) -> String {
"Insert terminal output".into()
}
fn requires_argument(&self) -> bool {
false
}
fn complete_argument(
self: Arc<Self>,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(vec![LINE_COUNT_ARG.to_string()]))
}
fn run(
self: Arc<Self>,
argument: Option<&str>,
workspace: WeakView<Workspace>,
_delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
};
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
return Task::ready(Err(anyhow::anyhow!("no terminal panel open")));
};
let Some(active_terminal) = terminal_panel
.read(cx)
.pane()
.read(cx)
.active_item()
.and_then(|t| t.downcast::<TerminalView>())
else {
return Task::ready(Err(anyhow::anyhow!("no active terminal")));
};
let line_count = argument.and_then(|a| parse_argument(a)).unwrap_or(20);
let lines = active_terminal
.read(cx)
.model()
.read(cx)
.last_n_non_empty_lines(line_count);
let mut text = String::new();
text.push_str("Terminal output:\n");
text.push_str(&lines.join("\n"));
let range = 0..text.len();
Task::ready(Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::Terminal,
label: "Terminal".into(),
}],
run_commands_in_text: false,
}))
}
}
fn parse_argument(argument: &str) -> Option<usize> {
let mut args = argument.split(' ');
if args.next() == Some(LINE_COUNT_ARG) {
if let Some(line_count) = args.next().and_then(|s| s.parse::<usize>().ok()) {
return Some(line_count);
}
}
None
}

View File

@@ -18,5 +18,4 @@ derive_more.workspace = true
gpui.workspace = true
language.workspace = true
parking_lot.workspace = true
serde.workspace = true
workspace.workspace = true

View File

@@ -1,15 +1,14 @@
mod slash_command_registry;
use anyhow::Result;
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
use gpui::{AnyElement, AppContext, ElementId, Task, WeakView, WindowContext};
use language::{CodeLabel, LspAdapterDelegate};
use serde::{Deserialize, Serialize};
pub use slash_command_registry::*;
use std::{
ops::Range,
sync::{atomic::AtomicBool, Arc},
};
use workspace::{ui::IconName, Workspace};
use workspace::Workspace;
pub fn init(cx: &mut AppContext) {
SlashCommandRegistry::default_global(cx);
@@ -23,7 +22,7 @@ pub trait SlashCommand: 'static + Send + Sync {
fn description(&self) -> String;
fn menu_text(&self) -> String;
fn complete_argument(
self: Arc<Self>,
&self,
query: String,
cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
@@ -50,16 +49,14 @@ pub type RenderFoldPlaceholder = Arc<
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
>;
#[derive(Default)]
pub struct SlashCommandOutput {
pub text: String,
pub sections: Vec<SlashCommandOutputSection<usize>>,
pub run_commands_in_text: bool,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone)]
pub struct SlashCommandOutputSection<T> {
pub range: Range<T>,
pub icon: IconName,
pub label: SharedString,
pub render_placeholder: RenderFoldPlaceholder,
}

View File

@@ -141,13 +141,8 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
let auto_updater = cx.new_model(|cx| {
let updater = AutoUpdater::new(version, http_client);
let poll_for_updates = ReleaseChannel::try_global(cx)
.map(|channel| channel.poll_for_updates())
.unwrap_or(false);
if option_env!("ZED_UPDATE_EXPLANATION").is_none()
&& env::var("ZED_UPDATE_EXPLANATION").is_err()
&& poll_for_updates
{
let mut update_subscription = AutoUpdateSetting::get_global(cx)
.0
@@ -191,13 +186,6 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
return;
}
if !ReleaseChannel::try_global(cx)
.map(|channel| channel.poll_for_updates())
.unwrap_or(false)
{
return;
}
if let Some(updater) = AutoUpdater::get(cx) {
updater.update(cx, |updater, cx| updater.poll(cx));
} else {

View File

@@ -86,16 +86,10 @@ impl Render for Breadcrumbs {
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
if let Some(editor) = editor.upgrade() {
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
outline::toggle(editor, &outline::Toggle, cx)
}
})
.tooltip(|cx| {
Tooltip::for_action(
"Show symbol outline",
&editor::actions::ToggleOutline,
cx,
)
}),
.tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
),
None => element
// Match the height of the `ButtonLike` in the other arm.

View File

@@ -13,7 +13,6 @@ path = "src/call.rs"
doctest = false
[features]
no-webrtc = ["live_kit_client/no-webrtc"]
test-support = [
"client/test-support",
"collections/test-support",

View File

@@ -114,6 +114,7 @@ impl ActiveCall {
async fn handle_incoming_call(
this: Model<Self>,
envelope: TypedEnvelope<proto::IncomingCall>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<proto::Ack> {
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
@@ -141,6 +142,7 @@ impl ActiveCall {
async fn handle_call_canceled(
this: Model<Self>,
envelope: TypedEnvelope<proto::CallCanceled>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, _| {

View File

@@ -697,6 +697,7 @@ impl Room {
async fn handle_room_updated(
this: Model<Self>,
envelope: TypedEnvelope<proto::RoomUpdated>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let room = envelope

View File

@@ -138,6 +138,7 @@ impl ChannelBuffer {
async fn handle_update_channel_buffer(
this: Model<Self>,
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let ops = update_channel_buffer
@@ -159,6 +160,7 @@ impl ChannelBuffer {
async fn handle_update_channel_buffer_collaborators(
this: Model<Self>,
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {

View File

@@ -528,6 +528,7 @@ impl ChannelChat {
async fn handle_message_sent(
this: Model<Self>,
message: TypedEnvelope<proto::ChannelMessageSent>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
@@ -552,6 +553,7 @@ impl ChannelChat {
async fn handle_message_removed(
this: Model<Self>,
message: TypedEnvelope<proto::RemoveChannelMessage>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
@@ -563,6 +565,7 @@ impl ChannelChat {
async fn handle_message_updated(
this: Model<Self>,
message: TypedEnvelope<proto::ChannelMessageUpdate>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;

View File

@@ -888,6 +888,7 @@ impl ChannelStore {
async fn handle_update_channels(
this: Model<Self>,
message: TypedEnvelope<proto::UpdateChannels>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, _| {
@@ -901,6 +902,7 @@ impl ChannelStore {
async fn handle_update_user_channels(
this: Model<Self>,
message: TypedEnvelope<proto::UpdateUserChannels>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {

View File

@@ -21,8 +21,6 @@ anyhow.workspace = true
clap.workspace = true
ipc-channel = "0.18"
once_cell.workspace = true
parking_lot.workspace = true
paths.workspace = true
release_channel.workspace = true
serde.workspace = true
util.workspace = true

View File

@@ -3,12 +3,10 @@
use anyhow::{Context, Result};
use clap::Parser;
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
use parking_lot::Mutex;
use std::{
env, fs, io,
path::{Path, PathBuf},
process::ExitStatus,
sync::Arc,
thread::{self, JoinHandle},
};
use util::paths::PathLikeWithPosition;
@@ -56,7 +54,7 @@ struct Args {
fn parse_path_with_position(
argument_str: &str,
) -> Result<PathLikeWithPosition<PathBuf>, std::convert::Infallible> {
PathLikeWithPosition::parse_str(argument_str, |_, path_str| {
PathLikeWithPosition::parse_str(argument_str, |path_str| {
Ok(Path::new(path_str).to_path_buf())
})
}
@@ -125,34 +123,26 @@ fn main() -> Result<()> {
None
};
let exit_status = Arc::new(Mutex::new(None));
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn(move || {
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
let (tx, rx) = (handshake.requests, handshake.responses);
tx.send(CliRequest::Open {
paths,
wait: args.wait,
open_new_workspace,
dev_server_token: args.dev_server_token,
})?;
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
let exit_status = exit_status.clone();
move || {
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
let (tx, rx) = (handshake.requests, handshake.responses);
tx.send(CliRequest::Open {
paths,
wait: args.wait,
open_new_workspace,
dev_server_token: args.dev_server_token,
})?;
while let Ok(response) = rx.recv() {
match response {
CliResponse::Ping => {}
CliResponse::Stdout { message } => println!("{message}"),
CliResponse::Stderr { message } => eprintln!("{message}"),
CliResponse::Exit { status } => {
exit_status.lock().replace(status);
return Ok(());
}
}
while let Ok(response) = rx.recv() {
match response {
CliResponse::Ping => {}
CliResponse::Stdout { message } => println!("{message}"),
CliResponse::Stderr { message } => eprintln!("{message}"),
CliResponse::Exit { status } => std::process::exit(status),
}
Ok(())
}
Ok(())
});
if args.foreground {
@@ -162,9 +152,6 @@ fn main() -> Result<()> {
sender.join().unwrap()?;
}
if let Some(exit_status) = exit_status.lock().take() {
std::process::exit(exit_status);
}
Ok(())
}
@@ -185,6 +172,7 @@ 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};
@@ -233,7 +221,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)?;

View File

@@ -13,12 +13,20 @@ 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}
chrono = { workspace = true, features = ["serde"] }
clock.workspace = true
collections.workspace = true
@@ -30,7 +38,6 @@ 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
@@ -51,7 +58,6 @@ time.workspace = true
tiny_http = "0.8"
url.workspace = true
util.workspace = true
worktree.workspace = true
[dev-dependencies]
clock = { workspace = true, features = ["test-support"] }
@@ -67,5 +73,3 @@ windows.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
cocoa.workspace = true
isahc = { workspace = true, features = ["static-curl"] }
async-native-tls = { version = "0.5.0", features = ["vendored"] }

View File

@@ -509,7 +509,7 @@ impl Client {
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
if use_zed_development_auth {
Arc::new(DevelopmentCredentialsProvider {
path: paths::config_dir().join("development_auth"),
path: util::paths::CONFIG_DIR.join("development_auth"),
})
} else {
Arc::new(KeychainCredentialsProvider)
@@ -689,22 +689,6 @@ impl Client {
entity: WeakModel<E>,
handler: H,
) -> Subscription
where
M: EnvelopedMessage,
E: 'static,
H: 'static + Sync + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
F: 'static + Future<Output = Result<()>>,
{
self.add_message_handler_impl(entity, move |model, message, _, cx| {
handler(model, message, cx)
})
}
fn add_message_handler_impl<M, E, H, F>(
self: &Arc<Self>,
entity: WeakModel<E>,
handler: H,
) -> Subscription
where
M: EnvelopedMessage,
E: 'static,
@@ -753,11 +737,19 @@ impl Client {
where
M: RequestMessage,
E: 'static,
H: 'static + Sync + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
H: 'static
+ Sync
+ Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F
+ Send
+ Sync,
F: 'static + Future<Output = Result<M::Response>>,
{
self.add_message_handler_impl(model, move |handle, envelope, this, cx| {
Self::respond_to_request(envelope.receipt(), handler(handle, envelope, cx), this)
self.add_message_handler(model, move |handle, envelope, this, cx| {
Self::respond_to_request(
envelope.receipt(),
handler(handle, envelope, this.clone(), cx),
this,
)
})
}
@@ -765,11 +757,11 @@ impl Client {
where
M: EntityMessage,
E: 'static,
H: 'static + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
F: 'static + Future<Output = Result<()>>,
{
self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, _, cx| {
handler(subscriber.downcast::<E>().unwrap(), message, cx)
self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
handler(subscriber.downcast::<E>().unwrap(), message, client, cx)
})
}
@@ -816,13 +808,13 @@ impl Client {
where
M: EntityMessage + RequestMessage,
E: 'static,
H: 'static + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
H: 'static + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F + Send + Sync,
F: 'static + Future<Output = Result<M::Response>>,
{
self.add_entity_message_handler::<M, E, _, _>(move |entity, envelope, client, cx| {
self.add_model_message_handler(move |entity, envelope, client, cx| {
Self::respond_to_request::<M, _>(
envelope.receipt(),
handler(entity.downcast::<E>().unwrap(), envelope, cx),
handler(entity, envelope, client.clone(), cx),
client,
)
})
@@ -1920,7 +1912,7 @@ mod tests {
let (done_tx1, mut done_rx1) = smol::channel::unbounded();
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
client.add_model_message_handler(
move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, mut cx| {
move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
match model.update(&mut cx, |model, _| model.id).unwrap() {
1 => done_tx1.try_send(()).unwrap(),
2 => done_tx2.try_send(()).unwrap(),
@@ -1982,7 +1974,7 @@ mod tests {
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
let subscription1 = client.add_message_handler(
model.downgrade(),
move |_, _: TypedEnvelope<proto::Ping>, _| {
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
done_tx1.try_send(()).unwrap();
async { Ok(()) }
},
@@ -1990,7 +1982,7 @@ mod tests {
drop(subscription1);
let _subscription2 = client.add_message_handler(
model.downgrade(),
move |_, _: TypedEnvelope<proto::Ping>, _| {
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
done_tx2.try_send(()).unwrap();
async { Ok(()) }
},
@@ -2016,7 +2008,7 @@ mod tests {
let (done_tx, mut done_rx) = smol::channel::unbounded();
let subscription = client.add_message_handler(
model.clone().downgrade(),
move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, mut cx| {
move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
model
.update(&mut cx, |model, _| model.subscription.take())
.unwrap();

View File

@@ -3,7 +3,6 @@ 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};
@@ -24,7 +23,6 @@ use tempfile::NamedTempFile;
#[cfg(not(debug_assertions))]
use util::ResultExt;
use util::TryFutureExt;
use worktree::{UpdatedEntriesSet, WorktreeId};
use self::event_coalescer::EventCoalescer;
@@ -49,31 +47,12 @@ 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;
@@ -201,16 +180,6 @@ 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(),
@@ -223,7 +192,7 @@ impl Telemetry {
let state = state.clone();
async move {
if let Some(tempfile) =
NamedTempFile::new_in(paths::config_dir().as_path()).log_err()
NamedTempFile::new_in(util::paths::CONFIG_DIR.as_path()).log_err()
{
state.lock().log_file = Some(tempfile);
}
@@ -481,52 +450,6 @@ 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();
@@ -590,6 +513,10 @@ impl Telemetry {
return;
}
if ZED_CLIENT_CHECKSUM_SEED.is_none() {
return;
};
let this = self.clone();
self.executor
.spawn(
@@ -625,7 +552,9 @@ impl Telemetry {
serde_json::to_writer(&mut json_bytes, &request_body)?;
}
let checksum = calculate_json_checksum(&json_bytes).unwrap_or("".to_string());
let Some(checksum) = calculate_json_checksum(&json_bytes) else {
return Ok(());
};
let request = http::Request::builder()
.method(Method::POST)

View File

@@ -242,6 +242,7 @@ impl UserStore {
async fn handle_update_invite_info(
this: Model<Self>,
message: TypedEnvelope<proto::UpdateInviteInfo>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
@@ -257,6 +258,7 @@ impl UserStore {
async fn handle_show_contacts(
this: Model<Self>,
_: TypedEnvelope<proto::ShowContacts>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
@@ -270,6 +272,7 @@ impl UserStore {
async fn handle_update_contacts(
this: Model<Self>,
message: TypedEnvelope<proto::UpdateContacts>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, _| {

View File

@@ -122,11 +122,6 @@ spec:
secretKeyRef:
name: anthropic
key: api_key
- name: GOOGLE_AI_API_KEY
valueFrom:
secretKeyRef:
name: google-ai
key: api_key
- name: BLOB_STORE_ACCESS_KEY
valueFrom:
secretKeyRef:

View File

@@ -308,12 +308,13 @@ pub async fn post_panic(
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
let panic = report.panic;
if panic.os_name == "Linux" && panic.os_version == Some("1.0.0".to_string()) {
return Err(Error::Http(
StatusCode::BAD_REQUEST,
"invalid os version".into(),
))?;
}
// 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()"} ))
tracing::error!(
service = "client",
@@ -401,7 +402,12 @@ pub async fn post_events(
))?;
};
let checksum_matched = checksum == expected;
if checksum != expected {
return Err(Error::Http(
StatusCode::BAD_REQUEST,
"invalid checksum".into(),
))?;
}
let request_body: telemetry_events::EventRequestBody =
serde_json::from_slice(&body).map_err(|err| {
@@ -426,7 +432,6 @@ 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(_) => {}
@@ -439,7 +444,6 @@ 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(
@@ -447,7 +451,6 @@ pub async fn post_events(
&wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Assistant(event) => {
to_upload
@@ -457,7 +460,6 @@ pub async fn post_events(
&wrapper,
&request_body,
first_event_at,
checksum_matched,
))
}
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
@@ -465,42 +467,36 @@ 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
@@ -515,7 +511,6 @@ pub async fn post_events(
&request_body,
metadata,
first_event_at,
checksum_matched,
))
}
}
@@ -663,30 +658,29 @@ where
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
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,
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,
#[serde(serialize_with = "serialize_country_code")]
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,
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>,
}
impl EditorEventRow {
@@ -696,7 +690,6 @@ 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 =
@@ -707,7 +700,6 @@ 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(),
@@ -751,7 +743,6 @@ pub struct InlineCompletionEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
}
impl InlineCompletionEventRow {
@@ -761,7 +752,6 @@ 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 =
@@ -772,7 +762,6 @@ 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(),
@@ -801,7 +790,6 @@ pub struct CallEventRow {
release_channel: String,
os_name: String,
os_version: String,
checksum_matched: bool,
// ClientEventBase
installation_id: String,
@@ -821,7 +809,6 @@ impl CallEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -832,7 +819,6 @@ 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(),
@@ -854,7 +840,6 @@ pub struct AssistantEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -879,7 +864,6 @@ impl AssistantEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -890,7 +874,6 @@ 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(),
@@ -925,7 +908,6 @@ pub struct CpuEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
}
impl CpuEventRow {
@@ -934,7 +916,6 @@ impl CpuEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -945,7 +926,6 @@ 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(),
@@ -966,7 +946,6 @@ pub struct MemoryEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -988,7 +967,6 @@ impl MemoryEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -999,7 +977,6 @@ 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(),
@@ -1020,7 +997,6 @@ pub struct AppEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1041,7 +1017,6 @@ impl AppEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1052,7 +1027,6 @@ 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(),
@@ -1072,7 +1046,6 @@ pub struct SettingEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1093,7 +1066,6 @@ impl SettingEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1103,7 +1075,6 @@ 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(),
@@ -1125,7 +1096,6 @@ pub struct ExtensionEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1151,7 +1121,6 @@ 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 =
@@ -1162,7 +1131,6 @@ 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(),
@@ -1194,7 +1162,6 @@ pub struct EditEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1219,7 +1186,6 @@ impl EditEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1233,7 +1199,6 @@ 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(),
@@ -1255,7 +1220,6 @@ pub struct ActionEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1278,7 +1242,6 @@ impl ActionEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1289,7 +1252,6 @@ 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(),

View File

@@ -379,7 +379,6 @@ 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,
)

View File

@@ -2583,13 +2583,14 @@ async fn rejoin_dev_server_projects(
)
.await?
};
notify_rejoined_projects(&mut rejoined_projects, &session)?;
response.send(proto::RejoinRemoteProjectsResponse {
rejoined_projects: rejoined_projects
.iter()
.into_iter()
.map(|project| project.to_proto())
.collect(),
})?;
notify_rejoined_projects(&mut rejoined_projects, &session)
})
}
async fn reconnect_dev_server(
@@ -4502,7 +4503,6 @@ async fn complete_with_google_ai(
session.http_client.clone(),
google_ai::API_URL,
api_key.as_ref(),
&request.model.clone(),
crate::ai::language_model_request_to_google_ai(request)?,
)
.await

View File

@@ -73,7 +73,6 @@ impl ConnectionPool {
pub fn reset(&mut self) {
self.connections.clear();
self.connected_users.clear();
self.connected_dev_servers.clear();
self.channels.clear();
}

View File

@@ -504,29 +504,6 @@ async fn test_dev_server_reconnect(
.unwrap();
}
#[gpui::test]
async fn test_dev_server_restart(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
let (server, client1) = TestServer::start1(cx1).await;
let (_dev_server, remote_workspace) =
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx2).await;
let cx = VisualTestContext::from_window(remote_workspace.into(), cx1).as_mut();
server.reset().await;
cx.run_until_parked();
cx.simulate_keystrokes("cmd-p 1 enter");
remote_workspace
.update(cx, |ws, cx| {
ws.active_item_as::<Editor>(cx)
.unwrap()
.update(cx, |ed, cx| {
assert_eq!(ed.text(cx).to_string(), "remote\nremote\nremote");
})
})
.unwrap();
}
#[gpui::test]
async fn test_create_dev_server_project_path_validation(
cx1: &mut gpui::TestAppContext,

View File

@@ -28,7 +28,7 @@ use language::{
use multi_buffer::MultiBufferRow;
use project::{
project_settings::{InlineBlameSettings, ProjectSettings},
SERVER_PROGRESS_THROTTLE_TIMEOUT,
SERVER_PROGRESS_DEBOUNCE_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::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
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::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
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::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
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::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
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::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(1, 22),
@@ -629,7 +629,7 @@ async fn test_collaborating_with_code_actions(
)],
),
(
lsp::Url::from_file_path("/a/other.rs").unwrap(),
lsp::Uri::from_file_path("/a/other.rs").unwrap().into(),
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::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(1, 22),
@@ -699,7 +699,7 @@ async fn test_collaborating_with_code_actions(
)],
),
(
lsp::Url::from_file_path("/a/other.rs").unwrap(),
lsp::Uri::from_file_path("/a/other.rs").unwrap().into(),
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::Url::from_file_path("/dir/one.rs").unwrap(),
lsp::Uri::from_file_path("/dir/one.rs").unwrap().into(),
vec![lsp::TextEdit::new(
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
"THREE".to_string(),
)],
),
(
lsp::Url::from_file_path("/dir/two.rs").unwrap(),
lsp::Uri::from_file_path("/dir/two.rs").unwrap().into(),
vec![
lsp::TextEdit::new(
lsp::Range::new(
@@ -1006,8 +1006,6 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
let fake_language_server = fake_language_servers.next().await.unwrap();
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(
@@ -1017,10 +1015,11 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
},
)),
});
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
executor.run_until_parked();
project_a.read_with(cx_a, |project, _| {
let status = project.language_server_statuses().next().unwrap().1;
let status = project.language_server_statuses().next().unwrap();
assert_eq!(status.name, "the-language-server");
assert_eq!(status.pending_work.len(), 1);
assert_eq!(
@@ -1037,11 +1036,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
project_b.read_with(cx_b, |project, _| {
let status = project.language_server_statuses().next().unwrap().1;
let status = project.language_server_statuses().next().unwrap();
assert_eq!(status.name, "the-language-server");
});
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(
@@ -1051,10 +1049,11 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
},
)),
});
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
executor.run_until_parked();
project_a.read_with(cx_a, |project, _| {
let status = project.language_server_statuses().next().unwrap().1;
let status = project.language_server_statuses().next().unwrap();
assert_eq!(status.name, "the-language-server");
assert_eq!(status.pending_work.len(), 1);
assert_eq!(
@@ -1064,7 +1063,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().1;
let status = project.language_server_statuses().next().unwrap();
assert_eq!(status.name, "the-language-server");
assert_eq!(status.pending_work.len(), 1);
assert_eq!(
@@ -1204,7 +1203,7 @@ async fn test_share_project(
buffer_a.read_with(cx_a, |buffer, _| {
buffer
.snapshot()
.selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
.remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX)
.count()
== 1
});
@@ -1245,7 +1244,7 @@ async fn test_share_project(
buffer_a.read_with(cx_a, |buffer, _| {
buffer
.snapshot()
.selections_in_range(text::Anchor::MIN..text::Anchor::MAX, false)
.remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX)
.count()
== 0
});
@@ -1314,7 +1313,7 @@ async fn test_on_input_format_from_host_to_guest(
|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
assert_eq!(
params.text_document_position.position,
@@ -1442,7 +1441,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::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
assert_eq!(
params.text_document_position.position,
@@ -1611,7 +1610,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
Ok(Some(vec![lsp::InlayHint {
@@ -1874,7 +1873,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
let character = if other_hints { 0 } else { 2 };

View File

@@ -3897,7 +3897,7 @@ async fn test_collaborating_with_diagnostics(
.await;
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
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::Url::from_file_path("/a/a.rs").unwrap(),
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
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::Url::from_file_path("/a/a.rs").unwrap(),
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
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::Url::from_file_path("/a/a.rs").unwrap(),
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
version: None,
diagnostics: vec![],
},
@@ -4189,7 +4189,9 @@ 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::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(),
uri: lsp::Uri::from_file_path(Path::new("/test").join(file_name))
.unwrap()
.into(),
version: None,
diagnostics: vec![lsp::Diagnostic {
severity: Some(lsp::DiagnosticSeverity::WARNING),
@@ -4607,7 +4609,7 @@ async fn test_definition(
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
lsp::Location::new(
lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
lsp::Uri::from_file_path("/root/dir-2/b.rs").unwrap().into(),
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
),
)))
@@ -4636,7 +4638,7 @@ async fn test_definition(
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
lsp::Location::new(
lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
lsp::Uri::from_file_path("/root/dir-2/b.rs").unwrap().into(),
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
),
)))
@@ -4672,7 +4674,7 @@ async fn test_definition(
);
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
lsp::Location::new(
lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
lsp::Uri::from_file_path("/root/dir-2/c.rs").unwrap().into(),
lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
),
)))
@@ -4772,7 +4774,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().unwrap().1;
let status = project.language_server_statuses().next().cloned().unwrap();
assert_eq!(status.name, "my-fake-lsp-adapter");
assert_eq!(
status.pending_work.values().next().unwrap().message,
@@ -4784,15 +4786,21 @@ async fn test_references(
lsp_response_tx
.unbounded_send(Ok(Some(vec![
lsp::Location {
uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
uri: lsp::Uri::from_file_path("/root/dir-1/two.rs")
.unwrap()
.into(),
range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
},
lsp::Location {
uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
uri: lsp::Uri::from_file_path("/root/dir-1/two.rs")
.unwrap()
.into(),
range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
},
lsp::Location {
uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
uri: lsp::Uri::from_file_path("/root/dir-2/three.rs")
.unwrap()
.into(),
range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
},
])))
@@ -4802,7 +4810,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().1;
let status = project.language_server_statuses().next().unwrap();
assert!(status.pending_work.is_empty());
assert_eq!(references.len(), 3);
@@ -4830,7 +4838,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().unwrap().1;
let status = project.language_server_statuses().next().cloned().unwrap();
assert_eq!(status.name, "my-fake-lsp-adapter");
assert_eq!(
status.pending_work.values().next().unwrap().message,
@@ -4847,7 +4855,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().1;
let status = project.language_server_statuses().next().unwrap();
assert!(status.pending_work.is_empty());
});
}
@@ -4904,15 +4912,7 @@ 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,
Default::default(),
Default::default(),
)
.unwrap(),
SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
cx,
)
});
@@ -5300,7 +5300,9 @@ async fn test_project_symbols(
lsp::SymbolInformation {
name: "TWO".into(),
location: lsp::Location {
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
uri: lsp::Uri::from_file_path("/code/crate-2/two.rs")
.unwrap()
.into(),
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
},
kind: lsp::SymbolKind::CONSTANT,
@@ -5390,7 +5392,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::Url::from_file_path("/root/b.rs").unwrap(),
lsp::Uri::from_file_path("/root/b.rs").unwrap().into(),
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
),
)))

View File

@@ -875,15 +875,8 @@ impl RandomizedTest for ProjectCollaborationTest {
let mut search = project.update(cx, |project, cx| {
project.search(
SearchQuery::text(
query,
false,
false,
false,
Default::default(),
Default::default(),
)
.unwrap(),
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
.unwrap(),
cx,
)
});
@@ -1108,7 +1101,7 @@ impl RandomizedTest for ProjectCollaborationTest {
files
.into_iter()
.map(|file| lsp::Location {
uri: lsp::Url::from_file_path(file).unwrap(),
uri: lsp::Uri::from_file_path(file).unwrap().into(),
range: Default::default(),
})
.collect(),

View File

@@ -35,12 +35,10 @@ 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

View File

@@ -22,7 +22,7 @@ use settings::Settings;
use std::{sync::Arc, time::Duration};
use time::{OffsetDateTime, UtcOffset};
use ui::{
prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, PopoverMenu,
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
TabBar, Tooltip,
};
use util::{ResultExt, TryFutureExt};
@@ -679,7 +679,7 @@ impl ChatPanel {
cx,
div()
.child(
PopoverMenu::new(("menu", message_id))
popover_menu(("menu", message_id))
.trigger(IconButton::new(
("trigger", message_id),
IconName::Ellipsis,

View File

@@ -25,15 +25,8 @@ 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,
Default::default(),
Default::default()
)
.unwrap();
static ref MENTIONS_SEARCH: SearchQuery =
SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
}
pub struct MessageEditor {

View File

@@ -10,12 +10,11 @@ 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, ThemeSettings};
use theme::ActiveTheme;
use ui::{
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike,
ButtonStyle, ContextMenu, Icon, IconButton, IconName, Indicator, TintColor, TitleBar, Tooltip,
};
use util::ResultExt;
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
@@ -74,7 +73,6 @@ 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))
@@ -388,173 +386,8 @@ impl CollabTitlebarItem {
}
}
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()
})
}
// 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_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
if let Some(dev_server) =
@@ -906,13 +739,12 @@ 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() {
PopoverMenu::new("user-menu")
popover_menu("user-menu")
.menu(|cx| {
ContextMenu::build(cx, |menu, _| {
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
.action("Extensions", extensions_ui::Extensions.boxed_clone())
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
.separator()
.action("Sign Out", client::SignOut.boxed_clone())
})
@@ -935,13 +767,12 @@ impl CollabTitlebarItem {
)
.anchor(gpui::AnchorCorner::TopRight)
} else {
PopoverMenu::new("user-menu")
popover_menu("user-menu")
.menu(|cx| {
ContextMenu::build(cx, |menu, _| {
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
.action("Extensions", extensions_ui::Extensions.boxed_clone())
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
})
.into()
})

View File

@@ -124,6 +124,5 @@ fn notification_window_options(
display_id: Some(screen.id()),
window_background: WindowBackgroundAppearance::default(),
app_id: Some(app_id.to_owned()),
window_min_size: Size::default(),
}
}

View File

@@ -3,8 +3,9 @@ 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;
@@ -112,7 +113,13 @@ impl IncomingCallNotification {
impl Render for IncomingCallNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
// TODO: Is there a better place for us to initialize the font?
let (ui_font, ui_font_size) = {
let theme_settings = ThemeSettings::get_global(cx);
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
};
cx.set_rem_size(ui_font_size);
div().size_full().font(ui_font).child(
CollabNotification::new(

View File

@@ -4,8 +4,9 @@ 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;
@@ -123,7 +124,13 @@ impl ProjectSharedNotification {
impl Render for ProjectSharedNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font = theme::setup_ui_font(cx);
// TODO: Is there a better place for us to initialize the font?
let (ui_font, ui_font_size) = {
let theme_settings = ThemeSettings::get_global(cx);
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
};
cx.set_rem_size(ui_font_size);
div().size_full().font(ui_font).child(
CollabNotification::new(

View File

@@ -38,7 +38,6 @@ 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

View File

@@ -33,7 +33,7 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
use util::{fs::remove_matching, maybe, ResultExt};
use util::{fs::remove_matching, maybe, paths, ResultExt};
pub use copilot_completion_provider::CopilotCompletionProvider;
pub use sign_in::CopilotCodeVerification;
@@ -188,7 +188,7 @@ impl Status {
}
struct RegisteredBuffer {
uri: lsp::Url,
uri: lsp::RawUri,
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: lsp::Url = uri_for_buffer(buffer, cx);
let uri = 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::Url {
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::RawUri {
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
lsp::Uri::from_file_path(file.abs_path(cx)).unwrap().into()
} 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()).await?;
let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_dir() {
@@ -1042,6 +1042,8 @@ 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;
@@ -1050,9 +1052,8 @@ 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::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
.parse()
.unwrap();
let buffer_1_uri =
lsp::RawUri::from_str(&format!("buffer://{}", buffer_1.entity_id().as_u64())).unwrap();
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -1068,9 +1069,8 @@ mod tests {
);
let buffer_2 = cx.new_model(|cx| Buffer::local("Goodbye", cx));
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
.parse()
.unwrap();
let buffer_2_uri =
lsp::RawUri::from_str(&format!("buffer://{}", buffer_2.entity_id().as_u64())).unwrap();
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -1119,7 +1119,9 @@ mod tests {
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
}
);
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
let buffer_1_uri: lsp::RawUri = lsp::Uri::from_file_path("/root/child/buffer-1")
.unwrap()
.into();
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await,

View File

@@ -1121,7 +1121,10 @@ 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());
assert_eq!(
params.text_document_position.text_document.uri,
url.clone().into()
);
assert_eq!(
params.text_document_position.position,
complete_from_position

View File

@@ -102,7 +102,7 @@ pub struct GetCompletionsDocument {
pub tab_size: u32,
pub indent_size: u32,
pub insert_spaces: bool,
pub uri: lsp::Url,
pub uri: lsp::RawUri,
pub relative_path: String,
pub position: lsp::Position,
pub version: usize,

View File

@@ -21,7 +21,6 @@ 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

View File

@@ -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::database_dir(), &$crate::RELEASE_CHANNEL)));
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
}
};
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
@@ -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::database_dir(), &$crate::RELEASE_CHANNEL)));
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
}
};
}

View File

@@ -124,6 +124,7 @@ impl Store {
async fn handle_dev_server_projects_update(
this: Model<Self>,
envelope: TypedEnvelope<proto::DevServerProjectsUpdate>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {

View File

@@ -137,7 +137,7 @@ impl ProjectDiagnosticsEditor {
this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.emit(EditorEvent::TitleChanged);
if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
if this.editor.read(cx).is_focused(cx) || this.focus_handle.is_focused(cx) {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
} else {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
@@ -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, _event, cx| this.focus_out(cx))
cx.on_focus_out(&focus_handle, |this, cx| this.focus_out(cx))
.detach();
let excerpts = cx.new_model(|cx| {

View File

@@ -1,7 +1,9 @@
use std::time::Duration;
use editor::Editor;
use gpui::{
rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
ViewContext, WeakView,
percentage, rems, Animation, AnimationExt, EventEmitter, IntoElement, ParentElement, Render,
Styled, Subscription, Transformation, View, ViewContext, WeakView,
};
use language::Diagnostic;
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
@@ -59,7 +61,42 @@ impl Render for DiagnosticIndicator {
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
};
let status = if let Some(diagnostic) = &self.current_diagnostic {
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 message = diagnostic.message.split('\n').next().unwrap().to_string();
Some(
Button::new("diagnostic_message", message)

View File

@@ -1,6 +1,5 @@
//! This module contains all actions supported by [`Editor`].
use super::*;
use gpui::action_as;
use util::serde::default_true;
#[derive(PartialEq, Clone, Deserialize, Default)]
@@ -170,7 +169,6 @@ gpui::actions!(
AddSelectionBelow,
Backspace,
Cancel,
CancelLanguageServerWork,
ConfirmRename,
ContextMenuFirst,
ContextMenuLast,
@@ -268,7 +266,6 @@ gpui::actions!(
SelectAllMatches,
SelectDown,
SelectLargerSyntaxNode,
SelectEnclosingSymbol,
SelectLeft,
SelectLine,
SelectRight,
@@ -282,8 +279,6 @@ gpui::actions!(
SelectToPreviousWordStart,
SelectToStartOfParagraph,
SelectUp,
SelectPageDown,
SelectPageUp,
ShowCharacterPalette,
ShowInlineCompletion,
ShuffleLines,
@@ -294,7 +289,6 @@ gpui::actions!(
TabPrev,
ToggleGitBlame,
ToggleGitBlameInline,
ToggleSelectionMenu,
ToggleHunkDiff,
ToggleInlayHints,
ToggleLineNumbers,
@@ -309,7 +303,3 @@ gpui::actions!(
UniqueLinesCaseSensitive,
]
);
action_as!(outline, ToggleOutline as Toggle);
action_as!(go_to_line, ToggleGoToLine as Toggle);

View File

@@ -18,7 +18,7 @@
//! [EditorElement]: crate::element::EditorElement
mod block_map;
mod crease_map;
mod flap_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 crease_map::*;
pub use flap_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.
crease_map: CreaseMap,
flap_map: FlapMap,
fold_placeholder: FoldPlaceholder,
pub clip_at_line_ends: bool,
}
@@ -139,7 +139,7 @@ impl DisplayMap {
excerpt_header_height,
excerpt_footer_height,
);
let crease_map = CreaseMap::default();
let flap_map = FlapMap::default();
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
@@ -151,7 +151,7 @@ impl DisplayMap {
tab_map,
wrap_map,
block_map,
crease_map,
flap_map,
fold_placeholder,
text_highlights: Default::default(),
inlay_highlights: Default::default(),
@@ -169,7 +169,7 @@ impl DisplayMap {
let (wrap_snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits).snapshot;
let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits);
DisplaySnapshot {
buffer_snapshot: self.buffer.read(cx).snapshot(cx),
@@ -178,7 +178,7 @@ impl DisplayMap {
tab_snapshot,
wrap_snapshot,
block_snapshot,
crease_snapshot: self.crease_map.snapshot(),
flap_snapshot: self.flap_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_creases(
pub fn insert_flaps(
&mut self,
creases: impl IntoIterator<Item = Crease>,
flaps: impl IntoIterator<Item = Flap>,
cx: &mut ModelContext<Self>,
) -> Vec<CreaseId> {
) -> Vec<FlapId> {
let snapshot = self.buffer.read(cx).snapshot(cx);
self.crease_map.insert(creases, &snapshot)
self.flap_map.insert(flaps, &snapshot)
}
pub fn remove_creases(
pub fn remove_flaps(
&mut self,
crease_ids: impl IntoIterator<Item = CreaseId>,
flap_ids: impl IntoIterator<Item = FlapId>,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
self.crease_map.remove(crease_ids, &snapshot)
self.flap_map.remove(flap_ids, &snapshot)
}
pub fn insert_blocks(
@@ -348,25 +348,6 @@ impl DisplayMap {
block_map.remove(ids);
}
pub fn row_for_block(
&mut self,
block_id: BlockId,
cx: &mut ModelContext<Self>,
) -> Option<DisplayRow> {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
let block_map = self.block_map.read(snapshot, edits);
let block_row = block_map.row_for_block(block_id)?;
Some(DisplayRow(block_row.0))
}
pub fn highlight_text(
&mut self,
type_id: TypeId,
@@ -491,7 +472,7 @@ pub struct HighlightedChunk<'a> {
pub struct DisplaySnapshot {
pub buffer_snapshot: MultiBufferSnapshot,
pub fold_snapshot: FoldSnapshot,
pub crease_snapshot: CreaseSnapshot,
pub flap_snapshot: FlapSnapshot,
inlay_snapshot: InlaySnapshot,
tab_snapshot: TabSnapshot,
wrap_snapshot: WrapSnapshot,
@@ -974,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(crease) = self
.crease_snapshot
if let Some(flap) = self
.flap_snapshot
.query_row(buffer_row, &self.buffer_snapshot)
{
Some((
crease.range.to_point(&self.buffer_snapshot),
crease.placeholder.clone(),
flap.range.to_point(&self.buffer_snapshot),
flap.placeholder.clone(),
))
} else if self.starts_indent(MultiBufferRow(start.row))
&& !self.is_line_folded(MultiBufferRow(start.row))
@@ -1002,23 +983,8 @@ impl DisplaySnapshot {
break;
}
}
let mut row_before_line_breaks = end.unwrap_or(max_point);
while row_before_line_breaks.row > start.row
&& self
.buffer_snapshot
.is_line_blank(MultiBufferRow(row_before_line_breaks.row))
{
row_before_line_breaks.row -= 1;
}
row_before_line_breaks = Point::new(
row_before_line_breaks.row,
self.buffer_snapshot
.line_len(MultiBufferRow(row_before_line_breaks.row)),
);
Some((start..row_before_line_breaks, self.fold_placeholder.clone()))
let end = end.unwrap_or(max_point);
Some((start..end, self.fold_placeholder.clone()))
} else {
None
}
@@ -1055,22 +1021,6 @@ 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);
@@ -1980,7 +1930,7 @@ pub mod tests {
}
#[gpui::test]
fn test_creases(cx: &mut gpui::AppContext) {
fn test_flaps(cx: &mut gpui::AppContext) {
init_test(cx, |_| {});
let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
@@ -2003,8 +1953,8 @@ pub mod tests {
let range =
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
map.crease_map.insert(
[Crease::new(
map.flap_map.insert(
[Flap::new(
range,
FoldPlaceholder::test(),
|_row, _status, _toggle, _cx| div(),

View File

@@ -37,11 +37,6 @@ pub struct BlockMap {
excerpt_footer_height: u8,
}
pub struct BlockMapReader<'a> {
blocks: &'a Vec<Arc<Block>>,
pub snapshot: BlockSnapshot,
}
pub struct BlockMapWriter<'a>(&'a mut BlockMap);
#[derive(Clone)]
@@ -251,15 +246,12 @@ impl BlockMap {
map
}
pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader {
pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockSnapshot {
self.sync(&wrap_snapshot, edits);
*self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
BlockMapReader {
blocks: &self.blocks,
snapshot: BlockSnapshot {
wrap_snapshot,
transforms: self.transforms.borrow().clone(),
},
BlockSnapshot {
wrap_snapshot,
transforms: self.transforms.borrow().clone(),
}
}
@@ -614,62 +606,6 @@ impl std::ops::DerefMut for BlockPoint {
}
}
impl<'a> Deref for BlockMapReader<'a> {
type Target = BlockSnapshot;
fn deref(&self) -> &Self::Target {
&self.snapshot
}
}
impl<'a> DerefMut for BlockMapReader<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.snapshot
}
}
impl<'a> BlockMapReader<'a> {
pub fn row_for_block(&self, block_id: BlockId) -> Option<BlockRow> {
let block = self.blocks.iter().find(|block| block.id == block_id)?;
let buffer_row = block
.position
.to_point(self.wrap_snapshot.buffer_snapshot())
.row;
let wrap_row = self
.wrap_snapshot
.make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
.row();
let start_wrap_row = WrapRow(
self.wrap_snapshot
.prev_row_boundary(WrapPoint::new(wrap_row, 0)),
);
let end_wrap_row = WrapRow(
self.wrap_snapshot
.next_row_boundary(WrapPoint::new(wrap_row, 0))
.unwrap_or(self.wrap_snapshot.max_point().row() + 1),
);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
cursor.seek(&start_wrap_row, Bias::Left, &());
while let Some(transform) = cursor.item() {
if cursor.start().0 > end_wrap_row {
break;
}
if let Some(BlockType::Custom(id)) =
transform.block.as_ref().map(|block| block.block_type())
{
if id == block_id {
return Some(cursor.start().1);
}
}
cursor.next(&());
}
None
}
}
impl<'a> BlockMapWriter<'a> {
pub fn insert(
&mut self,
@@ -1848,15 +1784,6 @@ mod tests {
expected_block_positions
);
for (block_row, block) in expected_block_positions {
if let BlockType::Custom(block_id) = block.block_type() {
assert_eq!(
blocks_snapshot.row_for_block(block_id),
Some(BlockRow(block_row))
);
}
}
let mut expected_longest_rows = Vec::new();
let mut longest_line_len = -1_isize;
for (row, line) in expected_lines.iter().enumerate() {

View File

@@ -9,36 +9,36 @@ use ui::WindowContext;
use crate::FoldPlaceholder;
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct CreaseId(usize);
pub struct FlapId(usize);
#[derive(Default)]
pub struct CreaseMap {
snapshot: CreaseSnapshot,
next_id: CreaseId,
id_to_range: HashMap<CreaseId, Range<Anchor>>,
pub struct FlapMap {
snapshot: FlapSnapshot,
next_id: FlapId,
id_to_range: HashMap<FlapId, Range<Anchor>>,
}
#[derive(Clone, Default)]
pub struct CreaseSnapshot {
creases: SumTree<CreaseItem>,
pub struct FlapSnapshot {
flaps: SumTree<FlapItem>,
}
impl CreaseSnapshot {
/// Returns the first Crease starting on the specified buffer row.
impl FlapSnapshot {
/// Returns the first Flap starting on the specified buffer row.
pub fn query_row<'a>(
&'a self,
row: MultiBufferRow,
snapshot: &'a MultiBufferSnapshot,
) -> Option<&'a Crease> {
) -> Option<&'a Flap> {
let start = snapshot.anchor_before(Point::new(row.0, 0));
let mut cursor = self.creases.cursor::<ItemSummary>();
let mut cursor = self.flaps.cursor::<ItemSummary>();
cursor.seek(&start, Bias::Left, snapshot);
while let Some(item) = cursor.item() {
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
match Ord::cmp(&item.flap.range.start.to_point(snapshot).row, &row.0) {
Ordering::Less => cursor.next(snapshot),
Ordering::Equal => {
if item.crease.range.start.is_valid(snapshot) {
return Some(&item.crease);
if item.flap.range.start.is_valid(snapshot) {
return Some(&item.flap);
} else {
cursor.next(snapshot);
}
@@ -49,17 +49,17 @@ impl CreaseSnapshot {
return None;
}
pub fn crease_items_with_offsets(
pub fn flap_items_with_offsets(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<(CreaseId, Range<Point>)> {
let mut cursor = self.creases.cursor::<ItemSummary>();
) -> Vec<(FlapId, Range<Point>)> {
let mut cursor = self.flaps.cursor::<ItemSummary>();
let mut results = Vec::new();
cursor.next(snapshot);
while let Some(item) = cursor.item() {
let start_point = item.crease.range.start.to_point(snapshot);
let end_point = item.crease.range.end.to_point(snapshot);
let start_point = item.flap.range.start.to_point(snapshot);
let end_point = item.flap.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 Crease {
pub struct Flap {
pub range: Range<Anchor>,
pub placeholder: FoldPlaceholder,
pub render_toggle: RenderToggleFn,
pub render_trailer: RenderTrailerFn,
}
impl Crease {
impl Flap {
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
range: Range<Anchor>,
placeholder: FoldPlaceholder,
@@ -115,7 +115,7 @@ impl Crease {
+ 'static,
TrailerElement: IntoElement,
{
Crease {
Flap {
range,
placeholder,
render_toggle: Arc::new(move |row, folded, toggle, cx| {
@@ -128,52 +128,50 @@ impl Crease {
}
}
impl std::fmt::Debug for Crease {
impl std::fmt::Debug for Flap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Crease")
.field("range", &self.range)
.finish()
f.debug_struct("Flap").field("range", &self.range).finish()
}
}
#[derive(Clone, Debug)]
struct CreaseItem {
id: CreaseId,
crease: Crease,
struct FlapItem {
id: FlapId,
flap: Flap,
}
impl CreaseMap {
pub fn snapshot(&self) -> CreaseSnapshot {
impl FlapMap {
pub fn snapshot(&self) -> FlapSnapshot {
self.snapshot.clone()
}
pub fn insert(
&mut self,
creases: impl IntoIterator<Item = Crease>,
flaps: impl IntoIterator<Item = Flap>,
snapshot: &MultiBufferSnapshot,
) -> Vec<CreaseId> {
) -> Vec<FlapId> {
let mut new_ids = Vec::new();
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);
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);
let id = self.next_id;
self.next_id.0 += 1;
self.id_to_range.insert(id, crease.range.clone());
new_creases.push(CreaseItem { crease, id }, snapshot);
self.id_to_range.insert(id, flap.range.clone());
new_flaps.push(FlapItem { flap, id }, snapshot);
new_ids.push(id);
}
new_creases.append(cursor.suffix(snapshot), snapshot);
new_creases
new_flaps.append(cursor.suffix(snapshot), snapshot);
new_flaps
};
new_ids
}
pub fn remove(
&mut self,
ids: impl IntoIterator<Item = CreaseId>,
ids: impl IntoIterator<Item = FlapId>,
snapshot: &MultiBufferSnapshot,
) {
let mut removals = Vec::new();
@@ -186,24 +184,24 @@ impl CreaseMap {
AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(&a_id))
});
self.snapshot.creases = {
let mut new_creases = SumTree::new();
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
self.snapshot.flaps = {
let mut new_flaps = SumTree::new();
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
for (id, range) in removals {
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
new_flaps.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
while let Some(item) = cursor.item() {
cursor.next(snapshot);
if item.id == id {
break;
} else {
new_creases.push(item.clone(), snapshot);
new_flaps.push(item.clone(), snapshot);
}
}
}
new_creases.append(cursor.suffix(snapshot), snapshot);
new_creases
new_flaps.append(cursor.suffix(snapshot), snapshot);
new_flaps
};
}
}
@@ -229,17 +227,17 @@ impl sum_tree::Summary for ItemSummary {
}
}
impl sum_tree::Item for CreaseItem {
impl sum_tree::Item for FlapItem {
type Summary = ItemSummary;
fn summary(&self) -> Self::Summary {
ItemSummary {
range: self.crease.range.clone(),
range: self.flap.range.clone(),
}
}
}
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `FlapItem`s.
impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
@@ -259,48 +257,48 @@ mod test {
use multi_buffer::MultiBuffer;
#[gpui::test]
fn test_insert_and_remove_creases(cx: &mut AppContext) {
fn test_insert_and_remove_flaps(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 crease_map = CreaseMap::default();
let mut flap_map = FlapMap::default();
// Insert creases
let creases = [
Crease::new(
// Insert flaps
let flaps = [
Flap::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(),
),
Crease::new(
Flap::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 crease_ids = crease_map.insert(creases, &snapshot);
assert_eq!(crease_ids.len(), 2);
let flap_ids = flap_map.insert(flaps, &snapshot);
assert_eq!(flap_ids.len(), 2);
// Verify creases are inserted
let crease_snapshot = crease_map.snapshot();
assert!(crease_snapshot
// Verify flaps are inserted
let flap_snapshot = flap_map.snapshot();
assert!(flap_snapshot
.query_row(MultiBufferRow(1), &snapshot)
.is_some());
assert!(crease_snapshot
assert!(flap_snapshot
.query_row(MultiBufferRow(3), &snapshot)
.is_some());
// Remove creases
crease_map.remove(crease_ids, &snapshot);
// Remove flaps
flap_map.remove(flap_ids, &snapshot);
// Verify creases are removed
let crease_snapshot = crease_map.snapshot();
assert!(crease_snapshot
// Verify flaps are removed
let flap_snapshot = flap_map.snapshot();
assert!(flap_snapshot
.query_row(MultiBufferRow(1), &snapshot)
.is_none());
assert!(crease_snapshot
assert!(flap_snapshot
.query_row(MultiBufferRow(3), &snapshot)
.is_none());
}

View File

@@ -462,8 +462,11 @@ impl InlayMap {
if buffer_edits.is_empty() {
if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
|| snapshot.buffer.non_text_state_update_count()
!= buffer_snapshot.non_text_state_update_count()
|| snapshot.buffer.parse_count() != buffer_snapshot.parse_count()
|| snapshot.buffer.diagnostics_update_count()
!= buffer_snapshot.diagnostics_update_count()
|| snapshot.buffer.git_diff_update_count()
!= buffer_snapshot.git_diff_update_count()
|| snapshot.buffer.trailing_excerpt_update_count()
!= buffer_snapshot.trailing_excerpt_update_count()
{

View File

@@ -66,12 +66,11 @@ 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, 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,
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,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -449,7 +448,6 @@ 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.
@@ -457,9 +455,6 @@ pub struct Editor {
pub display_map: Model<DisplayMap>,
pub selections: SelectionsCollection,
pub scroll_manager: ScrollManager,
/// When inline assist editors are linked, they all render cursors because
/// typing enters text into each of them, even the ones that aren't focused.
pub(crate) show_cursor_when_unfocused: bool,
columnar_selection_tail: Option<Anchor>,
add_selections_state: Option<AddSelectionsState>,
select_next_state: Option<SelectNextState>,
@@ -484,7 +479,6 @@ pub struct Editor {
show_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
show_code_actions: Option<bool>,
show_runnables: Option<bool>,
show_wrap_guides: Option<bool>,
show_indent_guides: Option<bool>,
placeholder_text: Option<Arc<str>>,
@@ -536,13 +530,11 @@ pub struct Editor {
next_editor_action_id: EditorActionId,
editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
use_autoclose: bool,
use_auto_surround: bool,
auto_replace_emoji_shortcode: bool,
show_git_blame_gutter: bool,
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<
@@ -557,7 +549,6 @@ 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)]
@@ -567,7 +558,6 @@ pub struct EditorSnapshot {
show_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
show_code_actions: Option<bool>,
show_runnables: Option<bool>,
render_git_blame_gutter: bool,
pub display_snapshot: DisplaySnapshot,
pub placeholder_text: Option<Arc<str>>,
@@ -1640,7 +1630,7 @@ impl Editor {
clone
}
pub fn new(
fn new(
mode: EditorMode,
buffer: Model<MultiBuffer>,
project: Option<Model<Project>>,
@@ -1745,8 +1735,6 @@ 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 {
@@ -1757,8 +1745,6 @@ impl Editor {
let mut this = Self {
focus_handle,
show_cursor_when_unfocused: false,
last_focused_descendant: None,
buffer: buffer.clone(),
display_map: display_map.clone(),
selections,
@@ -1785,7 +1771,6 @@ impl Editor {
show_line_numbers: None,
show_git_diff_gutter: None,
show_code_actions: None,
show_runnables: None,
show_wrap_guides: None,
show_indent_guides,
placeholder_text: None,
@@ -1819,7 +1804,6 @@ impl Editor {
use_modal_editing: mode == EditorMode::Full,
read_only: false,
use_autoclose: true,
use_auto_surround: true,
auto_replace_emoji_shortcode: false,
leader_peer_id: None,
remote_id: None,
@@ -1844,7 +1828,6 @@ 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,
@@ -1873,7 +1856,6 @@ 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);
@@ -2035,7 +2017,6 @@ impl Editor {
show_line_numbers: self.show_line_numbers,
show_git_diff_gutter: self.show_git_diff_gutter,
show_code_actions: self.show_code_actions,
show_runnables: self.show_runnables,
render_git_blame_gutter: self.render_git_blame_gutter(cx),
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
scroll_anchor: self.scroll_manager.anchor(),
@@ -2198,10 +2179,6 @@ impl Editor {
self.use_autoclose = autoclose;
}
pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
self.use_auto_surround = auto_surround;
}
pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
self.auto_replace_emoji_shortcode = auto_replace;
}
@@ -2515,7 +2492,6 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
if !self.focus_handle.is_focused(cx) {
self.last_focused_descendant = None;
cx.focus(&self.focus_handle);
}
@@ -2564,47 +2540,14 @@ impl Editor {
}
}
let point_to_delete: Option<usize> = {
let selected_points: Vec<Selection<Point>> =
self.selections.disjoint_in_range(start..end, cx);
if !add || click_count > 1 {
None
} else if selected_points.len() > 0 {
Some(selected_points[0].id)
} else {
let clicked_point_already_selected =
self.selections.disjoint.iter().find(|selection| {
selection.start.to_point(buffer) == start.to_point(buffer)
|| selection.end.to_point(buffer) == end.to_point(buffer)
});
if let Some(selection) = clicked_point_already_selected {
Some(selection.id)
} else {
None
}
}
};
let selections_count = self.selections.count();
self.change_selections(auto_scroll.then(|| Autoscroll::newest()), cx, |s| {
if let Some(point_to_delete) = point_to_delete {
s.delete(point_to_delete);
if selections_count == 1 {
s.set_pending_anchor_range(start..end, mode);
}
} else {
if !add {
s.clear_disjoint();
} else if click_count > 1 {
s.delete(newest_selection.id)
}
s.set_pending_anchor_range(start..end, mode);
if !add {
s.clear_disjoint();
} else if click_count > 1 {
s.delete(newest_selection.id)
}
s.set_pending_anchor_range(start..end, mode);
});
}
@@ -2616,7 +2559,6 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
if !self.focus_handle.is_focused(cx) {
self.last_focused_descendant = None;
cx.focus(&self.focus_handle);
}
@@ -2934,7 +2876,7 @@ impl Editor {
// `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
// and they are removing the character that triggered IME popup.
for (pair, enabled) in scope.brackets() {
if !pair.close && !pair.surround {
if !pair.close {
continue;
}
@@ -2952,10 +2894,9 @@ impl Editor {
}
if let Some(bracket_pair) = bracket_pair {
let snapshot_settings = snapshot.settings_at(selection.start, cx);
let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
let auto_surround =
self.use_auto_surround && snapshot_settings.use_auto_surround;
let autoclose = self.use_autoclose
&& snapshot.settings_at(selection.start, cx).use_autoclose;
if selection.is_empty() {
if is_bracket_pair_start {
let prefix_len = bracket_pair.start.len() - text.len();
@@ -2977,7 +2918,6 @@ impl Editor {
&bracket_pair.start[..prefix_len],
));
if autoclose
&& bracket_pair.close
&& following_text_allows_autoclose
&& preceding_text_matches_prefix
{
@@ -3029,8 +2969,7 @@ impl Editor {
}
// If an opening bracket is 1 character long and is typed while
// text is selected, then surround that text with the bracket pair.
else if auto_surround
&& bracket_pair.surround
else if autoclose
&& is_bracket_pair_start
&& bracket_pair.start.chars().count() == 1
{
@@ -6474,96 +6413,82 @@ impl Editor {
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
}
pub fn do_paste(
&mut self,
text: &String,
clipboard_selections: Option<Vec<ClipboardSelection>>,
handle_entire_lines: bool,
cx: &mut ViewContext<Self>,
) {
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
if self.read_only(cx) {
return;
}
let clipboard_text = Cow::Borrowed(text);
self.transact(cx, |this, 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);
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(..);
}
drop(snapshot);
buffer.edit(
edits,
Some(AutoindentMode::Block {
original_indent_columns,
}),
cx,
);
});
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
}
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 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);
}
}
});
}
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;
@@ -6772,20 +6697,6 @@ impl Editor {
})
}
pub fn select_page_up(&mut self, _: &SelectPageUp, cx: &mut ViewContext<Self>) {
let Some(row_count) = self.visible_row_count() else {
return;
};
let text_layout_details = &self.text_layout_details(cx);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_heads_with(|map, head, goal| {
movement::up_by_rows(map, head, row_count, goal, false, &text_layout_details)
})
})
}
pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
if self.take_rename(true, cx).is_some() {
return;
@@ -6796,7 +6707,9 @@ impl Editor {
return;
}
let Some(row_count) = self.visible_row_count() else {
let row_count = if let Some(row_count) = self.visible_line_count() {
row_count as u32 - 1
} else {
return;
};
@@ -6871,20 +6784,6 @@ impl Editor {
}
}
pub fn select_page_down(&mut self, _: &SelectPageDown, cx: &mut ViewContext<Self>) {
let Some(row_count) = self.visible_row_count() else {
return;
};
let text_layout_details = &self.text_layout_details(cx);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_heads_with(|map, head, goal| {
movement::down_by_rows(map, head, row_count, goal, false, &text_layout_details)
})
})
}
pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
if self.take_rename(true, cx).is_some() {
return;
@@ -6905,7 +6804,9 @@ impl Editor {
return;
}
let Some(row_count) = self.visible_row_count() else {
let row_count = if let Some(row_count) = self.visible_line_count() {
row_count as u32 - 1
} else {
return;
};
@@ -8226,58 +8127,6 @@ impl Editor {
});
}
pub fn select_enclosing_symbol(
&mut self,
_: &SelectEnclosingSymbol,
cx: &mut ViewContext<Self>,
) {
let buffer = self.buffer.read(cx).snapshot(cx);
let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
fn update_selection(
selection: &Selection<usize>,
buffer_snap: &MultiBufferSnapshot,
) -> Option<Selection<usize>> {
let cursor = selection.head();
let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
for symbol in symbols.iter().rev() {
let start = symbol.range.start.to_offset(&buffer_snap);
let end = symbol.range.end.to_offset(&buffer_snap);
let new_range = start..end;
if start < selection.start || end > selection.end {
return Some(Selection {
id: selection.id,
start: new_range.start,
end: new_range.end,
goal: SelectionGoal::None,
reversed: selection.reversed,
});
}
}
None
}
let mut selected_larger_symbol = false;
let new_selections = old_selections
.iter()
.map(|selection| match update_selection(selection, &buffer) {
Some(new_selection) => {
if new_selection.range() != selection.range() {
selected_larger_symbol = true;
}
new_selection
}
None => selection.clone(),
})
.collect::<Vec<_>>();
if selected_larger_symbol {
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(new_selections);
});
}
}
pub fn select_larger_syntax_node(
&mut self,
_: &SelectLargerSyntaxNode,
@@ -8340,10 +8189,6 @@ impl Editor {
}
fn refresh_runnables(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
if !EditorSettings::get_global(cx).gutter.runnables {
self.clear_tasks();
return Task::ready(());
}
let project = self.project.clone();
cx.spawn(|this, mut cx| async move {
let Ok(display_snapshot) = this.update(&mut cx, |this, cx| {
@@ -9105,7 +8950,7 @@ impl Editor {
});
language_server_name.map(|language_server_name| {
project.open_local_buffer_via_lsp(
lsp_location.uri.clone(),
lsp::Uri::from(lsp_location.uri.clone()),
server_id,
language_server_name,
cx,
@@ -9266,12 +9111,6 @@ impl Editor {
Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), true, cx)
});
editor.update(cx, |editor, cx| {
if let Some(first_range) = ranges_to_highlight.first() {
editor.change_selections(None, cx, |selections| {
selections.clear_disjoint();
selections.select_anchor_ranges(std::iter::once(first_range.clone()));
});
}
editor.highlight_background::<Self>(
&ranges_to_highlight,
|theme| theme.editor_highlighted_line_background,
@@ -9634,20 +9473,6 @@ 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();
}
@@ -10063,31 +9888,22 @@ impl Editor {
}
}
pub fn row_for_block(
&self,
block_id: BlockId,
pub fn insert_flaps(
&mut self,
flaps: impl IntoIterator<Item = Flap>,
cx: &mut ViewContext<Self>,
) -> Option<DisplayRow> {
) -> Vec<FlapId> {
self.display_map
.update(cx, |map, cx| map.row_for_block(block_id, cx))
.update(cx, |map, cx| map.insert_flaps(flaps, cx))
}
pub fn insert_creases(
pub fn remove_flaps(
&mut self,
creases: impl IntoIterator<Item = Crease>,
cx: &mut ViewContext<Self>,
) -> Vec<CreaseId> {
self.display_map
.update(cx, |map, cx| map.insert_creases(creases, cx))
}
pub fn remove_creases(
&mut self,
ids: impl IntoIterator<Item = CreaseId>,
ids: impl IntoIterator<Item = FlapId>,
cx: &mut ViewContext<Self>,
) {
self.display_map
.update(cx, |map, cx| map.remove_creases(ids, cx));
.update(cx, |map, cx| map.remove_flaps(ids, cx));
}
pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
@@ -10270,11 +10086,6 @@ impl Editor {
cx.notify();
}
pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut ViewContext<Self>) {
self.show_runnables = Some(show_runnables);
cx.notify();
}
pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut ViewContext<Self>) {
self.show_wrap_guides = Some(show_wrap_guides);
cx.notify();
@@ -10336,20 +10147,6 @@ 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 {
@@ -10657,10 +10454,6 @@ 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);
}
@@ -11015,11 +10808,6 @@ impl Editor {
&& self.focus_handle.is_focused(cx)
}
pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut ViewContext<Self>) {
self.show_cursor_when_unfocused = is_enabled;
cx.notify();
}
fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
cx.notify();
}
@@ -11139,7 +10927,6 @@ impl Editor {
}
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
self.tasks_update_task = Some(self.refresh_runnables(cx));
self.refresh_inline_completion(true, cx);
self.refresh_inlay_hints(
InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
@@ -11514,13 +11301,9 @@ impl Editor {
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(EditorEvent::Focused);
if let Some(descendant) = self
.last_focused_descendant
.take()
.and_then(|descendant| descendant.upgrade())
{
cx.focus(&descendant);
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);
} else {
if let Some(blame) = self.blame.as_ref() {
blame.update(cx, GitBlame::focus)
@@ -11542,12 +11325,6 @@ 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
@@ -11840,7 +11617,7 @@ impl EditorSnapshot {
.map(|(_, collaborator)| (collaborator.replica_id, collaborator))
.collect::<HashMap<_, _>>();
self.buffer_snapshot
.selections_in_range(range, false)
.remote_selections_in_range(range)
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
let collaborator = collaborators_by_replica_id.get(&replica_id)?;
let participant_index = participant_indices.get(&collaborator.user_id).copied();
@@ -11895,7 +11672,7 @@ impl EditorSnapshot {
let gutter_settings = EditorSettings::get_global(cx).gutter;
let show_line_numbers = self
.show_line_numbers
.unwrap_or(gutter_settings.line_numbers);
.unwrap_or_else(|| gutter_settings.line_numbers);
let line_gutter_width = if show_line_numbers {
// Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
let min_width_for_number_on_gutter = em_width * 4.0;
@@ -11906,16 +11683,14 @@ impl EditorSnapshot {
let show_code_actions = self
.show_code_actions
.unwrap_or(gutter_settings.code_actions);
let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
.unwrap_or_else(|| gutter_settings.code_actions);
let git_blame_entries_width = self
.render_git_blame_gutter
.then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
left_padding += if show_code_actions || show_runnables {
left_padding += if show_code_actions {
em_width * 3.0
} else if show_git_gutter && show_line_numbers {
em_width * 2.0
@@ -11953,8 +11728,8 @@ impl EditorSnapshot {
) -> Option<AnyElement> {
let folded = self.is_line_folded(buffer_row);
if let Some(crease) = self
.crease_snapshot
if let Some(flap) = self
.flap_snapshot
.query_row(buffer_row, &self.buffer_snapshot)
{
let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
@@ -11969,7 +11744,7 @@ impl EditorSnapshot {
}
});
Some((crease.render_toggle)(
Some((flap.render_toggle)(
buffer_row,
folded,
toggle_callback,
@@ -11995,16 +11770,16 @@ impl EditorSnapshot {
}
}
pub fn render_crease_trailer(
pub fn render_flap_trailer(
&self,
buffer_row: MultiBufferRow,
cx: &mut WindowContext,
) -> Option<AnyElement> {
let folded = self.is_line_folded(buffer_row);
let crease = self
.crease_snapshot
let flap = self
.flap_snapshot
.query_row(buffer_row, &self.buffer_snapshot)?;
Some((crease.render_trailer)(buffer_row, folded, cx))
Some((flap.render_trailer)(buffer_row, folded, cx))
}
}
@@ -12322,12 +12097,9 @@ impl ViewInputHandler for Editor {
// Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
let use_autoclose = this.use_autoclose;
let use_auto_surround = this.use_auto_surround;
this.set_use_autoclose(false);
this.set_use_auto_surround(false);
this.handle_input(text, cx);
this.set_use_autoclose(use_autoclose);
this.set_use_auto_surround(use_auto_surround);
if let Some(new_selected_range) = new_selected_range_utf16 {
let snapshot = this.buffer.read(cx).read(cx);

View File

@@ -15,7 +15,6 @@ pub struct EditorSettings {
pub toolbar: Toolbar,
pub scrollbar: Scrollbar,
pub gutter: Gutter,
pub scroll_beyond_last_line: ScrollBeyondLastLine,
pub vertical_scroll_margin: f32,
pub scroll_sensitivity: f32,
pub relative_line_numbers: bool,
@@ -68,7 +67,6 @@ 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)]
@@ -85,7 +83,6 @@ pub struct Scrollbar {
pub struct Gutter {
pub line_numbers: bool,
pub code_actions: bool,
pub runnables: bool,
pub folds: bool,
}
@@ -117,22 +114,6 @@ pub enum MultiCursorModifier {
CmdOrCtrl,
}
/// Whether the editor will scroll beyond the last line.
///
/// Default: one_page
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ScrollBeyondLastLine {
/// The editor will not scroll beyond the last line.
Off,
/// The editor will scroll beyond the last line by one page.
OnePage,
/// The editor will scroll beyond the last line by the same number of lines as vertical_scroll_margin.
VerticalScrollMargin,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct EditorSettingsContent {
/// Whether the cursor blinks in the editor.
@@ -148,7 +129,6 @@ 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.
///
@@ -175,10 +155,6 @@ pub struct EditorSettingsContent {
pub scrollbar: Option<ScrollbarContent>,
/// Gutter related settings
pub gutter: Option<GutterContent>,
/// Whether the editor will scroll beyond the last line.
///
/// Default: one_page
pub scroll_beyond_last_line: Option<ScrollBeyondLastLine>,
/// The number of lines to keep above/below the cursor when auto-scrolling.
///
/// Default: 3.
@@ -226,15 +202,10 @@ pub struct ToolbarContent {
///
/// Default: true
pub breadcrumbs: Option<bool>,
/// Whether to display quick action buttons in the editor toolbar.
/// Whether to display quik 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
@@ -277,10 +248,6 @@ pub struct GutterContent {
///
/// Default: true
pub code_actions: Option<bool>,
/// Whether to show runnable buttons in the gutter.
///
/// Default: true
pub runnables: Option<bool>,
/// Whether to show fold buttons in the gutter.
///
/// Default: true

View File

@@ -436,57 +436,6 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
);
}
#[gpui::test]
fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let editor = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
build_editor(buffer, cx)
});
_ = editor.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
});
_ = editor.update(cx, |view, cx| {
view.end_selection(cx);
});
_ = editor.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
});
_ = editor.update(cx, |view, cx| {
view.end_selection(cx);
});
assert_eq!(
editor
.update(cx, |view, cx| view.selections.display_ranges(cx))
.unwrap(),
[
DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
]
);
_ = editor.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
});
_ = editor.update(cx, |view, cx| {
view.end_selection(cx);
});
assert_eq!(
editor
.update(cx, |view, cx| view.selections.display_ranges(cx))
.unwrap(),
[DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
);
}
#[gpui::test]
fn test_canceling_pending_selection(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -909,175 +858,6 @@ fn test_fold_action(cx: &mut TestAppContext) {
});
}
#[gpui::test]
fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let view = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(
&"
class Foo:
# Hello!
def a():
print(1)
def b():
print(2)
def c():
print(3)
"
.unindent(),
cx,
);
build_editor(buffer.clone(), cx)
});
_ = view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
]);
});
view.fold(&Fold, cx);
assert_eq!(
view.display_text(cx),
"
class Foo:
# Hello!
def a():
print(1)
def b():⋯
def c():⋯
"
.unindent(),
);
view.fold(&Fold, cx);
assert_eq!(
view.display_text(cx),
"
class Foo:⋯
"
.unindent(),
);
view.unfold_lines(&UnfoldLines, cx);
assert_eq!(
view.display_text(cx),
"
class Foo:
# Hello!
def a():
print(1)
def b():⋯
def c():⋯
"
.unindent(),
);
view.unfold_lines(&UnfoldLines, cx);
assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
});
}
#[gpui::test]
fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let view = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(
&"
class Foo:
# Hello!
def a():
print(1)
def b():
print(2)
def c():
print(3)
"
.unindent(),
cx,
);
build_editor(buffer.clone(), cx)
});
_ = view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
]);
});
view.fold(&Fold, cx);
assert_eq!(
view.display_text(cx),
"
class Foo:
# Hello!
def a():
print(1)
def b():⋯
def c():⋯
"
.unindent(),
);
view.fold(&Fold, cx);
assert_eq!(
view.display_text(cx),
"
class Foo:⋯
"
.unindent(),
);
view.unfold_lines(&UnfoldLines, cx);
assert_eq!(
view.display_text(cx),
"
class Foo:
# Hello!
def a():
print(1)
def b():⋯
def c():⋯
"
.unindent(),
);
view.unfold_lines(&UnfoldLines, cx);
assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
});
}
#[gpui::test]
fn test_move_cursor(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -4855,14 +4635,12 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: false,
surround: false,
newline: true,
},
],
@@ -4906,7 +4684,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
@@ -4919,44 +4697,32 @@ async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/*".to_string(),
end: " */".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "\"".to_string(),
end: "\"".to_string(),
close: true,
surround: true,
newline: false,
},
BracketPair {
start: "<".to_string(),
end: ">".to_string(),
close: false,
surround: true,
newline: true,
},
],
..Default::default()
},
@@ -5084,16 +4850,6 @@ async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
cx.assert_editor_state("a\"ˇ\"");
cx.update_editor(|view, cx| view.handle_input("\"", cx));
cx.assert_editor_state("a\"\"ˇ");
// Don't autoclose pair if autoclose is disabled
cx.set_state("ˇ");
cx.update_editor(|view, cx| view.handle_input("<", cx));
cx.assert_editor_state("");
// Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
cx.set_state("«aˇ» b");
cx.update_editor(|view, cx| view.handle_input("<", cx));
cx.assert_editor_state("<«aˇ»> b");
}
#[gpui::test]
@@ -5112,21 +4868,18 @@ async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestA
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: false,
newline: true,
},
],
@@ -5540,14 +5293,12 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/* ".to_string(),
end: "*/".to_string(),
close: true,
surround: true,
..Default::default()
},
],
@@ -5696,7 +5447,6 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
}],
..Default::default()
@@ -5808,21 +5558,18 @@ async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppC
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: true,
newline: true,
},
],
@@ -6114,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::Url::from_file_path("/file.rs").unwrap()
lsp::Uri::from_file_path("/file.rs").unwrap().into()
);
assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new(
@@ -6140,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::Url::from_file_path("/file.rs").unwrap()
lsp::Uri::from_file_path("/file.rs").unwrap().into()
);
futures::future::pending::<()>().await;
unreachable!()
@@ -6189,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::Url::from_file_path("/file.rs").unwrap()
lsp::Uri::from_file_path("/file.rs").unwrap().into()
);
assert_eq!(params.options.tab_size, 8);
Ok(Some(vec![]))
@@ -6392,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),
format!("[{} formatted]", params.text_document.uri.as_str()),
)]))
})
.detach();
@@ -6466,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::Url::from_file_path("/file.rs").unwrap()
lsp::Uri::from_file_path("/file.rs").unwrap().into()
);
assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new(
@@ -6492,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::Url::from_file_path("/file.rs").unwrap()
lsp::Uri::from_file_path("/file.rs").unwrap().into()
);
futures::future::pending::<()>().await;
unreachable!()
@@ -6542,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::Url::from_file_path("/file.rs").unwrap()
lsp::Uri::from_file_path("/file.rs").unwrap().into()
);
assert_eq!(params.options.tab_size, 8);
Ok(Some(vec![]))
@@ -6616,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::Url::from_file_path("/file.rs").unwrap()
lsp::Uri::from_file_path("/file.rs").unwrap().into()
);
assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new(
@@ -6638,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::Url::from_file_path("/file.rs").unwrap()
lsp::Uri::from_file_path("/file.rs").unwrap().into()
);
futures::future::pending::<()>().await;
unreachable!()
@@ -7790,14 +7537,12 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/* ".to_string(),
end: " */".to_string(),
close: true,
surround: true,
newline: true,
},
],
@@ -8283,7 +8028,7 @@ async fn go_to_prev_overlapping_diagnostic(
.update_diagnostics(
LanguageServerId(0),
lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path("/root/file").unwrap(),
uri: lsp::Uri::from_file_path("/root/file").unwrap().into(),
version: None,
diagnostics: vec![
lsp::Diagnostic {
@@ -8599,7 +8344,6 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
}],
disabled_scopes_by_bracket_ix: Vec::new(),
@@ -8656,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::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
assert_eq!(
params.text_document_position.position,
@@ -11859,7 +11603,6 @@ fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -
settings: IndentGuideSettings {
enabled: true,
line_width: 1,
active_line_width: 1,
..Default::default()
},
}
@@ -12306,7 +12049,7 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppCont
}
#[gpui::test]
fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let editor = cx.add_window(|cx| {
@@ -12327,7 +12070,7 @@ fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
}
let crease = Crease::new(
let flap = Flap::new(
range,
FoldPlaceholder::test(),
{
@@ -12344,7 +12087,7 @@ fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
|_row, _folded, _cx| div(),
);
editor.insert_creases(Some(crease), cx);
editor.insert_flaps(Some(flap), cx);
let snapshot = editor.snapshot(cx);
let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
snapshot
@@ -12408,7 +12151,10 @@ 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());
assert_eq!(
params.text_document_position.text_document.uri,
url.clone().into()
);
assert_eq!(
params.text_document_position.position,
complete_from_position

View File

@@ -1,4 +1,3 @@
use crate::editor_settings::ScrollBeyondLastLine;
use crate::{
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
display_map::{
@@ -168,8 +167,6 @@ impl EditorElement {
register_action(view, cx, Editor::move_up);
register_action(view, cx, Editor::move_up_by_lines);
register_action(view, cx, Editor::select_up_by_lines);
register_action(view, cx, Editor::select_page_down);
register_action(view, cx, Editor::select_page_up);
register_action(view, cx, Editor::cancel);
register_action(view, cx, Editor::newline);
register_action(view, cx, Editor::newline_above);
@@ -276,7 +273,6 @@ impl EditorElement {
register_action(view, cx, Editor::toggle_comments);
register_action(view, cx, Editor::select_larger_syntax_node);
register_action(view, cx, Editor::select_smaller_syntax_node);
register_action(view, cx, Editor::select_enclosing_symbol);
register_action(view, cx, Editor::move_to_enclosing_bracket);
register_action(view, cx, Editor::undo_selection);
register_action(view, cx, Editor::redo_selection);
@@ -345,7 +341,6 @@ 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) {
@@ -860,28 +855,6 @@ impl EditorElement {
}
selections.extend(remote_selections.into_values());
} else if !editor.is_focused(cx) && editor.show_cursor_when_unfocused {
let player = if editor.read_only(cx) {
cx.theme().players().read_only()
} else {
self.style.local_player
};
let layouts = snapshot
.buffer_snapshot
.selections_in_range(&(start_anchor..end_anchor), true)
.map(move |(_, line_mode, cursor_shape, selection)| {
SelectionLayout::new(
selection,
line_mode,
cursor_shape,
&snapshot.display_snapshot,
false,
false,
None,
)
})
.collect::<Vec<_>>();
selections.push((player, layouts));
}
(selections, active_rows, newest_selection_head)
}
@@ -1113,17 +1086,11 @@ impl EditorElement {
point(bounds.lower_right().x, bounds.lower_left().y),
);
let settings = EditorSettings::get_global(cx);
let scroll_beyond_last_line: f32 = match settings.scroll_beyond_last_line {
ScrollBeyondLastLine::OnePage => rows_per_page,
ScrollBeyondLastLine::Off => 1.0,
ScrollBeyondLastLine::VerticalScrollMargin => 1.0 + settings.vertical_scroll_margin,
};
let total_rows = snapshot.max_point().row().as_f32() + scroll_beyond_last_line;
let height = bounds.size.height;
let total_rows = snapshot.max_point().row().as_f32() + rows_per_page;
let px_per_row = height / total_rows;
let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.0);
let row_height = (height - thumb_height) / snapshot.max_point().row().as_f32();
Some(ScrollbarLayout {
hitbox: cx.insert_hitbox(track_bounds, false),
@@ -1169,7 +1136,7 @@ impl EditorElement {
}
#[allow(clippy::too_many_arguments)]
fn prepaint_crease_trailers(
fn prepaint_flap_trailers(
&self,
trailers: Vec<Option<AnyElement>>,
lines: &[LineWithInvisibles],
@@ -1178,7 +1145,7 @@ impl EditorElement {
scroll_pixel_position: gpui::Point<Pixels>,
em_width: Pixels,
cx: &mut WindowContext,
) -> Vec<Option<CreaseTrailerLayout>> {
) -> Vec<Option<FlapTrailerLayout>> {
trailers
.into_iter()
.enumerate()
@@ -1203,7 +1170,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(CreaseTrailerLayout {
Some(FlapTrailerLayout {
element,
bounds: Bounds::new(origin, size),
})
@@ -1299,7 +1266,7 @@ impl EditorElement {
display_row: DisplayRow,
display_snapshot: &DisplaySnapshot,
line_layout: &LineWithInvisibles,
crease_trailer: Option<&CreaseTrailerLayout>,
flap_trailer: Option<&FlapTrailerLayout>,
em_width: Pixels,
content_origin: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<Pixels>,
@@ -1339,8 +1306,8 @@ impl EditorElement {
let start_x = {
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
let line_end = if let Some(crease_trailer) = crease_trailer {
crease_trailer.bounds.right()
let line_end = if let Some(flap_trailer) = flap_trailer {
flap_trailer.bounds.right()
} else {
content_origin.x - scroll_pixel_position.x + line_layout.width
};
@@ -1812,7 +1779,7 @@ impl EditorElement {
}
}
fn layout_crease_trailers(
fn layout_flap_trailers(
&self,
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
snapshot: &EditorSnapshot,
@@ -1822,7 +1789,7 @@ impl EditorElement {
.into_iter()
.map(|row| {
if let Some(multibuffer_row) = row {
snapshot.render_crease_trailer(multibuffer_row, cx)
snapshot.render_flap_trailer(multibuffer_row, cx)
} else {
None
}
@@ -2814,12 +2781,7 @@ impl EditorElement {
)),
};
let requested_line_width = if indent_guide.active {
settings.active_line_width
} else {
settings.line_width
}
.clamp(1, 10);
let requested_line_width = settings.line_width.clamp(1, 10);
let mut line_indicator_width = 0.;
if let Some(color) = line_color {
cx.paint_quad(fill(
@@ -2848,12 +2810,38 @@ impl EditorElement {
}
}
fn paint_line_numbers(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
fn paint_gutter(&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 {
@@ -2868,9 +2856,29 @@ 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(layout: &EditorLayout, cx: &mut WindowContext) {
fn paint_diff_hunks(
gutter_bounds: Bounds<Pixels>,
layout: &EditorLayout,
cx: &mut WindowContext,
) {
if layout.display_hunks.is_empty() {
return;
}
@@ -2883,7 +2891,7 @@ impl EditorElement {
let hunk_bounds = Self::diff_hunk_bounds(
&layout.position_map.snapshot,
line_height,
layout.gutter_hitbox.bounds,
gutter_bounds,
&hunk,
);
Some((
@@ -3000,45 +3008,7 @@ 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| {
@@ -3105,8 +3075,8 @@ impl EditorElement {
self.paint_redactions(layout, cx);
self.paint_cursors(layout, cx);
self.paint_inline_blame(layout, cx);
cx.with_element_namespace("crease_trailers", |cx| {
for trailer in layout.crease_trailers.iter_mut().flatten() {
cx.with_element_namespace("flap_trailers", |cx| {
for trailer in layout.flap_trailers.iter_mut().flatten() {
trailer.element.paint(cx);
}
});
@@ -3654,12 +3624,12 @@ impl EditorElement {
let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
if forbid_vertical_scroll {
scroll_position.y = current_scroll_position.y;
if scroll_position == current_scroll_position {
return;
}
}
if scroll_position != current_scroll_position {
editor.scroll(scroll_position, axis, cx);
cx.stop_propagation();
}
editor.scroll(scroll_position, axis, cx);
cx.stop_propagation();
});
}
}
@@ -4644,29 +4614,13 @@ impl Element for EditorElement {
let content_origin =
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
let height_in_lines = bounds.size.height / line_height;
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
(snapshot.max_point().row().as_f32() - height_in_lines + 1.).max(0.)
} else {
let settings = EditorSettings::get_global(cx);
let max_row = snapshot.max_point().row().as_f32();
match settings.scroll_beyond_last_line {
ScrollBeyondLastLine::OnePage => max_row,
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.0).max(0.0),
ScrollBeyondLastLine::VerticalScrollMargin => {
(max_row - height_in_lines + 1.0 + settings.vertical_scroll_margin)
.max(0.0)
}
}
};
let mut autoscroll_containing_element = false;
let mut autoscroll_horizontally = false;
self.editor.update(cx, |editor, cx| {
autoscroll_containing_element =
editor.autoscroll_requested() || editor.has_pending_selection();
autoscroll_horizontally =
editor.autoscroll_vertically(bounds, line_height, max_scroll_top, cx);
editor.autoscroll_vertically(bounds, line_height, cx);
snapshot = editor.snapshot(cx);
});
@@ -4674,6 +4628,7 @@ impl Element for EditorElement {
// The scroll position is a fractional point, the whole number of which represents
// the top of the window in terms of display rows.
let start_row = DisplayRow(scroll_position.y as u32);
let height_in_lines = bounds.size.height / line_height;
let max_row = snapshot.max_point().row();
let end_row = cmp::min(
(scroll_position.y + height_in_lines).ceil() as u32,
@@ -4750,8 +4705,8 @@ impl Element for EditorElement {
cx,
)
});
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
self.layout_flap_trailers(buffer_rows.iter().copied(), &snapshot, cx)
});
let display_hunks = self.layout_git_gutters(
@@ -4812,9 +4767,9 @@ impl Element for EditorElement {
cx,
);
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
self.prepaint_crease_trailers(
crease_trailers,
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
self.prepaint_flap_trailers(
flap_trailers,
&line_layouts,
line_height,
content_origin,
@@ -4830,12 +4785,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 crease_trailer_layout = crease_trailers[line_ix].as_ref();
let flap_trailer_layout = flap_trailers[line_ix].as_ref();
inline_blame = self.layout_inline_blame(
display_row,
&snapshot.display_snapshot,
line_layout,
crease_trailer_layout,
flap_trailer_layout,
em_width,
content_origin,
scroll_pixel_position,
@@ -4857,7 +4812,7 @@ impl Element for EditorElement {
let scroll_max = point(
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
max_scroll_top,
max_row.as_f32(),
);
self.editor.update(cx, |editor, cx| {
@@ -4981,18 +4936,14 @@ impl Element for EditorElement {
}
}
let test_indicators = if gutter_settings.runnables {
self.layout_run_indicators(
line_height,
scroll_pixel_position,
&gutter_dimensions,
&gutter_hitbox,
&snapshot,
cx,
)
} else {
vec![]
};
let test_indicators = self.layout_run_indicators(
line_height,
scroll_pixel_position,
&gutter_dimensions,
&gutter_hitbox,
&snapshot,
cx,
);
if !cx.has_active_drag() {
self.layout_hover_popovers(
@@ -5094,7 +5045,7 @@ impl Element for EditorElement {
test_indicators,
code_actions_indicator,
gutter_fold_toggles,
crease_trailers,
flap_trailers,
tab_invisible,
space_invisible,
}
@@ -5161,10 +5112,8 @@ 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_blamed_display_rows(layout, cx);
self.paint_line_numbers(layout, cx);
self.paint_gutter(layout, cx)
}
self.paint_text(layout, cx);
@@ -5175,11 +5124,6 @@ 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);
});
@@ -5225,7 +5169,7 @@ pub struct EditorLayout {
code_actions_indicator: Option<AnyElement>,
test_indicators: Vec<AnyElement>,
gutter_fold_toggles: Vec<Option<AnyElement>>,
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
flap_trailers: Vec<Option<FlapTrailerLayout>>,
mouse_context_menu: Option<AnyElement>,
tab_invisible: ShapedLine,
space_invisible: ShapedLine,
@@ -5349,7 +5293,7 @@ impl ScrollbarLayout {
}
}
struct CreaseTrailerLayout {
struct FlapTrailerLayout {
element: AnyElement,
bounds: Bounds<Pixels>,
}

View File

@@ -55,14 +55,12 @@ mod tests {
start: "{".to_string(),
end: "}".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: false,
surround: false,
newline: true,
},
],

View File

@@ -741,7 +741,7 @@ mod tests {
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url.clone(),
target_uri: url.clone().into(),
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(),
target_uri: url.clone().into(),
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(),
target_uri: url.clone().into(),
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,
target_uri: url.into(),
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,
target_uri: url.into(),
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,
target_uri: url.into(),
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();
let expected_uri = expected_uri.clone().into();
async move {
assert_eq!(params.text_document.uri, expected_uri);
Ok(Some(vec![lsp::InlayHint {

View File

@@ -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();
let task_uri = closure_uri.clone().into();
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(),
uri: task_uri.clone().into(),
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,
uri: task_uri.into(),
range: struct_target_range,
}),
tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(

View File

@@ -165,16 +165,10 @@ pub fn indent_guides_in_range(
.indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx)
.into_iter()
.filter(|indent_guide| {
let start =
MultiBufferRow(indent_guide.multibuffer_row_range.start.0.saturating_sub(1));
// Filter out indent guides that are inside a fold
let is_folded = snapshot.is_line_folded(start);
let line_indent = snapshot.line_indent_for_buffer_row(start);
let contained_in_fold =
line_indent.len(indent_guide.tab_size) <= indent_guide.indent_level();
!(is_folded && contained_in_fold)
!snapshot.is_line_folded(MultiBufferRow(
indent_guide.multibuffer_row_range.start.0.saturating_sub(1),
))
})
.collect()
}

Some files were not shown because too many files have changed in this diff Show More