Compare commits

..

1 Commits

Author SHA1 Message Date
cameron
0b95099056 move remote diff base loading from Repository to GitStore
Co-authored-by: Cole Miller <cole@zed.dev>
2025-11-25 22:18:11 +00:00
262 changed files with 4325 additions and 12021 deletions

70
.github/ISSUE_TEMPLATE/1.bug-report.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: Report an issue
description: Report an issue with Zed.
type: Bug
body:
- type: markdown
attributes:
value: |
Feature requests should be opened in [discussions](https://github.com/zed-industries/zed/discussions/new/choose).
Before opening a new issue, please do a [search](https://github.com/zed-industries/zed/issues) of existing issues and :+1: upvote the existing issue instead. This will help us maintain a proper signal-to-noise ratio.
If you need help with your own project, you can ask a question in our [Discord Support Forums](https://discord.com/invite/zedindustries).
- type: textarea
attributes:
label: Reproduction steps
description: A step-by-step description of how to reproduce the issue from a **clean Zed install**. Any code must be sufficient to reproduce (make sure to include context!). Include code as text, not just as a screenshot. **Issues with insufficient detail may be summarily closed**.
placeholder: |
1. Start Zed
2. Click X
3. Y will happen
validations:
required: true
- type: textarea
attributes:
label: Current vs. Expected behavior
description: |
A clear and concise description of what is the current behavior (screenshots, videos), vs. what you expected the behavior to be.
**Skipping this/failure to provide complete information will result in the issue being closed.**
placeholder: "Based on my reproduction steps above, when I click X, I expect this to happen, but instead Y happens."
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your Zed log file to this issue.
description: |
Open the command palette in Zed, then type `zed: open log` to see the last 1000 lines. Or type `zed: reveal log in file manager` in the command palette to reveal the log file itself.
value: |
<details><summary>Zed.log</summary>
<!-- Paste your log inside the code block. -->
```log
```
</details>
validations:
required: false
- type: textarea
attributes:
label: If applicable, provide details about your model provider
placeholder: |
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc.)
- Model Name: (Claude Sonnet 4.5, Gemini 3 Pro, GPT-5)
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
- Other details (ACPs, MCPs, other settings, etc.):
validations:
required: false
- type: textarea
attributes:
label: Zed version and system specs
description: |
Open the command palette in Zed, then type “zed: copy system specs into clipboard”. **Skipping this/failure to provide complete information will result in the issue being closed**.
placeholder: |
Zed: v0.215.0 (Zed Nightly bfe141ea79aa4984028934067ba75c48d99136ae)
OS: macOS 15.1
Memory: 36 GiB
Architecture: aarch64
validations:
required: true

View File

@@ -1,99 +0,0 @@
name: Report a bug
description: Report a problem with Zed.
type: Bug
labels: "state:needs triage"
body:
- type: markdown
attributes:
value: |
Is this bug already reported? Upvote to get it noticed faster. [Here's the search](https://github.com/zed-industries/zed/issues). Upvote means giving it a :+1: reaction.
Feature request? Please open in [discussions](https://github.com/zed-industries/zed/discussions/new/choose) instead.
Just have a question or need support? Welcome to [Discord Support Forums](https://discord.com/invite/zedindustries).
- type: textarea
attributes:
label: Reproduction steps
description: A step-by-step description of how to reproduce the bug from a **clean Zed install**. The more context you provide, the easier it is to find and fix the problem fast.
placeholder: |
1. Start Zed
2. Click X
validations:
required: true
- type: textarea
attributes:
label: Current vs. Expected behavior
description: |
Current behavior (screenshots, videos, etc. are appreciated), vs. what you expected the behavior to be.
placeholder: |
Current behavior: <screenshot with an arrow> The icon is blue. Expected behavior: The icon should be red because this is what the setting is documented to do.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed version and system specs
description: |
Open the command palette in Zed, then type “zed: copy system specs into clipboard”.
placeholder: |
Zed: v0.215.0 (Zed Nightly bfe141ea79aa4984028934067ba75c48d99136ae)
OS: macOS 15.1
Memory: 36 GiB
Architecture: aarch64
validations:
required: true
- type: textarea
attributes:
label: Attach Zed log file
description: |
Open the command palette in Zed, then type `zed: open log` to see the last 1000 lines. Or type `zed: reveal log in file manager` in the command palette to reveal the log file itself.
value: |
<details><summary>Zed.log</summary>
<!-- Paste your log inside the code block. -->
```log
```
</details>
validations:
required: false
- type: textarea
attributes:
label: Relevant Zed settings
description: |
Open the command palette in Zed, then type “zed: open settings file” and copy/paste any relevant (e.g., LSP-specific) settings.
value: |
<details><summary>settings.json</summary>
<!-- Paste your settings inside the code block. -->
```json
```
</details>
validations:
required: false
- type: textarea
attributes:
label: (for AI issues) Model provider details
placeholder: |
- Provider: (Anthropic via ZedPro, Anthropic via API key, Copilot Chat, Mistral, OpenAI, etc.)
- Model Name: (Claude Sonnet 4.5, Gemini 3 Pro, GPT-5)
- Mode: (Agent Panel, Inline Assistant, Terminal Assistant or Text Threads)
- Other details (ACPs, MCPs, other settings, etc.):
validations:
required: false
- type: dropdown
attributes:
label: If you are using WSL on Windows, what flavor of Linux are you using?
multiple: false
options:
- Arch Linux
- Ubuntu
- Fedora
- Mint
- Pop!_OS
- NixOS
- Other

View File

@@ -1,7 +1,6 @@
name: Report a crash
description: Zed is crashing or freezing or hanging.
type: Crash
labels: "state:needs triage"
body:
- type: textarea
attributes:

View File

@@ -1,167 +0,0 @@
# Generated from xtask::workflows::extension_bump
# Rebuild with `cargo xtask workflows`.
name: extension_bump
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
CARGO_INCREMENTAL: '0'
ZED_EXTENSION_CLI_SHA: 7cfce605704d41ca247e3f84804bf323f6c6caaf
on:
workflow_call:
inputs:
bump-type:
description: bump-type
type: string
default: patch
secrets:
app-id:
description: The app ID used to create the PR
required: true
app-secret:
description: The app secret for the corresponding app ID
required: true
jobs:
check_extension:
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- id: cache-zed-extension-cli
name: extension_tests::cache_zed_extension_cli
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: zed-extension
key: zed-extension-${{ env.ZED_EXTENSION_CLI_SHA }}
- name: extension_tests::download_zed_extension_cli
if: steps.cache-zed-extension-cli.outputs.cache-hit != 'true'
run: |
wget --quiet "https://zed-extension-cli.nyc3.digitaloceanspaces.com/$ZED_EXTENSION_CLI_SHA/x86_64-unknown-linux-gnu/zed-extension"
chmod +x zed-extension
shell: bash -euxo pipefail {0}
- name: extension_tests::check
run: |
mkdir -p /tmp/ext-scratch
mkdir -p /tmp/ext-output
./zed-extension --source-dir . --scratch-dir /tmp/ext-scratch --output-dir /tmp/ext-output
shell: bash -euxo pipefail {0}
timeout-minutes: 1
check_bump_needed:
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
fetch-depth: 10
- id: compare-versions-check
name: extension_bump::compare_versions
run: |
CURRENT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
git checkout "$(git log -1 --format=%H)"~1
PREV_COMMIT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
[[ "$CURRENT_VERSION" == "$PREV_COMMIT_VERSION" ]] && \
echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
echo "needs_bump=false" >> "$GITHUB_OUTPUT"
echo "current_version=${CURRENT_VERSION}" >> "$GITHUB_OUTPUT"
shell: bash -euxo pipefail {0}
outputs:
needs_bump: ${{ steps.compare-versions-check.outputs.needs_bump }}
current_version: ${{ steps.compare-versions-check.outputs.current_version }}
timeout-minutes: 1
bump_extension_version:
needs:
- check_extension
- check_bump_needed
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && needs.check_bump_needed.outputs.needs_bump == 'true'
runs-on: namespace-profile-8x16-ubuntu-2204
steps:
- id: generate-token
name: extension_bump::generate_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.app-id }}
private-key: ${{ secrets.app-secret }}
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: extension_bump::install_bump_2_version
run: pip install bump2version
shell: bash -euxo pipefail {0}
- id: bump-version
name: extension_bump::bump_version
run: |
OLD_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
cat <<EOF > .bumpversion.cfg
[bumpversion]
current_version = "$OLD_VERSION"
[bumpversion:file:Cargo.toml]
[bumpversion:file:extension.toml]
EOF
bump2version --verbose ${{ inputs.bump-type }}
NEW_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
cargo update --workspace
rm .bumpversion.cfg
echo "old_version=${OLD_VERSION}" >> "$GITHUB_OUTPUT"
echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
shell: bash -euxo pipefail {0}
- name: extension_bump::create_pull_request
uses: peter-evans/create-pull-request@v7
with:
title: Bump version to ${{ steps.bump-version.outputs.new_version }}
body: This PR bumps the version of this extension to v${{ steps.bump-version.outputs.new_version }}
commit-message: Bump version to v${{ steps.bump-version.outputs.new_version }}
branch: bump-from-${{ steps.bump-version.outputs.old_version }}
committer: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
base: main
delete-branch: true
token: ${{ steps.generate-token.outputs.token }}
sign-commits: true
timeout-minutes: 1
create_version_label:
needs:
- check_extension
- check_bump_needed
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && needs.check_bump_needed.outputs.needs_bump == 'false'
runs-on: namespace-profile-8x16-ubuntu-2204
steps:
- id: generate-token
name: extension_bump::generate_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.app-id }}
private-key: ${{ secrets.app-secret }}
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: extension_bump::create_version_tag
uses: actions/github-script@v7
with:
script: |-
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/v${{ needs.check_bump_needed.outputs.current_version }}',
sha: context.sha
})
github-token: ${{ steps.generate-token.outputs.token }}
timeout-minutes: 1
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

86
Cargo.lock generated
View File

@@ -1380,7 +1380,6 @@ dependencies = [
"http_client",
"markdown_preview",
"release_channel",
"semver",
"serde",
"serde_json",
"smol",
@@ -5308,7 +5307,6 @@ dependencies = [
"telemetry",
"theme",
"ui",
"util",
"workspace",
"zed_actions",
"zeta",
@@ -5367,7 +5365,6 @@ dependencies = [
"db",
"edit_prediction",
"emojis",
"feature_flags",
"file_icons",
"fs",
"futures 0.3.31",
@@ -6409,7 +6406,6 @@ dependencies = [
"git",
"gpui",
"ignore",
"is_executable",
"libc",
"log",
"notify 8.2.0",
@@ -7109,7 +7105,6 @@ dependencies = [
"futures 0.3.31",
"fuzzy",
"git",
"git_hosting_providers",
"gpui",
"indoc",
"itertools 0.14.0",
@@ -7131,7 +7126,6 @@ dependencies = [
"serde",
"serde_json",
"settings",
"smol",
"strum 0.27.2",
"telemetry",
"theme",
@@ -8442,15 +8436,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "is_executable"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4"
dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@@ -9017,7 +9002,6 @@ dependencies = [
"chrono",
"collections",
"futures 0.3.31",
"globset",
"gpui",
"http_client",
"itertools 0.14.0",
@@ -11546,7 +11530,7 @@ dependencies = [
[[package]]
name = "pet"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"clap",
"env_logger 0.10.2",
@@ -11571,7 +11555,6 @@ dependencies = [
"pet-python-utils",
"pet-reporter",
"pet-telemetry",
"pet-uv",
"pet-venv",
"pet-virtualenv",
"pet-virtualenvwrapper",
@@ -11584,7 +11567,7 @@ dependencies = [
[[package]]
name = "pet-conda"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11603,7 +11586,7 @@ dependencies = [
[[package]]
name = "pet-core"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"clap",
"lazy_static",
@@ -11618,7 +11601,7 @@ dependencies = [
[[package]]
name = "pet-env-var-path"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -11634,7 +11617,7 @@ dependencies = [
[[package]]
name = "pet-fs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11643,7 +11626,7 @@ dependencies = [
[[package]]
name = "pet-global-virtualenvs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11656,7 +11639,7 @@ dependencies = [
[[package]]
name = "pet-homebrew"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -11674,7 +11657,7 @@ dependencies = [
[[package]]
name = "pet-jsonrpc"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -11687,7 +11670,7 @@ dependencies = [
[[package]]
name = "pet-linux-global-python"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11700,7 +11683,7 @@ dependencies = [
[[package]]
name = "pet-mac-commandlinetools"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11713,7 +11696,7 @@ dependencies = [
[[package]]
name = "pet-mac-python-org"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11726,7 +11709,7 @@ dependencies = [
[[package]]
name = "pet-mac-xcode"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11739,7 +11722,7 @@ dependencies = [
[[package]]
name = "pet-pipenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11752,7 +11735,7 @@ dependencies = [
[[package]]
name = "pet-pixi"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11764,7 +11747,7 @@ dependencies = [
[[package]]
name = "pet-poetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"base64 0.22.1",
"lazy_static",
@@ -11785,7 +11768,7 @@ dependencies = [
[[package]]
name = "pet-pyenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -11803,7 +11786,7 @@ dependencies = [
[[package]]
name = "pet-python-utils"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11820,7 +11803,7 @@ dependencies = [
[[package]]
name = "pet-reporter"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -11834,7 +11817,7 @@ dependencies = [
[[package]]
name = "pet-telemetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11846,22 +11829,10 @@ dependencies = [
"regex",
]
[[package]]
name = "pet-uv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
dependencies = [
"log",
"pet-core",
"pet-python-utils",
"serde",
"toml 0.9.8",
]
[[package]]
name = "pet-venv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11873,7 +11844,7 @@ dependencies = [
[[package]]
name = "pet-virtualenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11885,7 +11856,7 @@ dependencies = [
[[package]]
name = "pet-virtualenvwrapper"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11898,7 +11869,7 @@ dependencies = [
[[package]]
name = "pet-windows-registry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -11916,7 +11887,7 @@ dependencies = [
[[package]]
name = "pet-windows-store"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da#1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -15117,7 +15088,6 @@ dependencies = [
"editor",
"futures 0.3.31",
"gpui",
"itertools 0.14.0",
"language",
"lsp",
"menu",
@@ -17343,8 +17313,8 @@ dependencies = [
[[package]]
name = "tiktoken-rs"
version = "0.9.1"
source = "git+https://github.com/zed-industries/tiktoken-rs?rev=7249f999c5fdf9bf3cc5c288c964454e4dac0c00#7249f999c5fdf9bf3cc5c288c964454e4dac0c00"
version = "0.8.0"
source = "git+https://github.com/zed-industries/tiktoken-rs?rev=30c32a4522751699adeda0d5840c71c3b75ae73d#30c32a4522751699adeda0d5840c71c3b75ae73d"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -21199,7 +21169,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.216.0"
version = "0.215.0"
dependencies = [
"acp_tools",
"activity_indicator",
@@ -21522,7 +21492,7 @@ dependencies = [
[[package]]
name = "zed_proto"
version = "0.2.3"
version = "0.2.2"
dependencies = [
"zed_extension_api 0.1.0",
]

View File

@@ -583,14 +583,14 @@ partial-json-fixer = "0.5.3"
parse_int = "0.9"
pciid-parser = "0.8.0"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
portable-pty = "0.9.0"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
@@ -655,7 +655,7 @@ sysinfo = "0.37.0"
take-until = "0.2.0"
tempfile = "3.20.0"
thiserror = "2.0.12"
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "7249f999c5fdf9bf3cc5c288c964454e4dac0c00" }
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "30c32a4522751699adeda0d5840c71c3b75ae73d" }
time = { version = "0.3", features = [
"macros",
"parsing",

View File

@@ -43,9 +43,8 @@ design
= @danilo-leal
docs
= @miguelraz
= @probably-neb
= @yeskunall
= @miguelraz
extension
= @kubkon

View File

@@ -1,10 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_2)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58747 12.9359C4.35741 12.778 4.17558 12.625 4.17558 12.625L10.092 2.37749C10.092 2.37749 10.3355 2.46782 10.5367 2.56426C10.7903 2.6858 11.0003 2.80429 11.0003 2.80429C13.8681 4.46005 14.8523 8.13267 13.1965 11.0005C11.5407 13.8684 7.8681 14.8525 5.00023 13.1967C5.00023 13.1967 4.79936 13.0812 4.58747 12.9359ZM10.5003 3.67032L5.50023 12.3307C7.89013 13.7105 10.9506 12.8904 12.3305 10.5006C13.7102 8.1106 12.8902 5.05015 10.5003 3.67032ZM3.07664 11.4314C2.87558 11.1403 2.804 11.0006 2.804 11.0006C1.77036 9.20524 1.69456 6.92215 2.80404 5.00046C3.91353 3.07877 5.92859 2.00291 8.0003 2.00036C8.0003 2.00036 8.28 1.99964 8.51289 2.02194C8.86375 2.05556 9.09702 2.10083 9.09702 2.10083L3.43905 11.9007C3.43905 11.9007 3.30482 11.7618 3.07664 11.4314ZM7.40178 3.03702C5.89399 3.22027 4.48727 4.08506 3.67008 5.50052C2.85288 6.9159 2.80733 8.56653 3.40252 9.96401L7.40178 3.03702Z" fill="black" stroke="black" stroke-width="0.1"/>
</g>
<defs>
<clipPath id="clip0_1_2">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1279,7 +1279,6 @@
"escape": "workspace::CloseWindow",
"ctrl-m": "settings_editor::Minimize",
"ctrl-f": "search::FocusSearch",
"ctrl-,": "settings_editor::OpenCurrentFile",
"left": "settings_editor::ToggleFocusNav",
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
@@ -1335,12 +1334,5 @@
"alt-left": "dev::Zeta2ContextGoBack",
"alt-right": "dev::Zeta2ContextGoForward"
}
},
{
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "branch_picker::DeleteBranch"
}
}
]

View File

@@ -1382,7 +1382,6 @@
"escape": "workspace::CloseWindow",
"cmd-m": "settings_editor::Minimize",
"cmd-f": "search::FocusSearch",
"cmd-,": "settings_editor::OpenCurrentFile",
"left": "settings_editor::ToggleFocusNav",
"cmd-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
@@ -1439,12 +1438,5 @@
"alt-left": "dev::Zeta2ContextGoBack",
"alt-right": "dev::Zeta2ContextGoForward"
}
},
{
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"cmd-shift-backspace": "branch_picker::DeleteBranch"
}
}
]

View File

@@ -240,18 +240,18 @@
"shift-alt-p": "agent::ManageProfiles",
"ctrl-i": "agent::ToggleProfileSelector",
"shift-alt-/": "agent::ToggleModelSelector",
"shift-alt-j": "agent::ToggleNavigationMenu",
"shift-alt-i": "agent::ToggleOptionsMenu",
"ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"ctrl-shift-j": "agent::ToggleNavigationMenu",
"ctrl-alt-i": "agent::ToggleOptionsMenu",
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
"super-ctrl-b": "agent::ToggleBurnMode",
"alt-enter": "agent::ContinueWithBurnMode",
"shift-alt-a": "agent::AllowOnce",
"ctrl-y": "agent::AllowOnce",
"ctrl-alt-y": "agent::AllowAlways",
"shift-alt-z": "agent::RejectOnce"
"ctrl-alt-z": "agent::RejectOnce"
}
},
{
@@ -1305,7 +1305,6 @@
"escape": "workspace::CloseWindow",
"ctrl-m": "settings_editor::Minimize",
"ctrl-f": "search::FocusSearch",
"ctrl-,": "settings_editor::OpenCurrentFile",
"left": "settings_editor::ToggleFocusNav",
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
@@ -1362,12 +1361,5 @@
"alt-left": "dev::Zeta2ContextGoBack",
"alt-right": "dev::Zeta2ContextGoForward"
}
},
{
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "branch_picker::DeleteBranch"
}
}
]

View File

@@ -1350,8 +1350,6 @@
// "load_direnv": "direct"
// 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
// "load_direnv": "shell_hook"
// 3. Don't load direnv configuration at all.
// "load_direnv": "disabled"
"load_direnv": "direct",
"edit_predictions": {
// A list of globs representing files that edit predictions should be disabled for.

View File

@@ -96,9 +96,9 @@
"terminal.ansi.bright_white": "#fafafaff",
"terminal.ansi.dim_white": "#575d65ff",
"link_text.hover": "#74ade8ff",
"version_control.added": "#27a657ff",
"version_control.added": "#2EA048ff",
"version_control.modified": "#d3b020ff",
"version_control.deleted": "#e06c76ff",
"version_control.deleted": "#78081Bff",
"version_control.conflict_marker.ours": "#a1c1811a",
"version_control.conflict_marker.theirs": "#74ade81a",
"conflict": "#dec184ff",
@@ -497,9 +497,9 @@
"terminal.ansi.bright_white": "#ffffffff",
"terminal.ansi.dim_white": "#aaaaaaff",
"link_text.hover": "#5c78e2ff",
"version_control.added": "#27a657ff",
"version_control.added": "#2EA048ff",
"version_control.modified": "#d3b020ff",
"version_control.deleted": "#e06c76ff",
"version_control.deleted": "#F85149ff",
"conflict": "#a48819ff",
"conflict.background": "#faf2e6ff",
"conflict.border": "#f4e7d1ff",

View File

@@ -35,9 +35,7 @@ impl Diff {
.await
.log_err();
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(language.clone(), cx)
})?;
buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx))?;
let diff = build_buffer_diff(
old_text.unwrap_or("".into()).into(),
@@ -263,7 +261,7 @@ impl PendingDiff {
self.new_buffer.read(cx).as_rope().clone(),
);
let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
buffer.set_language_immediate(language, cx);
buffer.set_language(language, cx);
buffer
});

View File

@@ -528,7 +528,7 @@ impl Render for AcpTools {
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.size_full(),
)
.vertical_scrollbar_for(&connection.list_state, window, cx)
.vertical_scrollbar_for(connection.list_state.clone(), window, cx)
.into_any()
}
}

View File

@@ -424,20 +424,4 @@ impl ThreadsDatabase {
Ok(())
})
}
pub fn delete_threads(&self) -> Task<Result<()>> {
let connection = self.connection.clone();
self.executor.spawn(async move {
let connection = connection.lock();
let mut delete = connection.exec_bound::<()>(indoc! {"
DELETE FROM threads
"})?;
delete(())?;
Ok(())
})
}
}

View File

@@ -188,15 +188,6 @@ impl HistoryStore {
})
}
pub fn delete_threads(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let database_future = ThreadsDatabase::connect(cx);
cx.spawn(async move |this, cx| {
let database = database_future.await.map_err(|err| anyhow!(err))?;
database.delete_threads().await?;
this.update(cx, |this, cx| this.reload(cx))
})
}
pub fn delete_text_thread(
&mut self,
path: Arc<Path>,

View File

@@ -177,7 +177,7 @@ fn search_paths(glob: &str, project: Entity<Project>, cx: &mut App) -> Task<Resu
let mut results = Vec::new();
for snapshot in snapshots {
for entry in snapshot.entries(false, 0) {
if path_matcher.is_match(&snapshot.root_name().join(&entry.path)) {
if path_matcher.is_match(snapshot.root_name().join(&entry.path).as_std_path()) {
results.push(snapshot.absolutize(&entry.path));
}
}

View File

@@ -32,21 +32,8 @@ pub struct GrepToolInput {
/// Do NOT specify a path here! This will only be matched against the code **content**.
pub regex: String,
/// A glob pattern for the paths of files to include in the search.
/// Supports standard glob patterns like "**/*.rs" or "frontend/src/**/*.ts".
/// Supports standard glob patterns like "**/*.rs" or "src/**/*.ts".
/// If omitted, all files in the project will be searched.
///
/// The glob pattern is matched against the full path including the project root directory.
///
/// <example>
/// If the project has the following root directories:
///
/// - /a/b/backend
/// - /c/d/frontend
///
/// Use "backend/**/*.rs" to search only Rust files in the backend root directory.
/// Use "frontend/src/**/*.ts" to search TypeScript files only in the frontend root directory (sub-directory "src").
/// Use "**/*.rs" to search Rust files across all root directories.
/// </example>
pub include_pattern: Option<String>,
/// Optional starting position for paginated results (0-based).
/// When not provided, starts from the beginning.
@@ -145,7 +132,8 @@ impl AgentTool for GrepTool {
let exclude_patterns = global_settings
.file_scan_exclusions
.sources()
.chain(global_settings.private_files.sources());
.iter()
.chain(global_settings.private_files.sources().iter());
match PathMatcher::new(exclude_patterns, path_style) {
Ok(matcher) => matcher,

View File

@@ -118,8 +118,7 @@ impl MessageEditor {
);
let editor = cx.new(|cx| {
let buffer =
cx.new(|cx| Buffer::local("", cx).with_language_immediate(Arc::new(language), cx));
let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let mut editor = Editor::new(mode, buffer, None, window, cx);
@@ -1424,7 +1423,7 @@ mod tests {
rel_path("b/eight.txt"),
];
let slash = PathStyle::local().primary_separator();
let slash = PathStyle::local().separator();
let mut opened_editors = Vec::new();
for path in paths {

View File

@@ -1,5 +1,5 @@
use crate::acp::AcpThreadView;
use crate::{AgentPanel, RemoveHistory, RemoveSelectedThread};
use crate::{AgentPanel, RemoveSelectedThread};
use agent::{HistoryEntry, HistoryStore};
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
use editor::{Editor, EditorEvent};
@@ -12,7 +12,7 @@ use std::{fmt::Display, ops::Range};
use text::Bias;
use time::{OffsetDateTime, UtcOffset};
use ui::{
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tab, Tooltip, WithScrollbar,
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tooltip, WithScrollbar,
prelude::*,
};
@@ -25,7 +25,6 @@ pub struct AcpThreadHistory {
search_query: SharedString,
visible_items: Vec<ListItemType>,
local_timezone: UtcOffset,
confirming_delete_history: bool,
_update_task: Task<()>,
_subscriptions: Vec<gpui::Subscription>,
}
@@ -99,7 +98,6 @@ impl AcpThreadHistory {
)
.unwrap(),
search_query: SharedString::default(),
confirming_delete_history: false,
_subscriptions: vec![search_editor_subscription, history_store_subscription],
_update_task: Task::ready(()),
};
@@ -333,24 +331,6 @@ impl AcpThreadHistory {
task.detach_and_log_err(cx);
}
fn remove_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.history_store.update(cx, |store, cx| {
store.delete_threads(cx).detach_and_log_err(cx)
});
self.confirming_delete_history = false;
cx.notify();
}
fn prompt_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.confirming_delete_history = true;
cx.notify();
}
fn cancel_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.confirming_delete_history = false;
cx.notify();
}
fn render_list_items(
&mut self,
range: Range<usize>,
@@ -446,10 +426,9 @@ impl AcpThreadHistory {
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
})
.on_click(cx.listener(move |this, _, _, cx| {
this.remove_thread(ix, cx);
cx.stop_propagation()
})),
.on_click(
cx.listener(move |this, _, _, cx| this.remove_thread(ix, cx)),
),
)
} else {
None
@@ -468,8 +447,6 @@ impl Focusable for AcpThreadHistory {
impl Render for AcpThreadHistory {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let has_no_history = self.history_store.read(cx).is_empty(cx);
v_flex()
.key_context("ThreadHistory")
.size_full()
@@ -480,12 +457,9 @@ impl Render for AcpThreadHistory {
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::remove_selected_thread))
.on_action(cx.listener(|this, _: &RemoveHistory, window, cx| {
this.remove_history(window, cx);
}))
.child(
h_flex()
.h(Tab::container_height(cx))
.h(px(41.)) // Match the toolbar perfectly
.w_full()
.py_1()
.px_2()
@@ -507,7 +481,7 @@ impl Render for AcpThreadHistory {
.overflow_hidden()
.flex_grow();
if has_no_history {
if self.history_store.read(cx).is_empty(cx) {
view.justify_center().items_center().child(
Label::new("You don't have any past threads yet.")
.size(LabelSize::Small)
@@ -528,74 +502,16 @@ impl Render for AcpThreadHistory {
)
.p_1()
.pr_4()
.track_scroll(&self.scroll_handle)
.track_scroll(self.scroll_handle.clone())
.flex_grow(),
)
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
.vertical_scrollbar_for(
self.scroll_handle.clone(),
window,
cx,
)
}
})
.when(!has_no_history, |this| {
this.child(
h_flex()
.p_2()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.when(!self.confirming_delete_history, |this| {
this.child(
Button::new("delete_history", "Delete All History")
.full_width()
.style(ButtonStyle::Outlined)
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.prompt_delete_history(window, cx);
})),
)
})
.when(self.confirming_delete_history, |this| {
this.w_full()
.gap_2()
.flex_wrap()
.justify_between()
.child(
h_flex()
.flex_wrap()
.gap_1()
.child(
Label::new("Delete all threads?")
.size(LabelSize::Small),
)
.child(
Label::new("You won't be able to recover them later.")
.size(LabelSize::Small)
.color(Color::Muted),
),
)
.child(
h_flex()
.gap_1()
.child(
Button::new("cancel_delete", "Cancel")
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.cancel_delete_history(window, cx);
})),
)
.child(
Button::new("confirm_delete", "Delete")
.style(ButtonStyle::Tinted(ui::TintColor::Error))
.color(Color::Error)
.label_size(LabelSize::Small)
.on_click(cx.listener(|_, _, window, cx| {
window.dispatch_action(
Box::new(RemoveHistory),
cx,
);
})),
),
)
}),
)
})
}
}

View File

@@ -498,17 +498,7 @@ impl AcpThreadView {
Some(new_version_available_tx),
);
let agent_name = agent.name();
let timeout = cx.background_executor().timer(Duration::from_secs(30));
let connect_task = smol::future::or(
agent.connect(root_dir.as_deref(), delegate, cx),
async move {
timeout.await;
Err(anyhow::Error::new(LoadError::Other(
format!("{agent_name} is unable to initialize after 30 seconds.").into(),
)))
},
);
let connect_task = agent.connect(root_dir.as_deref(), delegate, cx);
let load_task = cx.spawn_in(window, async move |this, cx| {
let connection = match connect_task.await {
Ok((connection, login)) => {
@@ -675,8 +665,6 @@ impl AcpThreadView {
})
});
this.message_editor.focus_handle(cx).focus(window);
cx.notify();
}
Err(err) => {
@@ -1011,10 +999,6 @@ impl AcpThreadView {
}
}
pub fn is_loading(&self) -> bool {
matches!(self.thread_state, ThreadState::Loading { .. })
}
fn resume_chat(&mut self, cx: &mut Context<Self>) {
self.thread_error.take();
let Some(thread) = self.thread() else {
@@ -3818,62 +3802,48 @@ impl AcpThreadView {
}))
}
fn render_plan_entries(
&self,
plan: &Plan,
window: &mut Window,
cx: &Context<Self>,
) -> impl IntoElement {
v_flex()
.id("plan_items_list")
.max_h_40()
.overflow_y_scroll()
.children(plan.entries.iter().enumerate().flat_map(|(index, entry)| {
let element = h_flex()
.py_1()
.px_2()
.gap_2()
.justify_between()
.bg(cx.theme().colors().editor_background)
.when(index < plan.entries.len() - 1, |parent| {
parent.border_color(cx.theme().colors().border).border_b_1()
})
.child(
h_flex()
.id(("plan_entry", index))
.gap_1p5()
.max_w_full()
.overflow_x_scroll()
.text_xs()
.text_color(cx.theme().colors().text_muted)
.child(match entry.status {
acp::PlanEntryStatus::Pending => Icon::new(IconName::TodoPending)
.size(IconSize::Small)
.color(Color::Muted)
.into_any_element(),
acp::PlanEntryStatus::InProgress => {
Icon::new(IconName::TodoProgress)
.size(IconSize::Small)
.color(Color::Accent)
.with_rotate_animation(2)
.into_any_element()
}
acp::PlanEntryStatus::Completed => {
Icon::new(IconName::TodoComplete)
.size(IconSize::Small)
.color(Color::Success)
.into_any_element()
}
})
.child(MarkdownElement::new(
entry.content.clone(),
plan_label_markdown_style(&entry.status, window, cx),
)),
);
fn render_plan_entries(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
v_flex().children(plan.entries.iter().enumerate().flat_map(|(index, entry)| {
let element = h_flex()
.py_1()
.px_2()
.gap_2()
.justify_between()
.bg(cx.theme().colors().editor_background)
.when(index < plan.entries.len() - 1, |parent| {
parent.border_color(cx.theme().colors().border).border_b_1()
})
.child(
h_flex()
.id(("plan_entry", index))
.gap_1p5()
.max_w_full()
.overflow_x_scroll()
.text_xs()
.text_color(cx.theme().colors().text_muted)
.child(match entry.status {
acp::PlanEntryStatus::Pending => Icon::new(IconName::TodoPending)
.size(IconSize::Small)
.color(Color::Muted)
.into_any_element(),
acp::PlanEntryStatus::InProgress => Icon::new(IconName::TodoProgress)
.size(IconSize::Small)
.color(Color::Accent)
.with_rotate_animation(2)
.into_any_element(),
acp::PlanEntryStatus::Completed => Icon::new(IconName::TodoComplete)
.size(IconSize::Small)
.color(Color::Success)
.into_any_element(),
})
.child(MarkdownElement::new(
entry.content.clone(),
plan_label_markdown_style(&entry.status, window, cx),
)),
);
Some(element)
}))
.into_any_element()
Some(element)
}))
}
fn render_edits_summary(
@@ -4011,136 +3981,126 @@ impl AcpThreadView {
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
pending_edits: bool,
cx: &Context<Self>,
) -> impl IntoElement {
) -> Div {
let editor_bg_color = cx.theme().colors().editor_background;
v_flex()
.id("edited_files_list")
.max_h_40()
.overflow_y_scroll()
.children(
changed_buffers
.iter()
.enumerate()
.flat_map(|(index, (buffer, _diff))| {
let file = buffer.read(cx).file()?;
let path = file.path();
let path_style = file.path_style(cx);
let separator = file.path_style(cx).primary_separator();
v_flex().children(changed_buffers.iter().enumerate().flat_map(
|(index, (buffer, _diff))| {
let file = buffer.read(cx).file()?;
let path = file.path();
let path_style = file.path_style(cx);
let separator = file.path_style(cx).separator();
let file_path = path.parent().and_then(|parent| {
if parent.is_empty() {
None
} else {
Some(
Label::new(format!(
"{}{separator}",
parent.display(path_style)
))
.color(Color::Muted)
.size(LabelSize::XSmall)
.buffer_font(cx),
)
}
});
let file_name = path.file_name().map(|name| {
Label::new(name.to_string())
let file_path = path.parent().and_then(|parent| {
if parent.is_empty() {
None
} else {
Some(
Label::new(format!("{}{separator}", parent.display(path_style)))
.color(Color::Muted)
.size(LabelSize::XSmall)
.buffer_font(cx)
.ml_1p5()
});
.buffer_font(cx),
)
}
});
let file_icon = FileIcons::get_icon(path.as_std_path(), cx)
.map(Icon::from_path)
.map(|icon| icon.color(Color::Muted).size(IconSize::Small))
.unwrap_or_else(|| {
Icon::new(IconName::File)
.color(Color::Muted)
.size(IconSize::Small)
});
let file_name = path.file_name().map(|name| {
Label::new(name.to_string())
.size(LabelSize::XSmall)
.buffer_font(cx)
.ml_1p5()
});
let overlay_gradient = linear_gradient(
90.,
linear_color_stop(editor_bg_color, 1.),
linear_color_stop(editor_bg_color.opacity(0.2), 0.),
);
let file_icon = FileIcons::get_icon(path.as_std_path(), cx)
.map(Icon::from_path)
.map(|icon| icon.color(Color::Muted).size(IconSize::Small))
.unwrap_or_else(|| {
Icon::new(IconName::File)
.color(Color::Muted)
.size(IconSize::Small)
});
let element = h_flex()
.group("edited-code")
.id(("file-container", index))
.py_1()
.pl_2()
.pr_1()
.gap_2()
.justify_between()
.bg(editor_bg_color)
.when(index < changed_buffers.len() - 1, |parent| {
parent.border_color(cx.theme().colors().border).border_b_1()
})
let overlay_gradient = linear_gradient(
90.,
linear_color_stop(editor_bg_color, 1.),
linear_color_stop(editor_bg_color.opacity(0.2), 0.),
);
let element = h_flex()
.group("edited-code")
.id(("file-container", index))
.py_1()
.pl_2()
.pr_1()
.gap_2()
.justify_between()
.bg(editor_bg_color)
.when(index < changed_buffers.len() - 1, |parent| {
parent.border_color(cx.theme().colors().border).border_b_1()
})
.child(
h_flex()
.id(("file-name-row", index))
.relative()
.pr_8()
.w_full()
.overflow_x_scroll()
.child(
h_flex()
.id(("file-name-row", index))
.relative()
.pr_8()
.w_full()
.overflow_x_scroll()
.child(
h_flex()
.id(("file-name-path", index))
.cursor_pointer()
.pr_0p5()
.gap_0p5()
.hover(|s| s.bg(cx.theme().colors().element_hover))
.rounded_xs()
.child(file_icon)
.children(file_name)
.children(file_path)
.tooltip(Tooltip::text("Go to File"))
.on_click({
let buffer = buffer.clone();
cx.listener(move |this, _, window, cx| {
this.open_edited_buffer(&buffer, window, cx);
})
}),
)
.child(
div()
.absolute()
.h_full()
.w_12()
.top_0()
.bottom_0()
.right_0()
.bg(overlay_gradient),
),
.id(("file-name-path", index))
.cursor_pointer()
.pr_0p5()
.gap_0p5()
.hover(|s| s.bg(cx.theme().colors().element_hover))
.rounded_xs()
.child(file_icon)
.children(file_name)
.children(file_path)
.tooltip(Tooltip::text("Go to File"))
.on_click({
let buffer = buffer.clone();
cx.listener(move |this, _, window, cx| {
this.open_edited_buffer(&buffer, window, cx);
})
}),
)
.child(
h_flex()
.gap_1()
.visible_on_hover("edited-code")
.child(
Button::new("review", "Review")
.label_size(LabelSize::Small)
.on_click({
let buffer = buffer.clone();
cx.listener(move |this, _, window, cx| {
this.open_edited_buffer(&buffer, window, cx);
})
}),
)
.child(Divider::vertical().color(DividerColor::BorderVariant))
.child(
Button::new("reject-file", "Reject")
.label_size(LabelSize::Small)
.disabled(pending_edits)
.on_click({
let buffer = buffer.clone();
let action_log = action_log.clone();
let telemetry = telemetry.clone();
move |_, _, cx| {
action_log.update(cx, |action_log, cx| {
action_log
div()
.absolute()
.h_full()
.w_12()
.top_0()
.bottom_0()
.right_0()
.bg(overlay_gradient),
),
)
.child(
h_flex()
.gap_1()
.visible_on_hover("edited-code")
.child(
Button::new("review", "Review")
.label_size(LabelSize::Small)
.on_click({
let buffer = buffer.clone();
cx.listener(move |this, _, window, cx| {
this.open_edited_buffer(&buffer, window, cx);
})
}),
)
.child(Divider::vertical().color(DividerColor::BorderVariant))
.child(
Button::new("reject-file", "Reject")
.label_size(LabelSize::Small)
.disabled(pending_edits)
.on_click({
let buffer = buffer.clone();
let action_log = action_log.clone();
let telemetry = telemetry.clone();
move |_, _, cx| {
action_log.update(cx, |action_log, cx| {
action_log
.reject_edits_in_ranges(
buffer.clone(),
vec![Anchor::min_max_range_for_buffer(
@@ -4150,38 +4110,37 @@ impl AcpThreadView {
cx,
)
.detach_and_log_err(cx);
})
}
}),
)
.child(
Button::new("keep-file", "Keep")
.label_size(LabelSize::Small)
.disabled(pending_edits)
.on_click({
let buffer = buffer.clone();
let action_log = action_log.clone();
let telemetry = telemetry.clone();
move |_, _, cx| {
action_log.update(cx, |action_log, cx| {
action_log.keep_edits_in_range(
buffer.clone(),
Anchor::min_max_range_for_buffer(
buffer.read(cx).remote_id(),
),
Some(telemetry.clone()),
cx,
);
})
}
}),
),
);
})
}
}),
)
.child(
Button::new("keep-file", "Keep")
.label_size(LabelSize::Small)
.disabled(pending_edits)
.on_click({
let buffer = buffer.clone();
let action_log = action_log.clone();
let telemetry = telemetry.clone();
move |_, _, cx| {
action_log.update(cx, |action_log, cx| {
action_log.keep_edits_in_range(
buffer.clone(),
Anchor::min_max_range_for_buffer(
buffer.read(cx).remote_id(),
),
Some(telemetry.clone()),
cx,
);
})
}
}),
),
);
Some(element)
}),
)
.into_any_element()
Some(element)
},
))
}
fn render_message_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
@@ -4202,10 +4161,8 @@ impl AcpThreadView {
.block_mouse_except_scroll();
let enable_editor = match self.thread_state {
ThreadState::Ready { .. } => true,
ThreadState::Loading { .. }
| ThreadState::Unauthenticated { .. }
| ThreadState::LoadError(..) => false,
ThreadState::Loading { .. } | ThreadState::Ready { .. } => true,
ThreadState::Unauthenticated { .. } | ThreadState::LoadError(..) => false,
};
v_flex()
@@ -4839,7 +4796,7 @@ impl AcpThreadView {
buffer.update(cx, |buffer, cx| {
buffer.set_text(markdown, cx);
buffer.set_language_immediate(Some(markdown_language), cx);
buffer.set_language(Some(markdown_language), cx);
buffer.set_capability(language::Capability::ReadWrite, cx);
})?;
@@ -5866,10 +5823,12 @@ fn placeholder_text(agent_name: &str, has_commands: bool) -> String {
impl Focusable for AcpThreadView {
fn focus_handle(&self, cx: &App) -> FocusHandle {
match self.thread_state {
ThreadState::Ready { .. } => self.active_editor(cx).focus_handle(cx),
ThreadState::Loading { .. }
| ThreadState::LoadError(_)
| ThreadState::Unauthenticated { .. } => self.focus_handle.clone(),
ThreadState::Loading { .. } | ThreadState::Ready { .. } => {
self.active_editor(cx).focus_handle(cx)
}
ThreadState::LoadError(_) | ThreadState::Unauthenticated { .. } => {
self.focus_handle.clone()
}
}
}
}
@@ -5937,7 +5896,7 @@ impl Render for AcpThreadView {
.flex_grow()
.into_any(),
)
.vertical_scrollbar_for(&self.list_state, window, cx)
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.into_any()
} else {
this.child(self.render_recent_history(cx)).into_any()
@@ -7399,54 +7358,4 @@ pub(crate) mod tests {
assert_eq!(text, expected_txt);
})
}
#[gpui::test]
async fn test_initialize_timeout(cx: &mut TestAppContext) {
init_test(cx);
struct InfiniteInitialize;
impl AgentServer for InfiniteInitialize {
fn telemetry_id(&self) -> &'static str {
"test"
}
fn logo(&self) -> ui::IconName {
ui::IconName::Ai
}
fn name(&self) -> SharedString {
"Test".into()
}
fn connect(
&self,
_root_dir: Option<&Path>,
_delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<gpui::Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>
{
cx.spawn(async |_| futures::future::pending().await)
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
let (thread_view, cx) = setup_thread_view(InfiniteInitialize, cx).await;
cx.executor().advance_clock(Duration::from_secs(31));
cx.run_until_parked();
let error = thread_view.read_with(cx, |thread_view, _| match &thread_view.thread_state {
ThreadState::LoadError(err) => err.clone(),
_ => panic!("Incorrect thread state"),
});
match error {
LoadError::Other(str) => assert!(str.contains("initialize")),
_ => panic!("Unexpected load error"),
}
}
}

View File

@@ -1209,7 +1209,7 @@ impl Render for AgentConfiguration {
.child(self.render_context_servers_section(window, cx))
.child(self.render_provider_configuration_section(cx)),
)
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
)
}
}

View File

@@ -516,7 +516,7 @@ impl Render for AddLlmProviderModal {
.child(
div()
.size_full()
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.child(
v_flex()
.id("modal_content")

View File

@@ -97,9 +97,7 @@ impl ConfigurationSource {
editor.set_show_gutter(false, cx);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(jsonc_language, cx)
})
buffer.update(cx, |buffer, cx| buffer.set_language(jsonc_language, cx))
}
editor
})
@@ -823,6 +821,7 @@ impl ConfigureContextServerModal {
impl Render for ConfigureContextServerModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let scroll_handle = self.scroll_handle.clone();
div()
.elevation_3(cx)
.w(rems(34.))
@@ -850,7 +849,7 @@ impl Render for ConfigureContextServerModal {
.id("modal-content")
.max_h(vh(0.7, window))
.overflow_y_scroll()
.track_scroll(&self.scroll_handle)
.track_scroll(&scroll_handle)
.child(self.render_modal_description(window, cx))
.child(self.render_modal_content(cx))
.child(match &self.state {
@@ -863,7 +862,7 @@ impl Render for ConfigureContextServerModal {
}
}),
)
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
.vertical_scrollbar_for(scroll_handle, window, cx),
),
)
.footer(self.render_modal_footer(cx)),

View File

@@ -138,7 +138,7 @@ impl ConfigureContextServerToolsModal {
items
})),
)
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.into_any_element()
}
}

View File

@@ -493,7 +493,7 @@ impl Item for AgentDiffPane {
Some("Assistant Diff Opened")
}
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}

View File

@@ -1,4 +1,7 @@
use std::{ops::Range, path::Path, rc::Rc, sync::Arc, time::Duration};
use std::ops::Range;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
use acp_thread::AcpThread;
use agent::{ContextServerRegistry, DbThreadMetadata, HistoryEntry, HistoryStore};
@@ -17,9 +20,10 @@ use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
use crate::ManageProfiles;
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
use crate::{
AddContextServer, AgentDiffPane, Follow, InlineAssistant, NewTextThread, NewThread,
OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, Follow, InlineAssistant,
NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory,
ResetTrialEndUpsell, ResetTrialUpsell, ToggleNavigationMenu, ToggleNewThreadMenu,
ToggleOptionsMenu,
acp::AcpThreadView,
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
slash_command::SlashCommandCompletionProvider,
@@ -43,9 +47,9 @@ use extension::ExtensionEvents;
use extension_host::ExtensionStore;
use fs::Fs;
use gpui::{
Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, Corner, DismissEvent,
Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription,
Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription, Task, UpdateGlobal,
WeakEntity, prelude::*,
};
use language::LanguageRegistry;
use language_model::{ConfigurationError, LanguageModelRegistry};
@@ -55,9 +59,10 @@ use rules_library::{RulesLibrary, open_rules_library};
use search::{BufferSearchBar, buffer_search};
use settings::{Settings, update_settings_file};
use theme::ThemeSettings;
use ui::utils::WithRemSize;
use ui::{
Callout, ContextMenu, ContextMenuEntry, KeyBinding, PopoverMenu, PopoverMenuHandle,
ProgressBar, Tab, Tooltip, prelude::*, utils::WithRemSize,
ProgressBar, Tab, Tooltip, prelude::*,
};
use util::ResultExt as _;
use workspace::{
@@ -609,14 +614,11 @@ impl AgentPanel {
if let Some(panel) = panel.upgrade() {
menu = Self::populate_recently_opened_menu_section(menu, panel, cx);
}
menu = menu
.action("View All", Box::new(OpenHistory))
menu.action("View All", Box::new(OpenHistory))
.end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
.fixed_width(px(320.).into())
.keep_open_on_confirm(false)
.key_context("NavigationMenu");
menu
.key_context("NavigationMenu")
});
weak_panel
.update(cx, |panel, cx| {
@@ -2155,41 +2157,28 @@ impl AgentPanel {
let selected_agent_label = self.selected_agent.label();
let is_thread_loading = self
.active_thread_view()
.map(|thread| thread.read(cx).is_loading())
.unwrap_or(false);
let has_custom_icon = selected_agent_custom_icon.is_some();
let selected_agent = div()
.id("selected_agent_icon")
.when_some(selected_agent_custom_icon, |this, icon_path| {
let label = selected_agent_label.clone();
this.px_1()
.child(Icon::from_external_svg(icon_path).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
})
})
.when(!has_custom_icon, |this| {
this.when_some(self.selected_agent.icon(), |this, icon| {
this.px_1().child(Icon::new(icon).color(Color::Muted))
let label = selected_agent_label.clone();
this.px_1()
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |_window, cx| {
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
})
})
})
.tooltip(move |_, cx| {
Tooltip::with_meta(selected_agent_label.clone(), None, "Selected Agent", cx)
});
let selected_agent = if is_thread_loading {
selected_agent
.with_animation(
"pulsating-icon",
Animation::new(Duration::from_secs(1))
.repeat()
.with_easing(pulsating_between(0.2, 0.6)),
|icon, delta| icon.opacity(delta),
)
.into_any_element()
} else {
selected_agent.into_any_element()
};
.into_any_element();
h_flex()
.id("agent-panel-toolbar")

View File

@@ -69,8 +69,6 @@ actions!(
CycleModeSelector,
/// Expands the message editor to full size.
ExpandMessageEditor,
/// Removes all thread history.
RemoveHistory,
/// Opens the conversation history view.
OpenHistory,
/// Adds a context server to the configuration.

View File

@@ -269,9 +269,9 @@ impl CodegenAlternative {
.language_registry();
let mut buffer = Buffer::local_normalized(text, line_ending, cx);
buffer.set_language_immediate(language, cx);
buffer.set_language(language, cx);
if let Some(language_registry) = language_registry {
buffer.set_language_registry(language_registry);
buffer.set_language_registry(language_registry)
}
buffer
});
@@ -1077,8 +1077,7 @@ mod tests {
}
}
"};
let buffer =
cx.new(|cx| Buffer::local(text, cx).with_language_immediate(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
@@ -1140,8 +1139,7 @@ mod tests {
le
}
"};
let buffer =
cx.new(|cx| Buffer::local(text, cx).with_language_immediate(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
@@ -1205,8 +1203,7 @@ mod tests {
" \n",
"}\n" //
);
let buffer =
cx.new(|cx| Buffer::local(text, cx).with_language_immediate(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
@@ -1322,8 +1319,7 @@ mod tests {
let x = 0;
}
"};
let buffer =
cx.new(|cx| Buffer::local(text, cx).with_language_immediate(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);

View File

@@ -7,9 +7,7 @@ use std::sync::atomic::AtomicBool;
use acp_thread::MentionUri;
use agent::{HistoryEntry, HistoryStore};
use anyhow::Result;
use editor::{
CompletionProvider, Editor, ExcerptId, code_context_menus::COMPLETION_MENU_MAX_WIDTH,
};
use editor::{CompletionProvider, Editor, ExcerptId};
use fuzzy::{PathMatch, StringMatch, StringMatchCandidate};
use gpui::{App, Entity, Task, WeakEntity};
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
@@ -27,7 +25,6 @@ use ui::prelude::*;
use util::ResultExt as _;
use util::paths::PathStyle;
use util::rel_path::RelPath;
use util::truncate_and_remove_front;
use workspace::Workspace;
use crate::AgentPanel;
@@ -339,20 +336,14 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
mention_set: WeakEntity<MentionSet>,
workspace: Entity<Workspace>,
project: Entity<Project>,
label_max_chars: usize,
cx: &mut App,
) -> Option<Completion> {
let path_style = project.read(cx).path_style(cx);
let (file_name, directory) =
extract_file_name_and_directory(&project_path.path, path_prefix, path_style);
let label = build_code_label_for_path(
&file_name,
directory.as_ref().map(|s| s.as_ref()),
None,
label_max_chars,
cx,
);
let label =
build_code_label_for_path(&file_name, directory.as_ref().map(|s| s.as_ref()), None, cx);
let abs_path = project.read(cx).absolute_path(&project_path, cx)?;
@@ -401,7 +392,6 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
editor: WeakEntity<Editor>,
mention_set: WeakEntity<MentionSet>,
workspace: Entity<Workspace>,
label_max_chars: usize,
cx: &mut App,
) -> Option<Completion> {
let project = workspace.read(cx).project().clone();
@@ -424,7 +414,6 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
&symbol.name,
Some(&file_name),
Some(symbol.range.start.0.row + 1),
label_max_chars,
cx,
);
@@ -863,7 +852,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
buffer: &Entity<Buffer>,
buffer_position: Anchor,
_trigger: CompletionContext,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Vec<CompletionResponse>>> {
let state = buffer.update(cx, |buffer, cx| {
@@ -872,7 +861,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
let offset_to_line = buffer.point_to_offset(line_start);
let mut lines = buffer.text_for_range(line_start..position).lines();
let line = lines.next()?;
PromptCompletion::try_parse(line, offset_to_line, &self.source.supported_modes(cx))
ContextCompletion::try_parse(line, offset_to_line, &self.source.supported_modes(cx))
});
let Some(state) = state else {
return Task::ready(Ok(Vec::new()));
@@ -891,7 +880,7 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
let editor = self.editor.clone();
let mention_set = self.mention_set.downgrade();
match state {
PromptCompletion::SlashCommand(SlashCommandCompletion {
ContextCompletion::SlashCommand(SlashCommandCompletion {
command, argument, ..
}) => {
let search_task = self.search_slash_commands(command.unwrap_or_default(), cx);
@@ -954,36 +943,11 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
}])
})
}
PromptCompletion::Mention(MentionCompletion { mode, argument, .. }) => {
ContextCompletion::Mention(MentionCompletion { mode, argument, .. }) => {
let query = argument.unwrap_or_default();
let search_task =
self.search_mentions(mode, query, Arc::<AtomicBool>::default(), cx);
// Calculate maximum characters available for the full label (file_name + space + directory)
// based on maximum menu width after accounting for padding, spacing, and icon width
let label_max_chars = {
// Base06 left padding + Base06 gap + Base06 right padding + icon width
let used_pixels = DynamicSpacing::Base06.px(cx) * 3.0
+ IconSize::XSmall.rems() * window.rem_size();
let style = window.text_style();
let font_id = window.text_system().resolve_font(&style.font());
let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
// Fallback em_width of 10px matches file_finder.rs fallback for TextSize::Small
let em_width = cx
.text_system()
.em_width(font_id, font_size)
.unwrap_or(px(10.0));
// Calculate available pixels for text (file_name + directory)
// Using max width since dynamic_width allows the menu to expand up to this
let available_pixels = COMPLETION_MENU_MAX_WIDTH - used_pixels;
// Convert to character count (total available for file_name + directory)
(f32::from(available_pixels) / f32::from(em_width)) as usize
};
cx.spawn(async move |_, cx| {
let matches = search_task.await;
@@ -1020,7 +984,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
mention_set.clone(),
workspace.clone(),
project.clone(),
label_max_chars,
cx,
)
}
@@ -1033,7 +996,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
editor.clone(),
mention_set.clone(),
workspace.clone(),
label_max_chars,
cx,
)
}
@@ -1123,12 +1085,12 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
let offset_to_line = buffer.point_to_offset(line_start);
let mut lines = buffer.text_for_range(line_start..position).lines();
if let Some(line) = lines.next() {
PromptCompletion::try_parse(line, offset_to_line, &self.source.supported_modes(cx))
ContextCompletion::try_parse(line, offset_to_line, &self.source.supported_modes(cx))
.filter(|completion| {
// Right now we don't support completing arguments of slash commands
let is_slash_command_with_argument = matches!(
completion,
PromptCompletion::SlashCommand(SlashCommandCompletion {
ContextCompletion::SlashCommand(SlashCommandCompletion {
argument: Some(_),
..
})
@@ -1198,13 +1160,12 @@ fn confirm_completion_callback<T: PromptCompletionProviderDelegate>(
})
}
#[derive(Debug, PartialEq)]
enum PromptCompletion {
enum ContextCompletion {
SlashCommand(SlashCommandCompletion),
Mention(MentionCompletion),
}
impl PromptCompletion {
impl ContextCompletion {
fn source_range(&self) -> Range<usize> {
match self {
Self::SlashCommand(completion) => completion.source_range.clone(),
@@ -1217,14 +1178,15 @@ impl PromptCompletion {
offset_to_line: usize,
supported_modes: &[PromptContextType],
) -> Option<Self> {
if line.contains('@') {
if let Some(mention) =
MentionCompletion::try_parse(line, offset_to_line, supported_modes)
{
return Some(Self::Mention(mention));
}
if let Some(command) = SlashCommandCompletion::try_parse(line, offset_to_line) {
Some(Self::SlashCommand(command))
} else if let Some(mention) =
MentionCompletion::try_parse(line, offset_to_line, supported_modes)
{
Some(Self::Mention(mention))
} else {
None
}
SlashCommandCompletion::try_parse(line, offset_to_line).map(Self::SlashCommand)
}
}
@@ -1633,7 +1595,6 @@ fn build_code_label_for_path(
file: &str,
directory: Option<&str>,
line_number: Option<u32>,
label_max_chars: usize,
cx: &App,
) -> CodeLabel {
let variable_highlight_id = cx
@@ -1647,13 +1608,7 @@ fn build_code_label_for_path(
label.push_str(" ", None);
if let Some(directory) = directory {
let file_name_chars = file.chars().count();
// Account for: file_name + space (ellipsis is handled by truncate_and_remove_front)
let directory_max_chars = label_max_chars
.saturating_sub(file_name_chars)
.saturating_sub(1);
let truncated_directory = truncate_and_remove_front(directory, directory_max_chars.max(5));
label.push_str(&truncated_directory, variable_highlight_id);
label.push_str(directory, variable_highlight_id);
}
if let Some(line_number) = line_number {
label.push_str(&format!(" L{}", line_number), variable_highlight_id);
@@ -1698,38 +1653,6 @@ fn selection_ranges(
mod tests {
use super::*;
#[test]
fn test_prompt_completion_parse() {
let supported_modes = vec![PromptContextType::File, PromptContextType::Symbol];
assert_eq!(
PromptCompletion::try_parse("/", 0, &supported_modes),
Some(PromptCompletion::SlashCommand(SlashCommandCompletion {
source_range: 0..1,
command: None,
argument: None,
}))
);
assert_eq!(
PromptCompletion::try_parse("@", 0, &supported_modes),
Some(PromptCompletion::Mention(MentionCompletion {
source_range: 0..1,
mode: None,
argument: None,
}))
);
assert_eq!(
PromptCompletion::try_parse("/test @file", 0, &supported_modes),
Some(PromptCompletion::Mention(MentionCompletion {
source_range: 6..11,
mode: Some(PromptContextType::File),
argument: None,
}))
);
}
#[test]
fn test_slash_command_completion_parse() {
assert_eq!(

View File

@@ -1456,7 +1456,6 @@ impl InlineAssistant {
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_gutter(false, cx);
editor.set_offset_content(false, cx);
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_read_only(true);
editor.set_show_edit_predictions(Some(false), window, cx);

View File

@@ -2556,11 +2556,7 @@ impl Item for TextThreadEditor {
Some(self.title(cx).to_string().into())
}
fn as_searchable(
&self,
handle: &Entity<Self>,
_: &App,
) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(handle.clone()))
}
@@ -3095,7 +3091,7 @@ mod tests {
"#
.unindent();
let mut buffer = Buffer::local(text, cx);
buffer.set_language_immediate(Some(markdown.clone()), cx);
buffer.set_language(Some(markdown.clone()), cx);
buffer
});
let snapshot = buffer.read(cx).snapshot();

View File

@@ -249,15 +249,10 @@ impl PasswordProxy {
fs::write(&askpass_script_path, askpass_script)
.await
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
make_file_executable(&askpass_script_path)
.await
.with_context(|| {
format!("marking askpass script executable at {askpass_script_path:?}")
})?;
// todo(shell): There might be no powershell on the system
make_file_executable(&askpass_script_path).await?;
#[cfg(target_os = "windows")]
let askpass_helper = format!(
"powershell.exe -ExecutionPolicy Bypass -File \"{}\"",
"powershell.exe -ExecutionPolicy Bypass -File {}",
askpass_script_path.display()
);

View File

@@ -233,11 +233,18 @@ fn collect_diagnostics(
options: Options,
cx: &mut App,
) -> Task<Result<Option<SlashCommandOutput>>> {
let error_source = if let Some(path_matcher) = &options.path_matcher {
debug_assert_eq!(path_matcher.sources().len(), 1);
Some(path_matcher.sources().first().cloned().unwrap_or_default())
} else {
None
};
let path_style = project.read(cx).path_style(cx);
let glob_is_exact_file_match = if let Some(path) = options
.path_matcher
.as_ref()
.and_then(|pm| pm.sources().next())
.and_then(|pm| pm.sources().first())
{
project
.read(cx)
@@ -259,13 +266,6 @@ fn collect_diagnostics(
.collect();
cx.spawn(async move |cx| {
let error_source = if let Some(path_matcher) = &options.path_matcher {
debug_assert_eq!(path_matcher.sources().count(), 1);
Some(path_matcher.sources().next().unwrap_or_default())
} else {
None
};
let mut output = SlashCommandOutput::default();
if let Some(error_source) = error_source.as_ref() {
@@ -277,7 +277,7 @@ fn collect_diagnostics(
let mut project_summary = DiagnosticSummary::default();
for (project_path, path, summary) in diagnostic_summaries {
if let Some(path_matcher) = &options.path_matcher
&& !path_matcher.is_match(&path)
&& !path_matcher.is_match(&path.as_std_path())
{
continue;
}

View File

@@ -1223,9 +1223,8 @@ impl TextThread {
cx.spawn(async move |this, cx| {
let markdown = markdown.await?;
this.update(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(markdown), cx)
});
this.buffer
.update(cx, |buffer, cx| buffer.set_language(Some(markdown), cx));
})
})
.detach_and_log_err(cx);
@@ -2934,7 +2933,6 @@ impl TextThread {
RenameOptions {
overwrite: true,
ignore_if_exists: true,
create_parents: false,
},
)
.await?;

View File

@@ -510,9 +510,7 @@ impl AutoUpdater {
(None, None, None)
};
let version = if let Some(mut version) = version {
version.pre = semver::Prerelease::EMPTY;
version.build = semver::BuildMetadata::EMPTY;
let version = if let Some(version) = version {
version.to_string()
} else {
"latest".to_string()
@@ -639,11 +637,10 @@ impl AutoUpdater {
if let AutoUpdateStatus::Updated { version, .. } = status {
match version {
VersionCheckType::Sha(cached_version) => {
let should_download =
parsed_fetched_version.as_ref().ok().is_none_or(|version| {
version.build.as_str().rsplit('.').next()
!= Some(&cached_version.full())
});
let should_download = parsed_fetched_version
.as_ref()
.ok()
.is_none_or(|version| version.build.as_str() != cached_version.full());
let newer_version = should_download
.then(|| VersionCheckType::Sha(AppCommitSha::new(fetched_version)));
return Ok(newer_version);
@@ -663,9 +660,10 @@ impl AutoUpdater {
.ok()
.flatten()
.map(|sha| {
parsed_fetched_version.as_ref().ok().is_none_or(|version| {
version.build.as_str().rsplit('.').next() != Some(&sha)
})
parsed_fetched_version
.as_ref()
.ok()
.is_none_or(|version| version.build.as_str() != sha)
})
.unwrap_or(true);
let newer_version = should_download
@@ -719,12 +717,9 @@ impl AutoUpdater {
}
fn check_if_fetched_version_is_newer_non_nightly(
mut installed_version: Version,
installed_version: Version,
fetched_version: Version,
) -> Result<Option<VersionCheckType>> {
// For non-nightly releases, ignore build and pre-release fields as they're not provided by our endpoints right now.
installed_version.build = semver::BuildMetadata::EMPTY;
installed_version.pre = semver::Prerelease::EMPTY;
let should_download = fetched_version > installed_version;
let newer_version = should_download.then(|| VersionCheckType::Semantic(fetched_version));
Ok(newer_version)

View File

@@ -20,7 +20,6 @@ gpui.workspace = true
http_client.workspace = true
markdown_preview.workspace = true
release_channel.workspace = true
semver.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true

View File

@@ -148,9 +148,7 @@ pub fn notify_if_app_was_updated(cx: &mut App) {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
cx.update(|cx| {
let mut version = updater.read(cx).current_version();
version.build = semver::BuildMetadata::EMPTY;
version.pre = semver::Prerelease::EMPTY;
let version = updater.read(cx).current_version();
let app_name = ReleaseChannel::global(cx).display_name();
show_app_notification(
NotificationId::unique::<UpdateNotification>(),

View File

@@ -1,7 +1,7 @@
use futures::channel::oneshot;
use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, TaskLabel};
use language::{BufferRow, Language, LanguageRegistry};
use language::{Language, LanguageRegistry};
use rope::Rope;
use std::{
cmp::Ordering,
@@ -11,7 +11,7 @@ use std::{
sync::{Arc, LazyLock},
};
use sum_tree::SumTree;
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _, ToPoint as _};
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _};
use util::ResultExt;
pub static CALCULATE_DIFF_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new);
@@ -88,7 +88,6 @@ struct PendingHunk {
#[derive(Debug, Clone)]
pub struct DiffHunkSummary {
buffer_range: Range<Anchor>,
diff_base_byte_range: Range<usize>,
}
impl sum_tree::Item for InternalDiffHunk {
@@ -97,7 +96,6 @@ impl sum_tree::Item for InternalDiffHunk {
fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
DiffHunkSummary {
buffer_range: self.buffer_range.clone(),
diff_base_byte_range: self.diff_base_byte_range.clone(),
}
}
}
@@ -108,7 +106,6 @@ impl sum_tree::Item for PendingHunk {
fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
DiffHunkSummary {
buffer_range: self.buffer_range.clone(),
diff_base_byte_range: self.diff_base_byte_range.clone(),
}
}
}
@@ -119,7 +116,6 @@ impl sum_tree::Summary for DiffHunkSummary {
fn zero(_cx: Self::Context<'_>) -> Self {
DiffHunkSummary {
buffer_range: Anchor::MIN..Anchor::MIN,
diff_base_byte_range: 0..0,
}
}
@@ -129,15 +125,6 @@ impl sum_tree::Summary for DiffHunkSummary {
.start
.min(&other.buffer_range.start, buffer);
self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
self.diff_base_byte_range.start = self
.diff_base_byte_range
.start
.min(other.diff_base_byte_range.start);
self.diff_base_byte_range.end = self
.diff_base_byte_range
.end
.max(other.diff_base_byte_range.end);
}
}
@@ -318,54 +305,6 @@ impl BufferDiffSnapshot {
let (new_id, new_empty) = (right.remote_id(), right.is_empty());
new_id == old_id || (new_empty && old_empty)
}
pub fn row_to_base_text_row(&self, row: BufferRow, buffer: &text::BufferSnapshot) -> u32 {
// TODO(split-diff) expose a parameter to reuse a cursor to avoid repeatedly seeking from the start
// Find the last hunk that starts before this position.
let mut cursor = self.inner.hunks.cursor::<DiffHunkSummary>(buffer);
let position = buffer.anchor_before(Point::new(row, 0));
cursor.seek(&position, Bias::Left);
if cursor
.item()
.is_none_or(|hunk| hunk.buffer_range.start.cmp(&position, buffer).is_gt())
{
cursor.prev();
}
let unclipped_point = if let Some(hunk) = cursor.item()
&& hunk.buffer_range.start.cmp(&position, buffer).is_le()
{
let mut unclipped_point = cursor
.end()
.diff_base_byte_range
.end
.to_point(self.base_text());
if position.cmp(&cursor.end().buffer_range.end, buffer).is_ge() {
unclipped_point +=
Point::new(row, 0) - cursor.end().buffer_range.end.to_point(buffer);
}
// Move the cursor so that at the next step we can clip with the start of the next hunk.
cursor.next();
unclipped_point
} else {
// Position is before the added region for the first hunk.
debug_assert!(self.inner.hunks.first().is_none_or(|first_hunk| {
position.cmp(&first_hunk.buffer_range.start, buffer).is_le()
}));
Point::new(row, 0)
};
let max_point = if let Some(next_hunk) = cursor.item() {
next_hunk
.diff_base_byte_range
.start
.to_point(self.base_text())
} else {
self.base_text().max_point()
};
unclipped_point.min(max_point).row
}
}
impl BufferDiffInner {
@@ -1007,7 +946,6 @@ impl BufferDiff {
if self.secondary_diff.is_some() {
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
diff_base_byte_range: 0..0,
});
cx.emit(BufferDiffEvent::DiffChanged {
changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)),
@@ -1297,7 +1235,7 @@ impl DiffHunk {
pub fn is_created_file(&self) -> bool {
self.diff_base_byte_range == (0..0)
&& self.buffer_range.start.is_min()
&& self.buffer_range.end.is_max()
&& self.buffer_range.end.is_min()
}
pub fn status(&self) -> DiffHunkStatus {
@@ -2302,62 +2240,4 @@ mod tests {
hunks = found_hunks;
}
}
#[gpui::test]
async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
let base_text = "
zero
one
two
three
four
five
six
seven
eight
"
.unindent();
let buffer_text = "
zero
ONE
two
NINE
five
seven
"
.unindent();
// zero
// - one
// + ONE
// two
// - three
// - four
// + NINE
// five
// - six
// seven
// + eight
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let buffer_snapshot = buffer.snapshot();
let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
let expected_results = [
// don't format me
(0, 0),
(1, 2),
(2, 2),
(3, 5),
(4, 5),
(5, 7),
(6, 9),
];
for (buffer_row, expected) in expected_results {
assert_eq!(
diff.row_to_base_text_row(buffer_row, &buffer_snapshot),
expected,
"{buffer_row}"
);
}
}
}

View File

@@ -59,11 +59,3 @@ pub fn agent_server_docs(cx: &App) -> String {
server_url = server_url(cx)
)
}
/// Returns the URL to Zed's edit prediction documentation.
pub fn edit_prediction_docs(cx: &App) -> String {
format!(
"{server_url}/docs/ai/edit-prediction",
server_url = server_url(cx)
)
}

View File

@@ -169,17 +169,6 @@ pub struct PredictEditsBody {
/// Info about the git repository state, only present when can_collect_data is true.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub git_info: Option<PredictEditsGitInfo>,
/// The trigger for this request.
#[serde(default)]
pub trigger: PredictEditsRequestTrigger,
}
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PredictEditsRequestTrigger {
Diagnostics,
Cli,
#[default]
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -211,31 +200,12 @@ pub struct RejectEditPredictionsBody {
pub rejections: Vec<EditPredictionRejection>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EditPredictionRejection {
pub request_id: String,
#[serde(default)]
pub reason: EditPredictionRejectReason,
pub was_shown: bool,
}
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum EditPredictionRejectReason {
/// New requests were triggered before this one completed
Canceled,
/// No edits returned
Empty,
/// Edits returned, but none remained after interpolation
InterpolatedEmpty,
/// The new prediction was preferred over the current one
Replaced,
/// The current prediction was preferred over the new one
CurrentPreferred,
/// The current prediction was discarded
#[default]
Discarded,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CompletionMode {

View File

@@ -9,7 +9,7 @@ use std::{
use strum::EnumIter;
use uuid::Uuid;
use crate::{PredictEditsGitInfo, PredictEditsRequestTrigger};
use crate::PredictEditsGitInfo;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanContextRetrievalRequest {
@@ -53,8 +53,6 @@ pub struct PredictEditsRequest {
pub prompt_max_bytes: Option<usize>,
#[serde(default)]
pub prompt_format: PromptFormat,
#[serde(default)]
pub trigger: PredictEditsRequestTrigger,
}
#[derive(Debug, Clone, Serialize, Deserialize)]

View File

@@ -64,16 +64,6 @@ async fn check_is_contributor(
}));
}
if ZedZippyBot::is_zed_zippy_bot(&params) {
return Ok(Json(CheckIsContributorResponse {
signed_at: Some(
ZedZippyBot::created_at()
.and_utc()
.to_rfc3339_opts(SecondsFormat::Millis, true),
),
}));
}
Ok(Json(CheckIsContributorResponse {
signed_at: app
.db
@@ -113,36 +103,6 @@ impl RenovateBot {
}
}
/// The Zed Zippy bot GitHub user (`zed-zippy[bot]`).
///
/// https://api.github.com/users/zed-zippy[bot]
struct ZedZippyBot;
impl ZedZippyBot {
const LOGIN: &'static str = "zed-zippy[bot]";
const USER_ID: i32 = 234243425;
/// Returns the `created_at` timestamp for the Zed Zippy bot user.
fn created_at() -> &'static NaiveDateTime {
static CREATED_AT: OnceLock<NaiveDateTime> = OnceLock::new();
CREATED_AT.get_or_init(|| {
chrono::DateTime::parse_from_rfc3339("2025-09-24T17:00:11Z")
.expect("failed to parse 'created_at' for 'zed-zippy[bot]'")
.naive_utc()
})
}
/// Returns whether the given contributor selector corresponds to the Zed Zippy bot user.
fn is_zed_zippy_bot(contributor: &ContributorSelector) -> bool {
match contributor {
ContributorSelector::GitHubLogin { github_login } => github_login == Self::LOGIN,
ContributorSelector::GitHubUserId { github_user_id } => {
github_user_id == &Self::USER_ID
}
}
}
}
#[derive(Debug, Deserialize)]
struct AddContributorBody {
github_user_id: i32,

View File

@@ -171,7 +171,7 @@ impl ChannelView {
let Some(markdown) = markdown else {
return;
};
buffer.set_language_immediate(Some(markdown), cx);
buffer.set_language(Some(markdown), cx);
})
})?;
@@ -541,7 +541,7 @@ impl Item for ChannelView {
})
}
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}

View File

@@ -8,9 +8,6 @@ license = "GPL-3.0-or-later"
[lints]
workspace = true
[features]
test-support = ["db/test-support"]
[lib]
path = "src/command_palette.rs"
doctest = false

View File

@@ -830,7 +830,7 @@ impl DapLogView {
async move |buffer, cx| {
let language = language.await.ok();
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(language, cx);
buffer.set_language(language, cx);
})
}
})
@@ -901,7 +901,7 @@ impl DapLogView {
async move |_, cx| {
let language = language.await.ok();
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(language, cx);
buffer.set_language(language, cx);
})
}
})
@@ -998,11 +998,7 @@ impl Item for DapLogView {
None
}
fn as_searchable(
&self,
handle: &Entity<Self>,
_: &App,
) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(handle.clone()))
}
}

View File

@@ -575,7 +575,7 @@ impl BreakpointList {
)
.with_horizontal_sizing_behavior(gpui::ListHorizontalSizingBehavior::Unconstrained)
.with_width_from_item(self.max_width_index)
.track_scroll(&self.scroll_handle)
.track_scroll(self.scroll_handle.clone())
.flex_1()
}
@@ -776,7 +776,7 @@ impl Render for BreakpointList {
.child(self.render_list(cx))
.custom_scrollbars(
ui::Scrollbars::new(ScrollAxes::Both)
.tracked_scroll_handle(&self.scroll_handle)
.tracked_scroll_handle(self.scroll_handle.clone())
.with_track_along(ScrollAxes::Both, cx.theme().colors().panel_background)
.tracked_entity(cx.entity_id()),
window,

View File

@@ -229,7 +229,7 @@ impl MemoryView {
rows
},
)
.track_scroll(&view_state.scroll_handle)
.track_scroll(view_state.scroll_handle)
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
.on_scroll_wheel(cx.listener(|this, evt: &ScrollWheelEvent, window, _| {
let mut view_state = this.view_state();
@@ -921,7 +921,7 @@ impl Render for MemoryView {
}))
.custom_scrollbars(
ui::Scrollbars::new(ui::ScrollAxes::Both)
.tracked_scroll_handle(&self.view_state_handle)
.tracked_scroll_handle(self.view_state_handle.clone())
.with_track_along(
ui::ScrollAxes::Both,
cx.theme().colors().panel_background,

View File

@@ -253,7 +253,7 @@ impl ModuleList {
range.map(|ix| this.render_entry(ix, cx)).collect()
}),
)
.track_scroll(&self.scroll_handle)
.track_scroll(self.scroll_handle.clone())
.size_full()
}
}
@@ -279,6 +279,6 @@ impl Render for ModuleList {
.size_full()
.p_1()
.child(self.render_list(window, cx))
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
}
}

View File

@@ -913,7 +913,7 @@ impl Render for StackFrameList {
)
})
.child(self.render_list(window, cx))
.vertical_scrollbar_for(&self.list_state, window, cx)
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
}
}

View File

@@ -1557,7 +1557,7 @@ impl Render for VariableList {
this.render_entries(range, window, cx)
}),
)
.track_scroll(&self.list_handle)
.track_scroll(self.list_handle.clone())
.with_width_from_item(self.max_width_index)
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.with_horizontal_sizing_behavior(gpui::ListHorizontalSizingBehavior::Unconstrained)
@@ -1574,10 +1574,10 @@ impl Render for VariableList {
)
.with_priority(1)
}))
// .vertical_scrollbar_for(&self.list_handle, window, cx)
// .vertical_scrollbar_for(self.list_handle.clone(), window, cx)
.custom_scrollbars(
ui::Scrollbars::new(ScrollAxes::Both)
.tracked_scroll_handle(&self.list_handle)
.tracked_scroll_handle(self.list_handle.clone())
.with_track_along(ScrollAxes::Both, cx.theme().colors().panel_background)
.tracked_entity(cx.entity_id()),
window,

View File

@@ -428,7 +428,7 @@ impl Item for StackTraceView {
}
}
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}

View File

@@ -224,7 +224,7 @@ fn main() {
.unwrap();
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(Arc::new(rust_lang())), cx);
buffer.set_language(Some(Arc::new(rust_lang())), cx);
});
let (editor, cx) = cx.add_window_view(|window, cx| {
@@ -1593,7 +1593,7 @@ def process_data(untyped_param, typed_param: int, another_typed: str):
.unwrap();
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(Arc::new(python_lang())), cx);
buffer.set_language(Some(Arc::new(python_lang())), cx);
});
let (editor, cx) = cx.add_window_view(|window, cx| {
@@ -2091,7 +2091,7 @@ async fn test_inline_values_util(
.unwrap();
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(Arc::new(language)), cx);
buffer.set_language(Some(Arc::new(language)), cx);
});
let (editor, cx) = cx.add_window_view(|window, cx| {

View File

@@ -890,7 +890,7 @@ impl Item for ProjectDiagnosticsEditor {
}
}
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}

View File

@@ -32,7 +32,6 @@ settings.workspace = true
supermaven.workspace = true
telemetry.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zeta.workspace = true

View File

@@ -11,7 +11,7 @@ use fs::Fs;
use gpui::{
Action, Animation, AnimationExt, App, AsyncWindowContext, Corner, Entity, FocusHandle,
Focusable, IntoElement, ParentElement, Render, Subscription, WeakEntity, actions, div,
ease_in_out, pulsating_between,
pulsating_between,
};
use indoc::indoc;
use language::{
@@ -34,7 +34,6 @@ use ui::{
Clickable, ContextMenu, ContextMenuEntry, DocumentationEdge, DocumentationSide, IconButton,
IconButtonShape, Indicator, PopoverMenu, PopoverMenuHandle, ProgressBar, Tooltip, prelude::*,
};
use util::ResultExt as _;
use workspace::{
StatusItemView, Toast, Workspace, create_and_open_local_file, item::ItemHandle,
notifications::NotificationId,
@@ -323,7 +322,7 @@ impl Render for EditPredictionButton {
let tooltip_meta = if self.user_store.read(cx).current_user().is_some() {
"Choose a Plan"
} else {
"Sign In To Use"
"Sign In"
};
return div().child(
@@ -358,7 +357,6 @@ impl Render for EditPredictionButton {
}
let show_editor_predictions = self.editor_show_predictions;
let user = self.user_store.read(cx).current_user();
let icon_button = IconButton::new("zed-predict-pending-button", zeta_icon)
.shape(IconButtonShape::Square)
@@ -374,18 +372,10 @@ impl Render for EditPredictionButton {
},
)
.when(!self.popover_menu_handle.is_deployed(), |element| {
let user = user.clone();
element.tooltip(move |_window, cx| {
if enabled {
if show_editor_predictions {
Tooltip::for_action("Edit Prediction", &ToggleMenu, cx)
} else if user.is_none() {
Tooltip::with_meta(
"Edit Prediction",
Some(&ToggleMenu),
"Sign In To Use",
cx,
)
} else {
Tooltip::with_meta(
"Edit Prediction",
@@ -408,25 +398,11 @@ impl Render for EditPredictionButton {
let this = cx.weak_entity();
let mut popover_menu = PopoverMenu::new("zeta")
.when(user.is_some(), |popover_menu| {
let this = this.clone();
popover_menu.menu(move |window, cx| {
this.update(cx, |this, cx| {
this.build_zeta_context_menu(provider, window, cx)
})
.ok()
})
})
.when(user.is_none(), |popover_menu| {
let this = this.clone();
popover_menu.menu(move |window, cx| {
this.update(cx, |this, cx| {
this.build_zeta_upsell_context_menu(window, cx)
})
.ok()
.menu(move |window, cx| {
this.update(cx, |this, cx| {
this.build_zeta_context_menu(provider, window, cx)
})
.ok()
})
.anchor(Corner::BottomRight)
.with_handle(self.popover_menu_handle.clone());
@@ -1069,55 +1045,6 @@ impl EditPredictionButton {
})
}
fn build_zeta_upsell_context_menu(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ContextMenu> {
ContextMenu::build(window, cx, |mut menu, _window, cx| {
menu = menu
.custom_row(move |_window, cx| {
let description = indoc! {
"Sign in for 2,000 worth of accepted suggestions at every keystroke, \
powered by Zeta, our open-source, open-data model."
};
v_flex()
.max_w_64()
.h(rems_from_px(148.))
.child(render_zeta_tab_animation(cx))
.child(Label::new("Edit Prediction"))
.child(
Label::new(description)
.color(Color::Muted)
.size(LabelSize::Small),
)
.into_any_element()
})
.separator()
.entry("Sign In & Start Using", None, |window, cx| {
let client = Client::global(cx);
window
.spawn(cx, async move |cx| {
client
.sign_in_with_optional_connect(true, &cx)
.await
.log_err();
})
.detach();
})
.link(
"Learn More",
OpenBrowser {
url: zed_urls::edit_prediction_docs(cx),
}
.boxed_clone(),
);
menu
})
}
pub fn update_enabled(&mut self, editor: Entity<Editor>, cx: &mut Context<Self>) {
let editor = editor.read(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -1321,66 +1248,6 @@ fn toggle_edit_prediction_mode(fs: Arc<dyn Fs>, mode: EditPredictionsMode, cx: &
}
}
fn render_zeta_tab_animation(cx: &App) -> impl IntoElement {
let tab = |n: u64, inverted: bool| {
let text_color = cx.theme().colors().text;
h_flex().child(
h_flex()
.text_size(TextSize::XSmall.rems(cx))
.text_color(text_color)
.child("tab")
.with_animation(
ElementId::Integer(n),
Animation::new(Duration::from_secs(4)).repeat(),
move |tab, delta| {
let n_f32 = n as f32;
let delta = if inverted {
(delta - 0.15 * (5.0 - n_f32)) / 0.7
} else {
(delta - 0.15 * n_f32) / 0.7
};
let delta = 1.0 - (0.5 - delta).abs() * 2.;
let delta = ease_in_out(delta.clamp(0., 1.));
let delta = 0.1 + 0.5 * delta;
tab.text_color(text_color.opacity(delta))
},
),
)
};
let tab_sequence = |inverted: bool| {
h_flex()
.gap_1()
.child(tab(0, inverted))
.child(tab(1, inverted))
.child(tab(2, inverted))
.child(tab(3, inverted))
.child(tab(4, inverted))
};
h_flex()
.my_1p5()
.p_4()
.justify_center()
.gap_2()
.rounded_xs()
.border_1()
.border_dashed()
.border_color(cx.theme().colors().border)
.bg(gpui::pattern_slash(
cx.theme().colors().border.opacity(0.5),
1.,
8.,
))
.child(tab_sequence(true))
.child(Icon::new(IconName::ZedPredict))
.child(tab_sequence(false))
}
fn copilot_settings_url(enterprise_uri: Option<&str>) -> String {
match enterprise_uri {
Some(uri) => {

View File

@@ -475,8 +475,7 @@ mod tests {
use util::test::{generate_marked_text, marked_text_offsets_by};
fn create_buffer(text: &str, cx: &mut TestAppContext) -> BufferSnapshot {
let buffer =
cx.new(|cx| Buffer::local(text, cx).with_language_immediate(rust_lang().into(), cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(rust_lang().into(), cx));
buffer.read_with(cx, |buffer, _| buffer.snapshot())
}

View File

@@ -1100,7 +1100,7 @@ mod test {
) {
let buffer = cx.new(|cx| {
let mut buffer = Buffer::local(source, cx);
buffer.set_language_immediate(Some(language.clone()), cx);
buffer.set_language(Some(language.clone()), cx);
buffer
});
cx.run_until_parked();

View File

@@ -148,9 +148,8 @@ mod test {
}
fn create_buffer(text: &str, cx: &mut TestAppContext) -> BufferSnapshot {
let buffer = cx.new(|cx| {
language::Buffer::local(text, cx).with_language_immediate(rust_lang().into(), cx)
});
let buffer =
cx.new(|cx| language::Buffer::local(text, cx).with_language(rust_lang().into(), cx));
buffer.read_with(cx, |buffer, _| buffer.snapshot())
}

View File

@@ -531,7 +531,7 @@ impl SyntaxIndex {
let buffer = cx.new(|cx| {
let mut buffer = Buffer::local(loaded_file.text, cx);
buffer.set_language_immediate(Some(language.clone()), cx);
buffer.set_language(Some(language.clone()), cx);
buffer
})?;

View File

@@ -41,7 +41,6 @@ dap.workspace = true
db.workspace = true
buffer_diff.workspace = true
emojis.workspace = true
feature_flags.workspace = true
file_icons.workspace = true
futures.workspace = true
fuzzy.workspace = true

View File

@@ -45,7 +45,7 @@ fn to_tab_point_benchmark(c: &mut Criterion) {
&snapshot,
|bench, snapshot| {
bench.iter(|| {
snapshot.fold_point_to_tab_point(fold_point);
snapshot.to_tab_point(fold_point);
});
},
);
@@ -79,7 +79,7 @@ fn to_fold_point_benchmark(c: &mut Criterion) {
);
let (_, snapshot) = TabMap::new(fold_snapshot, NonZeroU32::new(4).unwrap());
let tab_point = snapshot.fold_point_to_tab_point(fold_point);
let tab_point = snapshot.to_tab_point(fold_point);
(length, snapshot, tab_point)
};
@@ -94,7 +94,7 @@ fn to_fold_point_benchmark(c: &mut Criterion) {
&snapshot,
|bench, snapshot| {
bench.iter(|| {
snapshot.tab_point_to_fold_point(tab_point, Bias::Left);
snapshot.to_fold_point(tab_point, Bias::Left);
});
},
);

View File

@@ -7,7 +7,6 @@ use std::ops::Range;
use crate::Editor;
use collections::HashMap;
use gpui::{Context, HighlightStyle};
use itertools::Itertools;
use language::language_settings;
use multi_buffer::{Anchor, ExcerptId};
use ui::{ActiveTheme, utils::ensure_minimum_contrast};
@@ -27,20 +26,17 @@ impl Editor {
let accents_count = cx.theme().accents().0.len();
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
let all_excerpts = self.buffer().read(cx).excerpt_ids();
let anchors_in_multi_buffer = |current_excerpt: ExcerptId,
text_anchors: [text::Anchor; 4]|
-> Option<[Option<_>; 4]> {
let anchor_in_multi_buffer = |current_excerpt: ExcerptId, text_anchor: text::Anchor| {
multi_buffer_snapshot
.anchors_in_excerpt(current_excerpt, text_anchors)
.anchor_in_excerpt(current_excerpt, text_anchor)
.or_else(|| {
all_excerpts
.iter()
.filter(|&&excerpt_id| excerpt_id != current_excerpt)
.find_map(|&excerpt_id| {
multi_buffer_snapshot.anchors_in_excerpt(excerpt_id, text_anchors)
multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)
})
})?
.collect_array()
})
};
let bracket_matches_by_accent = self.visible_excerpts(cx).into_iter().fold(
@@ -81,24 +77,13 @@ impl Editor {
let buffer_close_range = buffer_snapshot
.anchor_before(pair.close_range.start)
..buffer_snapshot.anchor_after(pair.close_range.end);
let [
buffer_open_range_start,
buffer_open_range_end,
buffer_close_range_start,
buffer_close_range_end,
] = anchors_in_multi_buffer(
excerpt_id,
[
buffer_open_range.start,
buffer_open_range.end,
buffer_close_range.start,
buffer_close_range.end,
],
)?;
let multi_buffer_open_range =
buffer_open_range_start.zip(buffer_open_range_end);
anchor_in_multi_buffer(excerpt_id, buffer_open_range.start)
.zip(anchor_in_multi_buffer(excerpt_id, buffer_open_range.end));
let multi_buffer_close_range =
buffer_close_range_start.zip(buffer_close_range_end);
anchor_in_multi_buffer(excerpt_id, buffer_close_range.start).zip(
anchor_in_multi_buffer(excerpt_id, buffer_close_range.end),
);
let mut ranges = Vec::with_capacity(2);
if let Some((open_start, open_end)) = multi_buffer_open_range {
@@ -176,7 +161,7 @@ mod tests {
use gpui::{AppContext as _, UpdateGlobal as _};
use indoc::indoc;
use itertools::Itertools;
use language::{Capability, markdown_lang};
use language::Capability;
use languages::rust_lang;
use multi_buffer::{ExcerptRange, MultiBuffer};
use pretty_assertions::assert_eq;
@@ -276,31 +261,6 @@ where
);
}
#[gpui::test]
async fn test_markdown_bracket_colorization(cx: &mut gpui::TestAppContext) {
init_test(cx, |language_settings| {
language_settings.defaults.colorize_brackets = Some(true);
});
let mut cx = EditorLspTestContext::new(
Arc::into_inner(markdown_lang()).unwrap(),
lsp::ServerCapabilities::default(),
cx,
)
.await;
cx.set_state(indoc! {r#"ˇ[LLM-powered features](./ai/overview.md), [bring and configure your own API keys](./ai/llm-providers.md#use-your-own-keys)"#});
cx.executor().advance_clock(Duration::from_millis(100));
cx.executor().run_until_parked();
assert_eq!(
r#"«1[LLM-powered features]1»«1(./ai/overview.md)1», «1[bring and configure your own API keys]1»«1(./ai/llm-providers.md#use-your-own-keys)1»
1 hsla(207.80, 16.20%, 69.19%, 1.00)
"#,
&bracket_colors_markup(&mut cx),
"All markdown brackets should be colored based on their depth"
);
}
#[gpui::test]
async fn test_bracket_colorization_when_editing(cx: &mut gpui::TestAppContext) {
init_test(cx, |language_settings| {

View File

@@ -49,8 +49,6 @@ pub const MENU_GAP: Pixels = px(4.);
pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);
pub const MENU_ASIDE_MIN_WIDTH: Pixels = px(260.);
pub const MENU_ASIDE_MAX_WIDTH: Pixels = px(500.);
pub const COMPLETION_MENU_MIN_WIDTH: Pixels = px(280.);
pub const COMPLETION_MENU_MAX_WIDTH: Pixels = px(540.);
// Constants for the markdown cache. The purpose of this cache is to reduce flickering due to
// documentation not yet being parsed.
@@ -909,36 +907,33 @@ impl CompletionsMenu {
})
});
div()
.min_w(COMPLETION_MENU_MIN_WIDTH)
.max_w(COMPLETION_MENU_MAX_WIDTH)
.child(
ListItem::new(mat.candidate_id)
.inset(true)
.toggle_state(item_ix == selected_item)
.on_click(cx.listener(move |editor, _event, window, cx| {
cx.stop_propagation();
if let Some(task) = editor.confirm_completion(
&ConfirmCompletion {
item_ix: Some(item_ix),
},
window,
cx,
) {
task.detach_and_log_err(cx)
}
}))
.start_slot::<AnyElement>(start_slot)
.child(h_flex().overflow_hidden().child(completion_label))
.end_slot::<Label>(documentation_label),
)
div().min_w(px(280.)).max_w(px(540.)).child(
ListItem::new(mat.candidate_id)
.inset(true)
.toggle_state(item_ix == selected_item)
.on_click(cx.listener(move |editor, _event, window, cx| {
cx.stop_propagation();
if let Some(task) = editor.confirm_completion(
&ConfirmCompletion {
item_ix: Some(item_ix),
},
window,
cx,
) {
task.detach_and_log_err(cx)
}
}))
.start_slot::<AnyElement>(start_slot)
.child(h_flex().overflow_hidden().child(completion_label))
.end_slot::<Label>(documentation_label),
)
})
.collect()
}),
)
.occlude()
.max_h(max_height_in_lines as f32 * window.line_height())
.track_scroll(&self.scroll_handle)
.track_scroll(self.scroll_handle.clone())
.with_sizing_behavior(ListSizingBehavior::Infer)
.map(|this| {
if self.display_options.dynamic_width {
@@ -953,7 +948,7 @@ impl CompletionsMenu {
div().child(list).custom_scrollbars(
Scrollbars::for_settings::<CompletionMenuScrollBarSetting>()
.show_along(ScrollAxes::Vertical)
.tracked_scroll_handle(&self.scroll_handle),
.tracked_scroll_handle(self.scroll_handle.clone()),
window,
cx,
),
@@ -1604,7 +1599,7 @@ impl CodeActionsMenu {
)
.occlude()
.max_h(max_height_in_lines as f32 * window.line_height())
.track_scroll(&self.scroll_handle)
.track_scroll(self.scroll_handle.clone())
.with_width_from_item(
self.actions
.iter()

View File

@@ -43,9 +43,7 @@ pub use inlay_map::{InlayOffset, InlayPoint};
pub use invisibles::{is_invisible, replacement};
use collections::{HashMap, HashSet};
use gpui::{
App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle, WeakEntity,
};
use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
use language::{Point, Subscription as BufferSubscription, language_settings::language_settings};
use multi_buffer::{
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16,
@@ -100,14 +98,6 @@ type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHi
/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
///
/// A [`DisplayMap`] can have a `companion`, which is another [`DisplayMap`]
/// that is kept (roughly) in sync with this one. This is used to implement
/// side-by-side diffing.
///
/// Many mutating APIs on [`DisplayMap`] will apply the same mutations to the
/// `companion`, but some do not. These APIs are documented as not keeping the
/// two sides in sync.
///
/// See the [module level documentation](self) for more information.
pub struct DisplayMap {
/// The buffer that we are displaying.
@@ -129,8 +119,6 @@ pub struct DisplayMap {
inlay_highlights: InlayHighlights,
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
crease_map: CreaseMap,
/// Corresponds to leader/follower multibuffers (used in side-by-side diff)
companion: Option<WeakEntity<DisplayMap>>,
pub(crate) fold_placeholder: FoldPlaceholder,
pub clip_at_line_ends: bool,
pub(crate) masked: bool,
@@ -177,53 +165,10 @@ impl DisplayMap {
inlay_highlights: Default::default(),
clip_at_line_ends: false,
masked: false,
companion: None,
}
}
/// Link two [`DisplayMap`]s to be each other's companions, and then sync
/// them with respect to each other.
///
/// Both `self` and `other` must not have a companion when this method is called.
pub fn link_companions(&mut self, other: Entity<DisplayMap>, cx: &mut Context<Self>) {
debug_assert!(self.companion.is_none());
debug_assert!(other.read(cx).companion.is_none());
// Sync each side separately first to get up-to-date WrapSnapshots
let (display_snapshot, _) = self.sync(cx);
self.companion = Some(other.downgrade());
let other_snapshot = other.update(cx, {
let this = cx.weak_entity();
|other, cx| {
let (other_snapshot, _) = other.sync(cx);
other.companion = Some(this);
other_snapshot
}
});
// Recompute both block maps to reflect the presence of the companion
self.block_map.read(
display_snapshot.wrap_snapshot.clone(),
Some(other_snapshot.wrap_snapshot()),
text::Patch::new(vec![text::Edit {
old: wrap_map::WrapRow(0)..display_snapshot.wrap_snapshot.max_point().row(),
new: wrap_map::WrapRow(0)..display_snapshot.wrap_snapshot.max_point().row(),
}]),
);
other.update(cx, |other, _| {
other.block_map.read(
other_snapshot.wrap_snapshot().clone(),
Some(display_snapshot.wrap_snapshot()),
text::Patch::new(vec![text::Edit {
old: wrap_map::WrapRow(0)..other_snapshot.wrap_snapshot.max_point().row(),
new: wrap_map::WrapRow(0)..other_snapshot.wrap_snapshot.max_point().row(),
}]),
);
});
}
#[inline]
fn sync(&mut self, cx: &mut Context<Self>) -> (DisplaySnapshot, Option<DisplaySnapshot>) {
pub fn snapshot(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {
let tab_size = Self::tab_size(&self.buffer, cx);
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
@@ -231,61 +176,12 @@ impl DisplayMap {
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot, edits);
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot, edits, tab_size);
let (wrap_snapshot, wrap_edits) = self
let (wrap_snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(tab_snapshot, edits, cx));
let block_snapshot = self.block_map.read(wrap_snapshot, edits).snapshot;
let (block_snapshot, companion_snapshot) = if let Some(companion) = self
.companion
.as_ref()
.and_then(|companion| companion.upgrade())
{
companion.update(cx, |companion, cx| {
let tab_size = Self::tab_size(&companion.buffer, cx);
let buffer_snapshot = companion.buffer.read(cx).snapshot(cx);
let edits = companion.buffer_subscription.consume().into_inner();
let (inlay_snapshot, edits) = companion.inlay_map.sync(buffer_snapshot, edits);
let (fold_snapshot, edits) = companion.fold_map.read(inlay_snapshot, edits);
let (tab_snapshot, edits) = companion.tab_map.sync(fold_snapshot, edits, tab_size);
let (companion_wrap_snapshot, companion_wrap_edits) = companion
.wrap_map
.update(cx, |map, cx| map.sync(tab_snapshot, edits, cx));
let companion_block_snapshot = companion
.block_map
.read(
companion_wrap_snapshot.clone(),
Some(&wrap_snapshot),
companion_wrap_edits,
)
.snapshot;
let companion_snapshot = DisplaySnapshot {
block_snapshot: companion_block_snapshot,
diagnostics_max_severity: companion.diagnostics_max_severity,
crease_snapshot: companion.crease_map.snapshot(),
text_highlights: companion.text_highlights.clone(),
inlay_highlights: companion.inlay_highlights.clone(),
clip_at_line_ends: companion.clip_at_line_ends,
masked: companion.masked,
fold_placeholder: companion.fold_placeholder.clone(),
};
(
self.block_map
.read(wrap_snapshot, Some(&companion_wrap_snapshot), wrap_edits)
.snapshot,
Some(companion_snapshot),
)
})
} else {
(
self.block_map
.read(wrap_snapshot, None, wrap_edits)
.snapshot,
None,
)
};
let snapshot = DisplaySnapshot {
DisplaySnapshot {
block_snapshot,
diagnostics_max_severity: self.diagnostics_max_severity,
crease_snapshot: self.crease_map.snapshot(),
@@ -294,12 +190,7 @@ impl DisplayMap {
clip_at_line_ends: self.clip_at_line_ends,
masked: self.masked,
fold_placeholder: self.fold_placeholder.clone(),
};
(snapshot, companion_snapshot)
}
pub fn snapshot(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {
self.sync(cx).0
}
}
pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut Context<Self>) {
@@ -319,13 +210,17 @@ impl DisplayMap {
/// Creates folds for the given creases.
pub fn fold<T: Clone + ToOffset>(&mut self, creases: Vec<Crease<T>>, cx: &mut Context<Self>) {
let (display_snapshot, companion_snapshot) = self.sync(cx);
let companion_wrap_snapshot = companion_snapshot.as_ref().map(|s| s.wrap_snapshot());
let buffer_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 (mut fold_map, _, _) = self
.fold_map
.write(display_snapshot.inlay_snapshot.clone(), vec![]);
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(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));
self.block_map.read(snapshot, edits);
let inline = creases.iter().filter_map(|crease| {
if let Crease::Inline {
range, placeholder, ..
@@ -342,10 +237,7 @@ impl DisplayMap {
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self
.block_map
.write(snapshot, companion_wrap_snapshot, edits);
let mut block_map = self.block_map.write(snapshot, edits);
let blocks = creases.into_iter().filter_map(|crease| {
if let Crease::Block {
range,
@@ -371,10 +263,8 @@ impl DisplayMap {
blocks
.into_iter()
.map(|(range, render, height, style, priority)| {
let start = display_snapshot
.buffer_snapshot()
.anchor_before(range.start);
let end = display_snapshot.buffer_snapshot().anchor_after(range.end);
let start = buffer_snapshot.anchor_before(range.start);
let end = buffer_snapshot.anchor_after(range.end);
BlockProperties {
placement: BlockPlacement::Replace(start..=end),
render,
@@ -383,7 +273,6 @@ impl DisplayMap {
priority,
}
}),
companion_wrap_snapshot,
);
}
@@ -394,22 +283,22 @@ impl DisplayMap {
type_id: TypeId,
cx: &mut Context<Self>,
) {
let (display_snapshot, companion_snapshot) = self.sync(cx);
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 (mut fold_map, _, _) = self
.fold_map
.write(display_snapshot.inlay_snapshot.clone(), vec![]);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(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));
self.block_map.read(snapshot, edits);
let (snapshot, edits) = fold_map.remove_folds(ranges, type_id);
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));
self.block_map.write(
snapshot,
companion_snapshot.as_ref().map(|s| s.wrap_snapshot()),
edits,
);
self.block_map.write(snapshot, edits);
}
/// Removes any folds whose ranges intersect any of the given ranges.
@@ -419,137 +308,79 @@ impl DisplayMap {
inclusive: bool,
cx: &mut Context<Self>,
) {
let (display_snapshot, companion_snapshot) = self.sync(cx);
let companion_wrap_snapshot = companion_snapshot.as_ref().map(|s| s.wrap_snapshot());
let snapshot = self.buffer.read(cx).snapshot(cx);
let offset_ranges = ranges
.into_iter()
.map(|range| {
range.start.to_offset(&display_snapshot.buffer)
..range.end.to_offset(&display_snapshot.buffer)
})
.map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot))
.collect::<Vec<_>>();
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (mut fold_map, _, _) = self
.fold_map
.write(display_snapshot.inlay_snapshot.clone(), vec![]);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(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));
self.block_map.read(snapshot, edits);
let (snapshot, edits) =
fold_map.unfold_intersecting(offset_ranges.iter().cloned(), inclusive);
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 mut block_map = self
.block_map
.write(snapshot, companion_wrap_snapshot, edits);
block_map.remove_intersecting_replace_blocks(
offset_ranges,
inclusive,
companion_wrap_snapshot,
);
let mut block_map = self.block_map.write(snapshot, edits);
block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
}
pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
let (display_snapshot, companion_snapshot) = self.sync(cx);
let companion_wrap_snapshot = companion_snapshot.as_ref().map(|s| s.wrap_snapshot());
let mut block_map = self.block_map.write(
display_snapshot.wrap_snapshot().clone(),
companion_wrap_snapshot,
Default::default(),
);
block_map.disable_header_for_buffer(buffer_id);
if let Some((companion, companion_wrap_snapshot)) =
self.companion.as_ref().zip(companion_wrap_snapshot)
{
let _ = companion.update(cx, |companion, _| {
let mut block_map = companion.block_map.write(
companion_wrap_snapshot.clone(),
Some(display_snapshot.wrap_snapshot()),
Default::default(),
);
block_map.disable_header_for_buffer(buffer_id);
});
}
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 mut block_map = self.block_map.write(snapshot, edits);
block_map.disable_header_for_buffer(buffer_id)
}
pub fn fold_buffers(
&mut self,
buffer_ids: impl IntoIterator<Item = language::BufferId> + Clone,
buffer_ids: impl IntoIterator<Item = language::BufferId>,
cx: &mut Context<Self>,
) {
let (display_snapshot, companion_snapshot) = self.sync(cx);
let companion_wrap_snapshot = companion_snapshot.as_ref().map(|s| s.wrap_snapshot());
let mut block_map = self.block_map.write(
display_snapshot.wrap_snapshot().clone(),
companion_wrap_snapshot,
Default::default(),
);
block_map.fold_buffers(
buffer_ids.clone(),
self.buffer.read(cx),
companion_wrap_snapshot,
cx,
);
if let Some((companion, companion_wrap_snapshot)) =
self.companion.as_ref().zip(companion_wrap_snapshot)
{
let _ = companion.update(cx, |companion, cx| {
let mut block_map = companion.block_map.write(
companion_wrap_snapshot.clone(),
Some(display_snapshot.wrap_snapshot()),
Default::default(),
);
block_map.fold_buffers(
buffer_ids,
companion.buffer.read(cx),
Some(display_snapshot.wrap_snapshot()),
cx,
);
});
}
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 mut block_map = self.block_map.write(snapshot, edits);
block_map.fold_buffers(buffer_ids, self.buffer.read(cx), cx)
}
pub fn unfold_buffers(
&mut self,
buffer_ids: impl IntoIterator<Item = language::BufferId> + Clone,
buffer_ids: impl IntoIterator<Item = language::BufferId>,
cx: &mut Context<Self>,
) {
let (display_snapshot, companion_snapshot) = self.sync(cx);
let companion_wrap_snapshot = companion_snapshot.as_ref().map(|s| s.wrap_snapshot());
let mut block_map = self.block_map.write(
display_snapshot.wrap_snapshot().clone(),
companion_wrap_snapshot,
Default::default(),
);
block_map.unfold_buffers(
buffer_ids.clone(),
self.buffer.read(cx),
companion_wrap_snapshot,
cx,
);
if let Some((companion, companion_wrap_snapshot)) =
self.companion.as_ref().zip(companion_wrap_snapshot)
{
companion
.update(cx, |companion, cx| {
let mut block_map = companion.block_map.write(
companion_wrap_snapshot.clone(),
Some(display_snapshot.wrap_snapshot()),
Default::default(),
);
block_map.unfold_buffers(
buffer_ids,
companion.buffer.read(cx),
Some(&display_snapshot.wrap_snapshot()),
cx,
);
})
.ok();
}
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 mut block_map = self.block_map.write(snapshot, edits);
block_map.unfold_buffers(buffer_ids, self.buffer.read(cx), cx)
}
pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
@@ -578,61 +409,54 @@ impl DisplayMap {
self.crease_map.remove(crease_ids, &snapshot)
}
/// Warning: does not keep companion in sync
pub fn insert_blocks(
&mut self,
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
cx: &mut Context<Self>,
) -> Vec<CustomBlockId> {
debug_assert!(self.companion.is_none());
let (display_snapshot, companion_snapshot) = self.sync(cx);
let companion_wrap_snapshot = companion_snapshot.as_ref().map(|s| s.wrap_snapshot());
let mut block_map = self.block_map.write(
display_snapshot.wrap_snapshot().clone(),
companion_wrap_snapshot,
Default::default(),
);
block_map.insert(blocks, companion_wrap_snapshot)
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 mut block_map = self.block_map.write(snapshot, edits);
block_map.insert(blocks)
}
/// Warning: does not keep companion in sync
pub fn resize_blocks(&mut self, heights: HashMap<CustomBlockId, u32>, cx: &mut Context<Self>) {
debug_assert!(self.companion.is_none());
let (display_snapshot, companion_snapshot) = self.sync(cx);
let wrap_companion_snapshot = companion_snapshot.as_ref().map(|s| s.wrap_snapshot());
let mut block_map = self.block_map.write(
display_snapshot.wrap_snapshot().clone(),
wrap_companion_snapshot,
Default::default(),
);
block_map.resize(heights, wrap_companion_snapshot);
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 mut block_map = self.block_map.write(snapshot, edits);
block_map.resize(heights);
}
/// Warning: does not keep companion in sync
pub fn replace_blocks(&mut self, renderers: HashMap<CustomBlockId, RenderBlock>) {
debug_assert!(self.companion.is_none());
self.block_map.replace_blocks(renderers);
}
/// Warning: does not keep companion in sync
pub fn remove_blocks(&mut self, ids: HashSet<CustomBlockId>, cx: &mut Context<Self>) {
debug_assert!(self.companion.is_none());
let (display_snapshot, companion_snapshot) = self.sync(cx);
let wrap_companion_snapshot = companion_snapshot.as_ref().map(|s| s.wrap_snapshot());
let mut block_map = self.block_map.write(
display_snapshot.wrap_snapshot().clone(),
wrap_companion_snapshot,
Default::default(),
);
block_map.remove(ids, wrap_companion_snapshot);
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 mut block_map = self.block_map.write(snapshot, edits);
block_map.remove(ids);
}
pub fn row_for_block(
@@ -640,12 +464,16 @@ impl DisplayMap {
block_id: CustomBlockId,
cx: &mut Context<Self>,
) -> Option<DisplayRow> {
let (display_snapshot, _) = self.sync(cx);
let block_map = self.block_map.read(
display_snapshot.wrap_snapshot.clone(),
None,
Default::default(),
);
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))
}
@@ -741,23 +569,24 @@ impl DisplayMap {
widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
cx: &mut Context<Self>,
) -> bool {
let (display_snapshot, companion_snapshot) = self.sync(cx);
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 (mut fold_map, _, _) = self
.fold_map
.write(display_snapshot.inlay_snapshot.clone(), vec![]);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(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));
self.block_map.read(snapshot, edits);
let (snapshot, edits) = fold_map.update_fold_widths(widths);
let widths_changed = !edits.is_empty();
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));
self.block_map.read(
snapshot,
companion_snapshot.as_ref().map(|s| s.wrap_snapshot()),
edits,
);
self.block_map.read(snapshot, edits);
widths_changed
}
@@ -775,20 +604,24 @@ impl DisplayMap {
if to_remove.is_empty() && to_insert.is_empty() {
return;
}
let (_, companion_snapshot) = self.sync(cx);
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let tab_size = Self::tab_size(&self.buffer, cx);
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));
self.block_map.read(snapshot, edits);
let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
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));
self.block_map.read(
snapshot,
companion_snapshot.as_ref().map(|s| s.wrap_snapshot()),
edits,
);
self.block_map.read(snapshot, edits);
}
fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
@@ -1056,7 +889,7 @@ impl DisplaySnapshot {
pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
let inlay_point = self.inlay_snapshot().to_inlay_point(point);
let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
let tab_point = self.tab_snapshot().to_tab_point(fold_point);
let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
let block_point = self.block_snapshot.to_block_point(wrap_point);
DisplayPoint(block_point)
@@ -1086,10 +919,7 @@ impl DisplaySnapshot {
let block_point = point.0;
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
let fold_point = self
.tab_snapshot()
.tab_point_to_fold_point(tab_point, bias)
.0;
let fold_point = self.tab_snapshot().to_fold_point(tab_point, bias).0;
fold_point.to_inlay_point(self.fold_snapshot())
}
@@ -1097,13 +927,11 @@ impl DisplaySnapshot {
let block_point = point.0;
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
self.tab_snapshot()
.tab_point_to_fold_point(tab_point, bias)
.0
self.tab_snapshot().to_fold_point(tab_point, bias).0
}
pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
let tab_point = self.tab_snapshot().to_tab_point(fold_point);
let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
let block_point = self.block_snapshot.to_block_point(wrap_point);
DisplayPoint(block_point)
@@ -1756,10 +1584,7 @@ impl DisplayPoint {
pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> MultiBufferOffset {
let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
let fold_point = map
.tab_snapshot()
.tab_point_to_fold_point(tab_point, bias)
.0;
let fold_point = map.tab_snapshot().to_fold_point(tab_point, bias).0;
let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
map.inlay_snapshot()
.to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
@@ -2377,7 +2202,7 @@ pub mod tests {
})
});
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
@@ -2479,7 +2304,7 @@ pub mod tests {
cx.update(|cx| init_test(cx, |_| {}));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
@@ -2814,7 +2639,7 @@ pub mod tests {
cx.update(|cx| init_test(cx, |_| {}));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
@@ -2901,7 +2726,7 @@ pub mod tests {
let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));

File diff suppressed because it is too large Load Diff

View File

@@ -734,13 +734,6 @@ impl FoldSnapshot {
}
}
pub fn fold_point_cursor(&self) -> FoldPointCursor<'_> {
let cursor = self
.transforms
.cursor::<Dimensions<InlayPoint, FoldPoint>>(());
FoldPointCursor { cursor }
}
pub fn len(&self) -> FoldOffset {
FoldOffset(self.transforms.summary().output.len)
}
@@ -934,31 +927,6 @@ impl FoldSnapshot {
}
}
pub struct FoldPointCursor<'transforms> {
cursor: Cursor<'transforms, 'static, Transform, Dimensions<InlayPoint, FoldPoint>>,
}
impl FoldPointCursor<'_> {
pub fn map(&mut self, point: InlayPoint, bias: Bias) -> FoldPoint {
let cursor = &mut self.cursor;
if cursor.did_seek() {
cursor.seek_forward(&point, Bias::Right);
} else {
cursor.seek(&point, Bias::Right);
}
if cursor.item().is_some_and(|t| t.is_fold()) {
if bias == Bias::Left || point == cursor.start().0 {
cursor.start().1
} else {
cursor.end().1
}
} else {
let overshoot = point.0 - cursor.start().0.0;
FoldPoint(cmp::min(cursor.start().1.0 + overshoot, cursor.end().1.0))
}
}
}
fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: MBTextSummary) {
let mut did_merge = false;
transforms.update_last(

View File

@@ -879,16 +879,37 @@ impl InlaySnapshot {
}
}
}
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
self.inlay_point_cursor().map(point)
}
pub fn inlay_point_cursor(&self) -> InlayPointCursor<'_> {
let cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(());
InlayPointCursor {
cursor,
transforms: &self.transforms,
let mut cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(());
cursor.seek(&point, Bias::Left);
loop {
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
if point == cursor.end().0 {
while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
if inlay.position.bias() == Bias::Right {
break;
} else {
cursor.next();
}
}
return cursor.end().1;
} else {
let overshoot = point - cursor.start().0;
return InlayPoint(cursor.start().1.0 + overshoot);
}
}
Some(Transform::Inlay(inlay)) => {
if inlay.position.bias() == Bias::Left {
cursor.next();
} else {
return cursor.start().1;
}
}
None => {
return self.max_point();
}
}
}
}
@@ -1141,51 +1162,6 @@ impl InlaySnapshot {
}
}
pub struct InlayPointCursor<'transforms> {
cursor: Cursor<'transforms, 'static, Transform, Dimensions<Point, InlayPoint>>,
transforms: &'transforms SumTree<Transform>,
}
impl InlayPointCursor<'_> {
pub fn map(&mut self, point: Point) -> InlayPoint {
let cursor = &mut self.cursor;
if cursor.did_seek() {
cursor.seek_forward(&point, Bias::Left);
} else {
cursor.seek(&point, Bias::Left);
}
loop {
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
if point == cursor.end().0 {
while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
if inlay.position.bias() == Bias::Right {
break;
} else {
cursor.next();
}
}
return cursor.end().1;
} else {
let overshoot = point - cursor.start().0;
return InlayPoint(cursor.start().1.0 + overshoot);
}
}
Some(Transform::Inlay(inlay)) => {
if inlay.position.bias() == Bias::Left {
cursor.next();
} else {
return cursor.start().1;
}
}
None => {
return InlayPoint(self.transforms.summary().output.lines);
}
}
}
}
}
fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: MBTextSummary) {
if summary.len == MultiBufferOffset(0) {
return;

View File

@@ -137,10 +137,10 @@ impl TabMap {
let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
TabEdit {
old: old_snapshot.fold_point_to_tab_point(old_start)
..old_snapshot.fold_point_to_tab_point(old_end),
new: new_snapshot.fold_point_to_tab_point(new_start)
..new_snapshot.fold_point_to_tab_point(new_end),
old: old_snapshot.to_tab_point(old_start)
..old_snapshot.to_tab_point(old_end),
new: new_snapshot.to_tab_point(new_start)
..new_snapshot.to_tab_point(new_end),
}
})
.collect()
@@ -183,7 +183,7 @@ impl TabSnapshot {
pub fn line_len(&self, row: u32) -> u32 {
let max_point = self.max_point();
if row < max_point.row() {
self.fold_point_to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
.0
.column
} else {
@@ -196,8 +196,8 @@ impl TabSnapshot {
}
pub fn text_summary_for_range(&self, range: Range<TabPoint>) -> TextSummary {
let input_start = self.tab_point_to_fold_point(range.start, Bias::Left).0;
let input_end = self.tab_point_to_fold_point(range.end, Bias::Right).0;
let input_start = self.to_fold_point(range.start, Bias::Left).0;
let input_end = self.to_fold_point(range.end, Bias::Right).0;
let input_summary = self
.fold_snapshot
.text_summary_for_range(input_start..input_end);
@@ -241,11 +241,11 @@ impl TabSnapshot {
highlights: Highlights<'a>,
) -> TabChunks<'a> {
let (input_start, expanded_char_column, to_next_stop) =
self.tab_point_to_fold_point(range.start, Bias::Left);
self.to_fold_point(range.start, Bias::Left);
let input_column = input_start.column();
let input_start = input_start.to_offset(&self.fold_snapshot);
let input_end = self
.tab_point_to_fold_point(range.end, Bias::Right)
.to_fold_point(range.end, Bias::Right)
.0
.to_offset(&self.fold_snapshot);
let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {
@@ -292,28 +292,24 @@ impl TabSnapshot {
}
pub fn max_point(&self) -> TabPoint {
self.fold_point_to_tab_point(self.fold_snapshot.max_point())
self.to_tab_point(self.fold_snapshot.max_point())
}
pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {
self.fold_point_to_tab_point(
self.to_tab_point(
self.fold_snapshot
.clip_point(self.tab_point_to_fold_point(point, bias).0, bias),
.clip_point(self.to_fold_point(point, bias).0, bias),
)
}
pub fn fold_point_to_tab_point(&self, input: FoldPoint) -> TabPoint {
pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint {
let chunks = self.fold_snapshot.chunks_at(FoldPoint::new(input.row(), 0));
let tab_cursor = TabStopCursor::new(chunks);
let expanded = self.expand_tabs(tab_cursor, input.column());
TabPoint::new(input.row(), expanded)
}
pub fn tab_point_cursor(&self) -> TabPointCursor<'_> {
TabPointCursor { this: self }
}
pub fn tab_point_to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {
let chunks = self
.fold_snapshot
.chunks_at(FoldPoint::new(output.row(), 0));
@@ -330,14 +326,14 @@ impl TabSnapshot {
)
}
pub fn point_to_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
self.fold_point_to_tab_point(fold_point)
self.to_tab_point(fold_point)
}
pub fn tab_point_to_point(&self, point: TabPoint, bias: Bias) -> Point {
let fold_point = self.tab_point_to_fold_point(point, bias).0;
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
let fold_point = self.to_fold_point(point, bias).0;
let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
self.fold_snapshot
.inlay_snapshot
@@ -436,17 +432,6 @@ impl TabSnapshot {
}
}
// todo(lw): Implement TabPointCursor properly
pub struct TabPointCursor<'this> {
this: &'this TabSnapshot,
}
impl TabPointCursor<'_> {
pub fn map(&mut self, point: FoldPoint) -> TabPoint {
self.this.fold_point_to_tab_point(point)
}
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct TabPoint(pub Point);
@@ -542,14 +527,13 @@ pub struct TabChunks<'a> {
impl TabChunks<'_> {
pub(crate) fn seek(&mut self, range: Range<TabPoint>) {
let (input_start, expanded_char_column, to_next_stop) = self
.snapshot
.tab_point_to_fold_point(range.start, Bias::Left);
let (input_start, expanded_char_column, to_next_stop) =
self.snapshot.to_fold_point(range.start, Bias::Left);
let input_column = input_start.column();
let input_start = input_start.to_offset(&self.snapshot.fold_snapshot);
let input_end = self
.snapshot
.tab_point_to_fold_point(range.end, Bias::Right)
.to_fold_point(range.end, Bias::Right)
.0
.to_offset(&self.snapshot.fold_snapshot);
let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {
@@ -820,23 +804,23 @@ mod tests {
assert_eq!(
tab_snapshot.expected_to_fold_point(range.start, Bias::Left),
tab_snapshot.tab_point_to_fold_point(range.start, Bias::Left),
tab_snapshot.to_fold_point(range.start, Bias::Left),
"Failed with tab_point at column {ix}"
);
assert_eq!(
tab_snapshot.expected_to_fold_point(range.start, Bias::Right),
tab_snapshot.tab_point_to_fold_point(range.start, Bias::Right),
tab_snapshot.to_fold_point(range.start, Bias::Right),
"Failed with tab_point at column {ix}"
);
assert_eq!(
tab_snapshot.expected_to_fold_point(range.end, Bias::Left),
tab_snapshot.tab_point_to_fold_point(range.end, Bias::Left),
tab_snapshot.to_fold_point(range.end, Bias::Left),
"Failed with tab_point at column {ix}"
);
assert_eq!(
tab_snapshot.expected_to_fold_point(range.end, Bias::Right),
tab_snapshot.tab_point_to_fold_point(range.end, Bias::Right),
tab_snapshot.to_fold_point(range.end, Bias::Right),
"Failed with tab_point at column {ix}"
);
}
@@ -856,7 +840,7 @@ mod tests {
// This should panic with the expected vs actual mismatch
let tab_point = TabPoint::new(0, 9);
let result = tab_snapshot.tab_point_to_fold_point(tab_point, Bias::Left);
let result = tab_snapshot.to_fold_point(tab_point, Bias::Left);
let expected = tab_snapshot.expected_to_fold_point(tab_point, Bias::Left);
assert_eq!(result, expected);
@@ -900,26 +884,26 @@ mod tests {
assert_eq!(
tab_snapshot.expected_to_fold_point(range.start, Bias::Left),
tab_snapshot.tab_point_to_fold_point(range.start, Bias::Left),
tab_snapshot.to_fold_point(range.start, Bias::Left),
"Failed with input: {}, with idx: {ix}",
input
);
assert_eq!(
tab_snapshot.expected_to_fold_point(range.start, Bias::Right),
tab_snapshot.tab_point_to_fold_point(range.start, Bias::Right),
tab_snapshot.to_fold_point(range.start, Bias::Right),
"Failed with input: {}, with idx: {ix}",
input
);
assert_eq!(
tab_snapshot.expected_to_fold_point(range.end, Bias::Left),
tab_snapshot.tab_point_to_fold_point(range.end, Bias::Left),
tab_snapshot.to_fold_point(range.end, Bias::Left),
"Failed with input: {}, with idx: {ix}",
input
);
assert_eq!(
tab_snapshot.expected_to_fold_point(range.end, Bias::Right),
tab_snapshot.tab_point_to_fold_point(range.end, Bias::Right),
tab_snapshot.to_fold_point(range.end, Bias::Right),
"Failed with input: {}, with idx: {ix}",
input
);
@@ -959,13 +943,13 @@ mod tests {
let input_point = Point::new(0, ix as u32);
let output_point = Point::new(0, output.find(c).unwrap() as u32);
assert_eq!(
tab_snapshot.fold_point_to_tab_point(FoldPoint(input_point)),
tab_snapshot.to_tab_point(FoldPoint(input_point)),
TabPoint(output_point),
"to_tab_point({input_point:?})"
);
assert_eq!(
tab_snapshot
.tab_point_to_fold_point(TabPoint(output_point), Bias::Left)
.to_fold_point(TabPoint(output_point), Bias::Left)
.0,
FoldPoint(input_point),
"to_fold_point({output_point:?})"
@@ -1154,7 +1138,7 @@ mod tests {
let column = rng.random_range(0..=max_column + 10);
let fold_point = FoldPoint::new(row, column);
let actual = tab_snapshot.fold_point_to_tab_point(fold_point);
let actual = tab_snapshot.to_tab_point(fold_point);
let expected = tab_snapshot.expected_to_tab_point(fold_point);
assert_eq!(

View File

@@ -622,10 +622,9 @@ impl WrapSnapshot {
if transforms.item().is_some_and(|t| t.is_isomorphic()) {
input_start.0 += output_start.0 - transforms.start().0.0;
}
let input_end = self.to_tab_point(output_end);
let max_point = self.tab_snapshot.max_point();
let input_start = input_start.min(max_point);
let input_end = input_end.min(max_point);
let input_end = self
.to_tab_point(output_end)
.min(self.tab_snapshot.max_point());
WrapChunks {
input_chunks: self.tab_snapshot.chunks(
input_start..input_end,
@@ -778,12 +777,11 @@ impl WrapSnapshot {
}
pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point {
self.tab_snapshot
.tab_point_to_point(self.to_tab_point(point), bias)
self.tab_snapshot.to_point(self.to_tab_point(point), bias)
}
pub fn make_wrap_point(&self, point: Point, bias: Bias) -> WrapPoint {
self.tab_point_to_wrap_point(self.tab_snapshot.point_to_tab_point(point, bias))
self.tab_point_to_wrap_point(self.tab_snapshot.make_tab_point(point, bias))
}
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
@@ -793,14 +791,6 @@ impl WrapSnapshot {
WrapPoint(start.1.0 + (point.0 - start.0.0))
}
pub fn wrap_point_cursor(&self) -> WrapPointCursor<'_> {
WrapPointCursor {
cursor: self
.transforms
.cursor::<Dimensions<TabPoint, WrapPoint>>(()),
}
}
pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
if bias == Bias::Left {
let (start, _, item) = self
@@ -922,22 +912,6 @@ impl WrapSnapshot {
}
}
pub struct WrapPointCursor<'transforms> {
cursor: Cursor<'transforms, 'static, Transform, Dimensions<TabPoint, WrapPoint>>,
}
impl WrapPointCursor<'_> {
pub fn map(&mut self, point: TabPoint) -> WrapPoint {
let cursor = &mut self.cursor;
if cursor.did_seek() {
cursor.seek_forward(&point, Bias::Right);
} else {
cursor.seek(&point, Bias::Right);
}
WrapPoint(cursor.start().1.0 + (point.0 - cursor.start().0.0))
}
}
impl WrapChunks<'_> {
pub(crate) fn seek(&mut self, rows: Range<WrapRow>) {
let output_start = WrapPoint::new(rows.start, 0);
@@ -947,10 +921,10 @@ impl WrapChunks<'_> {
if self.transforms.item().is_some_and(|t| t.is_isomorphic()) {
input_start.0 += output_start.0 - self.transforms.start().0.0;
}
let input_end = self.snapshot.to_tab_point(output_end);
let max_point = self.snapshot.tab_snapshot.max_point();
let input_start = input_start.min(max_point);
let input_end = input_end.min(max_point);
let input_end = self
.snapshot
.to_tab_point(output_end)
.min(self.snapshot.tab_snapshot.max_point());
self.input_chunks.seek(input_start..input_end);
self.input_chunk = Chunk::default();
self.output_position = output_start;
@@ -1056,7 +1030,6 @@ impl Iterator for WrapRows<'_> {
RowInfo {
buffer_id: None,
buffer_row: None,
base_text_row: None,
multibuffer_row: None,
diff_status,
expand_info: None,

View File

@@ -36,7 +36,6 @@ mod persistence;
mod rust_analyzer_ext;
pub mod scroll;
mod selections_collection;
mod split;
pub mod tasks;
#[cfg(test)]
@@ -70,7 +69,6 @@ pub use multi_buffer::{
MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
ToPoint,
};
pub use split::SplittableEditor;
pub use text::Bias;
use ::git::{
@@ -1200,7 +1198,6 @@ pub struct Editor {
applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
accent_overrides: Vec<SharedString>,
fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
use_base_text_line_numbers: bool,
}
fn debounce_value(debounce_ms: u64) -> Option<Duration> {
@@ -1640,7 +1637,7 @@ pub(crate) struct FocusedBlock {
focus_handle: WeakFocusHandle,
}
#[derive(Clone, Debug)]
#[derive(Clone)]
enum JumpData {
MultiBufferRow {
row: MultiBufferRow,
@@ -2347,7 +2344,6 @@ impl Editor {
applicable_language_settings: HashMap::default(),
accent_overrides: Vec::new(),
fetched_tree_sitter_chunks: HashMap::default(),
use_base_text_line_numbers: false,
};
if is_minimap {
@@ -8424,14 +8420,8 @@ impl Editor {
(true, true) => ui::IconName::DebugDisabledLogBreakpoint,
};
let color = cx.theme().colors();
let color = if is_phantom {
if collides_with_existing {
Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
} else {
Color::Hint
}
Color::Hint
} else if is_rejected {
Color::Disabled
} else {
@@ -19208,10 +19198,6 @@ impl Editor {
self.display_map.read(cx).fold_placeholder.clone()
}
pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
self.use_base_text_line_numbers = show;
}
pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
self.buffer.update(cx, |buffer, cx| {
buffer.set_all_diff_hunks_expanded(cx);
@@ -21579,22 +21565,12 @@ impl Editor {
return Vec::new();
}
let theme_settings = theme::ThemeSettings::get_global(cx);
theme_settings
theme::ThemeSettings::get_global(cx)
.theme_overrides
.get(cx.theme().name.as_ref())
.map(|theme_style| &theme_style.accents)
.into_iter()
.flatten()
.chain(
theme_settings
.experimental_theme_overrides
.as_ref()
.map(|overrides| &overrides.accents)
.into_iter()
.flatten(),
)
.flat_map(|accent| accent.0.clone())
.collect()
}

View File

@@ -35,9 +35,7 @@ use language_settings::Formatter;
use languages::markdown_lang;
use languages::rust_lang;
use lsp::CompletionParams;
use multi_buffer::{
IndentGuide, MultiBufferFilterMode, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
};
use multi_buffer::{IndentGuide, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey};
use parking_lot::Mutex;
use pretty_assertions::{assert_eq, assert_ne};
use project::{
@@ -2878,7 +2876,7 @@ async fn test_delete_to_bracket(cx: &mut TestAppContext) {
);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(r#"macro!("// ˇCOMMENT");"#);
cx.update_editor(|editor, window, cx| {
@@ -3109,7 +3107,7 @@ async fn test_newline_yaml(cx: &mut TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(yaml_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
// Object (between 2 fields)
cx.set_state(indoc! {"
@@ -3272,7 +3270,7 @@ async fn test_newline_above(cx: &mut TestAppContext) {
);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
const a: ˇA = (
@@ -3320,7 +3318,7 @@ async fn test_newline_below(cx: &mut TestAppContext) {
);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
const a: ˇA = (
@@ -3367,7 +3365,7 @@ async fn test_newline_comments(cx: &mut TestAppContext) {
));
{
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
// Fooˇ
"});
@@ -3439,7 +3437,7 @@ async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext)
));
{
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
//ˇ
"});
@@ -3486,7 +3484,7 @@ async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
{
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
/**ˇ
"});
@@ -3695,7 +3693,7 @@ async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(lua_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
// Line with line comment should extend
cx.set_state(indoc! {"
@@ -3816,7 +3814,7 @@ async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppConte
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
.unwrap(),
);
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test when all cursors are not at suggested indent
// then simply move to their suggested indent location
@@ -4040,7 +4038,7 @@ async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
fn a() {
if b {
@@ -4139,7 +4137,7 @@ async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext
let mut cx = EditorTestContext::new(cx).await;
let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(yaml_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
cx.set_state(
r#"ˇ# ingress:
@@ -4174,7 +4172,7 @@ async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppCon
let mut cx = EditorTestContext::new(cx).await;
let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(yaml_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
cx.set_state(
r#"ˇingress:
@@ -4329,10 +4327,9 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
));
let toml_buffer =
cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language_immediate(toml_language, cx));
let rust_buffer = cx.new(|cx| {
Buffer::local("const c: usize = 3;\n", cx).with_language_immediate(rust_language, cx)
});
cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
let rust_buffer =
cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
let multibuffer = cx.new(|cx| {
let mut multibuffer = MultiBuffer::new(ReadWrite);
multibuffer.push_excerpts(
@@ -5031,7 +5028,7 @@ async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
None,
));
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(js_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
cx.set_state(indoc! {"
«testˇ»
@@ -5080,7 +5077,7 @@ async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
None,
));
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(js_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
cx.set_state(indoc! {"
«testˇ»
@@ -5123,7 +5120,7 @@ async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestApp
None,
));
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(plaintext_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
cx.set_state(indoc! {"
«testˇ»
@@ -6569,7 +6566,7 @@ async fn test_rewrap(cx: &mut TestAppContext) {
language: Arc<Language>,
cx: &mut EditorTestContext,
) {
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(unwrapped_text);
cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
cx.assert_editor_state(wrapped_text);
@@ -6974,7 +6971,7 @@ async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
language: Arc<Language>,
cx: &mut EditorTestContext,
) {
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(unwrapped_text);
cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
cx.assert_editor_state(wrapped_text);
@@ -6986,7 +6983,7 @@ async fn test_hard_wrap(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(git_commit_lang()), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
cx.update_editor(|editor, _, cx| {
editor.set_hard_wrap(Some(14), cx);
});
@@ -7425,7 +7422,7 @@ async fn test_paste_multiline(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(rust_lang()), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
// Cut an indented block, without the leading whitespace.
cx.set_state(indoc! {"
@@ -7567,7 +7564,7 @@ async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(rust_lang()), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
cx.set_state(indoc! {"
fn a() {
@@ -8971,7 +8968,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
"#
.unindent();
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
@@ -9156,7 +9153,7 @@ async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContex
let text = "let a = 2;";
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
@@ -9226,7 +9223,7 @@ async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppCon
"#
.unindent();
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
@@ -9403,7 +9400,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte
"#
.unindent();
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
@@ -9577,7 +9574,7 @@ async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
));
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
@@ -9790,7 +9787,7 @@ async fn test_autoindent(cx: &mut TestAppContext) {
let text = "fn a() {}";
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
editor
@@ -9859,7 +9856,7 @@ async fn test_autoindent_disabled(cx: &mut TestAppContext) {
let text = "fn a() {}";
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
editor
@@ -9998,7 +9995,7 @@ async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext)
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
cx.set_state(r#"struct A {ˇ}"#);
@@ -10085,9 +10082,7 @@ async fn test_autoindent_selections(cx: &mut TestAppContext) {
let buffer = editor.buffer().update(cx, |buffer, _| {
buffer.all_buffers().iter().next().unwrap().clone()
});
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(rust_lang()), cx)
});
buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
buffer
});
@@ -10180,7 +10175,7 @@ async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
cx.set_state(
@@ -10369,7 +10364,7 @@ async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppCont
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
cx.set_state(
@@ -10512,7 +10507,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
cx.executor().run_until_parked();
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(html_language), cx);
buffer.set_language(Some(html_language), cx);
});
cx.set_state(
@@ -10690,7 +10685,7 @@ async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
cx.language_registry().add(rust_language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(rust_language), cx);
buffer.set_language(Some(rust_language), cx);
});
cx.set_state(
@@ -10797,7 +10792,7 @@ async fn test_surround_with_pair(cx: &mut TestAppContext) {
"#
.unindent();
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
editor
@@ -10947,7 +10942,7 @@ async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
"#
.unindent();
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
editor
@@ -11075,7 +11070,7 @@ async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
cx.set_state(
@@ -11143,7 +11138,7 @@ async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
Some(tree_sitter_rust::LANGUAGE.into()),
));
let buffer = cx.new(|cx| Buffer::local("", cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
editor
@@ -13180,7 +13175,7 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
cx.set_state(
@@ -13321,7 +13316,7 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestA
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
// Ensure that signature_help is not called when no signature help is enabled.
@@ -15903,7 +15898,7 @@ async fn test_toggle_comment(cx: &mut TestAppContext) {
},
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// If multiple selections intersect a line, the line is only toggled once.
cx.set_state(indoc! {"
@@ -16024,7 +16019,7 @@ async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
},
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
let toggle_comments = &ToggleComments {
advance_downwards: false,
@@ -16154,7 +16149,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
let toggle_comments = &ToggleComments {
@@ -16312,7 +16307,7 @@ async fn test_toggle_block_comment(cx: &mut TestAppContext) {
cx.language_registry().add(html_language.clone());
cx.language_registry().add(javascript_language);
cx.update_buffer(|buffer, cx| {
buffer.set_language_immediate(Some(html_language), cx);
buffer.set_language(Some(html_language), cx);
});
// Toggle comments for empty selections
@@ -16747,7 +16742,7 @@ async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
"{{} }\n", //
);
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
editor
@@ -17642,7 +17637,7 @@ async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppC
cx,
);
buffer.set_language_registry(language_registry.clone());
buffer.set_language_immediate(Some(markdown_lang()), cx);
buffer.set_language(Some(markdown_lang()), cx);
buffer
});
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
@@ -22454,7 +22449,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
let editor = cx.new_window_entity(|window, cx| {
@@ -23943,7 +23938,7 @@ async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
)
.unwrap(),
);
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
<span>ˇ</span>
@@ -24828,7 +24823,7 @@ async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
},
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
));
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// Test typing > does not extend linked pair
cx.set_state("<divˇ<div></div>");
@@ -25028,7 +25023,7 @@ async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestApp
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test cursor move to start of each line on tab
// for `if`, `elif`, `else`, `while`, `with` and `for`
@@ -25138,7 +25133,7 @@ async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test `else` auto outdents when typed inside `if` block
cx.set_state(indoc! {"
@@ -25370,7 +25365,7 @@ async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
});
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test correct indent after newline on comment
cx.set_state(indoc! {"
@@ -25431,7 +25426,7 @@ async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppCo
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test cursor move to start of each line on tab
// for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
@@ -25525,7 +25520,7 @@ async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test indents on comment insert
cx.set_state(indoc! {"
@@ -25567,7 +25562,7 @@ async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test `else` auto outdents when typed inside `if` block
cx.set_state(indoc! {"
@@ -25720,7 +25715,7 @@ async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
});
let mut cx = EditorTestContext::new(cx).await;
let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
// test correct indent after newline on comment
cx.set_state(indoc! {"
@@ -27019,7 +27014,7 @@ async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
}
"#;
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
@@ -27211,7 +27206,7 @@ async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
cx.update_editor(|editor, window, cx| {
@@ -27241,7 +27236,7 @@ async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
cx.set_state(&format!(
"Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
));
@@ -27280,7 +27275,7 @@ async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_ma
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
cx.update_editor(|editor, window, cx| {
@@ -27308,7 +27303,7 @@ async fn test_paste_plain_text_from_other_app_replaces_selection_without_creatin
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
cx.update_editor(|editor, window, cx| {
@@ -27336,7 +27331,7 @@ async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_mar
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
cx.update_editor(|editor, window, cx| {
@@ -27388,7 +27383,7 @@ async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_mult
.unwrap();
let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
first_buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(markdown_language.clone()), cx);
buffer.set_language(Some(markdown_language.clone()), cx);
});
editor
@@ -27691,7 +27686,7 @@ async fn test_sticky_scroll(cx: &mut TestAppContext) {
.as_singleton()
.unwrap()
.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(rust_lang()), cx);
buffer.set_language(Some(rust_lang()), cx);
})
});
@@ -27781,7 +27776,7 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
.as_singleton()
.unwrap()
.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(rust_lang()), cx);
buffer.set_language(Some(rust_lang()), cx);
})
});
@@ -28203,289 +28198,3 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
3
"});
}
#[gpui::test]
async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut leader_cx = EditorTestContext::new(cx).await;
let diff_base = indoc!(
r#"
one
two
three
four
five
six
"#
);
let initial_state = indoc!(
r#"
ˇone
two
THREE
four
five
six
"#
);
leader_cx.set_state(initial_state);
leader_cx.set_head_text(&diff_base);
leader_cx.run_until_parked();
let follower = leader_cx.update_multibuffer(|leader, cx| {
leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
leader.set_all_diff_hunks_expanded(cx);
leader.get_or_create_follower(cx)
});
follower.update(cx, |follower, cx| {
follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
follower.set_all_diff_hunks_expanded(cx);
});
let follower_editor =
leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
// leader_cx.window.focus(&follower_editor.focus_handle(cx));
let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
cx.run_until_parked();
leader_cx.assert_editor_state(initial_state);
follower_cx.assert_editor_state(indoc! {
r#"
ˇone
two
three
four
five
six
"#
});
follower_cx.editor(|editor, _window, cx| {
assert!(editor.read_only(cx));
});
leader_cx.update_editor(|editor, _window, cx| {
editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
});
cx.run_until_parked();
leader_cx.assert_editor_state(indoc! {
r#"
ˇone
two
THREE
four
FIVE
six
"#
});
follower_cx.assert_editor_state(indoc! {
r#"
ˇone
two
three
four
five
six
"#
});
leader_cx.update_editor(|editor, _window, cx| {
editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
});
cx.run_until_parked();
leader_cx.assert_editor_state(indoc! {
r#"
ˇone
two
THREE
four
FIVE
six
SEVEN"#
});
follower_cx.assert_editor_state(indoc! {
r#"
ˇone
two
three
four
five
six
"#
});
leader_cx.update_editor(|editor, window, cx| {
editor.move_down(&MoveDown, window, cx);
editor.refresh_selected_text_highlights(true, window, cx);
});
leader_cx.run_until_parked();
}
#[gpui::test]
async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let base_text = "base\n";
let buffer_text = "buffer\n";
let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
let leader = cx.new(|cx| {
let mut leader = MultiBuffer::new(Capability::ReadWrite);
leader.set_all_diff_hunks_expanded(cx);
leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
leader
});
let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
follower.update(cx, |follower, _| {
follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
});
leader.update(cx, |leader, cx| {
leader.insert_excerpts_after(
ExcerptId::min(),
extra_buffer_2.clone(),
vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
cx,
);
leader.add_diff(extra_diff_2.clone(), cx);
leader.insert_excerpts_after(
ExcerptId::min(),
extra_buffer_1.clone(),
vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
cx,
);
leader.add_diff(extra_diff_1.clone(), cx);
leader.insert_excerpts_after(
ExcerptId::min(),
buffer1.clone(),
vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
cx,
);
leader.add_diff(diff1.clone(), cx);
});
cx.run_until_parked();
let mut cx = cx.add_empty_window();
let leader_editor = cx
.new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
let follower_editor = cx.new_window_entity(|window, cx| {
Editor::for_multibuffer(follower.clone(), None, window, cx)
});
let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
leader_cx.assert_editor_state(indoc! {"
ˇbuffer
dummy text 1
dummy text 2
"});
let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
follower_cx.assert_editor_state(indoc! {"
ˇbase
"});
}
#[gpui::test]
async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (editor, cx) = cx.add_window_view(|window, cx| {
let multi_buffer = MultiBuffer::build_multi(
[
("1\n2\n3\n", vec![Point::row_range(0..3)]),
("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
],
cx,
);
Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
});
let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
cx.assert_excerpts_with_selections(indoc! {"
[EXCERPT]
ˇ1
2
3
[EXCERPT]
1
2
3
4
5
6
7
8
9
"});
cx.update_editor(|editor, window, cx| {
editor.change_selections(None.into(), window, cx, |s| {
s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
});
});
cx.assert_excerpts_with_selections(indoc! {"
[EXCERPT]
1
2
3
[EXCERPT]
1
2
3
4
5
6
ˇ7
8
9
"});
cx.update_editor(|editor, _window, cx| {
editor.set_vertical_scroll_margin(0, cx);
});
cx.update_editor(|editor, window, cx| {
assert_eq!(editor.vertical_scroll_margin(), 0);
editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 12.0)
);
});
cx.update_editor(|editor, _window, cx| {
editor.set_vertical_scroll_margin(3, cx);
});
cx.update_editor(|editor, window, cx| {
assert_eq!(editor.vertical_scroll_margin(), 3);
editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 9.0)
);
});
}

View File

@@ -89,7 +89,7 @@ use text::{BufferId, SelectionGoal};
use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
use ui::utils::ensure_minimum_contrast;
use ui::{
ButtonLike, ContextMenu, Indicator, KeyBinding, POPOVER_Y_PADDING, Tooltip, prelude::*,
ButtonLike, ContextMenu, Indicator, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*,
right_click_menu, scrollbars::ShowScrollbar, text_for_keystroke,
};
use unicode_segmentation::UnicodeSegmentation;
@@ -3274,8 +3274,6 @@ impl EditorElement {
line_number.clear();
let non_relative_number = if relative.wrapped() {
row_info.buffer_row.or(row_info.wrapped_buffer_row)? + 1
} else if self.editor.read(cx).use_base_text_line_numbers {
row_info.base_text_row?.0 + 1
} else {
row_info.buffer_row? + 1
};
@@ -3284,7 +3282,6 @@ impl EditorElement {
&& row_info
.diff_status
.is_some_and(|status| status.is_deleted())
&& !self.editor.read(cx).use_base_text_line_numbers
{
return None;
}
@@ -3816,13 +3813,6 @@ impl EditorElement {
result.into_any()
}
Block::Spacer { height, id: _ } => v_flex()
.id(block_id)
.debug_bg_magenta()
.opacity(0.3)
.w_full()
.h(u32::from(*height) as f32 * window.line_height())
.into_any(),
};
// Discover the element's content height, then round up to the nearest multiple of line height.
@@ -3979,14 +3969,9 @@ impl EditorElement {
.children(toggle_chevron_icon)
.tooltip({
let focus_handle = focus_handle.clone();
let is_folded_for_tooltip = is_folded;
move |_window, cx| {
Tooltip::with_meta_in(
if is_folded_for_tooltip {
"Unfold Excerpt"
} else {
"Fold Excerpt"
},
"Toggle Excerpt Fold",
Some(&ToggleFold),
format!(
"{} to toggle all",
@@ -4038,7 +4023,7 @@ impl EditorElement {
)
.child(
h_flex()
.size_3()
.size(rems_from_px(12.0))
.justify_center()
.flex_shrink_0()
.children(indicator),
@@ -4046,12 +4031,11 @@ impl EditorElement {
.child(
h_flex()
.cursor_pointer()
.id("path_header_block")
.min_w_0()
.id("path header block")
.size_full()
.justify_between()
.overflow_hidden()
.child(h_flex().min_w_0().flex_1().gap_0p5().map(|path_header| {
.child(h_flex().gap_0p5().map(|path_header| {
let filename = filename
.map(SharedString::from)
.unwrap_or_else(|| "untitled".into());
@@ -4061,19 +4045,28 @@ impl EditorElement {
let path = path::Path::new(filename.as_str());
let icon =
FileIcons::get_icon(path, cx).unwrap_or_default();
el.child(Icon::from_path(icon).color(Color::Muted))
let icon = Icon::from_path(icon).color(Color::Muted);
el.child(icon)
})
.child(
ButtonLike::new("filename-button")
.style(ButtonStyle::Subtle)
.child(
Label::new(filename)
.single_line()
.color(file_status_label_color(file_status))
.when(
file_status.is_some_and(|s| s.is_deleted()),
|label| label.strikethrough(),
),
div()
.child(
Label::new(filename)
.single_line()
.color(file_status_label_color(
file_status,
))
.when(
file_status.is_some_and(|s| {
s.is_deleted()
}),
|label| label.strikethrough(),
),
)
.group_hover("", |div| div.underline()),
)
.on_click(window.listener_for(&self.editor, {
let jump_data = jump_data.clone();
@@ -4088,11 +4081,11 @@ impl EditorElement {
})),
)
.when_some(parent_path, |then, path| {
then.child(Label::new(path).truncate().color(
then.child(div().child(path).text_color(
if file_status.is_some_and(FileStatus::is_deleted) {
Color::Custom(colors.text_disabled)
colors.text_disabled
} else {
Color::Custom(colors.text_muted)
colors.text_muted
},
))
})
@@ -4101,13 +4094,18 @@ impl EditorElement {
can_open_excerpts && is_selected && relative_path.is_some(),
|el| {
el.child(
Button::new("open-file-button", "Open File")
ButtonLike::new("open-file-button")
.style(ButtonStyle::OutlinedGhost)
.key_binding(KeyBinding::for_action_in(
&OpenExcerpts,
&focus_handle,
cx,
))
.child(
h_flex()
.gap_2p5()
.child(Label::new("Open file"))
.child(KeyBinding::for_action_in(
&OpenExcerpts,
&focus_handle,
cx,
)),
)
.on_click(window.listener_for(&self.editor, {
let jump_data = jump_data.clone();
move |editor, e: &ClickEvent, window, cx| {
@@ -10714,9 +10712,9 @@ impl ScrollbarLayout {
show_thumb: bool,
axis: ScrollbarAxis,
) -> Self {
let text_units_per_page = viewport_size.to_f64() / glyph_space.to_f64();
let text_units_per_page = f64::from(viewport_size / glyph_space);
let visible_range = scroll_position..scroll_position + text_units_per_page;
let total_text_units = scroll_range / glyph_space.to_f64();
let total_text_units = scroll_range / f64::from(glyph_space);
let thumb_percentage = text_units_per_page / total_text_units;
let thumb_size = Pixels::from(ScrollOffset::from(track_length) * thumb_percentage)

View File

@@ -914,7 +914,7 @@ impl InfoPopover {
)
.custom_scrollbars(
Scrollbars::for_settings::<EditorSettings>()
.tracked_scroll_handle(&self.scroll_handle),
.tracked_scroll_handle(self.scroll_handle.clone()),
window,
cx,
)
@@ -1012,7 +1012,7 @@ impl DiagnosticPopover {
)
.custom_scrollbars(
Scrollbars::for_settings::<EditorSettings>()
.tracked_scroll_handle(&self.scroll_handle),
.tracked_scroll_handle(self.scroll_handle.clone()),
window,
cx,
),

View File

@@ -929,11 +929,7 @@ impl Item for Editor {
})
}
fn as_searchable(
&self,
handle: &Entity<Self>,
_: &App,
) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(handle.clone()))
}
@@ -1139,7 +1135,7 @@ impl SerializableItem for Editor {
buffer.update(cx, |buffer, cx| {
buffer.set_language_registry(language_registry);
if let Some(language) = language {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
}
buffer.set_text(contents, cx);
if let Some(entry) = buffer.peek_undo_stack() {

View File

@@ -638,7 +638,7 @@ mod jsx_tag_autoclose_tests {
cx.update_buffer(|buffer, cx| {
let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
buffer.set_language_immediate(Some(language), cx)
buffer.set_language(Some(language), cx)
});
cx
@@ -802,7 +802,7 @@ mod jsx_tag_autoclose_tests {
let buffer_a = cx.new(|cx| {
let mut buf = language::Buffer::local("<div", cx);
buf.set_language_immediate(
buf.set_language(
Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
cx,
);
@@ -810,7 +810,7 @@ mod jsx_tag_autoclose_tests {
});
let buffer_b = cx.new(|cx| {
let mut buf = language::Buffer::local("<pre", cx);
buf.set_language_immediate(
buf.set_language(
Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
cx,
);

View File

@@ -276,8 +276,7 @@ pub fn deploy_context_menu(
!has_git_repo,
"Copy Permalink",
Box::new(CopyPermalinkToLine),
)
.action_disabled_when(!has_git_repo, "File History", Box::new(git::FileHistory));
);
match focus {
Some(focus) => builder.context(focus),
None => builder,

View File

@@ -205,7 +205,7 @@ pub fn expand_macro_recursively(
workspace.update_in(cx, |workspace, window, cx| {
buffer.update(cx, |buffer, cx| {
buffer.set_text(macro_expansion.expansion, cx);
buffer.set_language_immediate(Some(rust_language), cx);
buffer.set_language(Some(rust_language), cx);
buffer.set_capability(Capability::ReadOnly, cx);
});
let multibuffer =

View File

@@ -71,20 +71,14 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Editor>,
) {
let display_snapshot = self.display_snapshot(cx);
let scroll_margin_rows = self.vertical_scroll_margin() as u32;
let new_screen_top = self
.selections
.newest_display(&display_snapshot)
.newest_display(&self.display_snapshot(cx))
.head()
.row()
.0;
let header_offset = display_snapshot
.buffer_snapshot()
.show_headers()
.then(|| display_snapshot.buffer_header_height())
.unwrap_or(0);
let new_screen_top = new_screen_top.saturating_sub(scroll_margin_rows + header_offset);
let new_screen_top = new_screen_top.saturating_sub(scroll_margin_rows);
self.set_scroll_top_row(DisplayRow(new_screen_top), window, cx);
}

View File

@@ -391,7 +391,7 @@ impl SignatureHelpPopover {
)
}),
)
.vertical_scrollbar_for(&self.scroll_handle, window, cx);
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx);
let controls = if self.signatures.len() > 1 {
let prev_button = IconButton::new("signature_help_prev", IconName::ChevronUp)

View File

@@ -1,262 +0,0 @@
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use gpui::{
Action, AppContext as _, Entity, EventEmitter, Focusable, NoAction, Subscription, WeakEntity,
};
use multi_buffer::{MultiBuffer, MultiBufferFilterMode};
use project::Project;
use ui::{
App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,
Styled as _, Window, div,
};
use workspace::{
ActivePaneDecorator, Item, ItemHandle, Pane, PaneGroup, SplitDirection, Workspace,
};
use crate::{Editor, EditorEvent};
struct SplitDiffFeatureFlag;
impl FeatureFlag for SplitDiffFeatureFlag {
const NAME: &'static str = "split-diff";
fn enabled_for_staff() -> bool {
true
}
}
#[derive(Clone, Copy, PartialEq, Eq, Action, Default)]
#[action(namespace = editor)]
struct SplitDiff;
#[derive(Clone, Copy, PartialEq, Eq, Action, Default)]
#[action(namespace = editor)]
struct UnsplitDiff;
pub struct SplittableEditor {
primary_editor: Entity<Editor>,
secondary: Option<SecondaryEditor>,
panes: PaneGroup,
workspace: WeakEntity<Workspace>,
_subscriptions: Vec<Subscription>,
}
struct SecondaryEditor {
editor: Entity<Editor>,
pane: Entity<Pane>,
has_latest_selection: bool,
_subscriptions: Vec<Subscription>,
}
impl SplittableEditor {
pub fn primary_editor(&self) -> &Entity<Editor> {
&self.primary_editor
}
pub fn last_selected_editor(&self) -> &Entity<Editor> {
if let Some(secondary) = &self.secondary
&& secondary.has_latest_selection
{
&secondary.editor
} else {
&self.primary_editor
}
}
pub fn new_unsplit(
buffer: Entity<MultiBuffer>,
project: Entity<Project>,
workspace: Entity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let primary_editor =
cx.new(|cx| Editor::for_multibuffer(buffer, Some(project.clone()), window, cx));
let pane = cx.new(|cx| {
let mut pane = Pane::new(
workspace.downgrade(),
project,
Default::default(),
None,
NoAction.boxed_clone(),
true,
window,
cx,
);
pane.set_should_display_tab_bar(|_, _| false);
pane.add_item(primary_editor.boxed_clone(), true, true, None, window, cx);
pane
});
let panes = PaneGroup::new(pane);
// TODO(split-diff) we might want to tag editor events with whether they came from primary/secondary
let subscriptions =
vec![
cx.subscribe(&primary_editor, |this, _, event: &EditorEvent, cx| {
if let EditorEvent::SelectionsChanged { .. } = event
&& let Some(secondary) = &mut this.secondary
{
secondary.has_latest_selection = false;
}
cx.emit(event.clone())
}),
];
window.defer(cx, {
let workspace = workspace.downgrade();
let primary_editor = primary_editor.downgrade();
move |window, cx| {
workspace
.update(cx, |workspace, cx| {
primary_editor.update(cx, |editor, cx| {
editor.added_to_workspace(workspace, window, cx);
})
})
.ok();
}
});
Self {
primary_editor,
secondary: None,
panes,
workspace: workspace.downgrade(),
_subscriptions: subscriptions,
}
}
fn split(&mut self, _: &SplitDiff, window: &mut Window, cx: &mut Context<Self>) {
if !cx.has_flag::<SplitDiffFeatureFlag>() {
return;
}
if self.secondary.is_some() {
return;
}
let Some(workspace) = self.workspace.upgrade() else {
return;
};
let project = workspace.read(cx).project().clone();
let follower = self.primary_editor.update(cx, |primary, cx| {
primary.buffer().update(cx, |buffer, cx| {
let follower = buffer.get_or_create_follower(cx);
buffer.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
follower
})
});
follower.update(cx, |follower, _| {
follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
});
let secondary_editor = workspace.update(cx, |workspace, cx| {
cx.new(|cx| {
let mut editor = Editor::for_multibuffer(follower, Some(project), window, cx);
// TODO(split-diff) this should be at the multibuffer level
editor.set_use_base_text_line_numbers(true, cx);
editor.added_to_workspace(workspace, window, cx);
editor
})
});
let secondary_pane = cx.new(|cx| {
let mut pane = Pane::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
Default::default(),
None,
NoAction.boxed_clone(),
true,
window,
cx,
);
pane.set_should_display_tab_bar(|_, _| false);
pane.add_item(
ItemHandle::boxed_clone(&secondary_editor),
false,
false,
None,
window,
cx,
);
pane
});
let subscriptions =
vec![
cx.subscribe(&secondary_editor, |this, _, event: &EditorEvent, cx| {
if let EditorEvent::SelectionsChanged { .. } = event
&& let Some(secondary) = &mut this.secondary
{
secondary.has_latest_selection = true;
}
cx.emit(event.clone())
}),
];
self.secondary = Some(SecondaryEditor {
editor: secondary_editor,
pane: secondary_pane.clone(),
has_latest_selection: false,
_subscriptions: subscriptions,
});
let primary_pane = self.panes.first_pane();
self.panes
.split(&primary_pane, &secondary_pane, SplitDirection::Left)
.unwrap();
cx.notify();
}
fn unsplit(&mut self, _: &UnsplitDiff, _: &mut Window, cx: &mut Context<Self>) {
let Some(secondary) = self.secondary.take() else {
return;
};
self.panes.remove(&secondary.pane).unwrap();
self.primary_editor.update(cx, |primary, cx| {
primary.buffer().update(cx, |buffer, _| {
buffer.set_filter_mode(None);
});
});
cx.notify();
}
pub fn added_to_workspace(
&mut self,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.workspace = workspace.weak_handle();
self.primary_editor.update(cx, |primary_editor, cx| {
primary_editor.added_to_workspace(workspace, window, cx);
});
if let Some(secondary) = &self.secondary {
secondary.editor.update(cx, |secondary_editor, cx| {
secondary_editor.added_to_workspace(workspace, window, cx);
});
}
}
}
impl EventEmitter<EditorEvent> for SplittableEditor {}
impl Focusable for SplittableEditor {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.primary_editor.read(cx).focus_handle(cx)
}
}
impl Render for SplittableEditor {
fn render(
&mut self,
window: &mut ui::Window,
cx: &mut ui::Context<Self>,
) -> impl ui::IntoElement {
let Some(active) = self.panes.panes().into_iter().next() else {
return div().into_any_element();
};
div()
.id("splittable-editor")
.on_action(cx.listener(Self::split))
.on_action(cx.listener(Self::unsplit))
.size_full()
.child(self.panes.render(
None,
&ActivePaneDecorator::new(active, &self.workspace),
window,
cx,
))
.into_any_element()
}
}

View File

@@ -247,11 +247,6 @@ pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestCo
lines[row as usize].push_str("§ -----");
}
}
Block::Spacer { height, id: _ } => {
for row in row.0..row.0 + u32::from(height) {
lines[row as usize].push_str("@@@");
}
}
}
}
lines.join("\n")

View File

@@ -67,7 +67,7 @@ impl EditorTestContext {
.await
.unwrap();
buffer.update(cx, |buffer, cx| {
buffer.set_language_immediate(Some(language), cx);
buffer.set_language(Some(language), cx);
});
let editor = cx.add_window(|window, cx| {

View File

@@ -1704,12 +1704,12 @@ impl Render for ExtensionsPage {
if count == 0 {
this.child(self.render_empty_state(cx)).into_any_element()
} else {
let scroll_handle = &self.list;
let scroll_handle = self.list.clone();
this.child(
uniform_list("entries", count, cx.processor(Self::render_extensions))
.flex_grow()
.pb_4()
.track_scroll(scroll_handle),
.track_scroll(scroll_handle.clone()),
)
.vertical_scrollbar_for(scroll_handle, window, cx)
.into_any_element()

View File

@@ -1060,7 +1060,7 @@ impl FileFinderDelegate {
(
filename.to_string(),
Vec::new(),
prefix.display(path_style).to_string() + path_style.primary_separator(),
prefix.display(path_style).to_string() + path_style.separator(),
Vec::new(),
)
} else {
@@ -1071,7 +1071,7 @@ impl FileFinderDelegate {
.map_or(String::new(), |f| f.to_string_lossy().into_owned()),
Vec::new(),
entry_path.absolute.parent().map_or(String::new(), |path| {
path.to_string_lossy().into_owned() + path_style.primary_separator()
path.to_string_lossy().into_owned() + path_style.separator()
}),
Vec::new(),
)

View File

@@ -1598,7 +1598,7 @@ async fn test_history_match_positions(cx: &mut gpui::TestAppContext) {
assert_eq!(file_label.highlight_indices(), &[0, 1, 2]);
assert_eq!(
path_label.text(),
format!("test{}", PathStyle::local().primary_separator())
format!("test{}", PathStyle::local().separator())
);
assert_eq!(path_label.highlight_indices(), &[] as &[usize]);
});

View File

@@ -559,7 +559,7 @@ impl PickerDelegate for OpenPathDelegate {
parent_path,
candidate.path.string,
if candidate.is_dir {
path_style.primary_separator()
path_style.separator()
} else {
""
}
@@ -569,7 +569,7 @@ impl PickerDelegate for OpenPathDelegate {
parent_path,
candidate.path.string,
if candidate.is_dir {
path_style.primary_separator()
path_style.separator()
} else {
""
}
@@ -826,13 +826,7 @@ impl PickerDelegate for OpenPathDelegate {
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
Arc::from(
format!(
"[directory{}]filename.ext",
self.path_style.primary_separator()
)
.as_str(),
)
Arc::from(format!("[directory{}]filename.ext", self.path_style.separator()).as_str())
}
fn separators_after_indices(&self) -> Vec<usize> {

View File

@@ -33,7 +33,6 @@ tempfile.workspace = true
text.workspace = true
time.workspace = true
util.workspace = true
is_executable = "1.0.5"
[target.'cfg(target_os = "macos")'.dependencies]
fsevent.workspace = true

View File

@@ -138,7 +138,6 @@ impl GitRepository for FakeGitRepository {
path: RepoPath,
content: Option<String>,
_env: Arc<HashMap<String, String>>,
_is_executable: bool,
) -> BoxFuture<'_, anyhow::Result<()>> {
self.with_state_async(true, move |state| {
if let Some(message) = &state.simulated_index_write_error_message {
@@ -432,10 +431,6 @@ impl GitRepository for FakeGitRepository {
})
}
fn delete_branch(&self, _name: String) -> BoxFuture<'_, Result<()>> {
unimplemented!()
}
fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture<'_, Result<git::blame::Blame>> {
self.with_state_async(false, move |state| {
state
@@ -446,25 +441,6 @@ impl GitRepository for FakeGitRepository {
})
}
fn file_history(&self, path: RepoPath) -> BoxFuture<'_, Result<git::repository::FileHistory>> {
self.file_history_paginated(path, 0, None)
}
fn file_history_paginated(
&self,
path: RepoPath,
_skip: usize,
_limit: Option<usize>,
) -> BoxFuture<'_, Result<git::repository::FileHistory>> {
async move {
Ok(git::repository::FileHistory {
entries: Vec::new(),
path,
})
}
.boxed()
}
fn stage_paths(
&self,
paths: Vec<RepoPath>,
@@ -598,15 +574,7 @@ impl GitRepository for FakeGitRepository {
unimplemented!()
}
fn get_push_remote(&self, _branch: String) -> BoxFuture<'_, Result<Option<Remote>>> {
unimplemented!()
}
fn get_branch_remote(&self, _branch: String) -> BoxFuture<'_, Result<Option<Remote>>> {
unimplemented!()
}
fn get_all_remotes(&self) -> BoxFuture<'_, Result<Vec<Remote>>> {
fn get_remotes(&self, _branch: Option<String>) -> BoxFuture<'_, Result<Vec<Remote>>> {
unimplemented!()
}

View File

@@ -32,7 +32,6 @@ use std::mem::MaybeUninit;
use async_tar::Archive;
use futures::{AsyncRead, Stream, StreamExt, future::BoxFuture};
use git::repository::{GitRepository, RealGitRepository};
use is_executable::IsExecutable;
use rope::Rope;
use serde::{Deserialize, Serialize};
use smol::io::AsyncWriteExt;
@@ -193,8 +192,6 @@ pub struct CopyOptions {
pub struct RenameOptions {
pub overwrite: bool,
pub ignore_if_exists: bool,
/// Whether to create parent directories if they do not exist.
pub create_parents: bool,
}
#[derive(Copy, Clone, Default)]
@@ -211,7 +208,6 @@ pub struct Metadata {
pub is_dir: bool,
pub len: u64,
pub is_fifo: bool,
pub is_executable: bool,
}
/// Filesystem modification time. The purpose of this newtype is to discourage use of operations
@@ -581,12 +577,6 @@ impl Fs for RealFs {
}
}
if options.create_parents {
if let Some(parent) = target.parent() {
self.create_dir(parent).await?;
}
}
smol::fs::rename(source, target).await?;
Ok(())
}
@@ -905,12 +895,6 @@ impl Fs for RealFs {
#[cfg(unix)]
let is_fifo = metadata.file_type().is_fifo();
let path_buf = path.to_path_buf();
let is_executable = self
.executor
.spawn(async move { path_buf.is_executable() })
.await;
Ok(Some(Metadata {
inode,
mtime: MTime(metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH)),
@@ -918,7 +902,6 @@ impl Fs for RealFs {
is_symlink,
is_dir: metadata.file_type().is_dir(),
is_fifo,
is_executable,
}))
}
@@ -2365,12 +2348,6 @@ impl Fs for FakeFs {
let old_path = normalize_path(old_path);
let new_path = normalize_path(new_path);
if options.create_parents {
if let Some(parent) = new_path.parent() {
self.create_dir(parent).await?;
}
}
let mut state = self.state.lock();
let moved_entry = state.write_path(&old_path, |e| {
if let btree_map::Entry::Occupied(e) = e {
@@ -2625,7 +2602,6 @@ impl Fs for FakeFs {
is_dir: false,
is_symlink,
is_fifo: false,
is_executable: false,
},
FakeFsEntry::Dir {
inode, mtime, len, ..
@@ -2636,7 +2612,6 @@ impl Fs for FakeFs {
is_dir: true,
is_symlink,
is_fifo: false,
is_executable: false,
},
FakeFsEntry::Symlink { .. } => unreachable!(),
}))
@@ -3410,63 +3385,4 @@ mod tests {
let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
assert_eq!(content, "Hello");
}
#[gpui::test]
async fn test_rename(executor: BackgroundExecutor) {
let fs = FakeFs::new(executor.clone());
fs.insert_tree(
path!("/root"),
json!({
"src": {
"file_a.txt": "content a",
"file_b.txt": "content b"
}
}),
)
.await;
fs.rename(
Path::new(path!("/root/src/file_a.txt")),
Path::new(path!("/root/src/new/renamed_a.txt")),
RenameOptions {
create_parents: true,
..Default::default()
},
)
.await
.unwrap();
// Assert that the `file_a.txt` file was being renamed and moved to a
// different directory that did not exist before.
assert_eq!(
fs.files(),
vec![
PathBuf::from(path!("/root/src/file_b.txt")),
PathBuf::from(path!("/root/src/new/renamed_a.txt")),
]
);
let result = fs
.rename(
Path::new(path!("/root/src/file_b.txt")),
Path::new(path!("/root/src/old/renamed_b.txt")),
RenameOptions {
create_parents: false,
..Default::default()
},
)
.await;
// Assert that the `file_b.txt` file was not renamed nor moved, as
// `create_parents` was set to `false`.
// different directory that did not exist before.
assert!(result.is_err());
assert_eq!(
fs.files(),
vec![
PathBuf::from(path!("/root/src/file_b.txt")),
PathBuf::from(path!("/root/src/new/renamed_a.txt")),
]
);
}
}

View File

@@ -107,7 +107,7 @@ pub fn match_fixed_path_set(
.display(path_style)
.chars()
.collect::<Vec<_>>();
path_prefix_chars.extend(path_style.primary_separator().chars());
path_prefix_chars.extend(path_style.separator().chars());
let lowercase_pfx = path_prefix_chars
.iter()
.map(|c| c.to_ascii_lowercase())

View File

@@ -43,8 +43,6 @@ actions!(
/// Shows git blame information for the current file.
#[action(deprecated_aliases = ["editor::ToggleGitBlame"])]
Blame,
/// Shows the git history for the current file.
FileHistory,
/// Stages the current file.
StageFile,
/// Unstages the current file.

View File

@@ -207,22 +207,6 @@ pub struct CommitDetails {
pub author_name: SharedString,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct FileHistoryEntry {
pub sha: SharedString,
pub subject: SharedString,
pub message: SharedString,
pub commit_timestamp: i64,
pub author_name: SharedString,
pub author_email: SharedString,
}
#[derive(Debug, Clone)]
pub struct FileHistory {
pub entries: Vec<FileHistoryEntry>,
pub path: RepoPath,
}
#[derive(Debug)]
pub struct CommitDiff {
pub files: Vec<CommitFile>,
@@ -416,7 +400,6 @@ pub trait GitRepository: Send + Sync {
path: RepoPath,
content: Option<String>,
env: Arc<HashMap<String, String>>,
is_executable: bool,
) -> BoxFuture<'_, anyhow::Result<()>>;
/// Returns the URL of the remote with the given name.
@@ -451,8 +434,6 @@ pub trait GitRepository: Send + Sync {
-> BoxFuture<'_, Result<()>>;
fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>>;
fn delete_branch(&self, name: String) -> BoxFuture<'_, Result<()>>;
fn worktrees(&self) -> BoxFuture<'_, Result<Vec<Worktree>>>;
fn create_worktree(
@@ -480,13 +461,6 @@ pub trait GitRepository: Send + Sync {
fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>>;
fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result<crate::blame::Blame>>;
fn file_history(&self, path: RepoPath) -> BoxFuture<'_, Result<FileHistory>>;
fn file_history_paginated(
&self,
path: RepoPath,
skip: usize,
limit: Option<usize>,
) -> BoxFuture<'_, Result<FileHistory>>;
/// Returns the absolute path to the repository. For worktrees, this will be the path to the
/// worktree's gitdir within the main repository (typically `.git/worktrees/<name>`).
@@ -584,11 +558,7 @@ pub trait GitRepository: Send + Sync {
cx: AsyncApp,
) -> BoxFuture<'_, Result<RemoteCommandOutput>>;
fn get_push_remote(&self, branch: String) -> BoxFuture<'_, Result<Option<Remote>>>;
fn get_branch_remote(&self, branch: String) -> BoxFuture<'_, Result<Option<Remote>>>;
fn get_all_remotes(&self) -> BoxFuture<'_, Result<Vec<Remote>>>;
fn get_remotes(&self, branch_name: Option<String>) -> BoxFuture<'_, Result<Vec<Remote>>>;
/// returns a list of remote branches that contain HEAD
fn check_for_pushed_commit(&self) -> BoxFuture<'_, Result<Vec<SharedString>>>;
@@ -1017,15 +987,12 @@ impl GitRepository for RealGitRepository {
path: RepoPath,
content: Option<String>,
env: Arc<HashMap<String, String>>,
is_executable: bool,
) -> BoxFuture<'_, anyhow::Result<()>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
let working_directory = working_directory?;
let mode = if is_executable { "100755" } else { "100644" };
if let Some(content) = content {
let mut child = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
@@ -1046,7 +1013,7 @@ impl GitRepository for RealGitRepository {
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.envs(env.iter())
.args(["update-index", "--add", "--cacheinfo", mode, sha])
.args(["update-index", "--add", "--cacheinfo", "100644", sha])
.arg(path.as_unix_str())
.output()
.await?;
@@ -1439,21 +1406,6 @@ impl GitRepository for RealGitRepository {
.boxed()
}
fn delete_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
let git_binary_path = self.any_git_binary_path.clone();
let working_directory = self.working_directory();
let executor = self.executor.clone();
self.executor
.spawn(async move {
GitBinary::new(git_binary_path, working_directory?, executor)
.run(&["branch", "-d", &name])
.await?;
anyhow::Ok(())
})
.boxed()
}
fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result<crate::blame::Blame>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
@@ -1475,94 +1427,6 @@ impl GitRepository for RealGitRepository {
.boxed()
}
fn file_history(&self, path: RepoPath) -> BoxFuture<'_, Result<FileHistory>> {
self.file_history_paginated(path, 0, None)
}
fn file_history_paginated(
&self,
path: RepoPath,
skip: usize,
limit: Option<usize>,
) -> BoxFuture<'_, Result<FileHistory>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
let working_directory = working_directory?;
// Use a unique delimiter with a hardcoded UUID to separate commits
// This essentially eliminates any chance of encountering the delimiter in actual commit data
let commit_delimiter =
concat!("<<COMMIT_END-", "3f8a9c2e-7d4b-4e1a-9f6c-8b5d2a1e4c3f>>",);
let format_string = format!(
"--pretty=format:%H%x00%s%x00%B%x00%at%x00%an%x00%ae{}",
commit_delimiter
);
let mut args = vec!["--no-optional-locks", "log", "--follow", &format_string];
let skip_str;
let limit_str;
if skip > 0 {
skip_str = skip.to_string();
args.push("--skip");
args.push(&skip_str);
}
if let Some(n) = limit {
limit_str = n.to_string();
args.push("-n");
args.push(&limit_str);
}
args.push("--");
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.args(&args)
.arg(path.as_unix_str())
.output()
.await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("git log failed: {stderr}");
}
let stdout = std::str::from_utf8(&output.stdout)?;
let mut entries = Vec::new();
for commit_block in stdout.split(commit_delimiter) {
let commit_block = commit_block.trim();
if commit_block.is_empty() {
continue;
}
let fields: Vec<&str> = commit_block.split('\0').collect();
if fields.len() >= 6 {
let sha = fields[0].trim().to_string().into();
let subject = fields[1].trim().to_string().into();
let message = fields[2].trim().to_string().into();
let commit_timestamp = fields[3].trim().parse().unwrap_or(0);
let author_name = fields[4].trim().to_string().into();
let author_email = fields[5].trim().to_string().into();
entries.push(FileHistoryEntry {
sha,
subject,
message,
commit_timestamp,
author_name,
author_email,
});
}
}
Ok(FileHistory { entries, path })
})
.boxed()
}
fn diff(&self, diff: DiffType) -> BoxFuture<'_, Result<String>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
@@ -1904,63 +1768,29 @@ impl GitRepository for RealGitRepository {
.boxed()
}
fn get_push_remote(&self, branch: String) -> BoxFuture<'_, Result<Option<Remote>>> {
fn get_remotes(&self, branch_name: Option<String>) -> BoxFuture<'_, Result<Vec<Remote>>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
let working_directory = working_directory?;
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.args(["rev-parse", "--abbrev-ref"])
.arg(format!("{branch}@{{push}}"))
.output()
.await?;
if !output.status.success() {
return Ok(None);
}
let remote_name = String::from_utf8_lossy(&output.stdout)
.split('/')
.next()
.map(|name| Remote {
name: name.trim().to_string().into(),
});
if let Some(branch_name) = branch_name {
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.args(["config", "--get"])
.arg(format!("branch.{}.remote", branch_name))
.output()
.await?;
Ok(remote_name)
})
.boxed()
}
if output.status.success() {
let remote_name = String::from_utf8_lossy(&output.stdout);
fn get_branch_remote(&self, branch: String) -> BoxFuture<'_, Result<Option<Remote>>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
let working_directory = working_directory?;
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.args(["config", "--get"])
.arg(format!("branch.{branch}.remote"))
.output()
.await?;
if !output.status.success() {
return Ok(None);
return Ok(vec![Remote {
name: remote_name.trim().to_string().into(),
}]);
}
}
let remote_name = String::from_utf8_lossy(&output.stdout);
return Ok(Some(Remote {
name: remote_name.trim().to_string().into(),
}));
})
.boxed()
}
fn get_all_remotes(&self) -> BoxFuture<'_, Result<Vec<Remote>>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
let working_directory = working_directory?;
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.args(["remote"])
@@ -1969,7 +1799,7 @@ impl GitRepository for RealGitRepository {
anyhow::ensure!(
output.status.success(),
"Failed to get all remotes:\n{}",
"Failed to get remotes:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let remote_names = String::from_utf8_lossy(&output.stdout)

View File

@@ -50,7 +50,6 @@ schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
strum.workspace = true
telemetry.workspace = true
theme.workspace = true
@@ -69,7 +68,6 @@ windows.workspace = true
[dev-dependencies]
ctor.workspace = true
editor = { workspace = true, features = ["test-support"] }
git_hosting_providers.workspace = true
gpui = { workspace = true, features = ["test-support"] }
indoc.workspace = true
pretty_assertions.workspace = true

View File

@@ -101,7 +101,6 @@ impl BlameRenderer for GitBlameRenderer {
repository.downgrade(),
workspace.clone(),
None,
None,
window,
cx,
)
@@ -326,7 +325,6 @@ impl BlameRenderer for GitBlameRenderer {
repository.downgrade(),
workspace.clone(),
None,
None,
window,
cx,
);
@@ -367,7 +365,6 @@ impl BlameRenderer for GitBlameRenderer {
repository.downgrade(),
workspace,
None,
None,
window,
cx,
)

View File

@@ -4,9 +4,9 @@ use fuzzy::StringMatchCandidate;
use collections::HashSet;
use git::repository::Branch;
use gpui::{
Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render,
SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, rems,
App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render, SharedString, Styled,
Subscription, Task, Window, rems,
};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use project::git_store::Repository;
@@ -14,25 +14,13 @@ use project::project_settings::ProjectSettings;
use settings::Settings;
use std::sync::Arc;
use time::OffsetDateTime;
use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
use ui::{HighlightedLabel, ListItem, ListItemSpacing, Tooltip, prelude::*};
use util::ResultExt;
use workspace::notifications::DetachAndPromptErr;
use workspace::{ModalView, Workspace};
use crate::{branch_picker, git_panel::show_error_toast};
actions!(
branch_picker,
[
/// Deletes the selected git branch.
DeleteBranch
]
);
pub fn register(workspace: &mut Workspace) {
workspace.register_action(|workspace, branch: &zed_actions::git::Branch, window, cx| {
open(workspace, branch, window, cx);
});
workspace.register_action(open);
workspace.register_action(switch);
workspace.register_action(checkout_branch);
}
@@ -61,18 +49,10 @@ pub fn open(
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let workspace_handle = workspace.weak_handle();
let repository = workspace.project().read(cx).active_repository(cx);
let style = BranchListStyle::Modal;
workspace.toggle_modal(window, cx, |window, cx| {
BranchList::new(
Some(workspace_handle),
repository,
style,
rems(34.),
window,
cx,
)
BranchList::new(repository, style, rems(34.), window, cx)
})
}
@@ -82,14 +62,7 @@ pub fn popover(
cx: &mut App,
) -> Entity<BranchList> {
cx.new(|cx| {
let list = BranchList::new(
None,
repository,
BranchListStyle::Popover,
rems(20.),
window,
cx,
);
let list = BranchList::new(repository, BranchListStyle::Popover, rems(20.), window, cx);
list.focus_handle(cx).focus(window);
list
})
@@ -104,13 +77,11 @@ enum BranchListStyle {
pub struct BranchList {
width: Rems,
pub picker: Entity<Picker<BranchListDelegate>>,
picker_focus_handle: FocusHandle,
_subscription: Subscription,
}
impl BranchList {
fn new(
workspace: Option<WeakEntity<Workspace>>,
repository: Option<Entity<Repository>>,
style: BranchListStyle,
width: Rems,
@@ -177,12 +148,8 @@ impl BranchList {
})
.detach_and_log_err(cx);
let delegate = BranchListDelegate::new(workspace, repository, style, cx);
let delegate = BranchListDelegate::new(repository, style);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let picker_focus_handle = picker.focus_handle(cx);
picker.update(cx, |picker, _| {
picker.delegate.focus_handle = picker_focus_handle.clone();
});
let _subscription = cx.subscribe(&picker, |_, _, _, cx| {
cx.emit(DismissEvent);
@@ -190,7 +157,6 @@ impl BranchList {
Self {
picker,
picker_focus_handle,
width,
_subscription,
}
@@ -205,26 +171,13 @@ impl BranchList {
self.picker
.update(cx, |picker, _| picker.delegate.modifiers = ev.modifiers)
}
fn handle_delete_branch(
&mut self,
_: &branch_picker::DeleteBranch,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.picker.update(cx, |picker, cx| {
picker
.delegate
.delete_branch_at(picker.delegate.selected_index, window, cx)
})
}
}
impl ModalView for BranchList {}
impl EventEmitter<DismissEvent> for BranchList {}
impl Focusable for BranchList {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.picker_focus_handle.clone()
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
@@ -234,7 +187,6 @@ impl Render for BranchList {
.key_context("GitBranchSelector")
.w(self.width)
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
.on_action(cx.listener(Self::handle_delete_branch))
.child(self.picker.clone())
.on_mouse_down_out({
cx.listener(move |this, _, window, cx| {
@@ -254,7 +206,6 @@ struct BranchEntry {
}
pub struct BranchListDelegate {
workspace: Option<WeakEntity<Workspace>>,
matches: Vec<BranchEntry>,
all_branches: Option<Vec<Branch>>,
default_branch: Option<SharedString>,
@@ -263,18 +214,11 @@ pub struct BranchListDelegate {
selected_index: usize,
last_query: String,
modifiers: Modifiers,
focus_handle: FocusHandle,
}
impl BranchListDelegate {
fn new(
workspace: Option<WeakEntity<Workspace>>,
repo: Option<Entity<Repository>>,
style: BranchListStyle,
cx: &mut Context<BranchList>,
) -> Self {
fn new(repo: Option<Entity<Repository>>, style: BranchListStyle) -> Self {
Self {
workspace,
matches: vec![],
repo,
style,
@@ -283,7 +227,6 @@ impl BranchListDelegate {
selected_index: 0,
last_query: Default::default(),
modifiers: Default::default(),
focus_handle: cx.focus_handle(),
}
}
@@ -312,59 +255,6 @@ impl BranchListDelegate {
});
cx.emit(DismissEvent);
}
fn delete_branch_at(&self, idx: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(branch_entry) = self.matches.get(idx) else {
return;
};
let Some(repo) = self.repo.clone() else {
return;
};
let workspace = self.workspace.clone();
let branch_name = branch_entry.branch.name().to_string();
let branch_ref = branch_entry.branch.ref_name.clone();
cx.spawn_in(window, async move |picker, cx| {
let result = repo
.update(cx, |repo, _| repo.delete_branch(branch_name.clone()))?
.await?;
if let Err(e) = result {
log::error!("Failed to delete branch: {}", e);
if let Some(workspace) = workspace.and_then(|w| w.upgrade()) {
cx.update(|_window, cx| {
show_error_toast(workspace, format!("branch -d {branch_name}"), e, cx)
})?;
}
return Ok(());
}
picker.update_in(cx, |picker, _, cx| {
picker
.delegate
.matches
.retain(|entry| entry.branch.ref_name != branch_ref);
if let Some(all_branches) = &mut picker.delegate.all_branches {
all_branches.retain(|branch| branch.ref_name != branch_ref);
}
if picker.delegate.matches.is_empty() {
picker.delegate.selected_index = 0;
} else if picker.delegate.selected_index >= picker.delegate.matches.len() {
picker.delegate.selected_index = picker.delegate.matches.len() - 1;
}
cx.notify();
})?;
anyhow::Ok(())
})
.detach();
}
}
impl PickerDelegate for BranchListDelegate {
@@ -482,7 +372,6 @@ impl PickerDelegate for BranchListDelegate {
let Some(entry) = self.matches.get(self.selected_index()) else {
return;
};
if entry.is_new {
let from_branch = if secondary {
self.default_branch.clone()
@@ -676,39 +565,6 @@ impl PickerDelegate for BranchListDelegate {
)
}
fn render_footer(
&self,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<AnyElement> {
let focus_handle = self.focus_handle.clone();
Some(
h_flex()
.w_full()
.p_1p5()
.gap_0p5()
.justify_end()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(
Button::new("delete-branch", "Delete")
.key_binding(
KeyBinding::for_action_in(
&branch_picker::DeleteBranch,
&focus_handle,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_, window, cx| {
window.dispatch_action(branch_picker::DeleteBranch.boxed_clone(), cx);
}),
)
.into_any(),
)
}
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
None
}

View File

@@ -323,7 +323,6 @@ impl Render for CommitTooltip {
repo.downgrade(),
workspace.clone(),
None,
None,
window,
cx,
);

File diff suppressed because it is too large Load Diff

View File

@@ -278,7 +278,7 @@ impl Item for FileDiffView {
}
}
fn as_searchable(&self, _: &Entity<Self>, _: &App) -> Option<Box<dyn SearchableItemHandle>> {
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}

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