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
304 changed files with 4628 additions and 12764 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

1
.gitignore vendored
View File

@@ -28,4 +28,3 @@ DerivedData/
.vscode
.wrangler
.flatpak-builder
.aider*

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"]
}
]

1144
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,6 @@ members = [
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/miner",
"crates/multi_buffer",
"crates/node_runtime",
"crates/notifications",
@@ -66,7 +65,6 @@ members = [
"crates/open_ai",
"crates/outline",
"crates/outline_panel",
"crates/paths",
"crates/picker",
"crates/prettier",
"crates/project",
@@ -79,7 +77,6 @@ members = [
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/dev_server_projects",
"crates/repl",
"crates/rich_text",
"crates/rope",
"crates/rpc",
@@ -137,7 +134,6 @@ members = [
"extensions/prisma",
"extensions/purescript",
"extensions/ruby",
"extensions/snippets",
"extensions/svelte",
"extensions/terraform",
"extensions/toml",
@@ -218,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" }
@@ -232,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" }
@@ -241,7 +235,6 @@ task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" }
miner = { path = "crates/miner" }
semantic_version = { path = "crates/semantic_version" }
settings = { path = "crates/settings" }
snippet = { path = "crates/snippet" }
@@ -271,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"] }
@@ -293,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"
@@ -304,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.23"
indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "1"
# We explicitly disable http2 support in isahc.
@@ -340,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"
@@ -356,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"
@@ -461,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 }
@@ -485,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",
@@ -282,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

@@ -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

@@ -27,13 +27,14 @@ use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use slash_command::{
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
project_command, prompt_command, rustdoc_command, search_command, tabs_command, term_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,
@@ -186,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);
}
_ => {}
@@ -273,7 +271,7 @@ pub fn init(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,
)
@@ -317,7 +315,6 @@ 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);

View File

@@ -15,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::{
@@ -58,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::{
@@ -300,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();
@@ -580,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();
@@ -622,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)
}))
@@ -1014,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,
@@ -1056,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),
@@ -1093,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)
@@ -1112,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(),
}
}
@@ -1179,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,
@@ -1490,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),
@@ -2044,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
@@ -2058,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))?;
@@ -2201,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>,
}
@@ -2253,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));
@@ -2266,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,
@@ -2276,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
}
@@ -2539,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({
@@ -2591,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)
@@ -2620,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),
);
})
}
@@ -2703,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)
@@ -2713,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,
@@ -2749,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);
@@ -3251,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(
@@ -3269,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,
@@ -3320,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()
@@ -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,7 +14,6 @@ use std::{
Arc,
},
};
use ui::ActiveTheme;
use workspace::Workspace;
pub mod active_command;
@@ -28,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>,
@@ -349,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,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

@@ -4503,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

@@ -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

@@ -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,
@@ -281,8 +279,6 @@ gpui::actions!(
SelectToPreviousWordStart,
SelectToStartOfParagraph,
SelectUp,
SelectPageDown,
SelectPageUp,
ShowCharacterPalette,
ShowInlineCompletion,
ShuffleLines,
@@ -293,7 +289,6 @@ gpui::actions!(
TabPrev,
ToggleGitBlame,
ToggleGitBlameInline,
ToggleSelectionMenu,
ToggleHunkDiff,
ToggleInlayHints,
ToggleLineNumbers,
@@ -308,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;
};
@@ -8288,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| {
@@ -9053,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,
@@ -9214,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,
@@ -9582,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();
}
@@ -10011,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 {
@@ -10218,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();
@@ -10284,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 {
@@ -10605,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);
}
@@ -10963,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();
}
@@ -11087,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(
@@ -11462,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)
@@ -11490,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
@@ -11788,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();
@@ -11843,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;
@@ -11854,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
@@ -11901,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| {
@@ -11917,7 +11744,7 @@ impl EditorSnapshot {
}
});
Some((crease.render_toggle)(
Some((flap.render_toggle)(
buffer_row,
folded,
toggle_callback,
@@ -11943,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))
}
}
@@ -12270,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);
@@ -344,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) {
@@ -859,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)
}
@@ -1112,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),
@@ -1168,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],
@@ -1177,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()
@@ -1202,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),
})
@@ -1298,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>,
@@ -1338,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
};
@@ -1811,7 +1779,7 @@ impl EditorElement {
}
}
fn layout_crease_trailers(
fn layout_flap_trailers(
&self,
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
snapshot: &EditorSnapshot,
@@ -1821,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
}
@@ -2813,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(
@@ -2847,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 {
@@ -2867,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;
}
@@ -2882,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((
@@ -2999,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| {
@@ -3104,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);
}
});
@@ -3653,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();
});
}
}
@@ -4643,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);
});
@@ -4673,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,
@@ -4749,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(
@@ -4811,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,
@@ -4829,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,
@@ -4856,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| {
@@ -4980,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(
@@ -5093,7 +5045,7 @@ impl Element for EditorElement {
test_indicators,
code_actions_indicator,
gutter_fold_toggles,
crease_trailers,
flap_trailers,
tab_invisible,
space_invisible,
}
@@ -5160,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);
@@ -5174,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);
});
@@ -5224,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,
@@ -5348,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

@@ -1307,7 +1307,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(file_with_hints).unwrap(),
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
);
let current_call_id =
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
@@ -1439,7 +1439,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(file_with_hints).unwrap(),
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
);
let current_call_id =
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
@@ -1613,7 +1613,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
Ok(Some(vec![lsp::InlayHint {
@@ -1666,7 +1666,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/other.md").unwrap(),
lsp::Uri::from_file_path("/a/other.md").unwrap().into(),
);
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
Ok(Some(vec![lsp::InlayHint {
@@ -1790,7 +1790,7 @@ pub mod tests {
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(file_with_hints).unwrap(),
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
);
Ok(Some(vec![
lsp::InlayHint {
@@ -2136,7 +2136,7 @@ pub mod tests {
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(file_with_hints).unwrap(),
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
);
Ok(Some(vec![lsp::InlayHint {
position: lsp::Position::new(0, i),
@@ -2305,7 +2305,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
);
task_lsp_request_ranges.lock().push(params.range);
@@ -2673,11 +2673,11 @@ pub mod tests {
let task_editor_edited = Arc::clone(&closure_editor_edited);
async move {
let hint_text = if params.text_document.uri
== lsp::Url::from_file_path("/a/main.rs").unwrap()
== lsp::Uri::from_file_path("/a/main.rs").unwrap().into()
{
"main hint"
} else if params.text_document.uri
== lsp::Url::from_file_path("/a/other.rs").unwrap()
== lsp::Uri::from_file_path("/a/other.rs").unwrap().into()
{
"other hint"
} else {
@@ -2981,11 +2981,11 @@ pub mod tests {
let task_editor_edited = Arc::clone(&closure_editor_edited);
async move {
let hint_text = if params.text_document.uri
== lsp::Url::from_file_path("/a/main.rs").unwrap()
== lsp::Uri::from_file_path("/a/main.rs").unwrap().into()
{
"main hint"
} else if params.text_document.uri
== lsp::Url::from_file_path("/a/other.rs").unwrap()
== lsp::Uri::from_file_path("/a/other.rs").unwrap().into()
{
"other hint"
} else {
@@ -3177,7 +3177,7 @@ pub mod tests {
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 query_start = params.range.start;
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
@@ -3244,7 +3244,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(file_with_hints).unwrap(),
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
);
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;

View File

@@ -816,24 +816,22 @@ impl Item for Editor {
let buffer = multibuffer.buffer(buffer_id)?;
let buffer = buffer.read(cx);
let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
buffer
.snapshot()
.resolve_file_path(
cx,
self.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default(),
)
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string())
});
let filename = buffer
.snapshot()
.resolve_file_path(
cx,
self.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default(),
)
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string());
let settings = ThemeSettings::get_global(cx);
let mut breadcrumbs = vec![BreadcrumbText {
text,
text: filename,
highlights: None,
font: Some(settings.buffer_font.clone()),
}];
@@ -1201,22 +1199,20 @@ impl SearchableItem for Editor {
for (excerpt_id, search_buffer, search_range) in
buffer.excerpts_in_ranges(search_within_ranges)
{
if !search_range.is_empty() {
ranges.extend(
query
.search(&search_buffer, Some(search_range.clone()))
.await
.into_iter()
.map(|match_range| {
let start = search_buffer
.anchor_after(search_range.start + match_range.start);
let end = search_buffer
.anchor_before(search_range.start + match_range.end);
buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
}),
);
}
ranges.extend(
query
.search(&search_buffer, Some(search_range.clone()))
.await
.into_iter()
.map(|match_range| {
let start = search_buffer
.anchor_after(search_range.start + match_range.start);
let end = search_buffer
.anchor_before(search_range.start + match_range.end);
buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
}),
);
}
};

View File

@@ -1,7 +1,7 @@
use crate::{
Copy, CopyPermalinkToLine, Cut, DisplayPoint, Editor, EditorMode, FindAllReferences,
GoToDefinition, GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFinder,
SelectMode, ToggleCodeActions,
Copy, Cut, DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition,
GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFinder, SelectMode,
ToggleCodeActions,
};
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
use workspace::OpenInTerminal;
@@ -91,8 +91,7 @@ pub fn deploy_context_menu(
.action("Paste", Box::new(Paste))
.separator()
.action("Reveal in Finder", Box::new(RevealInFinder))
.action("Open in Terminal", Box::new(OpenInTerminal))
.action("Copy Permalink", Box::new(CopyPermalinkToLine));
.action("Open in Terminal", Box::new(OpenInTerminal));
match focus {
Some(focus) => builder.context(focus),
None => builder,

View File

@@ -2,7 +2,6 @@ mod actions;
pub(crate) mod autoscroll;
pub(crate) mod scroll_amount;
use crate::editor_settings::ScrollBeyondLastLine;
use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint},
hover_popover::hide_hover,
@@ -200,20 +199,8 @@ impl ScrollManager {
0,
)
} else {
let scroll_top = scroll_position.y;
let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
ScrollBeyondLastLine::OnePage => scroll_top,
ScrollBeyondLastLine::Off => scroll_top
.min((map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap() + 1.0),
ScrollBeyondLastLine::VerticalScrollMargin => scroll_top.min(
(map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap()
+ 1.0
+ self.vertical_scroll_margin,
),
};
let scroll_top_buffer_point =
DisplayPoint::new(DisplayRow(scroll_top as u32), 0).to_point(&map);
DisplayPoint::new(DisplayRow(scroll_position.y as u32), 0).to_point(&map);
let top_anchor = map
.buffer_snapshot
.anchor_at(scroll_top_buffer_point, Bias::Right);
@@ -223,7 +210,7 @@ impl ScrollManager {
anchor: top_anchor,
offset: point(
scroll_position.x.max(0.),
scroll_top - top_anchor.to_display_point(&map).row().as_f32(),
scroll_position.y - top_anchor.to_display_point(&map).row().as_f32(),
),
},
scroll_top_buffer_point.row,
@@ -343,11 +330,6 @@ impl Editor {
self.scroll_manager.visible_line_count
}
pub fn visible_row_count(&self) -> Option<u32> {
self.visible_line_count()
.map(|line_count| line_count as u32 - 1)
}
pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
let opened_first_time = self.scroll_manager.visible_line_count.is_none();
self.scroll_manager.visible_line_count = Some(lines);

View File

@@ -69,7 +69,6 @@ impl Editor {
&mut self,
bounds: Bounds<Pixels>,
line_height: Pixels,
max_scroll_top: f32,
cx: &mut ViewContext<Editor>,
) -> bool {
let viewport_height = bounds.size.height;
@@ -85,6 +84,11 @@ impl Editor {
}
}
}
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
(display_map.max_point().row().as_f32() - visible_lines + 1.).max(0.)
} else {
display_map.max_point().row().as_f32()
};
if scroll_position.y > max_scroll_top {
scroll_position.y = max_scroll_top;
}

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