Compare commits
196 Commits
v0.202.8
...
rustdoc-ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ebf440247 | ||
|
|
70c05262f4 | ||
|
|
82e55e4145 | ||
|
|
fee9cf4101 | ||
|
|
143d9fc95e | ||
|
|
3d6b46cf9a | ||
|
|
bb2d833373 | ||
|
|
eedfc5be5a | ||
|
|
0e76cc8036 | ||
|
|
6bd5251882 | ||
|
|
13de400a2a | ||
|
|
c3480c3d6f | ||
|
|
0cbacb8500 | ||
|
|
7327ef662b | ||
|
|
c1ca7303a8 | ||
|
|
92283285ae | ||
|
|
d80f9dda75 | ||
|
|
ebc22c290b | ||
|
|
7633bbf55a | ||
|
|
91cbb2ec25 | ||
|
|
40199266b6 | ||
|
|
9a8c5053c2 | ||
|
|
c446662862 | ||
|
|
6feae92616 | ||
|
|
ae840c6ef3 | ||
|
|
d7fd5910d7 | ||
|
|
8d5861322b | ||
|
|
5a9e18603d | ||
|
|
2a7761fe17 | ||
|
|
f23096034b | ||
|
|
1ed17fdd94 | ||
|
|
7ea7f4e767 | ||
|
|
035d7ddcf8 | ||
|
|
9d67276090 | ||
|
|
161d128d45 | ||
|
|
e1b0a98c34 | ||
|
|
ae0ee70abd | ||
|
|
893eb92f91 | ||
|
|
45fa6d81ac | ||
|
|
60ad82cc94 | ||
|
|
564ded71c1 | ||
|
|
63b3839a83 | ||
|
|
9f749881b3 | ||
|
|
946efb03df | ||
|
|
4b96ad3fba | ||
|
|
4368c1b56b | ||
|
|
e5a968b709 | ||
|
|
7aecab8e14 | ||
|
|
e4df866664 | ||
|
|
8770fcc841 | ||
|
|
6dcae2711d | ||
|
|
5e01fb8f1c | ||
|
|
88a79750cc | ||
|
|
4c411b9fc8 | ||
|
|
5ac6ae501f | ||
|
|
c01f12b15d | ||
|
|
dfa066dfe8 | ||
|
|
ac8c653ae6 | ||
|
|
d2318be8d9 | ||
|
|
a026163746 | ||
|
|
ad3ddd381d | ||
|
|
7e3fbeb59d | ||
|
|
8e7caa429d | ||
|
|
c894351544 | ||
|
|
a96015b3c5 | ||
|
|
2eb7ac97e0 | ||
|
|
f06c18765f | ||
|
|
2f279c5de4 | ||
|
|
60b95d9253 | ||
|
|
47ad1b2143 | ||
|
|
35c0d02c7c | ||
|
|
374a8bc4cb | ||
|
|
f06be6f3ec | ||
|
|
970242480a | ||
|
|
54cec5b484 | ||
|
|
60d17cccd3 | ||
|
|
8a8a9a4f07 | ||
|
|
634a1343dd | ||
|
|
2ba25b5c94 | ||
|
|
965dbc988f | ||
|
|
5b73b40df8 | ||
|
|
d910feac1d | ||
|
|
61175ab9cd | ||
|
|
2790eb604a | ||
|
|
acff65ed3f | ||
|
|
3315fd94d2 | ||
|
|
62083fe796 | ||
|
|
a852bcc094 | ||
|
|
f290daf7ea | ||
|
|
129bff8358 | ||
|
|
c833f8905b | ||
|
|
d74384f6e2 | ||
|
|
5abc398a0a | ||
|
|
9c8c3966df | ||
|
|
e48be30266 | ||
|
|
babc0c09f0 | ||
|
|
39d41ed822 | ||
|
|
b69ebbd7b7 | ||
|
|
f348737e8c | ||
|
|
1ca5e84019 | ||
|
|
d80f13242b | ||
|
|
e115584896 | ||
|
|
fe0ab30e8f | ||
|
|
253765aaa1 | ||
|
|
ad746f25f2 | ||
|
|
de576bd1b8 | ||
|
|
af26b627bf | ||
|
|
0a32aa8db1 | ||
|
|
b473f4a130 | ||
|
|
7d0a303785 | ||
|
|
f78f3e7729 | ||
|
|
1c2e2a00fe | ||
|
|
a70cf3f1d4 | ||
|
|
bdedb18c30 | ||
|
|
db508bbbe2 | ||
|
|
515282d719 | ||
|
|
f2c3f3b168 | ||
|
|
e9252a7a74 | ||
|
|
fcc3d1092f | ||
|
|
a790e514af | ||
|
|
92f739dbb9 | ||
|
|
3d4f917204 | ||
|
|
a13881746a | ||
|
|
11fb57a6d9 | ||
|
|
5001c03711 | ||
|
|
20d32d111c | ||
|
|
ff035e8a22 | ||
|
|
01266d10d6 | ||
|
|
4507f60b8d | ||
|
|
d13ba0162a | ||
|
|
7403a4ba17 | ||
|
|
52da72d80a | ||
|
|
384ffb883f | ||
|
|
c3ccdc0b44 | ||
|
|
e5cea54cbb | ||
|
|
cfd56a744d | ||
|
|
960d9ce48c | ||
|
|
52d119b637 | ||
|
|
8c18f059f1 | ||
|
|
930189ed83 | ||
|
|
08c23c92ca | ||
|
|
88e8f7af68 | ||
|
|
f2e62c98d1 | ||
|
|
8697b91ea0 | ||
|
|
47aaaa8bcf | ||
|
|
69933d5b81 | ||
|
|
909d7215c0 | ||
|
|
27777d4b8f | ||
|
|
4469b14512 | ||
|
|
29fc324a78 | ||
|
|
4ef9294123 | ||
|
|
4b0609840b | ||
|
|
2cb697e9f4 | ||
|
|
c8e99125bd | ||
|
|
835e5ba662 | ||
|
|
24ee98b3e1 | ||
|
|
213ee32b94 | ||
|
|
f127ba82d1 | ||
|
|
39d86eeb7f | ||
|
|
4981c33bf3 | ||
|
|
54609d4d00 | ||
|
|
ff03dda90a | ||
|
|
73b38c8306 | ||
|
|
38e5c8fb66 | ||
|
|
78c2f1621d | ||
|
|
0a9f407872 | ||
|
|
4e1a901059 | ||
|
|
8af212e785 | ||
|
|
b233df8343 | ||
|
|
9a97f9465b | ||
|
|
48299b5b24 | ||
|
|
4e4bfd6f4e | ||
|
|
5444fbd8fe | ||
|
|
58f896e5cd | ||
|
|
d43cf2c486 | ||
|
|
e2bf8e5d9c | ||
|
|
c158eb2442 | ||
|
|
71f900346c | ||
|
|
9ca4fb16b2 | ||
|
|
45ff22f793 | ||
|
|
fead511df9 | ||
|
|
07373d15ef | ||
|
|
b5e9b65e8c | ||
|
|
5d7f12ce88 | ||
|
|
1b9c471204 | ||
|
|
8cf663011f | ||
|
|
54f9b67de2 | ||
|
|
d99a17e357 | ||
|
|
c72e594afe | ||
|
|
b4d4294bee | ||
|
|
e5c0614e88 | ||
|
|
ea347b0aa1 | ||
|
|
a03897012e | ||
|
|
f4071bdd8e | ||
|
|
abd6009b41 | ||
|
|
a3e1611fa8 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,2 +1,5 @@
|
||||
# Prevent GitHub from displaying comments within JSON files as errors.
|
||||
*.json linguist-language=JSON-with-Comments
|
||||
|
||||
# Ensure the WSL script always has LF line endings, even on Windows
|
||||
crates/zed/resources/windows/zed-wsl text eol=lf
|
||||
|
||||
159
.github/actions/run_tests_windows/action.yml
vendored
159
.github/actions/run_tests_windows/action.yml
vendored
@@ -20,167 +20,8 @@ runs:
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Configure crash dumps
|
||||
shell: powershell
|
||||
run: |
|
||||
# Record the start time for this CI run
|
||||
$runStartTime = Get-Date
|
||||
$runStartTimeStr = $runStartTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
Write-Host "CI run started at: $runStartTimeStr"
|
||||
|
||||
# Save the timestamp for later use
|
||||
echo "CI_RUN_START_TIME=$($runStartTime.Ticks)" >> $env:GITHUB_ENV
|
||||
|
||||
# Create crash dump directory in workspace (non-persistent)
|
||||
$dumpPath = "$env:GITHUB_WORKSPACE\crash_dumps"
|
||||
New-Item -ItemType Directory -Force -Path $dumpPath | Out-Null
|
||||
|
||||
Write-Host "Setting up crash dump detection..."
|
||||
Write-Host "Workspace dump path: $dumpPath"
|
||||
|
||||
# Note: We're NOT modifying registry on stateful runners
|
||||
# Instead, we'll check default Windows crash locations after tests
|
||||
|
||||
- name: Run tests
|
||||
shell: powershell
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
$env:RUST_BACKTRACE = "full"
|
||||
|
||||
# Enable Windows debugging features
|
||||
$env:_NT_SYMBOL_PATH = "srv*https://msdl.microsoft.com/download/symbols"
|
||||
|
||||
# .NET crash dump environment variables (ephemeral)
|
||||
$env:COMPlus_DbgEnableMiniDump = "1"
|
||||
$env:COMPlus_DbgMiniDumpType = "4"
|
||||
$env:COMPlus_CreateDumpDiagnostics = "1"
|
||||
|
||||
cargo nextest run --workspace --no-fail-fast
|
||||
|
||||
- name: Analyze crash dumps
|
||||
if: always()
|
||||
shell: powershell
|
||||
run: |
|
||||
Write-Host "Checking for crash dumps..."
|
||||
|
||||
# Get the CI run start time from the environment
|
||||
$runStartTime = [DateTime]::new([long]$env:CI_RUN_START_TIME)
|
||||
Write-Host "Only analyzing dumps created after: $($runStartTime.ToString('yyyy-MM-dd HH:mm:ss'))"
|
||||
|
||||
# Check all possible crash dump locations
|
||||
$searchPaths = @(
|
||||
"$env:GITHUB_WORKSPACE\crash_dumps",
|
||||
"$env:LOCALAPPDATA\CrashDumps",
|
||||
"$env:TEMP",
|
||||
"$env:GITHUB_WORKSPACE",
|
||||
"$env:USERPROFILE\AppData\Local\CrashDumps",
|
||||
"C:\Windows\System32\config\systemprofile\AppData\Local\CrashDumps"
|
||||
)
|
||||
|
||||
$dumps = @()
|
||||
foreach ($path in $searchPaths) {
|
||||
if (Test-Path $path) {
|
||||
Write-Host "Searching in: $path"
|
||||
$found = Get-ChildItem "$path\*.dmp" -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.CreationTime -gt $runStartTime
|
||||
}
|
||||
if ($found) {
|
||||
$dumps += $found
|
||||
Write-Host " Found $($found.Count) dump(s) from this CI run"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($dumps) {
|
||||
Write-Host "Found $($dumps.Count) crash dump(s)"
|
||||
|
||||
# Install debugging tools if not present
|
||||
$cdbPath = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
|
||||
if (-not (Test-Path $cdbPath)) {
|
||||
Write-Host "Installing Windows Debugging Tools..."
|
||||
$url = "https://go.microsoft.com/fwlink/?linkid=2237387"
|
||||
Invoke-WebRequest -Uri $url -OutFile winsdksetup.exe
|
||||
Start-Process -Wait winsdksetup.exe -ArgumentList "/features OptionId.WindowsDesktopDebuggers /quiet"
|
||||
}
|
||||
|
||||
foreach ($dump in $dumps) {
|
||||
Write-Host "`n=================================="
|
||||
Write-Host "Analyzing crash dump: $($dump.Name)"
|
||||
Write-Host "Size: $([math]::Round($dump.Length / 1MB, 2)) MB"
|
||||
Write-Host "Time: $($dump.CreationTime)"
|
||||
Write-Host "=================================="
|
||||
|
||||
# Set symbol path
|
||||
$env:_NT_SYMBOL_PATH = "srv*C:\symbols*https://msdl.microsoft.com/download/symbols"
|
||||
|
||||
# Run analysis
|
||||
$analysisOutput = & $cdbPath -z $dump.FullName -c "!analyze -v; ~*k; lm; q" 2>&1 | Out-String
|
||||
|
||||
# Extract key information
|
||||
if ($analysisOutput -match "ExceptionCode:\s*([\w]+)") {
|
||||
Write-Host "Exception Code: $($Matches[1])"
|
||||
if ($Matches[1] -eq "c0000005") {
|
||||
Write-Host "Exception Type: ACCESS VIOLATION"
|
||||
}
|
||||
}
|
||||
|
||||
if ($analysisOutput -match "EXCEPTION_RECORD:\s*(.+)") {
|
||||
Write-Host "Exception Record: $($Matches[1])"
|
||||
}
|
||||
|
||||
if ($analysisOutput -match "FAULTING_IP:\s*\n(.+)") {
|
||||
Write-Host "Faulting Instruction: $($Matches[1])"
|
||||
}
|
||||
|
||||
# Save full analysis
|
||||
$analysisFile = "$($dump.FullName).analysis.txt"
|
||||
$analysisOutput | Out-File -FilePath $analysisFile
|
||||
Write-Host "`nFull analysis saved to: $analysisFile"
|
||||
|
||||
# Print stack trace section
|
||||
Write-Host "`n--- Stack Trace Preview ---"
|
||||
$stackSection = $analysisOutput -split "STACK_TEXT:" | Select-Object -Last 1
|
||||
$stackLines = $stackSection -split "`n" | Select-Object -First 20
|
||||
$stackLines | ForEach-Object { Write-Host $_ }
|
||||
Write-Host "--- End Stack Trace Preview ---"
|
||||
}
|
||||
|
||||
Write-Host "`n⚠️ Crash dumps detected! Download the 'crash-dumps' artifact for detailed analysis."
|
||||
|
||||
# Copy dumps to workspace for artifact upload
|
||||
$artifactPath = "$env:GITHUB_WORKSPACE\crash_dumps_collected"
|
||||
New-Item -ItemType Directory -Force -Path $artifactPath | Out-Null
|
||||
|
||||
foreach ($dump in $dumps) {
|
||||
$destName = "$($dump.Directory.Name)_$($dump.Name)"
|
||||
Copy-Item $dump.FullName -Destination "$artifactPath\$destName"
|
||||
if (Test-Path "$($dump.FullName).analysis.txt") {
|
||||
Copy-Item "$($dump.FullName).analysis.txt" -Destination "$artifactPath\$destName.analysis.txt"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Copied $($dumps.Count) dump(s) to artifact directory"
|
||||
} else {
|
||||
Write-Host "No crash dumps from this CI run found"
|
||||
}
|
||||
|
||||
- name: Upload crash dumps
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crash-dumps-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
crash_dumps_collected/*.dmp
|
||||
crash_dumps_collected/*.txt
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
- name: Check test results
|
||||
shell: powershell
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
# Re-check test results to fail the job if tests failed
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Tests failed with exit code: $LASTEXITCODE"
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
602
.github/workflows/release_nightly.yml
vendored
602
.github/workflows/release_nightly.yml
vendored
@@ -5,8 +5,8 @@ on:
|
||||
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
|
||||
- cron: "0 7 * * *"
|
||||
push:
|
||||
tags:
|
||||
- "nightly"
|
||||
branches:
|
||||
- rustdoc-action
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -18,118 +18,284 @@ env:
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
|
||||
jobs:
|
||||
style:
|
||||
# style:
|
||||
# timeout-minutes: 60
|
||||
# name: Check formatting and Clippy lints
|
||||
# if: github.repository_owner == 'zed-industries'
|
||||
# runs-on:
|
||||
# - self-hosted
|
||||
# - macOS
|
||||
# steps:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
# with:
|
||||
# clean: false
|
||||
# fetch-depth: 0
|
||||
|
||||
# - name: Run style checks
|
||||
# uses: ./.github/actions/check_style
|
||||
|
||||
# - name: Run clippy
|
||||
# run: ./script/clippy
|
||||
|
||||
# tests:
|
||||
# timeout-minutes: 60
|
||||
# name: Run tests
|
||||
# if: github.repository_owner == 'zed-industries'
|
||||
# runs-on:
|
||||
# - self-hosted
|
||||
# - macOS
|
||||
# needs: style
|
||||
# steps:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
# with:
|
||||
# clean: false
|
||||
|
||||
# - name: Run tests
|
||||
# uses: ./.github/actions/run_tests
|
||||
|
||||
# windows-tests:
|
||||
# timeout-minutes: 60
|
||||
# name: Run tests on Windows
|
||||
# if: github.repository_owner == 'zed-industries'
|
||||
# runs-on: [self-32vcpu-windows-2022]
|
||||
# steps:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
# with:
|
||||
# clean: false
|
||||
|
||||
# - name: Configure CI
|
||||
# run: |
|
||||
# New-Item -ItemType Directory -Path "./../.cargo" -Force
|
||||
# Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
|
||||
|
||||
# - name: Run tests
|
||||
# uses: ./.github/actions/run_tests_windows
|
||||
|
||||
# - name: Limit target directory size
|
||||
# run: ./script/clear-target-dir-if-larger-than.ps1 1024
|
||||
|
||||
# - name: Clean CI config file
|
||||
# if: always()
|
||||
# run: Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# bundle-mac:
|
||||
# timeout-minutes: 60
|
||||
# name: Create a macOS bundle
|
||||
# if: github.repository_owner == 'zed-industries'
|
||||
# runs-on:
|
||||
# - self-mini-macos
|
||||
# needs: tests
|
||||
# env:
|
||||
# MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
# MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
# APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
# APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
# APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
# steps:
|
||||
# - name: Install Node
|
||||
# uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
# with:
|
||||
# node-version: "18"
|
||||
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
# with:
|
||||
# clean: false
|
||||
|
||||
# - name: Set release channel to nightly
|
||||
# run: |
|
||||
# set -eu
|
||||
# version=$(git rev-parse --short HEAD)
|
||||
# echo "Publishing version: ${version} on release channel nightly"
|
||||
# echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
# - name: Setup Sentry CLI
|
||||
# uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
# with:
|
||||
# token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
# - name: Create macOS app bundle
|
||||
# run: script/bundle-mac
|
||||
|
||||
# - name: Upload Zed Nightly
|
||||
# run: script/upload-nightly macos
|
||||
|
||||
# bundle-linux-x86:
|
||||
# timeout-minutes: 60
|
||||
# name: Create a Linux *.tar.gz bundle for x86
|
||||
# if: github.repository_owner == 'zed-industries'
|
||||
# runs-on:
|
||||
# - namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
|
||||
# needs: tests
|
||||
# steps:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
# with:
|
||||
# clean: false
|
||||
|
||||
# - name: Add Rust to the PATH
|
||||
# run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
|
||||
# - name: Install Linux dependencies
|
||||
# run: ./script/linux && ./script/install-mold 2.34.0
|
||||
|
||||
# - name: Setup Sentry CLI
|
||||
# uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
# with:
|
||||
# token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
# - name: Limit target directory size
|
||||
# run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
# - name: Set release channel to nightly
|
||||
# run: |
|
||||
# set -euo pipefail
|
||||
# version=$(git rev-parse --short HEAD)
|
||||
# echo "Publishing version: ${version} on release channel nightly"
|
||||
# echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
# - name: Create Linux .tar.gz bundle
|
||||
# run: script/bundle-linux
|
||||
|
||||
# - name: Upload Zed Nightly
|
||||
# run: script/upload-nightly linux-targz
|
||||
|
||||
# bundle-linux-arm:
|
||||
# timeout-minutes: 60
|
||||
# name: Create a Linux *.tar.gz bundle for ARM
|
||||
# if: github.repository_owner == 'zed-industries'
|
||||
# runs-on:
|
||||
# - namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
|
||||
# needs: tests
|
||||
# steps:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
# with:
|
||||
# clean: false
|
||||
|
||||
# - name: Install Linux dependencies
|
||||
# run: ./script/linux
|
||||
|
||||
# - name: Setup Sentry CLI
|
||||
# uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
# with:
|
||||
# token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
# - name: Limit target directory size
|
||||
# run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
# - name: Set release channel to nightly
|
||||
# run: |
|
||||
# set -euo pipefail
|
||||
# version=$(git rev-parse --short HEAD)
|
||||
# echo "Publishing version: ${version} on release channel nightly"
|
||||
# echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
# - name: Create Linux .tar.gz bundle
|
||||
# run: script/bundle-linux
|
||||
|
||||
# - name: Upload Zed Nightly
|
||||
# run: script/upload-nightly linux-targz
|
||||
|
||||
# freebsd:
|
||||
# timeout-minutes: 60
|
||||
# if: false && github.repository_owner == 'zed-industries'
|
||||
# runs-on: github-8vcpu-ubuntu-2404
|
||||
# needs: tests
|
||||
# name: Build Zed on FreeBSD
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: Build FreeBSD remote-server
|
||||
# id: freebsd-build
|
||||
# uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
|
||||
# with:
|
||||
# # envs: "MYTOKEN MYTOKEN2"
|
||||
# usesh: true
|
||||
# release: 13.5
|
||||
# copyback: true
|
||||
# prepare: |
|
||||
# pkg install -y \
|
||||
# bash curl jq git \
|
||||
# rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
|
||||
# run: |
|
||||
# freebsd-version
|
||||
# sysctl hw.model
|
||||
# sysctl hw.ncpu
|
||||
# sysctl hw.physmem
|
||||
# sysctl hw.usermem
|
||||
# git config --global --add safe.directory /home/runner/work/zed/zed
|
||||
# rustup-init --profile minimal --default-toolchain none -y
|
||||
# . "$HOME/.cargo/env"
|
||||
# ./script/bundle-freebsd
|
||||
# mkdir -p out/
|
||||
# mv "target/zed-remote-server-freebsd-x86_64.gz" out/
|
||||
# rm -rf target/
|
||||
# cargo clean
|
||||
|
||||
# - name: Upload Zed Nightly
|
||||
# run: script/upload-nightly freebsd
|
||||
|
||||
# bundle-nix:
|
||||
# name: Build and cache Nix package
|
||||
# needs: tests
|
||||
# secrets: inherit
|
||||
# uses: ./.github/workflows/nix.yml
|
||||
|
||||
# bundle-windows-x64:
|
||||
# timeout-minutes: 60
|
||||
# name: Create a Windows installer
|
||||
# if: github.repository_owner == 'zed-industries'
|
||||
# runs-on: [self-32vcpu-windows-2022]
|
||||
# needs: windows-tests
|
||||
# env:
|
||||
# AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
# AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
# AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
|
||||
# ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
|
||||
# CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
|
||||
# ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
# FILE_DIGEST: SHA256
|
||||
# TIMESTAMP_DIGEST: SHA256
|
||||
# TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
|
||||
# steps:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
# with:
|
||||
# clean: false
|
||||
|
||||
# - name: Set release channel to nightly
|
||||
# working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
# run: |
|
||||
# $ErrorActionPreference = "Stop"
|
||||
# $version = git rev-parse --short HEAD
|
||||
# Write-Host "Publishing version: $version on release channel nightly"
|
||||
# "nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
|
||||
|
||||
# - name: Setup Sentry CLI
|
||||
# uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
# with:
|
||||
# token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
# - name: Build Zed installer
|
||||
# working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
# run: script/bundle-windows.ps1
|
||||
|
||||
# - name: Upload Zed Nightly
|
||||
# working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
# run: script/upload-nightly.ps1 windows
|
||||
|
||||
gpui-docs:
|
||||
timeout-minutes: 60
|
||||
name: Check formatting and Clippy lints
|
||||
name: Render rust docs for gpui
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
# TODO: build for multiple targets?
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- macOS
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
- name: Run clippy
|
||||
run: ./script/clippy
|
||||
|
||||
tests:
|
||||
timeout-minutes: 60
|
||||
name: Run tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- macOS
|
||||
needs: style
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
windows-tests:
|
||||
timeout-minutes: 60
|
||||
name: Run tests on Windows
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Configure CI
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path "./../.cargo" -Force
|
||||
Copy-Item -Path "./.cargo/ci-config.toml" -Destination "./../.cargo/config.toml"
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests_windows
|
||||
|
||||
- name: Limit target directory size
|
||||
run: ./script/clear-target-dir-if-larger-than.ps1 1024
|
||||
|
||||
- name: Clean CI config file
|
||||
if: always()
|
||||
run: Remove-Item -Recurse -Path "./../.cargo" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
bundle-mac:
|
||||
timeout-minutes: 60
|
||||
name: Create a macOS bundle
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-mini-macos
|
||||
needs: tests
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Create macOS app bundle
|
||||
run: script/bundle-mac
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly macos
|
||||
|
||||
bundle-linux-x86:
|
||||
timeout-minutes: 60
|
||||
name: Create a Linux *.tar.gz bundle for x86
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
|
||||
needs: tests
|
||||
- github-16vcpu-ubuntu-2404
|
||||
# needs: tests
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
@@ -137,187 +303,61 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux && ./script/install-mold 2.34.0
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
- name: Build rustdocs
|
||||
run: cargo doc -p gpui --no-deps
|
||||
|
||||
- name: Create Linux .tar.gz bundle
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly linux-targz
|
||||
|
||||
bundle-linux-arm:
|
||||
timeout-minutes: 60
|
||||
name: Create a Linux *.tar.gz bundle for ARM
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- namespace-profile-8x32-ubuntu-2004-arm-m4 # ubuntu 20.04 for minimal glibc
|
||||
needs: tests
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Check for broken links (in HTML)
|
||||
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
|
||||
with:
|
||||
clean: false
|
||||
args: --no-progress --exclude '^http' 'target/doc/'
|
||||
fail: true
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: ./script/linux
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
- name: Deploy rustdoc
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy target/doc --project-name=gpui-rustdoc
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
# update-nightly-tag:
|
||||
# name: Update nightly tag
|
||||
# if: github.repository_owner == 'zed-industries'
|
||||
# runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
# needs:
|
||||
# - bundle-mac
|
||||
# - bundle-linux-x86
|
||||
# - bundle-linux-arm
|
||||
# - bundle-windows-x64
|
||||
# steps:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
# - name: Update nightly tag
|
||||
# run: |
|
||||
# if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
|
||||
# echo "Nightly tag already points to current commit. Skipping tagging."
|
||||
# exit 0
|
||||
# fi
|
||||
# git config user.name github-actions
|
||||
# git config user.email github-actions@github.com
|
||||
# git tag -f nightly
|
||||
# git push origin nightly --force
|
||||
|
||||
- name: Create Linux .tar.gz bundle
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly linux-targz
|
||||
|
||||
freebsd:
|
||||
timeout-minutes: 60
|
||||
if: false && github.repository_owner == 'zed-industries'
|
||||
runs-on: github-8vcpu-ubuntu-2404
|
||||
needs: tests
|
||||
name: Build Zed on FreeBSD
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build FreeBSD remote-server
|
||||
id: freebsd-build
|
||||
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
|
||||
with:
|
||||
# envs: "MYTOKEN MYTOKEN2"
|
||||
usesh: true
|
||||
release: 13.5
|
||||
copyback: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
bash curl jq git \
|
||||
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
|
||||
run: |
|
||||
freebsd-version
|
||||
sysctl hw.model
|
||||
sysctl hw.ncpu
|
||||
sysctl hw.physmem
|
||||
sysctl hw.usermem
|
||||
git config --global --add safe.directory /home/runner/work/zed/zed
|
||||
rustup-init --profile minimal --default-toolchain none -y
|
||||
. "$HOME/.cargo/env"
|
||||
./script/bundle-freebsd
|
||||
mkdir -p out/
|
||||
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
|
||||
rm -rf target/
|
||||
cargo clean
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly freebsd
|
||||
|
||||
bundle-nix:
|
||||
name: Build and cache Nix package
|
||||
needs: tests
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/nix.yml
|
||||
|
||||
bundle-windows-x64:
|
||||
timeout-minutes: 60
|
||||
name: Create a Windows installer
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
needs: windows-tests
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_SIGNING_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_SIGNING_CLIENT_SECRET }}
|
||||
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
|
||||
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
|
||||
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
|
||||
FILE_DIGEST: SHA256
|
||||
TIMESTAMP_DIGEST: SHA256
|
||||
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
clean: false
|
||||
|
||||
- name: Set release channel to nightly
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$version = git rev-parse --short HEAD
|
||||
Write-Host "Publishing version: $version on release channel nightly"
|
||||
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
|
||||
with:
|
||||
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Build Zed installer
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/bundle-windows.ps1
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/upload-nightly.ps1 windows
|
||||
|
||||
update-nightly-tag:
|
||||
name: Update nightly tag
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
needs:
|
||||
- bundle-mac
|
||||
- bundle-linux-x86
|
||||
- bundle-linux-arm
|
||||
- bundle-windows-x64
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update nightly tag
|
||||
run: |
|
||||
if [ "$(git rev-parse nightly)" = "$(git rev-parse HEAD)" ]; then
|
||||
echo "Nightly tag already points to current commit. Skipping tagging."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git tag -f nightly
|
||||
git push origin nightly --force
|
||||
|
||||
- name: Create Sentry release
|
||||
uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c # v3
|
||||
env:
|
||||
SENTRY_ORG: zed-dev
|
||||
SENTRY_PROJECT: zed
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
with:
|
||||
environment: production
|
||||
# - name: Create Sentry release
|
||||
# uses: getsentry/action-release@526942b68292201ac6bbb99b9a0747d4abee354c # v3
|
||||
# env:
|
||||
# SENTRY_ORG: zed-dev
|
||||
# SENTRY_PROJECT: zed
|
||||
# SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
# with:
|
||||
# environment: production
|
||||
|
||||
@@ -27,6 +27,22 @@ By effectively engaging with the Zed team and community early in your process, w
|
||||
|
||||
We plan to set aside time each week to pair program with contributors on promising pull requests in Zed. This will be an experiment. We tend to prefer pairing over async code review on our team, and we'd like to see how well it works in an open source setting. If we're finding it difficult to get on the same page with async review, we may ask you to pair with us if you're open to it. The closer a contribution is to the goals outlined in our roadmap, the more likely we'll be to spend time pairing on it.
|
||||
|
||||
## Mandatory PR contents
|
||||
|
||||
Please ensure the PR contains
|
||||
|
||||
- Before & after screenshots, if there are visual adjustments introduced.
|
||||
|
||||
Examples of visual adjustments: tree-sitter query updates, UI changes, etc.
|
||||
|
||||
- A disclosure of the AI assistance usage, if any was used.
|
||||
|
||||
Any kind of AI assistance must be disclosed in the PR, along with the extent to which AI assistance was used (e.g. docs only vs. code generation).
|
||||
|
||||
If the PR responses are being generated by an AI, disclose that as well.
|
||||
|
||||
As a small exception, trivial tab-completion doesn't need to be disclosed, as long as it's limited to single keywords or short phrases.
|
||||
|
||||
## Tips to improve the chances of your PR getting reviewed and merged
|
||||
|
||||
- Discuss your plans ahead of time with the team
|
||||
@@ -49,6 +65,8 @@ If you would like to add a new icon to the Zed icon theme, [open a Discussion](h
|
||||
|
||||
## Bird's-eye view of Zed
|
||||
|
||||
We suggest you keep the [zed glossary](docs/src/development/GLOSSARY.md) at your side when starting out. It lists and explains some of the structures and terms you will see throughout the codebase.
|
||||
|
||||
Zed is made up of several smaller crates - let's go over those you're most likely to interact with:
|
||||
|
||||
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation.**
|
||||
|
||||
186
Cargo.lock
generated
186
Cargo.lock
generated
@@ -195,9 +195,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "agent-client-protocol"
|
||||
version = "0.2.0-alpha.6"
|
||||
version = "0.2.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d02292efd75080932b6466471d428c70e2ac06908ae24792fc7c36ecbaf67ca"
|
||||
checksum = "603941db1d130ee275840c465b73a2312727d4acef97449550ccf033de71301f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-broadcast",
|
||||
@@ -508,7 +508,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"piper",
|
||||
"polling",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-automata",
|
||||
"rustix-openpty",
|
||||
"serde",
|
||||
"signal-hook",
|
||||
@@ -2458,7 +2458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -4733,7 +4733,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b545b8c50194bdd008283985ab0b31dba153cfd5b3066a92770634fbc0d7d291"
|
||||
dependencies = [
|
||||
"nu-ansi-term 0.50.1",
|
||||
"nu-ansi-term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5632,8 +5632,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2"
|
||||
dependencies = [
|
||||
"bit-set 0.5.3",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5643,8 +5643,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||
dependencies = [
|
||||
"bit-set 0.8.0",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7294,8 +7294,8 @@ dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8300,7 +8300,7 @@ dependencies = [
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
@@ -8899,7 +8899,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"referencing",
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
"reqwest 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -8952,6 +8952,44 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keymap_editor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"component",
|
||||
"db",
|
||||
"editor",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"notifications",
|
||||
"paths",
|
||||
"project",
|
||||
"search",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"telemetry",
|
||||
"tempfile",
|
||||
"theme",
|
||||
"tree-sitter-json",
|
||||
"tree-sitter-rust",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"util",
|
||||
"vim",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "khronos-egl"
|
||||
version = "6.0.0"
|
||||
@@ -9109,6 +9147,7 @@ dependencies = [
|
||||
"icons",
|
||||
"image",
|
||||
"log",
|
||||
"open_router",
|
||||
"parking_lot",
|
||||
"proto",
|
||||
"schemars",
|
||||
@@ -9701,7 +9740,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
"rustc_version",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
@@ -9773,7 +9812,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "lsp-types"
|
||||
version = "0.95.1"
|
||||
source = "git+https://github.com/zed-industries/lsp-types?rev=39f629bdd03d59abd786ed9fc27e8bca02c0c0ec#39f629bdd03d59abd786ed9fc27e8bca02c0c0ec"
|
||||
source = "git+https://github.com/zed-industries/lsp-types?rev=0874f8742fe55b4dc94308c1e3c0069710d8eeaf#0874f8742fe55b4dc94308c1e3c0069710d8eeaf"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"serde",
|
||||
@@ -9916,9 +9955,11 @@ dependencies = [
|
||||
"editor",
|
||||
"fs",
|
||||
"gpui",
|
||||
"html5ever 0.27.0",
|
||||
"language",
|
||||
"linkify",
|
||||
"log",
|
||||
"markup5ever_rcdom",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark 0.12.2",
|
||||
"settings",
|
||||
@@ -9979,11 +10020,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10684,16 +10725,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.1"
|
||||
@@ -11192,6 +11223,8 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.27.1",
|
||||
"thiserror 2.0.12",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
@@ -11387,12 +11420,6 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.11.1"
|
||||
@@ -13383,17 +13410,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13404,7 +13422,7 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13413,12 +13431,6 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -13736,7 +13748,6 @@ dependencies = [
|
||||
"regex",
|
||||
"reqwest 0.12.15 (git+https://github.com/zed-industries/reqwest.git?rev=951c770a32f1998d6e999cef3e59e0013e6c4415)",
|
||||
"serde",
|
||||
"smol",
|
||||
"tokio",
|
||||
"workspace-hack",
|
||||
]
|
||||
@@ -14857,6 +14868,8 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"serde_path_to_error",
|
||||
"settings_ui_macros",
|
||||
"smallvec",
|
||||
"tree-sitter",
|
||||
"tree-sitter-json",
|
||||
@@ -14892,39 +14905,30 @@ name = "settings_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"component",
|
||||
"db",
|
||||
"debugger_ui",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"notifications",
|
||||
"paths",
|
||||
"project",
|
||||
"search",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"telemetry",
|
||||
"tempfile",
|
||||
"smallvec",
|
||||
"theme",
|
||||
"tree-sitter-json",
|
||||
"tree-sitter-rust",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"util",
|
||||
"vim",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "settings_ui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15310,7 +15314,6 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"indoc",
|
||||
"libsqlite3-sys",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"smol",
|
||||
"sqlformat",
|
||||
@@ -16741,6 +16744,7 @@ dependencies = [
|
||||
"db",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"keymap_editor",
|
||||
"notifications",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
@@ -16749,7 +16753,6 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"settings_ui",
|
||||
"smallvec",
|
||||
"story",
|
||||
"telemetry",
|
||||
@@ -17118,14 +17121,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term 0.46.0",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
@@ -17156,7 +17159,7 @@ checksum = "a7cf18d43cbf0bfca51f657132cc616a5097edc4424d538bae6fa60142eaf9f0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
"serde_json",
|
||||
"streaming-iterator",
|
||||
"tree-sitter-language",
|
||||
@@ -17186,8 +17189,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tree-sitter-cpp"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df2196ea9d47b4ab4a31b9297eaa5a5d19a0b121dceb9f118f6790ad0ab94743"
|
||||
source = "git+https://github.com/tree-sitter/tree-sitter-cpp?rev=5cb9b693cfd7bfacab1d9ff4acac1a4150700609#5cb9b693cfd7bfacab1d9ff4acac1a4150700609"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
@@ -19955,8 +19957,8 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"regalloc2",
|
||||
"regex",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"ring",
|
||||
"rust_decimal",
|
||||
"rustc-hash 1.1.0",
|
||||
@@ -20138,9 +20140,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xcb"
|
||||
version = "1.5.0"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1e2f212bb1a92cd8caac8051b829a6582ede155ccb60b5d5908b81b100952be"
|
||||
checksum = "f07c123b796139bfe0603e654eaf08e132e52387ba95b252c78bad3640ba37ea"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
@@ -20397,7 +20399,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.202.8"
|
||||
version = "0.204.0"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
@@ -20461,6 +20463,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"jj_ui",
|
||||
"journal",
|
||||
"keymap_editor",
|
||||
"language",
|
||||
"language_extension",
|
||||
"language_model",
|
||||
@@ -20591,7 +20594,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_html"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -20790,6 +20793,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"indoc",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"language_model",
|
||||
"log",
|
||||
@@ -20804,6 +20808,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"strum 0.27.1",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"theme",
|
||||
@@ -20811,7 +20816,6 @@ dependencies = [
|
||||
"tree-sitter-go",
|
||||
"tree-sitter-rust",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"uuid",
|
||||
"workspace",
|
||||
|
||||
22
Cargo.toml
22
Cargo.toml
@@ -54,6 +54,8 @@ members = [
|
||||
"crates/deepseek",
|
||||
"crates/diagnostics",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/edit_prediction",
|
||||
"crates/edit_prediction_button",
|
||||
"crates/editor",
|
||||
"crates/eval",
|
||||
"crates/explorer_command_injector",
|
||||
@@ -82,13 +84,12 @@ members = [
|
||||
"crates/http_client_tls",
|
||||
"crates/icons",
|
||||
"crates/image_viewer",
|
||||
"crates/edit_prediction",
|
||||
"crates/edit_prediction_button",
|
||||
"crates/inspector_ui",
|
||||
"crates/install_cli",
|
||||
"crates/jj",
|
||||
"crates/jj_ui",
|
||||
"crates/journal",
|
||||
"crates/keymap_editor",
|
||||
"crates/language",
|
||||
"crates/language_extension",
|
||||
"crates/language_model",
|
||||
@@ -146,6 +147,7 @@ members = [
|
||||
"crates/settings",
|
||||
"crates/settings_profile_selector",
|
||||
"crates/settings_ui",
|
||||
"crates/settings_ui_macros",
|
||||
"crates/snippet",
|
||||
"crates/snippet_provider",
|
||||
"crates/snippets_ui",
|
||||
@@ -156,9 +158,9 @@ members = [
|
||||
"crates/streaming_diff",
|
||||
"crates/sum_tree",
|
||||
"crates/supermaven",
|
||||
"crates/system_specs",
|
||||
"crates/supermaven_api",
|
||||
"crates/svg_preview",
|
||||
"crates/system_specs",
|
||||
"crates/tab_switcher",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
@@ -297,9 +299,7 @@ git_hosting_providers = { path = "crates/git_hosting_providers" }
|
||||
git_ui = { path = "crates/git_ui" }
|
||||
go_to_line = { path = "crates/go_to_line" }
|
||||
google_ai = { path = "crates/google_ai" }
|
||||
gpui = { path = "crates/gpui", default-features = false, features = [
|
||||
"http_client",
|
||||
] }
|
||||
gpui = { path = "crates/gpui", default-features = false }
|
||||
gpui_macros = { path = "crates/gpui_macros" }
|
||||
gpui_tokio = { path = "crates/gpui_tokio" }
|
||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||
@@ -314,6 +314,7 @@ install_cli = { path = "crates/install_cli" }
|
||||
jj = { path = "crates/jj" }
|
||||
jj_ui = { path = "crates/jj_ui" }
|
||||
journal = { path = "crates/journal" }
|
||||
keymap_editor = { path = "crates/keymap_editor" }
|
||||
language = { path = "crates/language" }
|
||||
language_extension = { path = "crates/language_extension" }
|
||||
language_model = { path = "crates/language_model" }
|
||||
@@ -373,6 +374,7 @@ semantic_version = { path = "crates/semantic_version" }
|
||||
session = { path = "crates/session" }
|
||||
settings = { path = "crates/settings" }
|
||||
settings_ui = { path = "crates/settings_ui" }
|
||||
settings_ui_macros = { path = "crates/settings_ui_macros" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
snippet_provider = { path = "crates/snippet_provider" }
|
||||
snippets_ui = { path = "crates/snippets_ui" }
|
||||
@@ -426,7 +428,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
agent-client-protocol = { version = "0.2.0-alpha.6", features = ["unstable"]}
|
||||
agent-client-protocol = { version = "0.2.0-alpha.4", features = ["unstable"]}
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||
any_vec = "0.14"
|
||||
@@ -519,7 +521,7 @@ libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "39f629bdd03d59abd786ed9fc27e8bca02c0c0ec" }
|
||||
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "0874f8742fe55b4dc94308c1e3c0069710d8eeaf" }
|
||||
mach2 = "0.5"
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
metal = "0.29"
|
||||
@@ -588,6 +590,7 @@ serde_json_lenient = { version = "0.2", features = [
|
||||
"preserve_order",
|
||||
"raw_value",
|
||||
] }
|
||||
serde_path_to_error = "0.1.17"
|
||||
serde_repr = "0.1"
|
||||
serde_urlencoded = "0.7"
|
||||
sha2 = "0.10"
|
||||
@@ -624,7 +627,7 @@ tower-http = "0.4.4"
|
||||
tree-sitter = { version = "0.25.6", features = ["wasm"] }
|
||||
tree-sitter-bash = "0.25.0"
|
||||
tree-sitter-c = "0.23"
|
||||
tree-sitter-cpp = "0.23"
|
||||
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
|
||||
tree-sitter-css = "0.23"
|
||||
tree-sitter-diff = "0.1.0"
|
||||
tree-sitter-elixir = "0.3"
|
||||
@@ -691,6 +694,7 @@ features = [
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Graphics_Imaging",
|
||||
"Win32_Graphics_Hlsl",
|
||||
"Win32_Networking_WinSock",
|
||||
"Win32_Security",
|
||||
"Win32_Security_Credentials",
|
||||
|
||||
1
assets/icons/list_filter.svg
Normal file
1
assets/icons/list_filter.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-filter-icon lucide-list-filter"><path d="M3 6h18"/><path d="M7 12h10"/><path d="M10 18h4"/></svg>
|
||||
|
After Width: | Height: | Size: 305 B |
@@ -16,7 +16,6 @@
|
||||
"up": "menu::SelectPrevious",
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel",
|
||||
"alt-shift-enter": "menu::Restart",
|
||||
@@ -64,8 +63,8 @@
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-k ctrl-q": "editor::Rewrap",
|
||||
"ctrl-k q": "editor::Rewrap",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"ctrl-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"cut": "editor::Cut",
|
||||
"shift-delete": "editor::Cut",
|
||||
"ctrl-x": "editor::Cut",
|
||||
@@ -131,8 +130,8 @@
|
||||
"bindings": {
|
||||
"shift-enter": "editor::Newline",
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter": "editor::NewlineAbove",
|
||||
"ctrl-shift-enter": "editor::NewlineBelow",
|
||||
"ctrl-enter": "editor::NewlineBelow",
|
||||
"ctrl-shift-enter": "editor::NewlineAbove",
|
||||
"ctrl-k ctrl-z": "editor::ToggleSoftWrap",
|
||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||
"find": "buffer_search::Deploy",
|
||||
@@ -171,6 +170,7 @@
|
||||
"context": "Markdown",
|
||||
"bindings": {
|
||||
"copy": "markdown::Copy",
|
||||
"ctrl-insert": "markdown::Copy",
|
||||
"ctrl-c": "markdown::Copy"
|
||||
}
|
||||
},
|
||||
@@ -259,6 +259,7 @@
|
||||
"context": "AgentPanel > Markdown",
|
||||
"bindings": {
|
||||
"copy": "markdown::CopyAsMarkdown",
|
||||
"ctrl-insert": "markdown::CopyAsMarkdown",
|
||||
"ctrl-c": "markdown::CopyAsMarkdown"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -70,9 +70,9 @@
|
||||
"cmd-k q": "editor::Rewrap",
|
||||
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-delete": "editor::DeleteToEndOfLine",
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"alt-delete": "editor::DeleteToNextWordEnd",
|
||||
"alt-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"ctrl-w": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"alt-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"cmd-x": "editor::Cut",
|
||||
"cmd-c": "editor::Copy",
|
||||
"cmd-v": "editor::Paste",
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-k ctrl-q": "editor::Rewrap",
|
||||
"ctrl-k q": "editor::Rewrap",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"ctrl-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"cut": "editor::Cut",
|
||||
"shift-delete": "editor::Cut",
|
||||
"ctrl-x": "editor::Cut",
|
||||
@@ -134,8 +134,8 @@
|
||||
"bindings": {
|
||||
"shift-enter": "editor::Newline",
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter": "editor::NewlineAbove",
|
||||
"ctrl-shift-enter": "editor::NewlineBelow",
|
||||
"ctrl-enter": "editor::NewlineBelow",
|
||||
"ctrl-shift-enter": "editor::NewlineAbove",
|
||||
"ctrl-k ctrl-z": "editor::ToggleSoftWrap",
|
||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||
"find": "buffer_search::Deploy",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"alt-,": "pane::GoBack", // xref-pop-marker-stack
|
||||
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
|
||||
"ctrl-d": "editor::Delete", // delete-char
|
||||
"alt-d": "editor::DeleteToNextWordEnd", // kill-word
|
||||
"alt-d": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }], // kill-word
|
||||
"ctrl-k": "editor::KillRingCut", // kill-line
|
||||
"ctrl-w": "editor::Cut", // kill-region
|
||||
"alt-w": "editor::Copy", // kill-ring-save
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
"ctrl-k ctrl-u": "editor::ConvertToUpperCase",
|
||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase",
|
||||
"shift-alt-m": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"ctrl-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"alt-right": "editor::MoveToNextSubwordEnd",
|
||||
"alt-left": "editor::MoveToPreviousSubwordStart",
|
||||
"alt-shift-right": "editor::SelectToNextSubwordEnd",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"alt-,": "pane::GoBack", // xref-pop-marker-stack
|
||||
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
|
||||
"ctrl-d": "editor::Delete", // delete-char
|
||||
"alt-d": "editor::DeleteToNextWordEnd", // kill-word
|
||||
"alt-d": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }], // kill-word
|
||||
"ctrl-k": "editor::KillRingCut", // kill-line
|
||||
"ctrl-w": "editor::Cut", // kill-region
|
||||
"alt-w": "editor::Copy", // kill-ring-save
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase",
|
||||
"cmd-shift-j": "editor::JoinLines",
|
||||
"shift-alt-m": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"ctrl-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"ctrl-right": "editor::MoveToNextSubwordEnd",
|
||||
"ctrl-left": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"alt-shift-backspace": "editor::DeleteToNextWordEnd",
|
||||
"alt-delete": "editor::DeleteToNextWordEnd",
|
||||
"alt-shift-delete": "editor::DeleteToNextWordEnd",
|
||||
"alt-backspace": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"alt-shift-backspace": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"alt-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"alt-shift-delete": ["editor::DeleteToNextWordEnd", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"ctrl-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||
"ctrl-delete": "editor::DeleteToNextSubwordEnd",
|
||||
"alt-left": ["editor::MoveToPreviousWordStart", { "stop_at_soft_wraps": true }],
|
||||
|
||||
@@ -337,7 +337,7 @@
|
||||
"ctrl-x ctrl-z": "editor::Cancel",
|
||||
"ctrl-x ctrl-e": "vim::LineDown",
|
||||
"ctrl-x ctrl-y": "vim::LineUp",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-w": ["editor::DeleteToPreviousWordStart", { "ignore_newlines": false, "ignore_brackets": false }],
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-t": "vim::Indent",
|
||||
"ctrl-d": "vim::Outdent",
|
||||
@@ -354,6 +354,15 @@
|
||||
"ctrl-s": "editor::ShowSignatureHelp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "showing_completions",
|
||||
"bindings": {
|
||||
"ctrl-d": "vim::ScrollDown",
|
||||
"ctrl-u": "vim::ScrollUp",
|
||||
"ctrl-e": "vim::LineDown",
|
||||
"ctrl-y": "vim::LineUp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "(vim_mode == normal || vim_mode == helix_normal) && !menu",
|
||||
"bindings": {
|
||||
@@ -435,7 +444,7 @@
|
||||
"g b": "vim::WindowBottom",
|
||||
|
||||
"shift-r": "editor::Paste",
|
||||
"x": "editor::SelectLine",
|
||||
"x": "vim::HelixSelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
"%": "editor::SelectAll",
|
||||
// Window mode
|
||||
|
||||
@@ -172,7 +172,7 @@ The user has specified the following rules that should be applied:
|
||||
Rules title: {{title}}
|
||||
{{/if}}
|
||||
``````
|
||||
{{contents}}}
|
||||
{{contents}}
|
||||
``````
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
@@ -188,8 +188,8 @@
|
||||
// 4. A box drawn around the following character
|
||||
// "hollow"
|
||||
//
|
||||
// Default: not set, defaults to "bar"
|
||||
"cursor_shape": null,
|
||||
// Default: "bar"
|
||||
"cursor_shape": "bar",
|
||||
// Determines when the mouse cursor should be hidden in an editor or input box.
|
||||
//
|
||||
// 1. Never hide the mouse cursor:
|
||||
@@ -223,9 +223,25 @@
|
||||
"current_line_highlight": "all",
|
||||
// Whether to highlight all occurrences of the selected text in an editor.
|
||||
"selection_highlight": true,
|
||||
// Whether the text selection should have rounded corners.
|
||||
"rounded_selection": true,
|
||||
// The debounce delay before querying highlights from the language
|
||||
// server based on the current cursor location.
|
||||
"lsp_highlight_debounce": 75,
|
||||
// The minimum APCA perceptual contrast between foreground and background colors.
|
||||
// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
|
||||
// especially for dark mode. Values range from 0 to 106.
|
||||
//
|
||||
// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
|
||||
// https://readtech.org/ARC/tests/bronze-simple-mode/
|
||||
// - 0: No contrast adjustment
|
||||
// - 45: Minimum for large fluent text (36px+)
|
||||
// - 60: Minimum for other content text
|
||||
// - 75: Minimum for body text
|
||||
// - 90: Preferred for body text
|
||||
//
|
||||
// This only affects text drawn over highlight backgrounds in the editor.
|
||||
"minimum_contrast_for_highlights": 45,
|
||||
// Whether to pop the completions menu while typing in an editor without
|
||||
// explicitly requesting it.
|
||||
"show_completions_on_input": true,
|
||||
@@ -266,8 +282,8 @@
|
||||
// - "warning"
|
||||
// - "info"
|
||||
// - "hint"
|
||||
// - null — allow all diagnostics (default)
|
||||
"diagnostics_max_severity": null,
|
||||
// - "all" — allow all diagnostics (default)
|
||||
"diagnostics_max_severity": "all",
|
||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
||||
@@ -279,6 +295,8 @@
|
||||
"redact_private_values": false,
|
||||
// The default number of lines to expand excerpts in the multibuffer by.
|
||||
"expand_excerpt_lines": 5,
|
||||
// The default number of context lines shown in multibuffer excerpts.
|
||||
"excerpt_context_lines": 2,
|
||||
// Globs to match against file paths to determine if a file is private.
|
||||
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
@@ -363,6 +381,8 @@
|
||||
// Whether to show code action buttons in the editor toolbar.
|
||||
"code_actions": false
|
||||
},
|
||||
// Whether to allow windows to tab together based on the user’s tabbing preference (macOS only).
|
||||
"use_system_window_tabs": false,
|
||||
// Titlebar related settings
|
||||
"title_bar": {
|
||||
// Whether to show the branch icon beside branch switcher in the titlebar.
|
||||
@@ -1756,7 +1776,7 @@
|
||||
"api_url": "http://localhost:1234/api/v0"
|
||||
},
|
||||
"deepseek": {
|
||||
"api_url": "https://api.deepseek.com"
|
||||
"api_url": "https://api.deepseek.com/v1"
|
||||
},
|
||||
"mistral": {
|
||||
"api_url": "https://api.mistral.ai/v1"
|
||||
@@ -1904,7 +1924,10 @@
|
||||
"debugger": {
|
||||
"stepping_granularity": "line",
|
||||
"save_breakpoints": true,
|
||||
"timeout": 2000,
|
||||
"dock": "bottom",
|
||||
"log_dap_communications": true,
|
||||
"format_dap_log_messages": true,
|
||||
"button": true
|
||||
},
|
||||
// Configures any number of settings profiles that are temporarily applied on
|
||||
|
||||
@@ -785,6 +785,7 @@ pub struct AcpThread {
|
||||
session_id: acp::SessionId,
|
||||
token_usage: Option<TokenUsage>,
|
||||
prompt_capabilities: acp::PromptCapabilities,
|
||||
available_commands: Vec<acp::AvailableCommand>,
|
||||
_observe_prompt_capabilities: Task<anyhow::Result<()>>,
|
||||
determine_shell: Shared<Task<String>>,
|
||||
terminals: HashMap<acp::TerminalId, Entity<Terminal>>,
|
||||
@@ -804,7 +805,6 @@ pub enum AcpThreadEvent {
|
||||
LoadError(LoadError),
|
||||
PromptCapabilitiesUpdated,
|
||||
Refusal,
|
||||
AvailableCommandsUpdated(Vec<acp::AvailableCommand>),
|
||||
}
|
||||
|
||||
impl EventEmitter<AcpThreadEvent> for AcpThread {}
|
||||
@@ -860,6 +860,7 @@ impl AcpThread {
|
||||
action_log: Entity<ActionLog>,
|
||||
session_id: acp::SessionId,
|
||||
mut prompt_capabilities_rx: watch::Receiver<acp::PromptCapabilities>,
|
||||
available_commands: Vec<acp::AvailableCommand>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let prompt_capabilities = *prompt_capabilities_rx.borrow();
|
||||
@@ -899,6 +900,7 @@ impl AcpThread {
|
||||
session_id,
|
||||
token_usage: None,
|
||||
prompt_capabilities,
|
||||
available_commands,
|
||||
_observe_prompt_capabilities: task,
|
||||
terminals: HashMap::default(),
|
||||
determine_shell,
|
||||
@@ -909,6 +911,10 @@ impl AcpThread {
|
||||
self.prompt_capabilities
|
||||
}
|
||||
|
||||
pub fn available_commands(&self) -> Vec<acp::AvailableCommand> {
|
||||
self.available_commands.clone()
|
||||
}
|
||||
|
||||
pub fn connection(&self) -> &Rc<dyn AgentConnection> {
|
||||
&self.connection
|
||||
}
|
||||
@@ -1004,9 +1010,6 @@ impl AcpThread {
|
||||
acp::SessionUpdate::Plan(plan) => {
|
||||
self.update_plan(plan, cx);
|
||||
}
|
||||
acp::SessionUpdate::AvailableCommandsUpdate { available_commands } => {
|
||||
cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -3077,6 +3080,7 @@ mod tests {
|
||||
audio: true,
|
||||
embedded_context: true,
|
||||
}),
|
||||
vec![],
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -231,13 +231,6 @@ impl AgentModelList {
|
||||
AgentModelList::Grouped(groups) => groups.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
AgentModelList::Flat(models) => models.len(),
|
||||
AgentModelList::Grouped(groups) => groups.values().len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
@@ -345,6 +338,7 @@ mod test_support {
|
||||
audio: true,
|
||||
embedded_context: true,
|
||||
}),
|
||||
vec![],
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{MultiBuffer, PathKey};
|
||||
use editor::{MultiBuffer, PathKey, multibuffer_context_lines};
|
||||
use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
@@ -64,7 +64,7 @@ impl Diff {
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer.clone(),
|
||||
hunk_ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
multibuffer.add_diff(diff, cx);
|
||||
@@ -279,7 +279,7 @@ impl PendingDiff {
|
||||
path_key,
|
||||
buffer,
|
||||
ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
multibuffer.add_diff(buffer_diff.clone(), cx);
|
||||
@@ -305,7 +305,7 @@ impl PendingDiff {
|
||||
PathKey::for_buffer(&self.new_buffer, cx),
|
||||
self.new_buffer.clone(),
|
||||
ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
let end = multibuffer.len(cx);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage, VersionCheckType};
|
||||
use editor::Editor;
|
||||
use extension_host::ExtensionStore;
|
||||
use extension_host::{ExtensionOperation, ExtensionStore};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
Animation, AnimationExt as _, App, Context, CursorStyle, Entity, EventEmitter,
|
||||
InteractiveElement as _, ParentElement as _, Render, SharedString, StatefulInteractiveElement,
|
||||
Styled, Transformation, Window, actions, percentage,
|
||||
App, Context, CursorStyle, Entity, EventEmitter, InteractiveElement as _, ParentElement as _,
|
||||
Render, SharedString, StatefulInteractiveElement, Styled, Window, actions,
|
||||
};
|
||||
use language::{
|
||||
BinaryStatus, LanguageRegistry, LanguageServerId, LanguageServerName,
|
||||
@@ -25,7 +24,10 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use ui::{ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use ui::{
|
||||
ButtonLike, CommonAnimationExt, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||
prelude::*,
|
||||
};
|
||||
use util::truncate_and_trailoff;
|
||||
use workspace::{StatusItemView, Workspace, item::ItemHandle};
|
||||
|
||||
@@ -405,13 +407,7 @@ impl ActivityIndicator {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
),
|
||||
message,
|
||||
@@ -433,11 +429,7 @@ impl ActivityIndicator {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!("Debug: {}", session.read(cx).adapter()),
|
||||
@@ -460,11 +452,7 @@ impl ActivityIndicator {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: job_info.message.into(),
|
||||
@@ -671,8 +659,9 @@ impl ActivityIndicator {
|
||||
}
|
||||
|
||||
// Show any application auto-update info.
|
||||
if let Some(updater) = &self.auto_updater {
|
||||
return match &updater.read(cx).status() {
|
||||
self.auto_updater
|
||||
.as_ref()
|
||||
.and_then(|updater| match &updater.read(cx).status() {
|
||||
AutoUpdateStatus::Checking => Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
@@ -728,28 +717,49 @@ impl ActivityIndicator {
|
||||
tooltip_message: None,
|
||||
}),
|
||||
AutoUpdateStatus::Idle => None,
|
||||
};
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
if let Some(extension_store) =
|
||||
ExtensionStore::try_global(cx).map(|extension_store| extension_store.read(cx))
|
||||
&& let Some((extension_id, operation)) =
|
||||
extension_store.outstanding_operations().iter().next()
|
||||
{
|
||||
let (message, icon, rotate) = match operation {
|
||||
ExtensionOperation::Install => (
|
||||
format!("Installing {extension_id} extension…"),
|
||||
IconName::LoadCircle,
|
||||
true,
|
||||
),
|
||||
ExtensionOperation::Upgrade => (
|
||||
format!("Updating {extension_id} extension…"),
|
||||
IconName::Download,
|
||||
false,
|
||||
),
|
||||
ExtensionOperation::Remove => (
|
||||
format!("Removing {extension_id} extension…"),
|
||||
IconName::LoadCircle,
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
if let Some(extension_store) =
|
||||
ExtensionStore::try_global(cx).map(|extension_store| extension_store.read(cx))
|
||||
&& let Some(extension_id) = extension_store.outstanding_operations().keys().next()
|
||||
{
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: format!("Updating {extension_id} extension…"),
|
||||
on_click: Some(Arc::new(|this, window, cx| {
|
||||
this.dismiss_error_message(&DismissErrorMessage, window, cx)
|
||||
})),
|
||||
tooltip_message: None,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
Some(Content {
|
||||
icon: Some(Icon::new(icon).size(IconSize::Small).map(|this| {
|
||||
if rotate {
|
||||
this.with_rotate_animation(3).into_any_element()
|
||||
} else {
|
||||
this.into_any_element()
|
||||
}
|
||||
})),
|
||||
message,
|
||||
on_click: Some(Arc::new(|this, window, cx| {
|
||||
this.dismiss_error_message(&Default::default(), window, cx)
|
||||
})),
|
||||
tooltip_message: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn version_tooltip_message(version: &VersionCheckType) -> String {
|
||||
|
||||
@@ -292,6 +292,7 @@ impl NativeAgent {
|
||||
action_log.clone(),
|
||||
session_id.clone(),
|
||||
prompt_capabilities_rx,
|
||||
vec![],
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -2477,6 +2477,7 @@ fn setup_context_server(
|
||||
path: "somebinary".into(),
|
||||
args: Vec::new(),
|
||||
env: None,
|
||||
timeout: None,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -224,6 +224,7 @@ impl AgentConnection for AcpConnection {
|
||||
session_id.clone(),
|
||||
// ACP doesn't currently support per-session prompt capabilities or changing capabilities dynamically.
|
||||
watch::Receiver::constant(self.agent_capabilities.prompt_capabilities),
|
||||
response.available_commands,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use language_models::provider::anthropic::AnthropicLanguageModelProvider;
|
||||
use settings::SettingsStore;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
@@ -39,7 +40,7 @@ impl ClaudeCode {
|
||||
Self::PACKAGE_NAME.into(),
|
||||
"node_modules/@anthropic-ai/claude-code/cli.js".into(),
|
||||
true,
|
||||
Some("0.2.5".parse().unwrap()),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
@@ -80,15 +81,8 @@ impl AgentServer for ClaudeCode {
|
||||
let settings = cx.read_global(|settings: &SettingsStore, _| {
|
||||
settings.get::<AllAgentServersSettings>(None).claude.clone()
|
||||
});
|
||||
let project = delegate.project().clone();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut project_env = project
|
||||
.update(cx, |project, cx| {
|
||||
project.directory_environment(root_dir.as_path().into(), cx)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let mut command = if let Some(settings) = settings {
|
||||
settings.command
|
||||
} else {
|
||||
@@ -104,13 +98,17 @@ impl AgentServer for ClaudeCode {
|
||||
})?
|
||||
.await?
|
||||
};
|
||||
project_env.extend(command.env.take().unwrap_or_default());
|
||||
command.env = Some(project_env);
|
||||
|
||||
command
|
||||
.env
|
||||
.get_or_insert_default()
|
||||
.insert("ANTHROPIC_API_KEY".to_owned(), "".to_owned());
|
||||
if let Some(api_key) = cx
|
||||
.update(AnthropicLanguageModelProvider::api_key)?
|
||||
.await
|
||||
.ok()
|
||||
{
|
||||
command
|
||||
.env
|
||||
.get_or_insert_default()
|
||||
.insert("ANTHROPIC_API_KEY".to_owned(), api_key.key);
|
||||
}
|
||||
|
||||
let root_dir_exists = fs.is_dir(&root_dir).await;
|
||||
anyhow::ensure!(
|
||||
|
||||
@@ -41,19 +41,12 @@ impl AgentServer for Gemini {
|
||||
let settings = cx.read_global(|settings: &SettingsStore, _| {
|
||||
settings.get::<AllAgentServersSettings>(None).gemini.clone()
|
||||
});
|
||||
let project = delegate.project().clone();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let ignore_system_version = settings
|
||||
.as_ref()
|
||||
.and_then(|settings| settings.ignore_system_version)
|
||||
.unwrap_or(true);
|
||||
let mut project_env = project
|
||||
.update(cx, |project, cx| {
|
||||
project.directory_environment(root_dir.as_path().into(), cx)
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let mut command = if let Some(settings) = settings
|
||||
&& let Some(command) = settings.custom_command()
|
||||
{
|
||||
@@ -74,12 +67,13 @@ impl AgentServer for Gemini {
|
||||
if !command.args.contains(&ACP_ARG.into()) {
|
||||
command.args.push(ACP_ARG.into());
|
||||
}
|
||||
|
||||
if let Some(api_key) = cx.update(GoogleLanguageModelProvider::api_key)?.await.ok() {
|
||||
project_env
|
||||
command
|
||||
.env
|
||||
.get_or_insert_default()
|
||||
.insert("GEMINI_API_KEY".to_owned(), api_key.key);
|
||||
}
|
||||
project_env.extend(command.env.take().unwrap_or_default());
|
||||
command.env = Some(project_env);
|
||||
|
||||
let root_dir_exists = fs.is_dir(&root_dir).await;
|
||||
anyhow::ensure!(
|
||||
|
||||
@@ -6,13 +6,13 @@ use collections::HashMap;
|
||||
use gpui::{App, SharedString};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
AllAgentServersSettings::register(cx);
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)]
|
||||
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUi)]
|
||||
pub struct AllAgentServersSettings {
|
||||
pub gemini: Option<BuiltinAgentServerSettings>,
|
||||
pub claude: Option<CustomAgentServerSettings>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use gpui::{App, Pixels, SharedString};
|
||||
use language_model::LanguageModel;
|
||||
use schemars::{JsonSchema, json_schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use crate::agent_profile::*;
|
||||
@@ -223,7 +223,7 @@ impl AgentSettingsContent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default, SettingsUi)]
|
||||
pub struct AgentSettingsContent {
|
||||
/// Whether the Agent is enabled.
|
||||
///
|
||||
|
||||
@@ -207,7 +207,7 @@ impl EntryViewState {
|
||||
self.entries.drain(range);
|
||||
}
|
||||
|
||||
pub fn agent_font_size_changed(&mut self, cx: &mut App) {
|
||||
pub fn settings_changed(&mut self, cx: &mut App) {
|
||||
for entry in self.entries.iter() {
|
||||
match entry {
|
||||
Entry::UserMessage { .. } | Entry::AssistantMessage { .. } => {}
|
||||
|
||||
@@ -2408,7 +2408,7 @@ mod tests {
|
||||
lsp::SymbolInformation {
|
||||
name: "MySymbol".into(),
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path(path!("/dir/a/one.txt")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/dir/a/one.txt")).unwrap(),
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 0),
|
||||
lsp::Position::new(0, 1),
|
||||
|
||||
@@ -71,7 +71,7 @@ impl AcpModelPickerDelegate {
|
||||
let (models, selected_model) = futures::join!(models_task, selected_model_task);
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.delegate.models = models.log_err();
|
||||
this.delegate.models = models.ok();
|
||||
this.delegate.selected_model = selected_model.ok();
|
||||
this.refresh(window, cx)
|
||||
})
|
||||
@@ -141,11 +141,6 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let filtered_models = match this
|
||||
.read_with(cx, |this, cx| {
|
||||
if let Some(models) = this.delegate.models.as_ref() {
|
||||
log::debug!("Filtering {} models.", models.len());
|
||||
} else {
|
||||
log::debug!("No models available.");
|
||||
}
|
||||
this.delegate.models.clone().map(move |models| {
|
||||
fuzzy_search(models, query, cx.background_executor().clone())
|
||||
})
|
||||
@@ -157,8 +152,6 @@ impl PickerDelegate for AcpModelPickerDelegate {
|
||||
None => AgentModelList::Flat(vec![]),
|
||||
};
|
||||
|
||||
log::debug!("Filtered models. {} available.", filtered_models.len());
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.delegate.filtered_entries =
|
||||
info_list_to_picker_entries(filtered_models).collect();
|
||||
|
||||
@@ -23,9 +23,9 @@ use gpui::{
|
||||
Action, Animation, AnimationExt, AnyView, App, BorderStyle, ClickEvent, ClipboardItem,
|
||||
CursorStyle, EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length,
|
||||
ListOffset, ListState, MouseButton, PlatformDisplay, SharedString, Stateful, StyleRefinement,
|
||||
Subscription, Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity,
|
||||
Window, WindowHandle, div, ease_in_out, linear_color_stop, linear_gradient, list, percentage,
|
||||
point, prelude::*, pulsating_between,
|
||||
Subscription, Task, TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, Window,
|
||||
WindowHandle, div, ease_in_out, linear_color_stop, linear_gradient, list, point, prelude::*,
|
||||
pulsating_between,
|
||||
};
|
||||
use language::Buffer;
|
||||
|
||||
@@ -43,10 +43,10 @@ use std::{collections::BTreeMap, rc::Rc, time::Duration};
|
||||
use task::SpawnInTerminal;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use text::Anchor;
|
||||
use theme::{AgentFontSize, ThemeSettings};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
Callout, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle,
|
||||
Scrollbar, ScrollbarState, SpinnerLabel, TintColor, Tooltip, prelude::*,
|
||||
Callout, CommonAnimationExt, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding,
|
||||
PopoverMenuHandle, Scrollbar, ScrollbarState, SpinnerLabel, TintColor, Tooltip, prelude::*,
|
||||
};
|
||||
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
|
||||
use workspace::{CollaboratorId, Workspace};
|
||||
@@ -290,7 +290,7 @@ pub struct AcpThreadView {
|
||||
is_loading_contents: bool,
|
||||
new_server_version_available: Option<SharedString>,
|
||||
_cancel_task: Option<Task<()>>,
|
||||
_subscriptions: [Subscription; 4],
|
||||
_subscriptions: [Subscription; 3],
|
||||
}
|
||||
|
||||
enum ThreadState {
|
||||
@@ -380,8 +380,7 @@ impl AcpThreadView {
|
||||
});
|
||||
|
||||
let subscriptions = [
|
||||
cx.observe_global_in::<SettingsStore>(window, Self::agent_font_size_changed),
|
||||
cx.observe_global_in::<AgentFontSize>(window, Self::agent_font_size_changed),
|
||||
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
|
||||
cx.subscribe_in(&message_editor, window, Self::handle_message_editor_event),
|
||||
cx.subscribe_in(&entry_view_state, window, Self::handle_entry_view_event),
|
||||
];
|
||||
@@ -431,7 +430,6 @@ impl AcpThreadView {
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.available_commands.replace(vec![]);
|
||||
self.new_server_version_available.take();
|
||||
cx.notify();
|
||||
}
|
||||
@@ -537,6 +535,26 @@ impl AcpThreadView {
|
||||
Ok(thread) => {
|
||||
let action_log = thread.read(cx).action_log().clone();
|
||||
|
||||
let mut available_commands = thread.read(cx).available_commands();
|
||||
|
||||
if connection
|
||||
.auth_methods()
|
||||
.iter()
|
||||
.any(|method| method.id.0.as_ref() == "claude-login")
|
||||
{
|
||||
available_commands.push(acp::AvailableCommand {
|
||||
name: "login".to_owned(),
|
||||
description: "Authenticate".to_owned(),
|
||||
input: None,
|
||||
});
|
||||
available_commands.push(acp::AvailableCommand {
|
||||
name: "logout".to_owned(),
|
||||
description: "Authenticate".to_owned(),
|
||||
input: None,
|
||||
});
|
||||
}
|
||||
this.available_commands.replace(available_commands);
|
||||
|
||||
this.prompt_capabilities
|
||||
.set(thread.read(cx).prompt_capabilities());
|
||||
|
||||
@@ -984,7 +1002,7 @@ impl AcpThreadView {
|
||||
this,
|
||||
AuthRequired {
|
||||
description: None,
|
||||
provider_id: None,
|
||||
provider_id: Some(language_model::ANTHROPIC_PROVIDER_ID),
|
||||
},
|
||||
agent,
|
||||
connection,
|
||||
@@ -1325,30 +1343,6 @@ impl AcpThreadView {
|
||||
.set(thread.read(cx).prompt_capabilities());
|
||||
}
|
||||
AcpThreadEvent::TokenUsageUpdated => {}
|
||||
AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
|
||||
let mut available_commands = available_commands.clone();
|
||||
|
||||
if thread
|
||||
.read(cx)
|
||||
.connection()
|
||||
.auth_methods()
|
||||
.iter()
|
||||
.any(|method| method.id.0.as_ref() == "claude-login")
|
||||
{
|
||||
available_commands.push(acp::AvailableCommand {
|
||||
name: "login".to_owned(),
|
||||
description: "Authenticate".to_owned(),
|
||||
input: None,
|
||||
});
|
||||
available_commands.push(acp::AvailableCommand {
|
||||
name: "logout".to_owned(),
|
||||
description: "Authenticate".to_owned(),
|
||||
input: None,
|
||||
});
|
||||
}
|
||||
|
||||
self.available_commands.replace(available_commands);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
@@ -2640,13 +2634,7 @@ impl AcpThreadView {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
),
|
||||
.with_rotate_animation(2)
|
||||
)
|
||||
})
|
||||
.child(
|
||||
@@ -3017,8 +3005,6 @@ impl AcpThreadView {
|
||||
let show_description =
|
||||
configuration_view.is_none() && description.is_none() && pending_auth_method.is_none();
|
||||
|
||||
let auth_methods = connection.auth_methods();
|
||||
|
||||
v_flex().flex_1().size_full().justify_end().child(
|
||||
v_flex()
|
||||
.p_2()
|
||||
@@ -3049,23 +3035,21 @@ impl AcpThreadView {
|
||||
.cloned()
|
||||
.map(|view| div().w_full().child(view)),
|
||||
)
|
||||
.when(show_description, |el| {
|
||||
el.child(
|
||||
Label::new(format!(
|
||||
"You are not currently authenticated with {}.{}",
|
||||
self.agent.name(),
|
||||
if auth_methods.len() > 1 {
|
||||
" Please choose one of the following options:"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.mb_1()
|
||||
.ml_5(),
|
||||
)
|
||||
})
|
||||
.when(
|
||||
show_description,
|
||||
|el| {
|
||||
el.child(
|
||||
Label::new(format!(
|
||||
"You are not currently authenticated with {}. Please choose one of the following options:",
|
||||
self.agent.name()
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.mb_1()
|
||||
.ml_5(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.when_some(pending_auth_method, |el, _| {
|
||||
el.child(
|
||||
h_flex()
|
||||
@@ -3077,21 +3061,12 @@ impl AcpThreadView {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(
|
||||
delta,
|
||||
)))
|
||||
},
|
||||
)
|
||||
.into_any_element(),
|
||||
.with_rotate_animation(2)
|
||||
)
|
||||
.child(Label::new("Authenticating…").size(LabelSize::Small)),
|
||||
)
|
||||
})
|
||||
.when(!auth_methods.is_empty(), |this| {
|
||||
.when(!connection.auth_methods().is_empty(), |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
@@ -3103,32 +3078,38 @@ impl AcpThreadView {
|
||||
.pt_2()
|
||||
.border_color(cx.theme().colors().border.opacity(0.8))
|
||||
})
|
||||
.children(connection.auth_methods().iter().enumerate().rev().map(
|
||||
|(ix, method)| {
|
||||
Button::new(
|
||||
SharedString::from(method.id.0.clone()),
|
||||
method.name.clone(),
|
||||
)
|
||||
.when(ix == 0, |el| {
|
||||
el.style(ButtonStyle::Tinted(ui::TintColor::Warning))
|
||||
})
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click({
|
||||
let method_id = method.id.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
telemetry::event!(
|
||||
"Authenticate Agent Started",
|
||||
agent = this.agent.telemetry_id(),
|
||||
method = method_id
|
||||
);
|
||||
|
||||
this.authenticate(method_id.clone(), window, cx)
|
||||
.children(
|
||||
connection
|
||||
.auth_methods()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.map(|(ix, method)| {
|
||||
Button::new(
|
||||
SharedString::from(method.id.0.clone()),
|
||||
method.name.clone(),
|
||||
)
|
||||
.when(ix == 0, |el| {
|
||||
el.style(ButtonStyle::Tinted(ui::TintColor::Warning))
|
||||
})
|
||||
})
|
||||
},
|
||||
)),
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click({
|
||||
let method_id = method.id.clone();
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
telemetry::event!(
|
||||
"Authenticate Agent Started",
|
||||
agent = this.agent.telemetry_id(),
|
||||
method = method_id
|
||||
);
|
||||
|
||||
this.authenticate(method_id.clone(), window, cx)
|
||||
})
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
}),
|
||||
})
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3393,13 +3374,7 @@ impl AcpThreadView {
|
||||
acp::PlanEntryStatus::InProgress => Icon::new(IconName::TodoProgress)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Accent)
|
||||
.with_animation(
|
||||
"running",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
acp::PlanEntryStatus::Completed => Icon::new(IconName::TodoComplete)
|
||||
.size(IconSize::Small)
|
||||
@@ -4757,9 +4732,9 @@ impl AcpThreadView {
|
||||
)
|
||||
}
|
||||
|
||||
fn agent_font_size_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn settings_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.entry_view_state.update(cx, |entry_view_state, cx| {
|
||||
entry_view_state.agent_font_size_changed(cx);
|
||||
entry_view_state.settings_changed(cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5193,11 +5168,7 @@ fn loading_contents_spinner(size: IconSize) -> AnyElement {
|
||||
Icon::new(IconName::LoadCircle)
|
||||
.size(size)
|
||||
.color(Color::Accent)
|
||||
.with_animation(
|
||||
"load_context_circle",
|
||||
Animation::new(Duration::from_secs(3)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.with_rotate_animation(3)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -5774,6 +5745,7 @@ pub(crate) mod tests {
|
||||
audio: true,
|
||||
embedded_context: true,
|
||||
}),
|
||||
vec![],
|
||||
cx,
|
||||
)
|
||||
})))
|
||||
@@ -5833,6 +5805,7 @@ pub(crate) mod tests {
|
||||
audio: true,
|
||||
embedded_context: true,
|
||||
}),
|
||||
Vec::new(),
|
||||
cx,
|
||||
)
|
||||
})))
|
||||
|
||||
@@ -23,9 +23,8 @@ use gpui::{
|
||||
AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry,
|
||||
ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla,
|
||||
ListAlignment, ListOffset, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful,
|
||||
StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation,
|
||||
UnderlineStyle, WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, percentage,
|
||||
pulsating_between,
|
||||
StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, UnderlineStyle,
|
||||
WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, pulsating_between,
|
||||
};
|
||||
use language::{Buffer, Language, LanguageRegistry};
|
||||
use language_model::{
|
||||
@@ -46,8 +45,8 @@ use std::time::Duration;
|
||||
use text::ToPoint;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
Banner, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
|
||||
Tooltip, prelude::*,
|
||||
Banner, CommonAnimationExt, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar,
|
||||
ScrollbarState, TextSize, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
@@ -2661,15 +2660,7 @@ impl ActiveThread {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(
|
||||
percentage(delta),
|
||||
))
|
||||
},
|
||||
)
|
||||
.with_rotate_animation(2)
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -2845,17 +2836,11 @@ impl ActiveThread {
|
||||
}
|
||||
ToolUseStatus::Pending
|
||||
| ToolUseStatus::InputStillStreaming
|
||||
| ToolUseStatus::Running => {
|
||||
let icon = Icon::new(IconName::ArrowCircle)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small);
|
||||
icon.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
| ToolUseStatus::Running => Icon::new(IconName::ArrowCircle)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
ToolUseStatus::Finished(_) => div().w_0().into_any_element(),
|
||||
ToolUseStatus::Error(_) => {
|
||||
let icon = Icon::new(IconName::Close)
|
||||
@@ -2944,15 +2929,7 @@ impl ActiveThread {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Accent)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(
|
||||
delta,
|
||||
)))
|
||||
},
|
||||
),
|
||||
.with_rotate_animation(2),
|
||||
)
|
||||
.child(
|
||||
Label::new("Running…")
|
||||
|
||||
@@ -3,7 +3,7 @@ mod configure_context_server_modal;
|
||||
mod manage_profiles_modal;
|
||||
mod tool_picker;
|
||||
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use agent_servers::{AgentServerCommand, AllAgentServersSettings, CustomAgentServerSettings};
|
||||
use agent_settings::AgentSettings;
|
||||
@@ -17,9 +17,8 @@ use extension::ExtensionManifest;
|
||||
use extension_host::ExtensionStore;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt as _, AnyView, App, AsyncWindowContext, Corner, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, Hsla, ScrollHandle, Subscription, Task, Transformation,
|
||||
WeakEntity, percentage,
|
||||
Action, AnyView, App, AsyncWindowContext, Corner, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Hsla, ScrollHandle, Subscription, Task, WeakEntity,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{
|
||||
@@ -32,8 +31,9 @@ use project::{
|
||||
};
|
||||
use settings::{Settings, SettingsStore, update_settings_file};
|
||||
use ui::{
|
||||
Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
|
||||
Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip, prelude::*,
|
||||
Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex,
|
||||
Indicator, PopoverMenu, Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip,
|
||||
prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{Workspace, create_and_open_local_file};
|
||||
@@ -670,10 +670,9 @@ impl AgentConfiguration {
|
||||
Icon::new(IconName::LoadCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Accent)
|
||||
.with_animation(
|
||||
SharedString::from(format!("{}-starting", context_server_id.0,)),
|
||||
Animation::new(Duration::from_secs(3)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
.with_keyed_rotate_animation(
|
||||
SharedString::from(format!("{}-starting", context_server_id.0)),
|
||||
3,
|
||||
)
|
||||
.into_any_element(),
|
||||
"Server is starting.",
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use context_server::{ContextServerCommand, ContextServerId};
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{
|
||||
Animation, AnimationExt as _, AsyncWindowContext, DismissEvent, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle,
|
||||
WeakEntity, percentage, prelude::*,
|
||||
AsyncWindowContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task,
|
||||
TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, prelude::*,
|
||||
};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||
@@ -24,7 +22,9 @@ use project::{
|
||||
};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||
use ui::{
|
||||
CommonAnimationExt, KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
@@ -638,11 +638,7 @@ impl ConfigureContextServerModal {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -10,12 +10,12 @@ use editor::{
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot,
|
||||
SelectionEffects, ToPoint,
|
||||
actions::{GoToHunk, GoToPreviousHunk},
|
||||
multibuffer_context_lines,
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt, AnyElement, AnyView, App, AppContext, Empty, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, Global, SharedString, Subscription, Task, Transformation,
|
||||
WeakEntity, Window, percentage, prelude::*,
|
||||
Action, AnyElement, AnyView, App, AppContext, Empty, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, Global, SharedString, Subscription, Task, WeakEntity, Window, prelude::*,
|
||||
};
|
||||
|
||||
use language::{Buffer, Capability, DiskState, OffsetRangeExt, Point};
|
||||
@@ -28,9 +28,8 @@ use std::{
|
||||
collections::hash_map::Entry,
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use ui::{IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider};
|
||||
use ui::{CommonAnimationExt, IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
|
||||
@@ -257,7 +256,7 @@ impl AgentDiffPane {
|
||||
path_key.clone(),
|
||||
buffer.clone(),
|
||||
diff_hunk_ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
multibuffer.add_diff(diff_handle, cx);
|
||||
@@ -1083,11 +1082,7 @@ impl Render for AgentDiffToolbar {
|
||||
Icon::new(IconName::LoadCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Accent)
|
||||
.with_animation(
|
||||
"load_circle",
|
||||
Animation::new(Duration::from_secs(3)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
),
|
||||
.with_rotate_animation(3),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
@@ -1533,7 +1528,6 @@ impl AgentDiff {
|
||||
| AcpThreadEvent::EntriesRemoved(_)
|
||||
| AcpThreadEvent::ToolAuthorizationRequired
|
||||
| AcpThreadEvent::PromptCapabilitiesUpdated
|
||||
| AcpThreadEvent::AvailableCommandsUpdated(_)
|
||||
| AcpThreadEvent::Retry(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,8 @@ impl InlineAssistant {
|
||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let enabled = AgentSettings::get_global(cx).enabled;
|
||||
let enabled = !DisableAiSettings::get_global(cx).disable_ai
|
||||
&& AgentSettings::get_global(cx).enabled;
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.set_assistant_enabled(enabled, cx)
|
||||
});
|
||||
|
||||
@@ -93,8 +93,8 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
};
|
||||
|
||||
let bottom_padding = match &self.mode {
|
||||
PromptEditorMode::Buffer { .. } => Pixels::from(0.),
|
||||
PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
|
||||
PromptEditorMode::Buffer { .. } => rems_from_px(2.0),
|
||||
PromptEditorMode::Terminal { .. } => rems_from_px(8.0),
|
||||
};
|
||||
|
||||
buttons.extend(self.render_buttons(window, cx));
|
||||
@@ -762,20 +762,22 @@ impl<T: 'static> PromptEditor<T> {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
let font_size = TextSize::Default.rems(cx);
|
||||
let line_height = font_size.to_pixels(window.rem_size()) * 1.3;
|
||||
fn render_editor(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
div()
|
||||
.key_context("InlineAssistEditor")
|
||||
.size_full()
|
||||
.p_2()
|
||||
.pl_1()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.bg(colors.editor_background)
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let font_size = settings.buffer_font_size(cx);
|
||||
let line_height = font_size * 1.2;
|
||||
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
color: colors.editor_foreground,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_features: settings.buffer_font.features.clone(),
|
||||
font_size: font_size.into(),
|
||||
@@ -786,7 +788,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||
EditorElement::new(
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
background: colors.editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
|
||||
@@ -2,10 +2,10 @@ use anyhow::Result;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
|
||||
/// Settings for slash commands.
|
||||
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
|
||||
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi)]
|
||||
pub struct SlashCommandSettings {
|
||||
/// Settings for the `/cargo-workspace` slash command.
|
||||
#[serde(default)]
|
||||
|
||||
@@ -25,8 +25,8 @@ use gpui::{
|
||||
Action, Animation, AnimationExt, AnyElement, AnyView, App, ClipboardEntry, ClipboardItem,
|
||||
Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement,
|
||||
IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size,
|
||||
StatefulInteractiveElement, Styled, Subscription, Task, Transformation, WeakEntity, actions,
|
||||
div, img, percentage, point, prelude::*, pulsating_between, size,
|
||||
StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, actions, div, img, point,
|
||||
prelude::*, pulsating_between, size,
|
||||
};
|
||||
use language::{
|
||||
BufferSnapshot, LspAdapterDelegate, ToOffset,
|
||||
@@ -53,8 +53,8 @@ use std::{
|
||||
};
|
||||
use text::SelectionGoal;
|
||||
use ui::{
|
||||
ButtonLike, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle, TintColor, Tooltip,
|
||||
prelude::*,
|
||||
ButtonLike, CommonAnimationExt, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle,
|
||||
TintColor, Tooltip, prelude::*,
|
||||
};
|
||||
use util::{ResultExt, maybe};
|
||||
use workspace::{
|
||||
@@ -1061,15 +1061,7 @@ impl TextThreadEditor {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(
|
||||
percentage(delta),
|
||||
))
|
||||
},
|
||||
)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
);
|
||||
note = Some(Self::esc_kbd(cx).into_any_element());
|
||||
@@ -2790,11 +2782,7 @@ fn invoked_slash_command_fold_placeholder(
|
||||
.child(Label::new(format!("/{}", command.name)))
|
||||
.map(|parent| match &command.status {
|
||||
InvokedSlashCommandStatus::Running(_) => {
|
||||
parent.child(Icon::new(IconName::ArrowCircle).with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(4)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
))
|
||||
parent.child(Icon::new(IconName::ArrowCircle).with_rotate_animation(4))
|
||||
}
|
||||
InvokedSlashCommandStatus::Error(message) => parent.child(
|
||||
Label::new(format!("error: {message}"))
|
||||
|
||||
@@ -62,6 +62,8 @@ impl AgentNotification {
|
||||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
tabbing_identifier: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::sync::Arc;
|
||||
|
||||
use client::{Client, UserStore, zed_urls};
|
||||
use cloud_llm_client::Plan;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyElement, App, Entity, IntoElement, RenderOnce, Transformation,
|
||||
Window, percentage,
|
||||
};
|
||||
use ui::{Divider, Vector, VectorName, prelude::*};
|
||||
use gpui::{AnyElement, App, Entity, IntoElement, RenderOnce, Window};
|
||||
use ui::{CommonAnimationExt, Divider, Vector, VectorName, prelude::*};
|
||||
|
||||
use crate::{SignInStatus, YoungAccountBanner, plan_definitions::PlanDefinitions};
|
||||
|
||||
@@ -147,11 +144,7 @@ impl RenderOnce for AiUpsellCard {
|
||||
rems_from_px(72.),
|
||||
)
|
||||
.color(Color::Custom(cx.theme().colors().text_accent.alpha(0.3)))
|
||||
.with_animation(
|
||||
"loading_stamp",
|
||||
Animation::new(Duration::from_secs(10)).repeat(),
|
||||
|this, delta| this.transform(Transformation::rotate(percentage(delta))),
|
||||
),
|
||||
.with_rotate_animation(10),
|
||||
);
|
||||
|
||||
let pro_trial_stamp = div()
|
||||
|
||||
@@ -373,7 +373,7 @@ pub async fn complete(
|
||||
.uri(uri)
|
||||
.header("Anthropic-Version", "2023-06-01")
|
||||
.header("Anthropic-Beta", beta_headers)
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("X-Api-Key", api_key.trim())
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
let serialized_request =
|
||||
@@ -526,7 +526,7 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
.uri(uri)
|
||||
.header("Anthropic-Version", "2023-06-01")
|
||||
.header("Anthropic-Beta", beta_headers)
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("X-Api-Key", api_key.trim())
|
||||
.header("Content-Type", "application/json");
|
||||
let serialized_request =
|
||||
serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
|
||||
|
||||
@@ -492,7 +492,7 @@ mod custom_path_matcher {
|
||||
pub fn new(globs: &[String]) -> Result<Self, globset::Error> {
|
||||
let globs = globs
|
||||
.iter()
|
||||
.map(|glob| Glob::new(&SanitizedPath::from(glob).to_glob_string()))
|
||||
.map(|glob| Glob::new(&SanitizedPath::new(glob).to_glob_string()))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect();
|
||||
let sources_with_trailing_slash = globs
|
||||
|
||||
@@ -35,7 +35,7 @@ impl Tool for DeletePathTool {
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &Entity<Project>, _: &App) -> bool {
|
||||
false
|
||||
true
|
||||
}
|
||||
|
||||
fn may_perform_edits(&self) -> bool {
|
||||
|
||||
@@ -11,11 +11,13 @@ use assistant_tool::{
|
||||
AnyToolCard, Tool, ToolCard, ToolResult, ToolResultContent, ToolResultOutput, ToolUseStatus,
|
||||
};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey};
|
||||
use editor::{
|
||||
Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey, multibuffer_context_lines,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task,
|
||||
TextStyleRefinement, Transformation, WeakEntity, percentage, pulsating_between, px,
|
||||
TextStyleRefinement, WeakEntity, pulsating_between, px,
|
||||
};
|
||||
use indoc::formatdoc;
|
||||
use language::{
|
||||
@@ -42,7 +44,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{Disclosure, Tooltip, prelude::*};
|
||||
use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -474,7 +476,7 @@ impl Tool for EditFileTool {
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer,
|
||||
diff_hunk_ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
multibuffer.add_diff(buffer_diff, cx);
|
||||
@@ -703,7 +705,7 @@ impl EditFileToolCard {
|
||||
PathKey::for_buffer(buffer, cx),
|
||||
buffer.clone(),
|
||||
ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
let end = multibuffer.len(cx);
|
||||
@@ -791,7 +793,7 @@ impl EditFileToolCard {
|
||||
path_key,
|
||||
buffer,
|
||||
ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
multibuffer.add_diff(buffer_diff.clone(), cx);
|
||||
@@ -937,11 +939,7 @@ impl ToolCard for EditFileToolCard {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
),
|
||||
.with_rotate_animation(2),
|
||||
)
|
||||
})
|
||||
.when_some(error_message, |header, error_message| {
|
||||
|
||||
@@ -8,8 +8,8 @@ use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{Tool, ToolCard, ToolResult, ToolUseStatus};
|
||||
use futures::{FutureExt as _, future::Shared};
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyWindowHandle, App, AppContext, Empty, Entity, EntityId, Task,
|
||||
TextStyleRefinement, Transformation, WeakEntity, Window, percentage,
|
||||
AnyWindowHandle, App, AppContext, Empty, Entity, EntityId, Task, TextStyleRefinement,
|
||||
WeakEntity, Window,
|
||||
};
|
||||
use language::LineEnding;
|
||||
use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat};
|
||||
@@ -28,7 +28,7 @@ use std::{
|
||||
};
|
||||
use terminal_view::TerminalView;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{Disclosure, Tooltip, prelude::*};
|
||||
use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*};
|
||||
use util::{
|
||||
ResultExt, get_system_shell, markdown::MarkdownInlineCode, size::format_file_size,
|
||||
time::duration_alt_display,
|
||||
@@ -522,11 +522,7 @@ impl ToolCard for TerminalToolCard {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
),
|
||||
.with_rotate_animation(2),
|
||||
)
|
||||
})
|
||||
.when(tool_failed || command_failed, |header| {
|
||||
|
||||
@@ -2,9 +2,9 @@ use anyhow::Result;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct AudioSettings {
|
||||
/// Opt into the new audio system.
|
||||
#[serde(rename = "experimental.rodio_audio", default)]
|
||||
@@ -12,7 +12,7 @@ pub struct AudioSettings {
|
||||
}
|
||||
|
||||
/// Configuration of audio in Zed.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
|
||||
#[serde(default)]
|
||||
pub struct AudioSettingsContent {
|
||||
/// Whether to use the experimental audio system
|
||||
|
||||
@@ -10,7 +10,7 @@ use paths::remote_servers_dir;
|
||||
use release_channel::{AppCommitSha, ReleaseChannel};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources, SettingsStore};
|
||||
use settings::{Settings, SettingsSources, SettingsStore, SettingsUi};
|
||||
use smol::{fs, io::AsyncReadExt};
|
||||
use smol::{fs::File, process::Command};
|
||||
use std::{
|
||||
@@ -118,14 +118,14 @@ struct AutoUpdateSetting(bool);
|
||||
/// Whether or not to automatically check for updates.
|
||||
///
|
||||
/// Default: true
|
||||
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
|
||||
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi)]
|
||||
#[serde(transparent)]
|
||||
struct AutoUpdateSettingContent(bool);
|
||||
|
||||
impl Settings for AutoUpdateSetting {
|
||||
const KEY: Option<&'static str> = Some("auto_update");
|
||||
|
||||
type FileContent = Option<AutoUpdateSettingContent>;
|
||||
type FileContent = AutoUpdateSettingContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
let auto_update = [
|
||||
@@ -135,17 +135,19 @@ impl Settings for AutoUpdateSetting {
|
||||
sources.user,
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|value| value.copied().flatten())
|
||||
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
|
||||
.find_map(|value| value.copied())
|
||||
.unwrap_or(*sources.default);
|
||||
|
||||
Ok(Self(auto_update.0))
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.enum_setting("update.mode", current, |s| match s {
|
||||
let mut cur = &mut Some(*current);
|
||||
vscode.enum_setting("update.mode", &mut cur, |s| match s {
|
||||
"none" | "manual" => Some(AutoUpdateSettingContent(false)),
|
||||
_ => Some(AutoUpdateSettingContent(true)),
|
||||
});
|
||||
*current = cur.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::windows_impl::WM_JOB_UPDATED;
|
||||
type Job = fn(&Path) -> Result<()>;
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub(crate) const JOBS: [Job; 6] = [
|
||||
pub(crate) const JOBS: &[Job] = &[
|
||||
// Delete old files
|
||||
|app_dir| {
|
||||
let zed_executable = app_dir.join("Zed.exe");
|
||||
@@ -32,6 +32,12 @@ pub(crate) const JOBS: [Job; 6] = [
|
||||
std::fs::remove_file(&zed_cli)
|
||||
.context(format!("Failed to remove old file {}", zed_cli.display()))
|
||||
},
|
||||
|app_dir| {
|
||||
let zed_wsl = app_dir.join("bin\\zed");
|
||||
log::info!("Removing old file: {}", zed_wsl.display());
|
||||
std::fs::remove_file(&zed_wsl)
|
||||
.context(format!("Failed to remove old file {}", zed_wsl.display()))
|
||||
},
|
||||
// Copy new files
|
||||
|app_dir| {
|
||||
let zed_executable_source = app_dir.join("install\\Zed.exe");
|
||||
@@ -65,6 +71,22 @@ pub(crate) const JOBS: [Job; 6] = [
|
||||
zed_cli_dest.display()
|
||||
))
|
||||
},
|
||||
|app_dir| {
|
||||
let zed_wsl_source = app_dir.join("install\\bin\\zed");
|
||||
let zed_wsl_dest = app_dir.join("bin\\zed");
|
||||
log::info!(
|
||||
"Copying new file {} to {}",
|
||||
zed_wsl_source.display(),
|
||||
zed_wsl_dest.display()
|
||||
);
|
||||
std::fs::copy(&zed_wsl_source, &zed_wsl_dest)
|
||||
.map(|_| ())
|
||||
.context(format!(
|
||||
"Failed to copy new file {} to {}",
|
||||
zed_wsl_source.display(),
|
||||
zed_wsl_dest.display()
|
||||
))
|
||||
},
|
||||
// Clean up installer folder and updates folder
|
||||
|app_dir| {
|
||||
let updates_folder = app_dir.join("updates");
|
||||
@@ -85,7 +107,7 @@ pub(crate) const JOBS: [Job; 6] = [
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) const JOBS: [Job; 2] = [
|
||||
pub(crate) const JOBS: &[Job] = &[
|
||||
|_| {
|
||||
std::thread::sleep(Duration::from_millis(1000));
|
||||
if let Ok(config) = std::env::var("ZED_AUTO_UPDATE") {
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CallSettings {
|
||||
@@ -11,7 +11,7 @@ pub struct CallSettings {
|
||||
}
|
||||
|
||||
/// Configuration of voice calls in Zed.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
|
||||
pub struct CallSettingsContent {
|
||||
/// Whether the microphone should be muted when joining a channel or a call.
|
||||
///
|
||||
|
||||
@@ -14,6 +14,7 @@ pub enum CliRequest {
|
||||
paths: Vec<String>,
|
||||
urls: Vec<String>,
|
||||
diff_paths: Vec<[String; 2]>,
|
||||
wsl: Option<String>,
|
||||
wait: bool,
|
||||
open_new_workspace: Option<bool>,
|
||||
env: Option<HashMap<String, String>>,
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use clap::Parser;
|
||||
use cli::{CliRequest, CliResponse, IpcHandshake, ipc::IpcOneShotServer};
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
env, fs, io,
|
||||
@@ -85,6 +84,18 @@ struct Args {
|
||||
/// Run zed in dev-server mode
|
||||
#[arg(long)]
|
||||
dev_server_token: Option<String>,
|
||||
/// The username and WSL distribution to use when opening paths. If not specified,
|
||||
/// Zed will attempt to open the paths directly.
|
||||
///
|
||||
/// The username is optional, and if not specified, the default user for the distribution
|
||||
/// will be used.
|
||||
///
|
||||
/// Example: `me@Ubuntu` or `Ubuntu`.
|
||||
///
|
||||
/// WARN: You should not fill in this field by hand.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[arg(long, value_name = "USER@DISTRO")]
|
||||
wsl: Option<String>,
|
||||
/// Not supported in Zed CLI, only supported on Zed binary
|
||||
/// Will attempt to give the correct command to run
|
||||
#[arg(long)]
|
||||
@@ -129,14 +140,41 @@ fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
|
||||
Ok(canonicalized.to_string(|path| path.to_string_lossy().to_string()))
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
#[cfg(all(not(debug_assertions), target_os = "windows"))]
|
||||
unsafe {
|
||||
use ::windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};
|
||||
fn parse_path_in_wsl(source: &str, wsl: &str) -> Result<String> {
|
||||
let mut command = util::command::new_std_command("wsl.exe");
|
||||
|
||||
let _ = AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
let (user, distro_name) = if let Some((user, distro)) = wsl.split_once('@') {
|
||||
if user.is_empty() {
|
||||
anyhow::bail!("user is empty in wsl argument");
|
||||
}
|
||||
(Some(user), distro)
|
||||
} else {
|
||||
(None, wsl)
|
||||
};
|
||||
|
||||
if let Some(user) = user {
|
||||
command.arg("--user").arg(user);
|
||||
}
|
||||
|
||||
let output = command
|
||||
.arg("--distribution")
|
||||
.arg(distro_name)
|
||||
.arg("wslpath")
|
||||
.arg("-m")
|
||||
.arg(source)
|
||||
.output()?;
|
||||
|
||||
let result = String::from_utf8_lossy(&output.stdout);
|
||||
let prefix = format!("//wsl.localhost/{}", distro_name);
|
||||
|
||||
Ok(result
|
||||
.trim()
|
||||
.strip_prefix(&prefix)
|
||||
.unwrap_or(&result)
|
||||
.to_string())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
#[cfg(unix)]
|
||||
util::prevent_root_execution();
|
||||
|
||||
@@ -223,6 +261,8 @@ fn main() -> Result<()> {
|
||||
let env = {
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
{
|
||||
use collections::HashMap;
|
||||
|
||||
// On Linux, the desktop entry uses `cli` to spawn `zed`.
|
||||
// We need to handle env vars correctly since std::env::vars() may not contain
|
||||
// project-specific vars (e.g. those set by direnv).
|
||||
@@ -235,8 +275,19 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||
Some(std::env::vars().collect::<HashMap<_, _>>())
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// On Windows, by default, a child process inherits a copy of the environment block of the parent process.
|
||||
// So we don't need to pass env vars explicitly.
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "windows")))]
|
||||
{
|
||||
use collections::HashMap;
|
||||
|
||||
Some(std::env::vars().collect::<HashMap<_, _>>())
|
||||
}
|
||||
};
|
||||
|
||||
let exit_status = Arc::new(Mutex::new(None));
|
||||
@@ -253,6 +304,11 @@ fn main() -> Result<()> {
|
||||
]);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let wsl = args.wsl.as_ref();
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let wsl = None;
|
||||
|
||||
for path in args.paths_with_position.iter() {
|
||||
if path.starts_with("zed://")
|
||||
|| path.starts_with("http://")
|
||||
@@ -271,8 +327,10 @@ fn main() -> Result<()> {
|
||||
paths.push(tmp_file.path().to_string_lossy().to_string());
|
||||
let (tmp_file, _) = tmp_file.keep()?;
|
||||
anonymous_fd_tmp_files.push((file, tmp_file));
|
||||
} else if let Some(wsl) = wsl {
|
||||
urls.push(format!("file://{}", parse_path_in_wsl(path, wsl)?));
|
||||
} else {
|
||||
paths.push(parse_path_with_position(path)?)
|
||||
paths.push(parse_path_with_position(path)?);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,10 +346,16 @@ fn main() -> Result<()> {
|
||||
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
||||
let (tx, rx) = (handshake.requests, handshake.responses);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let wsl = args.wsl;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let wsl = None;
|
||||
|
||||
tx.send(CliRequest::Open {
|
||||
paths,
|
||||
urls,
|
||||
diff_paths,
|
||||
wsl,
|
||||
wait: args.wait,
|
||||
open_new_workspace,
|
||||
env,
|
||||
@@ -644,15 +708,15 @@ mod windows {
|
||||
Storage::FileSystem::{
|
||||
CreateFileW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE, OPEN_EXISTING, WriteFile,
|
||||
},
|
||||
System::Threading::CreateMutexW,
|
||||
System::Threading::{CREATE_NEW_PROCESS_GROUP, CreateMutexW},
|
||||
},
|
||||
core::HSTRING,
|
||||
};
|
||||
|
||||
use crate::{Detect, InstalledApp};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitStatus;
|
||||
use std::{io, os::windows::process::CommandExt};
|
||||
|
||||
fn check_single_instance() -> bool {
|
||||
let mutex = unsafe {
|
||||
@@ -691,6 +755,7 @@ mod windows {
|
||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
||||
if check_single_instance() {
|
||||
std::process::Command::new(self.0.clone())
|
||||
.creation_flags(CREATE_NEW_PROCESS_GROUP.0)
|
||||
.arg(ipc_url)
|
||||
.spawn()?;
|
||||
} else {
|
||||
|
||||
@@ -31,7 +31,7 @@ use release_channel::{AppVersion, ReleaseChannel};
|
||||
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
convert::TryFrom,
|
||||
@@ -96,7 +96,7 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
|
||||
pub struct ClientSettingsContent {
|
||||
server_url: Option<String>,
|
||||
}
|
||||
@@ -122,7 +122,7 @@ impl Settings for ClientSettings {
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi)]
|
||||
pub struct ProxySettingsContent {
|
||||
proxy: Option<String>,
|
||||
}
|
||||
@@ -287,6 +287,7 @@ pub enum Status {
|
||||
},
|
||||
ConnectionLost,
|
||||
Reauthenticating,
|
||||
Reauthenticated,
|
||||
Reconnecting,
|
||||
ReconnectionError {
|
||||
next_reconnection: Instant,
|
||||
@@ -298,6 +299,21 @@ impl Status {
|
||||
matches!(self, Self::Connected { .. })
|
||||
}
|
||||
|
||||
pub fn was_connected(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::ConnectionLost
|
||||
| Self::Reauthenticating
|
||||
| Self::Reauthenticated
|
||||
| Self::Reconnecting
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns whether the client is currently connected or was connected at some point.
|
||||
pub fn is_or_was_connected(&self) -> bool {
|
||||
self.is_connected() || self.was_connected()
|
||||
}
|
||||
|
||||
pub fn is_signing_in(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
@@ -511,7 +527,7 @@ pub struct TelemetrySettings {
|
||||
}
|
||||
|
||||
/// Control what info is collected by Zed.
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
|
||||
pub struct TelemetrySettingsContent {
|
||||
/// Send debug info like crash reports.
|
||||
///
|
||||
@@ -857,11 +873,13 @@ impl Client {
|
||||
try_provider: bool,
|
||||
cx: &AsyncApp,
|
||||
) -> Result<Credentials> {
|
||||
if self.status().borrow().is_signed_out() {
|
||||
let is_reauthenticating = if self.status().borrow().is_signed_out() {
|
||||
self.set_status(Status::Authenticating, cx);
|
||||
false
|
||||
} else {
|
||||
self.set_status(Status::Reauthenticating, cx);
|
||||
}
|
||||
true
|
||||
};
|
||||
|
||||
let mut credentials = None;
|
||||
|
||||
@@ -919,7 +937,14 @@ impl Client {
|
||||
self.cloud_client
|
||||
.set_credentials(credentials.user_id as u32, credentials.access_token.clone());
|
||||
self.state.write().credentials = Some(credentials.clone());
|
||||
self.set_status(Status::Authenticated, cx);
|
||||
self.set_status(
|
||||
if is_reauthenticating {
|
||||
Status::Reauthenticated
|
||||
} else {
|
||||
Status::Authenticated
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
||||
Ok(credentials)
|
||||
}
|
||||
@@ -1034,6 +1059,7 @@ impl Client {
|
||||
| Status::Authenticating
|
||||
| Status::AuthenticationError
|
||||
| Status::Reauthenticating
|
||||
| Status::Reauthenticated
|
||||
| Status::ReconnectionError { .. } => false,
|
||||
Status::Connected { .. } | Status::Connecting | Status::Reconnecting => {
|
||||
return ConnectionResult::Result(Ok(()));
|
||||
@@ -1670,21 +1696,10 @@ impl Client {
|
||||
);
|
||||
cx.spawn(async move |_| match future.await {
|
||||
Ok(()) => {
|
||||
log::debug!(
|
||||
"rpc message handled. client_id:{}, sender_id:{:?}, type:{}",
|
||||
client_id,
|
||||
original_sender_id,
|
||||
type_name
|
||||
);
|
||||
log::debug!("rpc message handled. client_id:{client_id}, sender_id:{original_sender_id:?}, type:{type_name}");
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
"error handling message. client_id:{}, sender_id:{:?}, type:{}, error:{:?}",
|
||||
client_id,
|
||||
original_sender_id,
|
||||
type_name,
|
||||
error
|
||||
);
|
||||
log::error!("error handling message. client_id:{client_id}, sender_id:{original_sender_id:?}, type:{type_name}, error:{error:#}");
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -216,7 +216,9 @@ impl UserStore {
|
||||
return Ok(());
|
||||
};
|
||||
match status {
|
||||
Status::Authenticated | Status::Connected { .. } => {
|
||||
Status::Authenticated
|
||||
| Status::Reauthenticated
|
||||
| Status::Connected { .. } => {
|
||||
if let Some(user_id) = client.user_id() {
|
||||
let response = client
|
||||
.cloud_client()
|
||||
|
||||
@@ -369,7 +369,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
.set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -488,7 +488,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
.set_request_handler::<lsp::request::Completion, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -615,7 +615,7 @@ async fn test_collaborating_with_code_actions(
|
||||
.set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(params.range.start, lsp::Position::new(0, 0));
|
||||
assert_eq!(params.range.end, lsp::Position::new(0, 0));
|
||||
@@ -637,7 +637,7 @@ async fn test_collaborating_with_code_actions(
|
||||
.set_request_handler::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(params.range.start, lsp::Position::new(1, 31));
|
||||
assert_eq!(params.range.end, lsp::Position::new(1, 31));
|
||||
@@ -649,7 +649,7 @@ async fn test_collaborating_with_code_actions(
|
||||
changes: Some(
|
||||
[
|
||||
(
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(1, 22),
|
||||
@@ -659,7 +659,7 @@ async fn test_collaborating_with_code_actions(
|
||||
)],
|
||||
),
|
||||
(
|
||||
lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/other.rs")).unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(0, 0),
|
||||
@@ -721,7 +721,7 @@ async fn test_collaborating_with_code_actions(
|
||||
changes: Some(
|
||||
[
|
||||
(
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(1, 22),
|
||||
@@ -731,7 +731,7 @@ async fn test_collaborating_with_code_actions(
|
||||
)],
|
||||
),
|
||||
(
|
||||
lsp::Url::from_file_path(path!("/a/other.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/other.rs")).unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(0, 0),
|
||||
@@ -949,14 +949,14 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
changes: Some(
|
||||
[
|
||||
(
|
||||
lsp::Url::from_file_path(path!("/dir/one.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/dir/one.rs")).unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
"THREE".to_string(),
|
||||
)],
|
||||
),
|
||||
(
|
||||
lsp::Url::from_file_path(path!("/dir/two.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/dir/two.rs")).unwrap(),
|
||||
vec![
|
||||
lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
@@ -1574,7 +1574,7 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -1717,7 +1717,7 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
@@ -1901,7 +1901,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
@@ -2151,7 +2151,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
|
||||
let character = if other_hints { 0 } else { 2 };
|
||||
@@ -2332,7 +2332,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
requests_made.fetch_add(1, atomic::Ordering::Release);
|
||||
Ok(vec![lsp::ColorInformation {
|
||||
@@ -2621,11 +2621,11 @@ async fn test_lsp_pull_diagnostics(
|
||||
let requests_made = closure_diagnostics_pulls_made.clone();
|
||||
let diagnostics_pulls_result_ids = closure_diagnostics_pulls_result_ids.clone();
|
||||
async move {
|
||||
let message = if lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
|
||||
let message = if lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
|
||||
== params.text_document.uri
|
||||
{
|
||||
expected_pull_diagnostic_main_message.to_string()
|
||||
} else if lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap()
|
||||
} else if lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
|
||||
== params.text_document.uri
|
||||
{
|
||||
expected_pull_diagnostic_lib_message.to_string()
|
||||
@@ -2717,7 +2717,7 @@ async fn test_lsp_pull_diagnostics(
|
||||
items: vec![
|
||||
lsp::WorkspaceDocumentDiagnosticReport::Full(
|
||||
lsp::WorkspaceFullDocumentDiagnosticReport {
|
||||
uri: lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
version: None,
|
||||
full_document_diagnostic_report:
|
||||
lsp::FullDocumentDiagnosticReport {
|
||||
@@ -2746,7 +2746,7 @@ async fn test_lsp_pull_diagnostics(
|
||||
),
|
||||
lsp::WorkspaceDocumentDiagnosticReport::Full(
|
||||
lsp::WorkspaceFullDocumentDiagnosticReport {
|
||||
uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
|
||||
version: None,
|
||||
full_document_diagnostic_report:
|
||||
lsp::FullDocumentDiagnosticReport {
|
||||
@@ -2821,7 +2821,7 @@ async fn test_lsp_pull_diagnostics(
|
||||
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
&lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
@@ -2842,7 +2842,7 @@ async fn test_lsp_pull_diagnostics(
|
||||
);
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
&lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
@@ -2870,7 +2870,7 @@ async fn test_lsp_pull_diagnostics(
|
||||
items: vec![
|
||||
lsp::WorkspaceDocumentDiagnosticReport::Full(
|
||||
lsp::WorkspaceFullDocumentDiagnosticReport {
|
||||
uri: lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
version: None,
|
||||
full_document_diagnostic_report:
|
||||
lsp::FullDocumentDiagnosticReport {
|
||||
@@ -2902,7 +2902,7 @@ async fn test_lsp_pull_diagnostics(
|
||||
),
|
||||
lsp::WorkspaceDocumentDiagnosticReport::Full(
|
||||
lsp::WorkspaceFullDocumentDiagnosticReport {
|
||||
uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
|
||||
version: None,
|
||||
full_document_diagnostic_report:
|
||||
lsp::FullDocumentDiagnosticReport {
|
||||
@@ -3051,7 +3051,7 @@ async fn test_lsp_pull_diagnostics(
|
||||
lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
|
||||
items: vec![lsp::WorkspaceDocumentDiagnosticReport::Full(
|
||||
lsp::WorkspaceFullDocumentDiagnosticReport {
|
||||
uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
|
||||
version: None,
|
||||
full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
|
||||
result_id: Some(format!(
|
||||
@@ -3425,16 +3425,16 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
assert_eq!(
|
||||
entries,
|
||||
vec![
|
||||
Some(blame_entry("1b1b1b", 0..1)),
|
||||
Some(blame_entry("0d0d0d", 1..2)),
|
||||
Some(blame_entry("3a3a3a", 2..3)),
|
||||
Some(blame_entry("4c4c4c", 3..4)),
|
||||
Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
|
||||
Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
|
||||
Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
|
||||
Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
|
||||
]
|
||||
);
|
||||
|
||||
blame.update(cx, |blame, _| {
|
||||
for (idx, entry) in entries.iter().flatten().enumerate() {
|
||||
let details = blame.details_for_entry(entry).unwrap();
|
||||
for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
|
||||
let details = blame.details_for_entry(*buffer, entry).unwrap();
|
||||
assert_eq!(details.message, format!("message for idx-{}", idx));
|
||||
assert_eq!(
|
||||
details.permalink.unwrap().to_string(),
|
||||
@@ -3474,9 +3474,9 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
entries,
|
||||
vec![
|
||||
None,
|
||||
Some(blame_entry("0d0d0d", 1..2)),
|
||||
Some(blame_entry("3a3a3a", 2..3)),
|
||||
Some(blame_entry("4c4c4c", 3..4)),
|
||||
Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
|
||||
Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
|
||||
Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -3511,8 +3511,8 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
Some(blame_entry("3a3a3a", 2..3)),
|
||||
Some(blame_entry("4c4c4c", 3..4)),
|
||||
Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
|
||||
Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -4040,7 +4040,7 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(params.position, lsp::Position::new(0, 0));
|
||||
Ok(Some(ExpandedMacro {
|
||||
@@ -4075,7 +4075,7 @@ async fn test_client_can_query_lsp_ext(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.position,
|
||||
|
||||
@@ -4075,7 +4075,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
.await;
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
&lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
@@ -4095,7 +4095,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
.unwrap();
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
&lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
@@ -4169,7 +4169,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
// Simulate a language server reporting more errors for a file.
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
&lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![
|
||||
lsp::Diagnostic {
|
||||
@@ -4265,7 +4265,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
// Simulate a language server reporting no errors for a file.
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
&lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
@@ -4372,7 +4372,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
||||
for file_name in file_names {
|
||||
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
|
||||
&lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
@@ -4838,7 +4838,7 @@ async fn test_definition(
|
||||
|_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
),
|
||||
)))
|
||||
@@ -4876,7 +4876,7 @@ async fn test_definition(
|
||||
|_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
|
||||
),
|
||||
)))
|
||||
@@ -4914,7 +4914,7 @@ async fn test_definition(
|
||||
);
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Url::from_file_path(path!("/root/dir-2/c.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/root/dir-2/c.rs")).unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
|
||||
),
|
||||
)))
|
||||
@@ -5049,15 +5049,15 @@ async fn test_references(
|
||||
lsp_response_tx
|
||||
.unbounded_send(Ok(Some(vec![
|
||||
lsp::Location {
|
||||
uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
|
||||
},
|
||||
lsp::Location {
|
||||
uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
|
||||
},
|
||||
lsp::Location {
|
||||
uri: lsp::Url::from_file_path(path!("/root/dir-2/three.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/dir-2/three.rs")).unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
|
||||
},
|
||||
])))
|
||||
@@ -5625,7 +5625,7 @@ async fn test_project_symbols(
|
||||
lsp::SymbolInformation {
|
||||
name: "TWO".into(),
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path(path!("/code/crate-2/two.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/code/crate-2/two.rs")).unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
},
|
||||
kind: lsp::SymbolKind::CONSTANT,
|
||||
@@ -5737,7 +5737,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
|_, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
|
||||
lsp::Location::new(
|
||||
lsp::Url::from_file_path(path!("/root/b.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/root/b.rs")).unwrap(),
|
||||
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
),
|
||||
)))
|
||||
|
||||
@@ -1101,7 +1101,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
files
|
||||
.into_iter()
|
||||
.map(|file| lsp::Location {
|
||||
uri: lsp::Url::from_file_path(file).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(file).unwrap(),
|
||||
range: Default::default(),
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -3047,7 +3047,7 @@ impl Render for CollabPanel {
|
||||
.on_action(cx.listener(CollabPanel::move_channel_down))
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.child(if !self.client.status().borrow().is_connected() {
|
||||
.child(if !self.client.status().borrow().is_or_was_connected() {
|
||||
self.render_signed_out(cx)
|
||||
} else {
|
||||
self.render_signed_in(window, cx)
|
||||
|
||||
@@ -66,5 +66,7 @@ fn notification_window_options(
|
||||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
tabbing_identifier: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gpui::Pixels;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
use workspace::dock::DockPosition;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -27,7 +27,7 @@ pub struct ChatPanelSettings {
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
|
||||
pub struct ChatPanelSettingsContent {
|
||||
/// When to show the panel button in the status bar.
|
||||
///
|
||||
@@ -50,7 +50,7 @@ pub struct NotificationPanelSettings {
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
|
||||
pub struct PanelSettingsContent {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
///
|
||||
@@ -66,7 +66,7 @@ pub struct PanelSettingsContent {
|
||||
pub default_width: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
|
||||
pub struct MessageEditorSettings {
|
||||
/// Whether to automatically replace emoji shortcodes with emoji characters.
|
||||
/// For example: typing `:wave:` gets replaced with `👋`.
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
};
|
||||
|
||||
const JSON_RPC_VERSION: &str = "2.0";
|
||||
const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
// Standard JSON-RPC error codes
|
||||
pub const PARSE_ERROR: i32 = -32700;
|
||||
@@ -60,6 +60,7 @@ pub(crate) struct Client {
|
||||
executor: BackgroundExecutor,
|
||||
#[allow(dead_code)]
|
||||
transport: Arc<dyn Transport>,
|
||||
request_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -143,6 +144,7 @@ pub struct ModelContextServerBinary {
|
||||
pub executable: PathBuf,
|
||||
pub args: Vec<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
pub timeout: Option<u64>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@@ -169,8 +171,9 @@ impl Client {
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_else(String::new);
|
||||
|
||||
let timeout = binary.timeout.map(Duration::from_millis);
|
||||
let transport = Arc::new(StdioTransport::new(binary, working_directory, &cx)?);
|
||||
Self::new(server_id, server_name.into(), transport, cx)
|
||||
Self::new(server_id, server_name.into(), transport, timeout, cx)
|
||||
}
|
||||
|
||||
/// Creates a new Client instance for a context server.
|
||||
@@ -178,6 +181,7 @@ impl Client {
|
||||
server_id: ContextServerId,
|
||||
server_name: Arc<str>,
|
||||
transport: Arc<dyn Transport>,
|
||||
request_timeout: Option<Duration>,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Self> {
|
||||
let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
|
||||
@@ -237,6 +241,7 @@ impl Client {
|
||||
io_tasks: Mutex::new(Some((input_task, output_task))),
|
||||
output_done_rx: Mutex::new(Some(output_done_rx)),
|
||||
transport,
|
||||
request_timeout,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -327,8 +332,13 @@ impl Client {
|
||||
method: &str,
|
||||
params: impl Serialize,
|
||||
) -> Result<T> {
|
||||
self.request_with(method, params, None, Some(REQUEST_TIMEOUT))
|
||||
.await
|
||||
self.request_with(
|
||||
method,
|
||||
params,
|
||||
None,
|
||||
self.request_timeout.or(Some(DEFAULT_REQUEST_TIMEOUT)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn request_with<T: DeserializeOwned>(
|
||||
|
||||
@@ -34,6 +34,8 @@ pub struct ContextServerCommand {
|
||||
pub path: PathBuf,
|
||||
pub args: Vec<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
/// Timeout for tool calls in milliseconds. Defaults to 60000 (60 seconds) if not specified.
|
||||
pub timeout: Option<u64>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ContextServerCommand {
|
||||
@@ -123,6 +125,7 @@ impl ContextServer {
|
||||
executable: Path::new(&command.path).to_path_buf(),
|
||||
args: command.args.clone(),
|
||||
env: command.env.clone(),
|
||||
timeout: command.timeout,
|
||||
},
|
||||
working_directory,
|
||||
cx.clone(),
|
||||
@@ -131,6 +134,7 @@ impl ContextServer {
|
||||
client::ContextServerId(self.id.0.clone()),
|
||||
self.id().0,
|
||||
transport.clone(),
|
||||
None,
|
||||
cx.clone(),
|
||||
)?,
|
||||
})
|
||||
|
||||
@@ -197,7 +197,7 @@ impl Status {
|
||||
}
|
||||
|
||||
struct RegisteredBuffer {
|
||||
uri: lsp::Url,
|
||||
uri: lsp::Uri,
|
||||
language_id: String,
|
||||
snapshot: BufferSnapshot,
|
||||
snapshot_version: i32,
|
||||
@@ -1108,9 +1108,9 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
|
||||
.unwrap_or_else(|| "plaintext".to_string())
|
||||
}
|
||||
|
||||
fn uri_for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Result<lsp::Url, ()> {
|
||||
fn uri_for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Result<lsp::Uri, ()> {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
||||
lsp::Url::from_file_path(file.abs_path(cx))
|
||||
lsp::Uri::from_file_path(file.abs_path(cx))
|
||||
} else {
|
||||
format!("buffer://{}", buffer.entity_id())
|
||||
.parse()
|
||||
@@ -1201,7 +1201,7 @@ mod tests {
|
||||
let (copilot, mut lsp) = Copilot::fake(cx);
|
||||
|
||||
let buffer_1 = cx.new(|cx| Buffer::local("Hello", cx));
|
||||
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
|
||||
let buffer_1_uri: lsp::Uri = format!("buffer://{}", buffer_1.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
||||
@@ -1219,7 +1219,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let buffer_2 = cx.new(|cx| Buffer::local("Goodbye", cx));
|
||||
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
|
||||
let buffer_2_uri: lsp::Uri = format!("buffer://{}", buffer_2.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
||||
@@ -1270,7 +1270,7 @@ mod tests {
|
||||
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
|
||||
}
|
||||
);
|
||||
let buffer_1_uri = lsp::Url::from_file_path(path!("/root/child/buffer-1")).unwrap();
|
||||
let buffer_1_uri = lsp::Uri::from_file_path(path!("/root/child/buffer-1")).unwrap();
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
|
||||
@@ -62,12 +62,6 @@ impl CopilotChatConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
// Copilot's base model; defined by Microsoft in premium requests table
|
||||
// This will be moved to the front of the Copilot model list, and will be used for
|
||||
// 'fast' requests (e.g. title generation)
|
||||
// https://docs.github.com/en/copilot/managing-copilot/monitoring-usage-and-entitlements/about-premium-requests
|
||||
const DEFAULT_MODEL_ID: &str = "gpt-4.1";
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Role {
|
||||
@@ -101,22 +95,39 @@ where
|
||||
Ok(models)
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct Model {
|
||||
billing: ModelBilling,
|
||||
capabilities: ModelCapabilities,
|
||||
id: String,
|
||||
name: String,
|
||||
policy: Option<ModelPolicy>,
|
||||
vendor: ModelVendor,
|
||||
is_chat_default: bool,
|
||||
// The model with this value true is selected by VSCode copilot if a premium request limit is
|
||||
// reached. Zed does not currently implement this behaviour
|
||||
is_chat_fallback: bool,
|
||||
model_picker_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||
struct ModelBilling {
|
||||
is_premium: bool,
|
||||
multiplier: f64,
|
||||
// List of plans a model is restricted to
|
||||
// Field is not present if a model is available for all plans
|
||||
#[serde(default)]
|
||||
restricted_to: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
struct ModelCapabilities {
|
||||
family: String,
|
||||
#[serde(default)]
|
||||
limits: ModelLimits,
|
||||
supports: ModelSupportedFeatures,
|
||||
#[serde(rename = "type")]
|
||||
model_type: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
@@ -604,6 +615,7 @@ async fn get_models(
|
||||
.into_iter()
|
||||
.filter(|model| {
|
||||
model.model_picker_enabled
|
||||
&& model.capabilities.model_type.as_str() == "chat"
|
||||
&& model
|
||||
.policy
|
||||
.as_ref()
|
||||
@@ -612,9 +624,7 @@ async fn get_models(
|
||||
.dedup_by(|a, b| a.capabilities.family == b.capabilities.family)
|
||||
.collect();
|
||||
|
||||
if let Some(default_model_position) =
|
||||
models.iter().position(|model| model.id == DEFAULT_MODEL_ID)
|
||||
{
|
||||
if let Some(default_model_position) = models.iter().position(|model| model.is_chat_default) {
|
||||
let default_model = models.remove(default_model_position);
|
||||
models.insert(0, default_model);
|
||||
}
|
||||
@@ -632,7 +642,9 @@ async fn request_models(
|
||||
.uri(models_url.as_ref())
|
||||
.header("Authorization", format!("Bearer {}", api_token))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Copilot-Integration-Id", "vscode-chat");
|
||||
.header("Copilot-Integration-Id", "vscode-chat")
|
||||
.header("Editor-Version", "vscode/1.103.2")
|
||||
.header("x-github-api-version", "2025-05-01");
|
||||
|
||||
let request = request_builder.body(AsyncBody::empty())?;
|
||||
|
||||
@@ -803,6 +815,10 @@ mod tests {
|
||||
let json = r#"{
|
||||
"data": [
|
||||
{
|
||||
"billing": {
|
||||
"is_premium": false,
|
||||
"multiplier": 0
|
||||
},
|
||||
"capabilities": {
|
||||
"family": "gpt-4",
|
||||
"limits": {
|
||||
@@ -816,6 +832,8 @@ mod tests {
|
||||
"type": "chat"
|
||||
},
|
||||
"id": "gpt-4",
|
||||
"is_chat_default": false,
|
||||
"is_chat_fallback": false,
|
||||
"model_picker_enabled": false,
|
||||
"name": "GPT 4",
|
||||
"object": "model",
|
||||
@@ -827,6 +845,16 @@ mod tests {
|
||||
"some-unknown-field": 123
|
||||
},
|
||||
{
|
||||
"billing": {
|
||||
"is_premium": true,
|
||||
"multiplier": 1,
|
||||
"restricted_to": [
|
||||
"pro",
|
||||
"pro_plus",
|
||||
"business",
|
||||
"enterprise"
|
||||
]
|
||||
},
|
||||
"capabilities": {
|
||||
"family": "claude-3.7-sonnet",
|
||||
"limits": {
|
||||
@@ -850,6 +878,8 @@ mod tests {
|
||||
"type": "chat"
|
||||
},
|
||||
"id": "claude-3.7-sonnet",
|
||||
"is_chat_default": false,
|
||||
"is_chat_fallback": false,
|
||||
"model_picker_enabled": true,
|
||||
"name": "Claude 3.7 Sonnet",
|
||||
"object": "model",
|
||||
|
||||
@@ -102,7 +102,7 @@ pub struct GetCompletionsDocument {
|
||||
pub tab_size: u32,
|
||||
pub indent_size: u32,
|
||||
pub insert_spaces: bool,
|
||||
pub uri: lsp::Url,
|
||||
pub uri: lsp::Uri,
|
||||
pub relative_path: String,
|
||||
pub position: lsp::Position,
|
||||
pub version: usize,
|
||||
|
||||
@@ -2,9 +2,9 @@ use dap_types::SteppingGranularity;
|
||||
use gpui::{App, Global};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DebugPanelDockPosition {
|
||||
Left,
|
||||
@@ -12,12 +12,16 @@ pub enum DebugPanelDockPosition {
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy, SettingsUi)]
|
||||
#[serde(default)]
|
||||
// todo(settings_ui) @ben: I'm pretty sure not having the fields be optional here is a bug,
|
||||
// it means the defaults will override previously set values if a single key is missing
|
||||
#[settings_ui(group = "Debugger", path = "debugger")]
|
||||
pub struct DebuggerSettings {
|
||||
/// Determines the stepping granularity.
|
||||
///
|
||||
/// Default: line
|
||||
#[settings_ui(skip)]
|
||||
pub stepping_granularity: SteppingGranularity,
|
||||
/// Whether the breakpoints should be reused across Zed sessions.
|
||||
///
|
||||
|
||||
@@ -234,6 +234,7 @@ impl PythonDebugAdapter {
|
||||
.await
|
||||
.map_err(|e| format!("{e:#?}"))?
|
||||
.success();
|
||||
|
||||
if !did_succeed {
|
||||
return Err("Failed to create base virtual environment".into());
|
||||
}
|
||||
|
||||
@@ -85,6 +85,10 @@ actions!(
|
||||
Rerun,
|
||||
/// Toggles expansion of the selected item in the debugger UI.
|
||||
ToggleExpandItem,
|
||||
/// Toggle the user frame filter in the stack frame list
|
||||
/// When toggled on, only frames from the user's code are shown
|
||||
/// When toggled off, all frames are shown
|
||||
ToggleUserFrames,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -272,12 +276,25 @@ pub fn init(cx: &mut App) {
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
let active_item = active_item.clone();
|
||||
move |_: &ToggleIgnoreBreakpoints, _, cx| {
|
||||
active_item
|
||||
.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.on_action(move |_: &ToggleUserFrames, _, cx| {
|
||||
if let Some((thread_status, stack_frame_list)) = active_item
|
||||
.read_with(cx, |item, cx| {
|
||||
(item.thread_status(cx), item.stack_frame_list().clone())
|
||||
})
|
||||
.ok()
|
||||
{
|
||||
stack_frame_list.update(cx, |stack_frame_list, cx| {
|
||||
stack_frame_list.toggle_frame_filter(thread_status, cx);
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::{rc::Rc, time::Duration};
|
||||
use std::rc::Rc;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{Animation, AnimationExt as _, Entity, Transformation, WeakEntity, percentage};
|
||||
use gpui::{Entity, WeakEntity};
|
||||
use project::debugger::session::{ThreadId, ThreadStatus};
|
||||
use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
||||
use ui::{CommonAnimationExt, ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
||||
use util::{maybe, truncate_and_trailoff};
|
||||
|
||||
use crate::{
|
||||
@@ -152,11 +152,7 @@ impl DebugPanel {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element()
|
||||
} else {
|
||||
match running_state.thread_status(cx).unwrap_or_default() {
|
||||
|
||||
@@ -1383,14 +1383,28 @@ impl PickerDelegate for DebugDelegate {
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.children({
|
||||
let action = menu::SecondaryConfirm.boxed_clone();
|
||||
KeyBinding::for_action(&*action, window, cx).map(|keybind| {
|
||||
Button::new("edit-debug-task", "Edit in debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(keybind)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(action.boxed_clone(), cx)
|
||||
})
|
||||
})
|
||||
if self.matches.is_empty() {
|
||||
Some(
|
||||
Button::new("edit-debug-json", "Edit debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(|_picker, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::OpenProjectDebugTasks.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
cx.emit(DismissEvent);
|
||||
})),
|
||||
)
|
||||
} else {
|
||||
KeyBinding::for_action(&*action, window, cx).map(|keybind| {
|
||||
Button::new("edit-debug-task", "Edit in debug.json")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(keybind)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(action.boxed_clone(), cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.map(|this| {
|
||||
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
|
||||
|
||||
@@ -270,12 +270,9 @@ pub(crate) fn deserialize_pane_layout(
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| match child {
|
||||
DebuggerPaneItem::Frames => Box::new(SubView::new(
|
||||
stack_frame_list.focus_handle(cx),
|
||||
stack_frame_list.clone().into(),
|
||||
DebuggerPaneItem::Frames,
|
||||
cx,
|
||||
)),
|
||||
DebuggerPaneItem::Frames => {
|
||||
Box::new(SubView::stack_frame_list(stack_frame_list.clone(), cx))
|
||||
}
|
||||
DebuggerPaneItem::Variables => Box::new(SubView::new(
|
||||
variable_list.focus_handle(cx),
|
||||
variable_list.clone().into(),
|
||||
|
||||
@@ -157,6 +157,29 @@ impl SubView {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn stack_frame_list(
|
||||
stack_frame_list: Entity<StackFrameList>,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let weak_list = stack_frame_list.downgrade();
|
||||
let this = Self::new(
|
||||
stack_frame_list.focus_handle(cx),
|
||||
stack_frame_list.into(),
|
||||
DebuggerPaneItem::Frames,
|
||||
cx,
|
||||
);
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
this.with_actions(Box::new(move |_, cx| {
|
||||
weak_list
|
||||
.update(cx, |this, _| this.render_control_strip())
|
||||
.unwrap_or_else(|_| div().into_any_element())
|
||||
}));
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn console(console: Entity<Console>, cx: &mut App) -> Entity<Self> {
|
||||
let weak_console = console.downgrade();
|
||||
let this = Self::new(
|
||||
|
||||
@@ -4,16 +4,17 @@ use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use dap::StackFrameId;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{
|
||||
AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState, MouseButton,
|
||||
Stateful, Subscription, Task, WeakEntity, list,
|
||||
Action, AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState,
|
||||
MouseButton, Stateful, Subscription, Task, WeakEntity, list,
|
||||
};
|
||||
use util::debug_panic;
|
||||
|
||||
use crate::StackTraceView;
|
||||
use crate::{StackTraceView, ToggleUserFrames};
|
||||
use language::PointUtf16;
|
||||
use project::debugger::breakpoint_store::ActiveStackFrame;
|
||||
use project::debugger::session::{Session, SessionEvent, StackFrame};
|
||||
use project::debugger::session::{Session, SessionEvent, StackFrame, ThreadStatus};
|
||||
use project::{ProjectItem, ProjectPath};
|
||||
use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*};
|
||||
use workspace::{ItemHandle, Workspace};
|
||||
@@ -26,6 +27,34 @@ pub enum StackFrameListEvent {
|
||||
BuiltEntries,
|
||||
}
|
||||
|
||||
/// Represents the filter applied to the stack frame list
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
enum StackFrameFilter {
|
||||
/// Show all frames
|
||||
All,
|
||||
/// Show only frames from the user's code
|
||||
OnlyUserFrames,
|
||||
}
|
||||
|
||||
impl StackFrameFilter {
|
||||
fn from_str_or_default(s: impl AsRef<str>) -> Self {
|
||||
match s.as_ref() {
|
||||
"user" => StackFrameFilter::OnlyUserFrames,
|
||||
"all" => StackFrameFilter::All,
|
||||
_ => StackFrameFilter::All,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StackFrameFilter> for String {
|
||||
fn from(filter: StackFrameFilter) -> Self {
|
||||
match filter {
|
||||
StackFrameFilter::All => "all".to_string(),
|
||||
StackFrameFilter::OnlyUserFrames => "user".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StackFrameList {
|
||||
focus_handle: FocusHandle,
|
||||
_subscription: Subscription,
|
||||
@@ -37,6 +66,8 @@ pub struct StackFrameList {
|
||||
opened_stack_frame_id: Option<StackFrameId>,
|
||||
scrollbar_state: ScrollbarState,
|
||||
list_state: ListState,
|
||||
list_filter: StackFrameFilter,
|
||||
filter_entries_indices: Vec<usize>,
|
||||
error: Option<SharedString>,
|
||||
_refresh_task: Task<()>,
|
||||
}
|
||||
@@ -73,6 +104,16 @@ impl StackFrameList {
|
||||
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
|
||||
let scrollbar_state = ScrollbarState::new(list_state.clone());
|
||||
|
||||
let list_filter = KEY_VALUE_STORE
|
||||
.read_kvp(&format!(
|
||||
"stack-frame-list-filter-{}",
|
||||
session.read(cx).adapter().0
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(StackFrameFilter::from_str_or_default)
|
||||
.unwrap_or(StackFrameFilter::All);
|
||||
|
||||
let mut this = Self {
|
||||
session,
|
||||
workspace,
|
||||
@@ -80,9 +121,11 @@ impl StackFrameList {
|
||||
state,
|
||||
_subscription,
|
||||
entries: Default::default(),
|
||||
filter_entries_indices: Vec::default(),
|
||||
error: None,
|
||||
selected_ix: None,
|
||||
opened_stack_frame_id: None,
|
||||
list_filter,
|
||||
list_state,
|
||||
scrollbar_state,
|
||||
_refresh_task: Task::ready(()),
|
||||
@@ -103,7 +146,15 @@ impl StackFrameList {
|
||||
) -> Vec<dap::StackFrame> {
|
||||
self.entries
|
||||
.iter()
|
||||
.flat_map(|frame| match frame {
|
||||
.enumerate()
|
||||
.filter(|(ix, _)| {
|
||||
self.list_filter == StackFrameFilter::All
|
||||
|| self
|
||||
.filter_entries_indices
|
||||
.binary_search_by_key(&ix, |ix| ix)
|
||||
.is_ok()
|
||||
})
|
||||
.flat_map(|(_, frame)| match frame {
|
||||
StackFrameEntry::Normal(frame) => vec![frame.clone()],
|
||||
StackFrameEntry::Label(frame) if show_labels => vec![frame.clone()],
|
||||
StackFrameEntry::Collapsed(frames) if show_collapsed => frames.clone(),
|
||||
@@ -126,7 +177,15 @@ impl StackFrameList {
|
||||
self.stack_frames(cx)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|stack_frame| stack_frame.dap)
|
||||
.enumerate()
|
||||
.filter(|(ix, _)| {
|
||||
self.list_filter == StackFrameFilter::All
|
||||
|| self
|
||||
.filter_entries_indices
|
||||
.binary_search_by_key(&ix, |ix| ix)
|
||||
.is_ok()
|
||||
})
|
||||
.map(|(_, stack_frame)| stack_frame.dap)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -192,7 +251,32 @@ impl StackFrameList {
|
||||
return;
|
||||
}
|
||||
};
|
||||
for stack_frame in &stack_frames {
|
||||
|
||||
let worktree_prefixes: Vec<_> = self
|
||||
.workspace
|
||||
.read_with(cx, |workspace, cx| {
|
||||
workspace
|
||||
.visible_worktrees(cx)
|
||||
.map(|tree| tree.read(cx).abs_path())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut filter_entries_indices = Vec::default();
|
||||
for (ix, stack_frame) in stack_frames.iter().enumerate() {
|
||||
let frame_in_visible_worktree = stack_frame.dap.source.as_ref().is_some_and(|source| {
|
||||
source.path.as_ref().is_some_and(|path| {
|
||||
worktree_prefixes
|
||||
.iter()
|
||||
.filter_map(|tree| tree.to_str())
|
||||
.any(|tree| path.starts_with(tree))
|
||||
})
|
||||
});
|
||||
|
||||
if frame_in_visible_worktree {
|
||||
filter_entries_indices.push(ix);
|
||||
}
|
||||
|
||||
match stack_frame.dap.presentation_hint {
|
||||
Some(dap::StackFramePresentationHint::Deemphasize)
|
||||
| Some(dap::StackFramePresentationHint::Subtle) => {
|
||||
@@ -225,8 +309,10 @@ impl StackFrameList {
|
||||
let collapsed_entries = std::mem::take(&mut collapsed_entries);
|
||||
if !collapsed_entries.is_empty() {
|
||||
entries.push(StackFrameEntry::Collapsed(collapsed_entries));
|
||||
self.filter_entries_indices.push(entries.len() - 1);
|
||||
}
|
||||
self.entries = entries;
|
||||
self.filter_entries_indices = filter_entries_indices;
|
||||
|
||||
if let Some(ix) = first_stack_frame_with_path
|
||||
.or(first_stack_frame)
|
||||
@@ -242,7 +328,14 @@ impl StackFrameList {
|
||||
self.selected_ix = ix;
|
||||
}
|
||||
|
||||
self.list_state.reset(self.entries.len());
|
||||
match self.list_filter {
|
||||
StackFrameFilter::All => {
|
||||
self.list_state.reset(self.entries.len());
|
||||
}
|
||||
StackFrameFilter::OnlyUserFrames => {
|
||||
self.list_state.reset(self.filter_entries_indices.len());
|
||||
}
|
||||
}
|
||||
cx.emit(StackFrameListEvent::BuiltEntries);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -572,6 +665,11 @@ impl StackFrameList {
|
||||
}
|
||||
|
||||
fn render_entry(&self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
|
||||
let ix = match self.list_filter {
|
||||
StackFrameFilter::All => ix,
|
||||
StackFrameFilter::OnlyUserFrames => self.filter_entries_indices[ix],
|
||||
};
|
||||
|
||||
match &self.entries[ix] {
|
||||
StackFrameEntry::Label(stack_frame) => self.render_label_entry(stack_frame, cx),
|
||||
StackFrameEntry::Normal(stack_frame) => self.render_normal_entry(ix, stack_frame, cx),
|
||||
@@ -702,6 +800,67 @@ impl StackFrameList {
|
||||
self.activate_selected_entry(window, cx);
|
||||
}
|
||||
|
||||
pub(crate) fn toggle_frame_filter(
|
||||
&mut self,
|
||||
thread_status: Option<ThreadStatus>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.list_filter = match self.list_filter {
|
||||
StackFrameFilter::All => StackFrameFilter::OnlyUserFrames,
|
||||
StackFrameFilter::OnlyUserFrames => StackFrameFilter::All,
|
||||
};
|
||||
|
||||
if let Some(database_id) = self
|
||||
.workspace
|
||||
.read_with(cx, |workspace, _| workspace.database_id())
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
let database_id: i64 = database_id.into();
|
||||
let save_task = KEY_VALUE_STORE.write_kvp(
|
||||
format!(
|
||||
"stack-frame-list-filter-{}-{}",
|
||||
self.session.read(cx).adapter().0,
|
||||
database_id,
|
||||
),
|
||||
self.list_filter.into(),
|
||||
);
|
||||
cx.background_spawn(save_task).detach();
|
||||
}
|
||||
|
||||
if let Some(ThreadStatus::Stopped) = thread_status {
|
||||
match self.list_filter {
|
||||
StackFrameFilter::All => {
|
||||
self.list_state.reset(self.entries.len());
|
||||
}
|
||||
StackFrameFilter::OnlyUserFrames => {
|
||||
self.list_state.reset(self.filter_entries_indices.len());
|
||||
if !self
|
||||
.selected_ix
|
||||
.map(|ix| self.filter_entries_indices.contains(&ix))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
self.selected_ix = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ix) = self.selected_ix {
|
||||
let scroll_to = match self.list_filter {
|
||||
StackFrameFilter::All => ix,
|
||||
StackFrameFilter::OnlyUserFrames => self
|
||||
.filter_entries_indices
|
||||
.binary_search_by_key(&ix, |ix| *ix)
|
||||
.expect("This index will always exist"),
|
||||
};
|
||||
self.list_state.scroll_to_reveal_item(scroll_to);
|
||||
}
|
||||
|
||||
cx.emit(StackFrameListEvent::BuiltEntries);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().p_1().size_full().child(
|
||||
list(
|
||||
@@ -711,6 +870,30 @@ impl StackFrameList {
|
||||
.size_full(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn render_control_strip(&self) -> AnyElement {
|
||||
let tooltip_title = match self.list_filter {
|
||||
StackFrameFilter::All => "Show stack frames from your project",
|
||||
StackFrameFilter::OnlyUserFrames => "Show all stack frames",
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.child(
|
||||
IconButton::new(
|
||||
"filter-by-visible-worktree-stack-frame-list",
|
||||
IconName::ListFilter,
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action(tooltip_title, &ToggleUserFrames, window, cx)
|
||||
})
|
||||
.toggle_state(self.list_filter == StackFrameFilter::OnlyUserFrames)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(ToggleUserFrames.boxed_clone(), cx)
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for StackFrameList {
|
||||
|
||||
@@ -752,3 +752,288 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_stack_frame_filter(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
|
||||
let test_file_content = r#"
|
||||
function main() {
|
||||
doSomething();
|
||||
}
|
||||
|
||||
function doSomething() {
|
||||
console.log('doing something');
|
||||
}
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
fs.insert_tree(
|
||||
path!("/project"),
|
||||
json!({
|
||||
"src": {
|
||||
"test.js": test_file_content,
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<Threads, _>(move |_, _| {
|
||||
Ok(dap::ThreadsResponse {
|
||||
threads: vec![dap::Thread {
|
||||
id: 1,
|
||||
name: "Thread 1".into(),
|
||||
}],
|
||||
})
|
||||
});
|
||||
|
||||
client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
|
||||
|
||||
let stack_frames = vec![
|
||||
StackFrame {
|
||||
id: 1,
|
||||
name: "main".into(),
|
||||
source: Some(dap::Source {
|
||||
name: Some("test.js".into()),
|
||||
path: Some(path!("/project/src/test.js").into()),
|
||||
source_reference: None,
|
||||
presentation_hint: None,
|
||||
origin: None,
|
||||
sources: None,
|
||||
adapter_data: None,
|
||||
checksums: None,
|
||||
}),
|
||||
line: 2,
|
||||
column: 1,
|
||||
end_line: None,
|
||||
end_column: None,
|
||||
can_restart: None,
|
||||
instruction_pointer_reference: None,
|
||||
module_id: None,
|
||||
presentation_hint: None,
|
||||
},
|
||||
StackFrame {
|
||||
id: 2,
|
||||
name: "node:internal/modules/cjs/loader".into(),
|
||||
source: Some(dap::Source {
|
||||
name: Some("loader.js".into()),
|
||||
path: Some(path!("/usr/lib/node/internal/modules/cjs/loader.js").into()),
|
||||
source_reference: None,
|
||||
presentation_hint: None,
|
||||
origin: None,
|
||||
sources: None,
|
||||
adapter_data: None,
|
||||
checksums: None,
|
||||
}),
|
||||
line: 100,
|
||||
column: 1,
|
||||
end_line: None,
|
||||
end_column: None,
|
||||
can_restart: None,
|
||||
instruction_pointer_reference: None,
|
||||
module_id: None,
|
||||
presentation_hint: Some(dap::StackFramePresentationHint::Deemphasize),
|
||||
},
|
||||
StackFrame {
|
||||
id: 3,
|
||||
name: "node:internal/modules/run_main".into(),
|
||||
source: Some(dap::Source {
|
||||
name: Some("run_main.js".into()),
|
||||
path: Some(path!("/usr/lib/node/internal/modules/run_main.js").into()),
|
||||
source_reference: None,
|
||||
presentation_hint: None,
|
||||
origin: None,
|
||||
sources: None,
|
||||
adapter_data: None,
|
||||
checksums: None,
|
||||
}),
|
||||
line: 50,
|
||||
column: 1,
|
||||
end_line: None,
|
||||
end_column: None,
|
||||
can_restart: None,
|
||||
instruction_pointer_reference: None,
|
||||
module_id: None,
|
||||
presentation_hint: Some(dap::StackFramePresentationHint::Deemphasize),
|
||||
},
|
||||
StackFrame {
|
||||
id: 4,
|
||||
name: "doSomething".into(),
|
||||
source: Some(dap::Source {
|
||||
name: Some("test.js".into()),
|
||||
path: Some(path!("/project/src/test.js").into()),
|
||||
source_reference: None,
|
||||
presentation_hint: None,
|
||||
origin: None,
|
||||
sources: None,
|
||||
adapter_data: None,
|
||||
checksums: None,
|
||||
}),
|
||||
line: 3,
|
||||
column: 1,
|
||||
end_line: None,
|
||||
end_column: None,
|
||||
can_restart: None,
|
||||
instruction_pointer_reference: None,
|
||||
module_id: None,
|
||||
presentation_hint: None,
|
||||
},
|
||||
];
|
||||
|
||||
// Store a copy for assertions
|
||||
let stack_frames_for_assertions = stack_frames.clone();
|
||||
|
||||
client.on_request::<StackTrace, _>({
|
||||
let stack_frames = Arc::new(stack_frames.clone());
|
||||
move |_, args| {
|
||||
assert_eq!(1, args.thread_id);
|
||||
|
||||
Ok(dap::StackTraceResponse {
|
||||
stack_frames: (*stack_frames).clone(),
|
||||
total_frames: None,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
client
|
||||
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
|
||||
reason: dap::StoppedEventReason::Pause,
|
||||
description: None,
|
||||
thread_id: Some(1),
|
||||
preserve_focus_hint: None,
|
||||
text: None,
|
||||
all_threads_stopped: None,
|
||||
hit_breakpoint_ids: None,
|
||||
}))
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// trigger threads to load
|
||||
active_debug_session_panel(workspace, cx).update(cx, |session, cx| {
|
||||
session.running_state().update(cx, |running_state, cx| {
|
||||
running_state
|
||||
.session()
|
||||
.update(cx, |session, cx| session.threads(cx));
|
||||
});
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// select first thread
|
||||
active_debug_session_panel(workspace, cx).update_in(cx, |session, window, cx| {
|
||||
session.running_state().update(cx, |running_state, cx| {
|
||||
running_state.select_current_thread(
|
||||
&running_state
|
||||
.session()
|
||||
.update(cx, |session, cx| session.threads(cx)),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// trigger stack frames to load
|
||||
active_debug_session_panel(workspace, cx).update(cx, |debug_panel_item, cx| {
|
||||
let stack_frame_list = debug_panel_item
|
||||
.running_state()
|
||||
.update(cx, |state, _| state.stack_frame_list().clone());
|
||||
|
||||
stack_frame_list.update(cx, |stack_frame_list, cx| {
|
||||
stack_frame_list.dap_stack_frames(cx);
|
||||
});
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
active_debug_session_panel(workspace, cx).update_in(cx, |debug_panel_item, window, cx| {
|
||||
let stack_frame_list = debug_panel_item
|
||||
.running_state()
|
||||
.update(cx, |state, _| state.stack_frame_list().clone());
|
||||
|
||||
stack_frame_list.update(cx, |stack_frame_list, cx| {
|
||||
stack_frame_list.build_entries(true, window, cx);
|
||||
|
||||
// Verify we have the expected collapsed structure
|
||||
assert_eq!(
|
||||
stack_frame_list.entries(),
|
||||
&vec![
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[0].clone()),
|
||||
StackFrameEntry::Collapsed(vec![
|
||||
stack_frames_for_assertions[1].clone(),
|
||||
stack_frames_for_assertions[2].clone()
|
||||
]),
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[3].clone()),
|
||||
]
|
||||
);
|
||||
|
||||
// Test 1: Verify filtering works
|
||||
let all_frames = stack_frame_list.flatten_entries(true, false);
|
||||
assert_eq!(all_frames.len(), 4, "Should see all 4 frames initially");
|
||||
|
||||
// Toggle to user frames only
|
||||
stack_frame_list
|
||||
.toggle_frame_filter(Some(project::debugger::session::ThreadStatus::Stopped), cx);
|
||||
|
||||
let user_frames = stack_frame_list.dap_stack_frames(cx);
|
||||
assert_eq!(user_frames.len(), 2, "Should only see 2 user frames");
|
||||
assert_eq!(user_frames[0].name, "main");
|
||||
assert_eq!(user_frames[1].name, "doSomething");
|
||||
|
||||
// Test 2: Verify filtering toggles correctly
|
||||
// Check we can toggle back and see all frames again
|
||||
|
||||
// Toggle back to all frames
|
||||
stack_frame_list
|
||||
.toggle_frame_filter(Some(project::debugger::session::ThreadStatus::Stopped), cx);
|
||||
|
||||
let all_frames_again = stack_frame_list.flatten_entries(true, false);
|
||||
assert_eq!(
|
||||
all_frames_again.len(),
|
||||
4,
|
||||
"Should see all 4 frames after toggling back"
|
||||
);
|
||||
|
||||
// Test 3: Verify collapsed entries stay expanded
|
||||
stack_frame_list.expand_collapsed_entry(1, cx);
|
||||
assert_eq!(
|
||||
stack_frame_list.entries(),
|
||||
&vec![
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[0].clone()),
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[1].clone()),
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[2].clone()),
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[3].clone()),
|
||||
]
|
||||
);
|
||||
|
||||
// Toggle filter twice
|
||||
stack_frame_list
|
||||
.toggle_frame_filter(Some(project::debugger::session::ThreadStatus::Stopped), cx);
|
||||
stack_frame_list
|
||||
.toggle_frame_filter(Some(project::debugger::session::ThreadStatus::Stopped), cx);
|
||||
|
||||
// Verify entries remain expanded
|
||||
assert_eq!(
|
||||
stack_frame_list.entries(),
|
||||
&vec![
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[0].clone()),
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[1].clone()),
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[2].clone()),
|
||||
StackFrameEntry::Normal(stack_frames_for_assertions[3].clone()),
|
||||
],
|
||||
"Expanded entries should remain expanded after toggling filter"
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub const DEEPSEEK_API_URL: &str = "https://api.deepseek.com";
|
||||
pub const DEEPSEEK_API_URL: &str = "https://api.deepseek.com/v1";
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -263,12 +263,12 @@ pub async fn stream_completion(
|
||||
api_key: &str,
|
||||
request: Request,
|
||||
) -> Result<BoxStream<'static, Result<StreamResponse>>> {
|
||||
let uri = format!("{api_url}/v1/chat/completions");
|
||||
let uri = format!("{api_url}/chat/completions");
|
||||
let request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key));
|
||||
.header("Authorization", format!("Bearer {}", api_key.trim()));
|
||||
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
let mut response = client.send(request).await?;
|
||||
|
||||
@@ -10,8 +10,9 @@ use anyhow::Result;
|
||||
use collections::{BTreeSet, HashMap};
|
||||
use diagnostic_renderer::DiagnosticBlock;
|
||||
use editor::{
|
||||
DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
|
||||
Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
|
||||
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
|
||||
multibuffer_context_lines,
|
||||
};
|
||||
use gpui::{
|
||||
AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
@@ -493,10 +494,11 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
|
||||
let mut excerpt_ranges: Vec<ExcerptRange<Point>> = Vec::new();
|
||||
let context_lines = cx.update(|_, cx| multibuffer_context_lines(cx))?;
|
||||
for b in blocks.iter() {
|
||||
let excerpt_range = context_range_for_entry(
|
||||
b.initial_range.clone(),
|
||||
DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
context_lines,
|
||||
buffer_snapshot.clone(),
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ use settings::SettingsStore;
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use unindent::Unindent as _;
|
||||
use util::{RandomCharIter, path, post_inc};
|
||||
@@ -70,7 +71,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let uri = lsp::Url::from_file_path(path!("/test/main.rs")).unwrap();
|
||||
let uri = lsp::Uri::from_file_path(path!("/test/main.rs")).unwrap();
|
||||
|
||||
// Create some diagnostics
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
@@ -167,7 +168,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
language_server_id,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/test/consts.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/test/consts.rs")).unwrap(),
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 15),
|
||||
@@ -243,7 +244,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
language_server_id,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/test/consts.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/test/consts.rs")).unwrap(),
|
||||
diagnostics: vec![
|
||||
lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
@@ -356,14 +357,14 @@ async fn test_diagnostics_with_folds(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
server_id_1,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(4, 0), lsp::Position::new(4, 4)),
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
message: "no method `tset`".to_string(),
|
||||
related_information: Some(vec![lsp::DiagnosticRelatedInformation {
|
||||
location: lsp::Location::new(
|
||||
lsp::Url::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(0, 9),
|
||||
lsp::Position::new(0, 13),
|
||||
@@ -465,7 +466,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
server_id_1,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 1)),
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
@@ -509,7 +510,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
server_id_2,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 1)),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
@@ -552,7 +553,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
server_id_1,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 1)),
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
@@ -571,7 +572,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
server_id_2,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/test/main.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/test/main.rs")).unwrap(),
|
||||
diagnostics: vec![],
|
||||
version: None,
|
||||
},
|
||||
@@ -608,7 +609,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
server_id_2,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/test/main.js")).unwrap(),
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(3, 0), lsp::Position::new(3, 1)),
|
||||
severity: Some(lsp::DiagnosticSeverity::WARNING),
|
||||
@@ -745,8 +746,8 @@ async fn test_random_diagnostics_blocks(cx: &mut TestAppContext, mut rng: StdRng
|
||||
.update_diagnostics(
|
||||
server_id,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(&path).unwrap_or_else(|_| {
|
||||
lsp::Url::parse("file:///test/fallback.rs").unwrap()
|
||||
uri: lsp::Uri::from_file_path(&path).unwrap_or_else(|_| {
|
||||
lsp::Uri::from_str("file:///test/fallback.rs").unwrap()
|
||||
}),
|
||||
diagnostics: diagnostics.clone(),
|
||||
version: None,
|
||||
@@ -934,8 +935,8 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S
|
||||
.update_diagnostics(
|
||||
server_id,
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(&path).unwrap_or_else(|_| {
|
||||
lsp::Url::parse("file:///test/fallback.rs").unwrap()
|
||||
uri: lsp::Uri::from_file_path(&path).unwrap_or_else(|_| {
|
||||
lsp::Uri::from_str("file:///test/fallback.rs").unwrap()
|
||||
}),
|
||||
diagnostics: diagnostics.clone(),
|
||||
version: None,
|
||||
@@ -985,7 +986,7 @@ async fn active_diagnostics_dismiss_after_invalidation(cx: &mut TestAppContext)
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
@@ -1028,7 +1029,7 @@ async fn active_diagnostics_dismiss_after_invalidation(cx: &mut TestAppContext)
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
@@ -1078,7 +1079,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![
|
||||
lsp::Diagnostic {
|
||||
@@ -1246,7 +1247,7 @@ async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
|
||||
lsp_store.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
|
||||
@@ -1299,7 +1300,7 @@ async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext)
|
||||
lsp_store.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/dir/file.rs")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range,
|
||||
@@ -1376,7 +1377,7 @@ async fn test_diagnostics_with_code(cx: &mut TestAppContext) {
|
||||
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let workspace = window.root(cx).unwrap();
|
||||
let uri = lsp::Url::from_file_path(path!("/root/main.js")).unwrap();
|
||||
let uri = lsp::Uri::from_file_path(path!("/root/main.js")).unwrap();
|
||||
|
||||
// Create diagnostics with code fields
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
@@ -1460,7 +1461,7 @@ async fn go_to_diagnostic_with_severity(cx: &mut TestAppContext) {
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![
|
||||
lsp::Diagnostic {
|
||||
@@ -1673,7 +1674,7 @@ fn random_lsp_diagnostic(
|
||||
);
|
||||
|
||||
related_info.push(lsp::DiagnosticRelatedInformation {
|
||||
location: lsp::Location::new(lsp::Url::from_file_path(path).unwrap(), info_range),
|
||||
location: lsp::Location::new(lsp::Uri::from_file_path(path).unwrap(), info_range),
|
||||
message: format!("related info {i} for diagnostic {unique_id}"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,21 +228,29 @@ pub struct ShowCompletions {
|
||||
pub struct HandleInput(pub String);
|
||||
|
||||
/// Deletes from the cursor to the end of the next word.
|
||||
/// Stops before the end of the next word, if whitespace sequences of length >= 2 are encountered.
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
|
||||
#[action(namespace = editor)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DeleteToNextWordEnd {
|
||||
#[serde(default)]
|
||||
pub ignore_newlines: bool,
|
||||
// Whether to stop before the end of the next word, if language-defined bracket is encountered.
|
||||
#[serde(default)]
|
||||
pub ignore_brackets: bool,
|
||||
}
|
||||
|
||||
/// Deletes from the cursor to the start of the previous word.
|
||||
/// Stops before the start of the previous word, if whitespace sequences of length >= 2 are encountered.
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)]
|
||||
#[action(namespace = editor)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DeleteToPreviousWordStart {
|
||||
#[serde(default)]
|
||||
pub ignore_newlines: bool,
|
||||
// Whether to stop before the start of the previous word, if language-defined bracket is encountered.
|
||||
#[serde(default)]
|
||||
pub ignore_brackets: bool,
|
||||
}
|
||||
|
||||
/// Folds all code blocks at the specified indentation level.
|
||||
@@ -753,6 +761,8 @@ actions!(
|
||||
UniqueLinesCaseInsensitive,
|
||||
/// Removes duplicate lines (case-sensitive).
|
||||
UniqueLinesCaseSensitive,
|
||||
UnwrapSyntaxNode
|
||||
UnwrapSyntaxNode,
|
||||
/// Wraps selections in tag specified by language.
|
||||
WrapSelectionsInTag
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::scroll::ScrollAmount;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollStrategy, SharedString,
|
||||
Size, StrikethroughStyle, StyledText, Task, UniformListScrollHandle, div, px, uniform_list,
|
||||
AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollHandle, ScrollStrategy,
|
||||
SharedString, Size, StrikethroughStyle, StyledText, Task, UniformListScrollHandle, div, px,
|
||||
uniform_list,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::CodeLabel;
|
||||
@@ -184,6 +186,20 @@ impl CodeContextMenu {
|
||||
CodeContextMenu::CodeActions(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_aside(
|
||||
&mut self,
|
||||
scroll_amount: ScrollAmount,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
match self {
|
||||
CodeContextMenu::Completions(completions_menu) => {
|
||||
completions_menu.scroll_aside(scroll_amount, window, cx)
|
||||
}
|
||||
CodeContextMenu::CodeActions(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ContextMenuOrigin {
|
||||
@@ -207,6 +223,9 @@ pub struct CompletionsMenu {
|
||||
filter_task: Task<()>,
|
||||
cancel_filter: Arc<AtomicBool>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
// The `ScrollHandle` used on the Markdown documentation rendered on the
|
||||
// side of the completions menu.
|
||||
pub scroll_handle_aside: ScrollHandle,
|
||||
resolve_completions: bool,
|
||||
show_completion_documentation: bool,
|
||||
last_rendered_range: Rc<RefCell<Option<Range<usize>>>>,
|
||||
@@ -281,6 +300,7 @@ impl CompletionsMenu {
|
||||
filter_task: Task::ready(()),
|
||||
cancel_filter: Arc::new(AtomicBool::new(false)),
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
scroll_handle_aside: ScrollHandle::new(),
|
||||
resolve_completions: true,
|
||||
last_rendered_range: RefCell::new(None).into(),
|
||||
markdown_cache: RefCell::new(VecDeque::new()).into(),
|
||||
@@ -351,6 +371,7 @@ impl CompletionsMenu {
|
||||
filter_task: Task::ready(()),
|
||||
cancel_filter: Arc::new(AtomicBool::new(false)),
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
scroll_handle_aside: ScrollHandle::new(),
|
||||
resolve_completions: false,
|
||||
show_completion_documentation: false,
|
||||
last_rendered_range: RefCell::new(None).into(),
|
||||
@@ -948,6 +969,7 @@ impl CompletionsMenu {
|
||||
.max_w(max_size.width)
|
||||
.max_h(max_size.height)
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.scroll_handle_aside)
|
||||
.occlude(),
|
||||
)
|
||||
.into_any_element(),
|
||||
@@ -1212,6 +1234,23 @@ impl CompletionsMenu {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn scroll_aside(
|
||||
&mut self,
|
||||
amount: ScrollAmount,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let mut offset = self.scroll_handle_aside.offset();
|
||||
|
||||
offset.y -= amount.pixels(
|
||||
window.line_height(),
|
||||
self.scroll_handle_aside.bounds().size.height - px(16.),
|
||||
) / 2.0;
|
||||
|
||||
cx.notify();
|
||||
self.scroll_handle_aside.set_offset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -190,7 +190,6 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use sum_tree::TreeMap;
|
||||
use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
|
||||
use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
|
||||
use theme::{
|
||||
@@ -220,7 +219,6 @@ use crate::{
|
||||
|
||||
pub const FILE_HEADER_HEIGHT: u32 = 2;
|
||||
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
|
||||
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
const MAX_LINE_LEN: usize = 1024;
|
||||
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
|
||||
@@ -228,7 +226,7 @@ const MAX_SELECTION_HISTORY_LEN: usize = 1024;
|
||||
pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
|
||||
#[doc(hidden)]
|
||||
pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
|
||||
const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
|
||||
pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
|
||||
|
||||
pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
@@ -1061,8 +1059,8 @@ pub struct Editor {
|
||||
placeholder_text: Option<Arc<str>>,
|
||||
highlight_order: usize,
|
||||
highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
|
||||
background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
|
||||
gutter_highlights: TreeMap<TypeId, GutterHighlight>,
|
||||
background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
|
||||
gutter_highlights: HashMap<TypeId, GutterHighlight>,
|
||||
scrollbar_marker_state: ScrollbarMarkerState,
|
||||
active_indent_guides_state: ActiveIndentGuidesState,
|
||||
nav_history: Option<ItemNavHistory>,
|
||||
@@ -2113,8 +2111,8 @@ impl Editor {
|
||||
placeholder_text: None,
|
||||
highlight_order: 0,
|
||||
highlighted_rows: HashMap::default(),
|
||||
background_highlights: TreeMap::default(),
|
||||
gutter_highlights: TreeMap::default(),
|
||||
background_highlights: HashMap::default(),
|
||||
gutter_highlights: HashMap::default(),
|
||||
scrollbar_marker_state: ScrollbarMarkerState::default(),
|
||||
active_indent_guides_state: ActiveIndentGuidesState::default(),
|
||||
nav_history: None,
|
||||
@@ -2589,7 +2587,7 @@ impl Editor {
|
||||
|| binding
|
||||
.keystrokes()
|
||||
.first()
|
||||
.is_some_and(|keystroke| keystroke.display_modifiers.modified())
|
||||
.is_some_and(|keystroke| keystroke.modifiers().modified())
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -6412,7 +6410,7 @@ impl Editor {
|
||||
PathKey::for_buffer(buffer_handle, cx),
|
||||
buffer_handle.clone(),
|
||||
edited_ranges,
|
||||
DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -6631,7 +6629,7 @@ impl Editor {
|
||||
buffer_row: Some(point.row),
|
||||
..Default::default()
|
||||
};
|
||||
let Some(blame_entry) = blame
|
||||
let Some((buffer, blame_entry)) = blame
|
||||
.update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
|
||||
.flatten()
|
||||
else {
|
||||
@@ -6641,12 +6639,19 @@ impl Editor {
|
||||
let anchor = self.selections.newest_anchor().head();
|
||||
let position = self.to_pixel_point(anchor, &snapshot, window);
|
||||
if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
|
||||
self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
|
||||
self.show_blame_popover(
|
||||
buffer,
|
||||
&blame_entry,
|
||||
position + last_bounds.origin,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn show_blame_popover(
|
||||
&mut self,
|
||||
buffer: BufferId,
|
||||
blame_entry: &BlameEntry,
|
||||
position: gpui::Point<Pixels>,
|
||||
ignore_timeout: bool,
|
||||
@@ -6670,7 +6675,7 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
let blame = blame.read(cx);
|
||||
let details = blame.details_for_entry(&blame_entry);
|
||||
let details = blame.details_for_entry(buffer, &blame_entry);
|
||||
let markdown = cx.new(|cx| {
|
||||
Markdown::new(
|
||||
details
|
||||
@@ -7696,16 +7701,16 @@ impl Editor {
|
||||
.keystroke()
|
||||
{
|
||||
modifiers_held = modifiers_held
|
||||
|| (&accept_keystroke.display_modifiers == modifiers
|
||||
&& accept_keystroke.display_modifiers.modified());
|
||||
|| (accept_keystroke.modifiers() == modifiers
|
||||
&& accept_keystroke.modifiers().modified());
|
||||
};
|
||||
if let Some(accept_partial_keystroke) = self
|
||||
.accept_edit_prediction_keybind(true, window, cx)
|
||||
.keystroke()
|
||||
{
|
||||
modifiers_held = modifiers_held
|
||||
|| (&accept_partial_keystroke.display_modifiers == modifiers
|
||||
&& accept_partial_keystroke.display_modifiers.modified());
|
||||
|| (accept_partial_keystroke.modifiers() == modifiers
|
||||
&& accept_partial_keystroke.modifiers().modified());
|
||||
}
|
||||
|
||||
if modifiers_held {
|
||||
@@ -9059,7 +9064,7 @@ impl Editor {
|
||||
|
||||
let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
|
||||
|
||||
let modifiers_color = if accept_keystroke.display_modifiers == window.modifiers() {
|
||||
let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
|
||||
Color::Accent
|
||||
} else {
|
||||
Color::Muted
|
||||
@@ -9071,19 +9076,19 @@ impl Editor {
|
||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||
.text_size(TextSize::XSmall.rems(cx))
|
||||
.child(h_flex().children(ui::render_modifiers(
|
||||
&accept_keystroke.display_modifiers,
|
||||
accept_keystroke.modifiers(),
|
||||
PlatformStyle::platform(),
|
||||
Some(modifiers_color),
|
||||
Some(IconSize::XSmall.rems().into()),
|
||||
true,
|
||||
)))
|
||||
.when(is_platform_style_mac, |parent| {
|
||||
parent.child(accept_keystroke.display_key.clone())
|
||||
parent.child(accept_keystroke.key().to_string())
|
||||
})
|
||||
.when(!is_platform_style_mac, |parent| {
|
||||
parent.child(
|
||||
Key::new(
|
||||
util::capitalize(&accept_keystroke.display_key),
|
||||
util::capitalize(accept_keystroke.key()),
|
||||
Some(Color::Default),
|
||||
)
|
||||
.size(Some(IconSize::XSmall.rems().into())),
|
||||
@@ -9264,7 +9269,7 @@ impl Editor {
|
||||
accept_keystroke.as_ref(),
|
||||
|el, accept_keystroke| {
|
||||
el.child(h_flex().children(ui::render_modifiers(
|
||||
&accept_keystroke.display_modifiers,
|
||||
accept_keystroke.modifiers(),
|
||||
PlatformStyle::platform(),
|
||||
Some(Color::Default),
|
||||
Some(IconSize::XSmall.rems().into()),
|
||||
@@ -9334,7 +9339,7 @@ impl Editor {
|
||||
.child(completion),
|
||||
)
|
||||
.when_some(accept_keystroke, |el, accept_keystroke| {
|
||||
if !accept_keystroke.display_modifiers.modified() {
|
||||
if !accept_keystroke.modifiers().modified() {
|
||||
return el;
|
||||
}
|
||||
|
||||
@@ -9353,7 +9358,7 @@ impl Editor {
|
||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||
.when(is_platform_style_mac, |parent| parent.gap_1())
|
||||
.child(h_flex().children(ui::render_modifiers(
|
||||
&accept_keystroke.display_modifiers,
|
||||
accept_keystroke.modifiers(),
|
||||
PlatformStyle::platform(),
|
||||
Some(if !has_completion {
|
||||
Color::Muted
|
||||
@@ -10462,6 +10467,86 @@ impl Editor {
|
||||
})
|
||||
}
|
||||
|
||||
fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
for selection in self.selections.disjoint_anchors().iter() {
|
||||
if snapshot
|
||||
.language_at(selection.start)
|
||||
.and_then(|lang| lang.config().wrap_characters.as_ref())
|
||||
.is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn wrap_selections_in_tag(
|
||||
&mut self,
|
||||
_: &WrapSelectionsInTag,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
let mut boundaries = Vec::new();
|
||||
|
||||
for selection in self.selections.all::<Point>(cx).iter() {
|
||||
let Some(wrap_config) = snapshot
|
||||
.language_at(selection.start)
|
||||
.and_then(|lang| lang.config().wrap_characters.clone())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
|
||||
let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
|
||||
|
||||
let start_before = snapshot.anchor_before(selection.start);
|
||||
let end_after = snapshot.anchor_after(selection.end);
|
||||
|
||||
edits.push((start_before..start_before, open_tag));
|
||||
edits.push((end_after..end_after, close_tag));
|
||||
|
||||
boundaries.push((
|
||||
start_before,
|
||||
end_after,
|
||||
wrap_config.start_prefix.len(),
|
||||
wrap_config.end_suffix.len(),
|
||||
));
|
||||
}
|
||||
|
||||
if edits.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let buffer = this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
buffer.snapshot(cx)
|
||||
});
|
||||
|
||||
let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
|
||||
for (start_before, end_after, start_prefix_len, end_suffix_len) in
|
||||
boundaries.into_iter()
|
||||
{
|
||||
let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
|
||||
let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
|
||||
new_selections.push(open_offset..open_offset);
|
||||
new_selections.push(close_offset..close_offset);
|
||||
}
|
||||
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_ranges(new_selections);
|
||||
});
|
||||
|
||||
this.request_autoscroll(Autoscroll::fit(), cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(project) = self.project.clone() else {
|
||||
return;
|
||||
@@ -11752,6 +11837,18 @@ impl Editor {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum CommentFormat {
|
||||
/// single line comment, with prefix for line
|
||||
Line(String),
|
||||
/// single line within a block comment, with prefix for line
|
||||
BlockLine(String),
|
||||
/// a single line of a block comment that includes the initial delimiter
|
||||
BlockCommentWithStart(BlockCommentConfig),
|
||||
/// a single line of a block comment that includes the ending delimiter
|
||||
BlockCommentWithEnd(BlockCommentConfig),
|
||||
}
|
||||
|
||||
// Split selections to respect paragraph, indent, and comment prefix boundaries.
|
||||
let wrap_ranges = selections.into_iter().flat_map(|selection| {
|
||||
let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
|
||||
@@ -11768,37 +11865,75 @@ impl Editor {
|
||||
let language_scope = buffer.language_scope_at(selection.head());
|
||||
|
||||
let indent_and_prefix_for_row =
|
||||
|row: u32| -> (IndentSize, Option<String>, Option<String>) {
|
||||
|row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
|
||||
let indent = buffer.indent_size_for_line(MultiBufferRow(row));
|
||||
let (comment_prefix, rewrap_prefix) =
|
||||
if let Some(language_scope) = &language_scope {
|
||||
let indent_end = Point::new(row, indent.len);
|
||||
let comment_prefix = language_scope
|
||||
let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
|
||||
&language_scope
|
||||
{
|
||||
let indent_end = Point::new(row, indent.len);
|
||||
let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
|
||||
let line_text_after_indent = buffer
|
||||
.text_for_range(indent_end..line_end)
|
||||
.collect::<String>();
|
||||
|
||||
let is_within_comment_override = buffer
|
||||
.language_scope_at(indent_end)
|
||||
.is_some_and(|scope| scope.override_name() == Some("comment"));
|
||||
let comment_delimiters = if is_within_comment_override {
|
||||
// we are within a comment syntax node, but we don't
|
||||
// yet know what kind of comment: block, doc or line
|
||||
match (
|
||||
language_scope.documentation_comment(),
|
||||
language_scope.block_comment(),
|
||||
) {
|
||||
(Some(config), _) | (_, Some(config))
|
||||
if buffer.contains_str_at(indent_end, &config.start) =>
|
||||
{
|
||||
Some(CommentFormat::BlockCommentWithStart(config.clone()))
|
||||
}
|
||||
(Some(config), _) | (_, Some(config))
|
||||
if line_text_after_indent.ends_with(config.end.as_ref()) =>
|
||||
{
|
||||
Some(CommentFormat::BlockCommentWithEnd(config.clone()))
|
||||
}
|
||||
(Some(config), _) | (_, Some(config))
|
||||
if buffer.contains_str_at(indent_end, &config.prefix) =>
|
||||
{
|
||||
Some(CommentFormat::BlockLine(config.prefix.to_string()))
|
||||
}
|
||||
(_, _) => language_scope
|
||||
.line_comment_prefixes()
|
||||
.iter()
|
||||
.find(|prefix| buffer.contains_str_at(indent_end, prefix))
|
||||
.map(|prefix| CommentFormat::Line(prefix.to_string())),
|
||||
}
|
||||
} else {
|
||||
// we not in an overridden comment node, but we may
|
||||
// be within a non-overridden line comment node
|
||||
language_scope
|
||||
.line_comment_prefixes()
|
||||
.iter()
|
||||
.find(|prefix| buffer.contains_str_at(indent_end, prefix))
|
||||
.map(|prefix| prefix.to_string());
|
||||
let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
|
||||
let line_text_after_indent = buffer
|
||||
.text_for_range(indent_end..line_end)
|
||||
.collect::<String>();
|
||||
let rewrap_prefix = language_scope
|
||||
.rewrap_prefixes()
|
||||
.iter()
|
||||
.find_map(|prefix_regex| {
|
||||
prefix_regex.find(&line_text_after_indent).map(|mat| {
|
||||
if mat.start() == 0 {
|
||||
Some(mat.as_str().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
(comment_prefix, rewrap_prefix)
|
||||
} else {
|
||||
(None, None)
|
||||
.map(|prefix| CommentFormat::Line(prefix.to_string()))
|
||||
};
|
||||
|
||||
let rewrap_prefix = language_scope
|
||||
.rewrap_prefixes()
|
||||
.iter()
|
||||
.find_map(|prefix_regex| {
|
||||
prefix_regex.find(&line_text_after_indent).map(|mat| {
|
||||
if mat.start() == 0 {
|
||||
Some(mat.as_str().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
(comment_delimiters, rewrap_prefix)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
(indent, comment_prefix, rewrap_prefix)
|
||||
};
|
||||
|
||||
@@ -11809,22 +11944,22 @@ impl Editor {
|
||||
let mut prev_row = first_row;
|
||||
let (
|
||||
mut current_range_indent,
|
||||
mut current_range_comment_prefix,
|
||||
mut current_range_comment_delimiters,
|
||||
mut current_range_rewrap_prefix,
|
||||
) = indent_and_prefix_for_row(first_row);
|
||||
|
||||
for row in non_blank_rows_iter.skip(1) {
|
||||
let has_paragraph_break = row > prev_row + 1;
|
||||
|
||||
let (row_indent, row_comment_prefix, row_rewrap_prefix) =
|
||||
let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
|
||||
indent_and_prefix_for_row(row);
|
||||
|
||||
let has_indent_change = row_indent != current_range_indent;
|
||||
let has_comment_change = row_comment_prefix != current_range_comment_prefix;
|
||||
let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
|
||||
|
||||
let has_boundary_change = has_comment_change
|
||||
|| row_rewrap_prefix.is_some()
|
||||
|| (has_indent_change && current_range_comment_prefix.is_some());
|
||||
|| (has_indent_change && current_range_comment_delimiters.is_some());
|
||||
|
||||
if has_paragraph_break || has_boundary_change {
|
||||
ranges.push((
|
||||
@@ -11832,13 +11967,13 @@ impl Editor {
|
||||
Point::new(current_range_start, 0)
|
||||
..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
|
||||
current_range_indent,
|
||||
current_range_comment_prefix.clone(),
|
||||
current_range_comment_delimiters.clone(),
|
||||
current_range_rewrap_prefix.clone(),
|
||||
from_empty_selection,
|
||||
));
|
||||
current_range_start = row;
|
||||
current_range_indent = row_indent;
|
||||
current_range_comment_prefix = row_comment_prefix;
|
||||
current_range_comment_delimiters = row_comment_delimiters;
|
||||
current_range_rewrap_prefix = row_rewrap_prefix;
|
||||
}
|
||||
prev_row = row;
|
||||
@@ -11849,7 +11984,7 @@ impl Editor {
|
||||
Point::new(current_range_start, 0)
|
||||
..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
|
||||
current_range_indent,
|
||||
current_range_comment_prefix,
|
||||
current_range_comment_delimiters,
|
||||
current_range_rewrap_prefix,
|
||||
from_empty_selection,
|
||||
));
|
||||
@@ -11863,7 +11998,7 @@ impl Editor {
|
||||
for (
|
||||
language_settings,
|
||||
wrap_range,
|
||||
indent_size,
|
||||
mut indent_size,
|
||||
comment_prefix,
|
||||
rewrap_prefix,
|
||||
from_empty_selection,
|
||||
@@ -11883,16 +12018,26 @@ impl Editor {
|
||||
|
||||
let tab_size = language_settings.tab_size;
|
||||
|
||||
let (line_prefix, inside_comment) = match &comment_prefix {
|
||||
Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
|
||||
(Some(prefix.as_str()), true)
|
||||
}
|
||||
Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
|
||||
(Some(prefix.as_ref()), true)
|
||||
}
|
||||
Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
|
||||
start: _,
|
||||
end: _,
|
||||
prefix,
|
||||
tab_size,
|
||||
})) => {
|
||||
indent_size.len += tab_size;
|
||||
(Some(prefix.as_ref()), true)
|
||||
}
|
||||
None => (None, false),
|
||||
};
|
||||
let indent_prefix = indent_size.chars().collect::<String>();
|
||||
let mut line_prefix = indent_prefix.clone();
|
||||
let mut inside_comment = false;
|
||||
if let Some(prefix) = &comment_prefix {
|
||||
line_prefix.push_str(prefix);
|
||||
inside_comment = true;
|
||||
}
|
||||
if let Some(prefix) = &rewrap_prefix {
|
||||
line_prefix.push_str(prefix);
|
||||
}
|
||||
let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
|
||||
|
||||
let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
|
||||
RewrapBehavior::InComments => inside_comment,
|
||||
@@ -11937,6 +12082,8 @@ impl Editor {
|
||||
let start_offset = start.to_offset(&buffer);
|
||||
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
|
||||
let selection_text = buffer.text_for_range(start..end).collect::<String>();
|
||||
let mut first_line_delimiter = None;
|
||||
let mut last_line_delimiter = None;
|
||||
let Some(lines_without_prefixes) = selection_text
|
||||
.lines()
|
||||
.enumerate()
|
||||
@@ -11944,6 +12091,46 @@ impl Editor {
|
||||
let line_trimmed = line.trim_start();
|
||||
if rewrap_prefix.is_some() && ix > 0 {
|
||||
Ok(line_trimmed)
|
||||
} else if let Some(
|
||||
CommentFormat::BlockCommentWithStart(BlockCommentConfig {
|
||||
start,
|
||||
prefix,
|
||||
end,
|
||||
tab_size,
|
||||
})
|
||||
| CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
|
||||
start,
|
||||
prefix,
|
||||
end,
|
||||
tab_size,
|
||||
}),
|
||||
) = &comment_prefix
|
||||
{
|
||||
let line_trimmed = line_trimmed
|
||||
.strip_prefix(start.as_ref())
|
||||
.map(|s| {
|
||||
let mut indent_size = indent_size;
|
||||
indent_size.len -= tab_size;
|
||||
let indent_prefix: String = indent_size.chars().collect();
|
||||
first_line_delimiter = Some((indent_prefix, start));
|
||||
s.trim_start()
|
||||
})
|
||||
.unwrap_or(line_trimmed);
|
||||
let line_trimmed = line_trimmed
|
||||
.strip_suffix(end.as_ref())
|
||||
.map(|s| {
|
||||
last_line_delimiter = Some(end);
|
||||
s.trim_end()
|
||||
})
|
||||
.unwrap_or(line_trimmed);
|
||||
let line_trimmed = line_trimmed
|
||||
.strip_prefix(prefix.as_ref())
|
||||
.unwrap_or(line_trimmed);
|
||||
Ok(line_trimmed)
|
||||
} else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
|
||||
line_trimmed.strip_prefix(prefix).with_context(|| {
|
||||
format!("line did not start with prefix {prefix:?}: {line:?}")
|
||||
})
|
||||
} else {
|
||||
line_trimmed
|
||||
.strip_prefix(&line_prefix.trim_start())
|
||||
@@ -11970,14 +12157,25 @@ impl Editor {
|
||||
line_prefix.clone()
|
||||
};
|
||||
|
||||
let wrapped_text = wrap_with_prefix(
|
||||
line_prefix,
|
||||
subsequent_lines_prefix,
|
||||
lines_without_prefixes.join("\n"),
|
||||
wrap_column,
|
||||
tab_size,
|
||||
options.preserve_existing_whitespace,
|
||||
);
|
||||
let wrapped_text = {
|
||||
let mut wrapped_text = wrap_with_prefix(
|
||||
line_prefix,
|
||||
subsequent_lines_prefix,
|
||||
lines_without_prefixes.join("\n"),
|
||||
wrap_column,
|
||||
tab_size,
|
||||
options.preserve_existing_whitespace,
|
||||
);
|
||||
|
||||
if let Some((indent, delimiter)) = first_line_delimiter {
|
||||
wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
|
||||
}
|
||||
if let Some(last_line) = last_line_delimiter {
|
||||
wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
|
||||
}
|
||||
|
||||
wrapped_text
|
||||
};
|
||||
|
||||
// TODO: should always use char-based diff while still supporting cursor behavior that
|
||||
// matches vim.
|
||||
@@ -12955,11 +13153,17 @@ impl Editor {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() {
|
||||
let cursor = if action.ignore_newlines {
|
||||
let mut cursor = if action.ignore_newlines {
|
||||
movement::previous_word_start(map, selection.head())
|
||||
} else {
|
||||
movement::previous_word_start_or_newline(map, selection.head())
|
||||
};
|
||||
cursor = movement::adjust_greedy_deletion(
|
||||
map,
|
||||
selection.head(),
|
||||
cursor,
|
||||
action.ignore_brackets,
|
||||
);
|
||||
selection.set_head(cursor, SelectionGoal::None);
|
||||
}
|
||||
});
|
||||
@@ -12980,7 +13184,9 @@ impl Editor {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() {
|
||||
let cursor = movement::previous_subword_start(map, selection.head());
|
||||
let mut cursor = movement::previous_subword_start(map, selection.head());
|
||||
cursor =
|
||||
movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
|
||||
selection.set_head(cursor, SelectionGoal::None);
|
||||
}
|
||||
});
|
||||
@@ -13056,11 +13262,17 @@ impl Editor {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() {
|
||||
let cursor = if action.ignore_newlines {
|
||||
let mut cursor = if action.ignore_newlines {
|
||||
movement::next_word_end(map, selection.head())
|
||||
} else {
|
||||
movement::next_word_end_or_newline(map, selection.head())
|
||||
};
|
||||
cursor = movement::adjust_greedy_deletion(
|
||||
map,
|
||||
selection.head(),
|
||||
cursor,
|
||||
action.ignore_brackets,
|
||||
);
|
||||
selection.set_head(cursor, SelectionGoal::None);
|
||||
}
|
||||
});
|
||||
@@ -13080,7 +13292,9 @@ impl Editor {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() {
|
||||
let cursor = movement::next_subword_end(map, selection.head());
|
||||
let mut cursor = movement::next_subword_end(map, selection.head());
|
||||
cursor =
|
||||
movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
|
||||
selection.set_head(cursor, SelectionGoal::None);
|
||||
}
|
||||
});
|
||||
@@ -16172,7 +16386,7 @@ impl Editor {
|
||||
PathKey::for_buffer(&location.buffer, cx),
|
||||
location.buffer.clone(),
|
||||
ranges_for_buffer,
|
||||
DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
ranges.extend(new_ranges)
|
||||
@@ -18879,7 +19093,7 @@ impl Editor {
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let cursor = self.selections.newest::<Point>(cx).head();
|
||||
let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
|
||||
let blame_entry = blame
|
||||
let (_, blame_entry) = blame
|
||||
.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows(
|
||||
@@ -18894,7 +19108,7 @@ impl Editor {
|
||||
})
|
||||
.flatten()?;
|
||||
let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
|
||||
let repo = blame.read(cx).repository(cx)?;
|
||||
let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
|
||||
let workspace = self.workspace()?.downgrade();
|
||||
renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
|
||||
None
|
||||
@@ -18930,18 +19144,17 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(project) = self.project() {
|
||||
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if buffer.read(cx).file().is_none() {
|
||||
if let Some(buffer) = self.buffer().read(cx).as_singleton()
|
||||
&& buffer.read(cx).file().is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let focused = self.focus_handle(cx).contains_focused(window, cx);
|
||||
|
||||
let project = project.clone();
|
||||
let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
|
||||
let blame = cx
|
||||
.new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
|
||||
self.blame_subscription =
|
||||
Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
|
||||
self.blame = Some(blame);
|
||||
@@ -19591,7 +19804,24 @@ impl Editor {
|
||||
let buffer = &snapshot.buffer_snapshot;
|
||||
let start = buffer.anchor_before(0);
|
||||
let end = buffer.anchor_after(buffer.len());
|
||||
self.background_highlights_in_range(start..end, &snapshot, cx.theme())
|
||||
self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn sorted_background_highlights_in_range(
|
||||
&self,
|
||||
search_range: Range<Anchor>,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
theme: &Theme,
|
||||
) -> Vec<(Range<DisplayPoint>, Hsla)> {
|
||||
let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
|
||||
res.sort_by(|a, b| {
|
||||
a.0.start
|
||||
.cmp(&b.0.start)
|
||||
.then_with(|| a.0.end.cmp(&b.0.end))
|
||||
.then_with(|| a.1.cmp(&b.1))
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
@@ -19656,6 +19886,9 @@ impl Editor {
|
||||
.is_some_and(|(_, highlights)| !highlights.is_empty())
|
||||
}
|
||||
|
||||
/// Returns all background highlights for a given range.
|
||||
///
|
||||
/// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
|
||||
pub fn background_highlights_in_range(
|
||||
&self,
|
||||
search_range: Range<Anchor>,
|
||||
@@ -19694,84 +19927,6 @@ impl Editor {
|
||||
results
|
||||
}
|
||||
|
||||
pub fn background_highlight_row_ranges<T: 'static>(
|
||||
&self,
|
||||
search_range: Range<Anchor>,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
count: usize,
|
||||
) -> Vec<RangeInclusive<DisplayPoint>> {
|
||||
let mut results = Vec::new();
|
||||
let Some((_, ranges)) = self
|
||||
.background_highlights
|
||||
.get(&HighlightKey::Type(TypeId::of::<T>()))
|
||||
else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let start_ix = match ranges.binary_search_by(|probe| {
|
||||
let cmp = probe
|
||||
.end
|
||||
.cmp(&search_range.start, &display_snapshot.buffer_snapshot);
|
||||
if cmp.is_gt() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
}) {
|
||||
Ok(i) | Err(i) => i,
|
||||
};
|
||||
let mut push_region = |start: Option<Point>, end: Option<Point>| {
|
||||
if let (Some(start_display), Some(end_display)) = (start, end) {
|
||||
results.push(
|
||||
start_display.to_display_point(display_snapshot)
|
||||
..=end_display.to_display_point(display_snapshot),
|
||||
);
|
||||
}
|
||||
};
|
||||
let mut start_row: Option<Point> = None;
|
||||
let mut end_row: Option<Point> = None;
|
||||
if ranges.len() > count {
|
||||
return Vec::new();
|
||||
}
|
||||
for range in &ranges[start_ix..] {
|
||||
if range
|
||||
.start
|
||||
.cmp(&search_range.end, &display_snapshot.buffer_snapshot)
|
||||
.is_ge()
|
||||
{
|
||||
break;
|
||||
}
|
||||
let end = range.end.to_point(&display_snapshot.buffer_snapshot);
|
||||
if let Some(current_row) = &end_row
|
||||
&& end.row == current_row.row
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let start = range.start.to_point(&display_snapshot.buffer_snapshot);
|
||||
if start_row.is_none() {
|
||||
assert_eq!(end_row, None);
|
||||
start_row = Some(start);
|
||||
end_row = Some(end);
|
||||
continue;
|
||||
}
|
||||
if let Some(current_end) = end_row.as_mut() {
|
||||
if start.row > current_end.row + 1 {
|
||||
push_region(start_row, end_row);
|
||||
start_row = Some(start);
|
||||
end_row = Some(end);
|
||||
} else {
|
||||
// Merge two hunks.
|
||||
*current_end = end;
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
// We might still have a hunk that was not rendered (if there was a search hit on the last line)
|
||||
push_region(start_row, end_row);
|
||||
results
|
||||
}
|
||||
|
||||
pub fn gutter_highlights_in_range(
|
||||
&self,
|
||||
search_range: Range<Anchor>,
|
||||
@@ -24016,3 +24171,10 @@ fn render_diff_hunk_controls(
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
pub fn multibuffer_context_lines(cx: &App) -> u32 {
|
||||
EditorSettings::try_get(cx)
|
||||
.map(|settings| settings.excerpt_context_lines)
|
||||
.unwrap_or(2)
|
||||
.clamp(1, 32)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use language::CursorShape;
|
||||
use project::project_settings::DiagnosticSeverity;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources, VsCodeSettings};
|
||||
use settings::{Settings, SettingsSources, SettingsUi, VsCodeSettings};
|
||||
use util::serde::default_true;
|
||||
|
||||
/// Imports from the VSCode settings at
|
||||
@@ -17,6 +17,7 @@ pub struct EditorSettings {
|
||||
pub cursor_shape: Option<CursorShape>,
|
||||
pub current_line_highlight: CurrentLineHighlight,
|
||||
pub selection_highlight: bool,
|
||||
pub rounded_selection: bool,
|
||||
pub lsp_highlight_debounce: u64,
|
||||
pub hover_popover_enabled: bool,
|
||||
pub hover_popover_delay: u64,
|
||||
@@ -37,6 +38,7 @@ pub struct EditorSettings {
|
||||
pub multi_cursor_modifier: MultiCursorModifier,
|
||||
pub redact_private_values: bool,
|
||||
pub expand_excerpt_lines: u32,
|
||||
pub excerpt_context_lines: u32,
|
||||
pub middle_click_paste: bool,
|
||||
#[serde(default)]
|
||||
pub double_click_in_multibuffer: DoubleClickInMultibuffer,
|
||||
@@ -55,10 +57,13 @@ pub struct EditorSettings {
|
||||
pub inline_code_actions: bool,
|
||||
pub drag_and_drop_selection: DragAndDropSelection,
|
||||
pub lsp_document_colors: DocumentColorsRenderMode,
|
||||
pub minimum_contrast_for_highlights: f32,
|
||||
}
|
||||
|
||||
/// How to render LSP `textDocument/documentColor` colors in the editor.
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DocumentColorsRenderMode {
|
||||
/// Do not query and render document colors.
|
||||
@@ -72,7 +77,7 @@ pub enum DocumentColorsRenderMode {
|
||||
Background,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CurrentLineHighlight {
|
||||
// Don't highlight the current line.
|
||||
@@ -86,7 +91,7 @@ pub enum CurrentLineHighlight {
|
||||
}
|
||||
|
||||
/// When to populate a new search's query based on the text under the cursor.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SeedQuerySetting {
|
||||
/// Always populate the search query with the word under the cursor.
|
||||
@@ -98,7 +103,9 @@ pub enum SeedQuerySetting {
|
||||
}
|
||||
|
||||
/// What to do when multibuffer is double clicked in some of its excerpts (parts of singleton buffers).
|
||||
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(
|
||||
Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DoubleClickInMultibuffer {
|
||||
/// Behave as a regular buffer and select the whole word.
|
||||
@@ -117,7 +124,9 @@ pub struct Jupyter {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(
|
||||
Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct JupyterContent {
|
||||
/// Whether the Jupyter feature is enabled.
|
||||
@@ -289,7 +298,9 @@ pub struct ScrollbarAxes {
|
||||
}
|
||||
|
||||
/// Whether to allow drag and drop text selection in buffer.
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi,
|
||||
)]
|
||||
pub struct DragAndDropSelection {
|
||||
/// When true, enables drag and drop text selection in buffer.
|
||||
///
|
||||
@@ -329,7 +340,7 @@ pub enum ScrollbarDiagnostics {
|
||||
/// The key to use for adding multiple cursors
|
||||
///
|
||||
/// Default: alt
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MultiCursorModifier {
|
||||
Alt,
|
||||
@@ -340,7 +351,7 @@ pub enum MultiCursorModifier {
|
||||
/// Whether the editor will scroll beyond the last line.
|
||||
///
|
||||
/// Default: one_page
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ScrollBeyondLastLine {
|
||||
/// The editor will not scroll beyond the last line.
|
||||
@@ -354,7 +365,9 @@ pub enum ScrollBeyondLastLine {
|
||||
}
|
||||
|
||||
/// Default options for buffer and project search items.
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi,
|
||||
)]
|
||||
pub struct SearchSettings {
|
||||
/// Whether to show the project search button in the status bar.
|
||||
#[serde(default = "default_true")]
|
||||
@@ -370,7 +383,9 @@ pub struct SearchSettings {
|
||||
}
|
||||
|
||||
/// What to do when go to definition yields no results.
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GoToDefinitionFallback {
|
||||
/// Disables the fallback.
|
||||
@@ -383,7 +398,9 @@ pub enum GoToDefinitionFallback {
|
||||
/// Determines when the mouse cursor should be hidden in an editor or input box.
|
||||
///
|
||||
/// Default: on_typing_and_movement
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum HideMouseMode {
|
||||
/// Never hide the mouse cursor
|
||||
@@ -398,7 +415,9 @@ pub enum HideMouseMode {
|
||||
/// Determines how snippets are sorted relative to other completion items.
|
||||
///
|
||||
/// Default: inline
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SnippetSortOrder {
|
||||
/// Place snippets at the top of the completion list
|
||||
@@ -412,7 +431,8 @@ pub enum SnippetSortOrder {
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
|
||||
#[settings_ui(group = "Editor")]
|
||||
pub struct EditorSettingsContent {
|
||||
/// Whether the cursor blinks in the editor.
|
||||
///
|
||||
@@ -421,7 +441,7 @@ pub struct EditorSettingsContent {
|
||||
/// Cursor shape for the default editor.
|
||||
/// Can be "bar", "block", "underline", or "hollow".
|
||||
///
|
||||
/// Default: None
|
||||
/// Default: bar
|
||||
pub cursor_shape: Option<CursorShape>,
|
||||
/// Determines when the mouse cursor should be hidden in an editor or input box.
|
||||
///
|
||||
@@ -439,6 +459,10 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub selection_highlight: Option<bool>,
|
||||
/// Whether the text selection should have rounded corners.
|
||||
///
|
||||
/// Default: true
|
||||
pub rounded_selection: Option<bool>,
|
||||
/// The debounce delay before querying highlights from the language
|
||||
/// server based on the current cursor location.
|
||||
///
|
||||
@@ -515,6 +539,11 @@ pub struct EditorSettingsContent {
|
||||
/// Default: 3
|
||||
pub expand_excerpt_lines: Option<u32>,
|
||||
|
||||
/// How many lines of context to provide in multibuffer excerpts by default
|
||||
///
|
||||
/// Default: 2
|
||||
pub excerpt_context_lines: Option<u32>,
|
||||
|
||||
/// Whether to enable middle-click paste on Linux
|
||||
///
|
||||
/// Default: true
|
||||
@@ -544,6 +573,12 @@ pub struct EditorSettingsContent {
|
||||
///
|
||||
/// Default: false
|
||||
pub show_signature_help_after_edits: Option<bool>,
|
||||
/// The minimum APCA perceptual contrast to maintain when
|
||||
/// rendering text over highlight backgrounds in the editor.
|
||||
///
|
||||
/// Values range from 0 to 106. Set to 0 to disable adjustments.
|
||||
/// Default: 45
|
||||
pub minimum_contrast_for_highlights: Option<f32>,
|
||||
|
||||
/// Whether to follow-up empty go to definition responses from the language server or not.
|
||||
/// `FindAllReferences` allows to look up references of the same symbol instead.
|
||||
@@ -583,7 +618,7 @@ pub struct EditorSettingsContent {
|
||||
}
|
||||
|
||||
// Status bar related settings
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
|
||||
pub struct StatusBarContent {
|
||||
/// Whether to display the active language button in the status bar.
|
||||
///
|
||||
@@ -596,7 +631,7 @@ pub struct StatusBarContent {
|
||||
}
|
||||
|
||||
// Toolbar related settings
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
|
||||
pub struct ToolbarContent {
|
||||
/// Whether to display breadcrumbs in the editor toolbar.
|
||||
///
|
||||
@@ -622,7 +657,9 @@ pub struct ToolbarContent {
|
||||
}
|
||||
|
||||
/// Scrollbar related settings
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default, SettingsUi,
|
||||
)]
|
||||
pub struct ScrollbarContent {
|
||||
/// When to show the scrollbar in the editor.
|
||||
///
|
||||
@@ -657,7 +694,9 @@ pub struct ScrollbarContent {
|
||||
}
|
||||
|
||||
/// Minimap related settings
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(
|
||||
Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, SettingsUi,
|
||||
)]
|
||||
pub struct MinimapContent {
|
||||
/// When to show the minimap in the editor.
|
||||
///
|
||||
@@ -705,7 +744,9 @@ pub struct ScrollbarAxesContent {
|
||||
}
|
||||
|
||||
/// Gutter related settings
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi,
|
||||
)]
|
||||
pub struct GutterContent {
|
||||
/// Whether to show line numbers in the gutter.
|
||||
///
|
||||
@@ -781,6 +822,7 @@ impl Settings for EditorSettings {
|
||||
"editor.selectionHighlight",
|
||||
&mut current.selection_highlight,
|
||||
);
|
||||
vscode.bool_setting("editor.roundedSelection", &mut current.rounded_selection);
|
||||
vscode.bool_setting("editor.hover.enabled", &mut current.hover_popover_enabled);
|
||||
vscode.u64_setting("editor.hover.delay", &mut current.hover_popover_delay);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,7 @@ use std::{
|
||||
use sum_tree::Bias;
|
||||
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, h_flex, prelude::*,
|
||||
right_click_menu,
|
||||
@@ -116,6 +117,7 @@ struct SelectionLayout {
|
||||
struct InlineBlameLayout {
|
||||
element: AnyElement,
|
||||
bounds: Bounds<Pixels>,
|
||||
buffer_id: BufferId,
|
||||
entry: BlameEntry,
|
||||
}
|
||||
|
||||
@@ -585,6 +587,9 @@ impl EditorElement {
|
||||
register_action(editor, window, Editor::edit_log_breakpoint);
|
||||
register_action(editor, window, Editor::enable_breakpoint);
|
||||
register_action(editor, window, Editor::disable_breakpoint);
|
||||
if editor.read(cx).enable_wrap_selections_in_tag(cx) {
|
||||
register_action(editor, window, Editor::wrap_selections_in_tag);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_key_listeners(&self, window: &mut Window, _: &mut App, layout: &EditorLayout) {
|
||||
@@ -1153,7 +1158,7 @@ impl EditorElement {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if let Some((bounds, blame_entry)) = &position_map.inline_blame_bounds {
|
||||
if let Some((bounds, buffer_id, blame_entry)) = &position_map.inline_blame_bounds {
|
||||
let mouse_over_inline_blame = bounds.contains(&event.position);
|
||||
let mouse_over_popover = editor
|
||||
.inline_blame_popover
|
||||
@@ -1166,7 +1171,7 @@ impl EditorElement {
|
||||
.is_some_and(|state| state.keyboard_grace);
|
||||
|
||||
if mouse_over_inline_blame || mouse_over_popover {
|
||||
editor.show_blame_popover(blame_entry, event.position, false, cx);
|
||||
editor.show_blame_popover(*buffer_id, blame_entry, event.position, false, cx);
|
||||
} else if !keyboard_grace {
|
||||
editor.hide_blame_popover(cx);
|
||||
}
|
||||
@@ -2450,7 +2455,7 @@ impl EditorElement {
|
||||
padding * em_width
|
||||
};
|
||||
|
||||
let entry = blame
|
||||
let (buffer_id, entry) = blame
|
||||
.update(cx, |blame, cx| {
|
||||
blame.blame_for_rows(&[*row_info], cx).next()
|
||||
})
|
||||
@@ -2485,13 +2490,22 @@ impl EditorElement {
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let bounds = Bounds::new(absolute_offset, size);
|
||||
|
||||
self.layout_blame_entry_popover(entry.clone(), blame, line_height, text_hitbox, window, cx);
|
||||
self.layout_blame_entry_popover(
|
||||
entry.clone(),
|
||||
blame,
|
||||
line_height,
|
||||
text_hitbox,
|
||||
row_info.buffer_id?,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), window, cx);
|
||||
|
||||
Some(InlineBlameLayout {
|
||||
element,
|
||||
bounds,
|
||||
buffer_id,
|
||||
entry,
|
||||
})
|
||||
}
|
||||
@@ -2502,6 +2516,7 @@ impl EditorElement {
|
||||
blame: Entity<GitBlame>,
|
||||
line_height: Pixels,
|
||||
text_hitbox: &Hitbox,
|
||||
buffer: BufferId,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
@@ -2526,6 +2541,7 @@ impl EditorElement {
|
||||
popover_state.markdown,
|
||||
workspace,
|
||||
&blame,
|
||||
buffer,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -2600,14 +2616,16 @@ impl EditorElement {
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.flat_map(|(ix, blame_entry)| {
|
||||
let (buffer_id, blame_entry) = blame_entry?;
|
||||
let mut element = render_blame_entry(
|
||||
ix,
|
||||
&blame,
|
||||
blame_entry?,
|
||||
blame_entry,
|
||||
&self.style,
|
||||
&mut last_used_color,
|
||||
self.editor.clone(),
|
||||
workspace.clone(),
|
||||
buffer_id,
|
||||
blame_renderer.clone(),
|
||||
cx,
|
||||
)?;
|
||||
@@ -3257,12 +3275,165 @@ impl EditorElement {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn bg_segments_per_row(
|
||||
rows: Range<DisplayRow>,
|
||||
selections: &[(PlayerColor, Vec<SelectionLayout>)],
|
||||
highlight_ranges: &[(Range<DisplayPoint>, Hsla)],
|
||||
base_background: Hsla,
|
||||
) -> Vec<Vec<(Range<DisplayPoint>, Hsla)>> {
|
||||
if rows.start >= rows.end {
|
||||
return Vec::new();
|
||||
}
|
||||
if !base_background.is_opaque() {
|
||||
// We don't actually know what color is behind this editor.
|
||||
return Vec::new();
|
||||
}
|
||||
let highlight_iter = highlight_ranges.iter().cloned();
|
||||
let selection_iter = selections.iter().flat_map(|(player_color, layouts)| {
|
||||
let color = player_color.selection;
|
||||
layouts.iter().filter_map(move |selection_layout| {
|
||||
if selection_layout.range.start != selection_layout.range.end {
|
||||
Some((selection_layout.range.clone(), color))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
let mut per_row_map = vec![Vec::new(); rows.len()];
|
||||
for (range, color) in highlight_iter.chain(selection_iter) {
|
||||
let covered_rows = if range.end.column() == 0 {
|
||||
cmp::max(range.start.row(), rows.start)..cmp::min(range.end.row(), rows.end)
|
||||
} else {
|
||||
cmp::max(range.start.row(), rows.start)
|
||||
..cmp::min(range.end.row().next_row(), rows.end)
|
||||
};
|
||||
for row in covered_rows.iter_rows() {
|
||||
let seg_start = if row == range.start.row() {
|
||||
range.start
|
||||
} else {
|
||||
DisplayPoint::new(row, 0)
|
||||
};
|
||||
let seg_end = if row == range.end.row() && range.end.column() != 0 {
|
||||
range.end
|
||||
} else {
|
||||
DisplayPoint::new(row, u32::MAX)
|
||||
};
|
||||
let ix = row.minus(rows.start) as usize;
|
||||
debug_assert!(row >= rows.start && row < rows.end);
|
||||
debug_assert!(ix < per_row_map.len());
|
||||
per_row_map[ix].push((seg_start..seg_end, color));
|
||||
}
|
||||
}
|
||||
for row_segments in per_row_map.iter_mut() {
|
||||
if row_segments.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let segments = mem::take(row_segments);
|
||||
let merged = Self::merge_overlapping_ranges(segments, base_background);
|
||||
*row_segments = merged;
|
||||
}
|
||||
per_row_map
|
||||
}
|
||||
|
||||
/// Merge overlapping ranges by splitting at all range boundaries and blending colors where
|
||||
/// multiple ranges overlap. The result contains non-overlapping ranges ordered from left to right.
|
||||
///
|
||||
/// Expects `start.row() == end.row()` for each range.
|
||||
fn merge_overlapping_ranges(
|
||||
ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
base_background: Hsla,
|
||||
) -> Vec<(Range<DisplayPoint>, Hsla)> {
|
||||
struct Boundary {
|
||||
pos: DisplayPoint,
|
||||
is_start: bool,
|
||||
index: usize,
|
||||
color: Hsla,
|
||||
}
|
||||
|
||||
let mut boundaries: SmallVec<[Boundary; 16]> = SmallVec::with_capacity(ranges.len() * 2);
|
||||
for (index, (range, color)) in ranges.iter().enumerate() {
|
||||
debug_assert!(
|
||||
range.start.row() == range.end.row(),
|
||||
"expects single-row ranges"
|
||||
);
|
||||
if range.start < range.end {
|
||||
boundaries.push(Boundary {
|
||||
pos: range.start,
|
||||
is_start: true,
|
||||
index,
|
||||
color: *color,
|
||||
});
|
||||
boundaries.push(Boundary {
|
||||
pos: range.end,
|
||||
is_start: false,
|
||||
index,
|
||||
color: *color,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if boundaries.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
boundaries
|
||||
.sort_unstable_by(|a, b| a.pos.cmp(&b.pos).then_with(|| a.is_start.cmp(&b.is_start)));
|
||||
|
||||
let mut processed_ranges: Vec<(Range<DisplayPoint>, Hsla)> = Vec::new();
|
||||
let mut active_ranges: SmallVec<[(usize, Hsla); 8]> = SmallVec::new();
|
||||
|
||||
let mut i = 0;
|
||||
let mut start_pos = boundaries[0].pos;
|
||||
|
||||
let boundaries_len = boundaries.len();
|
||||
while i < boundaries_len {
|
||||
let current_boundary_pos = boundaries[i].pos;
|
||||
if start_pos < current_boundary_pos {
|
||||
if !active_ranges.is_empty() {
|
||||
let mut color = base_background;
|
||||
for &(_, c) in &active_ranges {
|
||||
color = Hsla::blend(color, c);
|
||||
}
|
||||
if let Some((last_range, last_color)) = processed_ranges.last_mut() {
|
||||
if *last_color == color && last_range.end == start_pos {
|
||||
last_range.end = current_boundary_pos;
|
||||
} else {
|
||||
processed_ranges.push((start_pos..current_boundary_pos, color));
|
||||
}
|
||||
} else {
|
||||
processed_ranges.push((start_pos..current_boundary_pos, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
while i < boundaries_len && boundaries[i].pos == current_boundary_pos {
|
||||
let active_range = &boundaries[i];
|
||||
if active_range.is_start {
|
||||
let idx = active_range.index;
|
||||
let pos = active_ranges
|
||||
.binary_search_by_key(&idx, |(i, _)| *i)
|
||||
.unwrap_or_else(|p| p);
|
||||
active_ranges.insert(pos, (idx, active_range.color));
|
||||
} else {
|
||||
let idx = active_range.index;
|
||||
if let Ok(pos) = active_ranges.binary_search_by_key(&idx, |(i, _)| *i) {
|
||||
active_ranges.remove(pos);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
start_pos = current_boundary_pos;
|
||||
}
|
||||
|
||||
processed_ranges
|
||||
}
|
||||
|
||||
fn layout_lines(
|
||||
rows: Range<DisplayRow>,
|
||||
snapshot: &EditorSnapshot,
|
||||
style: &EditorStyle,
|
||||
editor_width: Pixels,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
bg_segments_per_row: &[Vec<(Range<DisplayPoint>, Hsla)>],
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Vec<LineWithInvisibles> {
|
||||
@@ -3318,6 +3489,7 @@ impl EditorElement {
|
||||
&snapshot.mode,
|
||||
editor_width,
|
||||
is_row_soft_wrapped,
|
||||
bg_segments_per_row,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -5909,7 +6081,7 @@ impl EditorElement {
|
||||
};
|
||||
|
||||
self.paint_lines_background(layout, window, cx);
|
||||
let invisible_display_ranges = self.paint_highlights(layout, window);
|
||||
let invisible_display_ranges = self.paint_highlights(layout, window, cx);
|
||||
self.paint_document_colors(layout, window);
|
||||
self.paint_lines(&invisible_display_ranges, layout, window, cx);
|
||||
self.paint_redactions(layout, window);
|
||||
@@ -5931,6 +6103,7 @@ impl EditorElement {
|
||||
&mut self,
|
||||
layout: &mut EditorLayout,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> SmallVec<[Range<DisplayPoint>; 32]> {
|
||||
window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
|
||||
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
|
||||
@@ -5947,7 +6120,11 @@ impl EditorElement {
|
||||
);
|
||||
}
|
||||
|
||||
let corner_radius = 0.15 * layout.position_map.line_height;
|
||||
let corner_radius = if EditorSettings::get_global(cx).rounded_selection {
|
||||
0.15 * layout.position_map.line_height
|
||||
} else {
|
||||
Pixels::ZERO
|
||||
};
|
||||
|
||||
for (player_color, selections) in &layout.selections {
|
||||
for selection in selections.iter() {
|
||||
@@ -7235,12 +7412,13 @@ fn render_blame_entry_popover(
|
||||
markdown: Entity<Markdown>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
blame: &Entity<GitBlame>,
|
||||
buffer: BufferId,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<AnyElement> {
|
||||
let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
|
||||
let blame = blame.read(cx);
|
||||
let repository = blame.repository(cx)?;
|
||||
let repository = blame.repository(cx, buffer)?;
|
||||
renderer.render_blame_entry_popover(
|
||||
blame_entry,
|
||||
scroll_handle,
|
||||
@@ -7261,6 +7439,7 @@ fn render_blame_entry(
|
||||
last_used_color: &mut Option<(PlayerColor, Oid)>,
|
||||
editor: Entity<Editor>,
|
||||
workspace: Entity<Workspace>,
|
||||
buffer: BufferId,
|
||||
renderer: Arc<dyn BlameRenderer>,
|
||||
cx: &mut App,
|
||||
) -> Option<AnyElement> {
|
||||
@@ -7281,8 +7460,8 @@ fn render_blame_entry(
|
||||
last_used_color.replace((sha_color, blame_entry.sha));
|
||||
|
||||
let blame = blame.read(cx);
|
||||
let details = blame.details_for_entry(&blame_entry);
|
||||
let repository = blame.repository(cx)?;
|
||||
let details = blame.details_for_entry(buffer, &blame_entry);
|
||||
let repository = blame.repository(cx, buffer)?;
|
||||
renderer.render_blame_entry(
|
||||
&style.text,
|
||||
blame_entry,
|
||||
@@ -7337,6 +7516,7 @@ impl LineWithInvisibles {
|
||||
editor_mode: &EditorMode,
|
||||
text_width: Pixels,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
bg_segments_per_row: &[Vec<(Range<DisplayPoint>, Hsla)>],
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Vec<Self> {
|
||||
@@ -7352,6 +7532,7 @@ impl LineWithInvisibles {
|
||||
let mut row = 0;
|
||||
let mut line_exceeded_max_len = false;
|
||||
let font_size = text_style.font_size.to_pixels(window.rem_size());
|
||||
let min_contrast = EditorSettings::get_global(cx).minimum_contrast_for_highlights;
|
||||
|
||||
let ellipsis = SharedString::from("⋯");
|
||||
|
||||
@@ -7364,10 +7545,16 @@ impl LineWithInvisibles {
|
||||
}]) {
|
||||
if let Some(replacement) = highlighted_chunk.replacement {
|
||||
if !line.is_empty() {
|
||||
let segments = bg_segments_per_row.get(row).map(|v| &v[..]).unwrap_or(&[]);
|
||||
let text_runs: &[TextRun] = if segments.is_empty() {
|
||||
&styles
|
||||
} else {
|
||||
&Self::split_runs_by_bg_segments(&styles, segments, min_contrast)
|
||||
};
|
||||
let shaped_line = window.text_system().shape_line(
|
||||
line.clone().into(),
|
||||
font_size,
|
||||
&styles,
|
||||
text_runs,
|
||||
None,
|
||||
);
|
||||
width += shaped_line.width;
|
||||
@@ -7445,10 +7632,16 @@ impl LineWithInvisibles {
|
||||
} else {
|
||||
for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
|
||||
if ix > 0 {
|
||||
let segments = bg_segments_per_row.get(row).map(|v| &v[..]).unwrap_or(&[]);
|
||||
let text_runs = if segments.is_empty() {
|
||||
&styles
|
||||
} else {
|
||||
&Self::split_runs_by_bg_segments(&styles, segments, min_contrast)
|
||||
};
|
||||
let shaped_line = window.text_system().shape_line(
|
||||
line.clone().into(),
|
||||
font_size,
|
||||
&styles,
|
||||
text_runs,
|
||||
None,
|
||||
);
|
||||
width += shaped_line.width;
|
||||
@@ -7536,6 +7729,81 @@ impl LineWithInvisibles {
|
||||
layouts
|
||||
}
|
||||
|
||||
/// Takes text runs and non-overlapping left-to-right background ranges with color.
|
||||
/// Returns new text runs with adjusted contrast as per background ranges.
|
||||
fn split_runs_by_bg_segments(
|
||||
text_runs: &[TextRun],
|
||||
bg_segments: &[(Range<DisplayPoint>, Hsla)],
|
||||
min_contrast: f32,
|
||||
) -> Vec<TextRun> {
|
||||
let mut output_runs: Vec<TextRun> = Vec::with_capacity(text_runs.len());
|
||||
let mut line_col = 0usize;
|
||||
let mut segment_ix = 0usize;
|
||||
|
||||
for text_run in text_runs.iter() {
|
||||
let run_start_col = line_col;
|
||||
let run_end_col = run_start_col + text_run.len;
|
||||
while segment_ix < bg_segments.len()
|
||||
&& (bg_segments[segment_ix].0.end.column() as usize) <= run_start_col
|
||||
{
|
||||
segment_ix += 1;
|
||||
}
|
||||
let mut cursor_col = run_start_col;
|
||||
let mut local_segment_ix = segment_ix;
|
||||
while local_segment_ix < bg_segments.len() {
|
||||
let (range, segment_color) = &bg_segments[local_segment_ix];
|
||||
let segment_start_col = range.start.column() as usize;
|
||||
let segment_end_col = range.end.column() as usize;
|
||||
if segment_start_col >= run_end_col {
|
||||
break;
|
||||
}
|
||||
if segment_start_col > cursor_col {
|
||||
let span_len = segment_start_col - cursor_col;
|
||||
output_runs.push(TextRun {
|
||||
len: span_len,
|
||||
font: text_run.font.clone(),
|
||||
color: text_run.color,
|
||||
background_color: text_run.background_color,
|
||||
underline: text_run.underline,
|
||||
strikethrough: text_run.strikethrough,
|
||||
});
|
||||
cursor_col = segment_start_col;
|
||||
}
|
||||
let segment_slice_end_col = segment_end_col.min(run_end_col);
|
||||
if segment_slice_end_col > cursor_col {
|
||||
let new_text_color =
|
||||
ensure_minimum_contrast(text_run.color, *segment_color, min_contrast);
|
||||
output_runs.push(TextRun {
|
||||
len: segment_slice_end_col - cursor_col,
|
||||
font: text_run.font.clone(),
|
||||
color: new_text_color,
|
||||
background_color: text_run.background_color,
|
||||
underline: text_run.underline,
|
||||
strikethrough: text_run.strikethrough,
|
||||
});
|
||||
cursor_col = segment_slice_end_col;
|
||||
}
|
||||
if segment_end_col >= run_end_col {
|
||||
break;
|
||||
}
|
||||
local_segment_ix += 1;
|
||||
}
|
||||
if cursor_col < run_end_col {
|
||||
output_runs.push(TextRun {
|
||||
len: run_end_col - cursor_col,
|
||||
font: text_run.font.clone(),
|
||||
color: text_run.color,
|
||||
background_color: text_run.background_color,
|
||||
underline: text_run.underline,
|
||||
strikethrough: text_run.strikethrough,
|
||||
});
|
||||
}
|
||||
line_col = run_end_col;
|
||||
segment_ix = local_segment_ix;
|
||||
}
|
||||
output_runs
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
line_height: Pixels,
|
||||
@@ -8449,12 +8717,20 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
);
|
||||
|
||||
let bg_segments_per_row = Self::bg_segments_per_row(
|
||||
start_row..end_row,
|
||||
&selections,
|
||||
&highlighted_ranges,
|
||||
self.style.background,
|
||||
);
|
||||
|
||||
let mut line_layouts = Self::layout_lines(
|
||||
start_row..end_row,
|
||||
&snapshot,
|
||||
&self.style,
|
||||
editor_width,
|
||||
is_row_soft_wrapped,
|
||||
&bg_segments_per_row,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -8488,7 +8764,7 @@ impl Element for EditorElement {
|
||||
return None;
|
||||
}
|
||||
let blame = editor.blame.as_ref()?;
|
||||
let blame_entry = blame
|
||||
let (_, blame_entry) = blame
|
||||
.update(cx, |blame, cx| {
|
||||
let row_infos =
|
||||
snapshot.row_infos(snapshot.longest_row()).next()?;
|
||||
@@ -9038,7 +9314,7 @@ impl Element for EditorElement {
|
||||
text_hitbox: text_hitbox.clone(),
|
||||
inline_blame_bounds: inline_blame_layout
|
||||
.as_ref()
|
||||
.map(|layout| (layout.bounds, layout.entry.clone())),
|
||||
.map(|layout| (layout.bounds, layout.buffer_id, layout.entry.clone())),
|
||||
display_hunks: display_hunks.clone(),
|
||||
diff_hunk_control_bounds,
|
||||
});
|
||||
@@ -9698,7 +9974,7 @@ pub(crate) struct PositionMap {
|
||||
pub snapshot: EditorSnapshot,
|
||||
pub text_hitbox: Hitbox,
|
||||
pub gutter_hitbox: Hitbox,
|
||||
pub inline_blame_bounds: Option<(Bounds<Pixels>, BlameEntry)>,
|
||||
pub inline_blame_bounds: Option<(Bounds<Pixels>, BufferId, BlameEntry)>,
|
||||
pub display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
|
||||
pub diff_hunk_control_bounds: Vec<(DisplayRow, Bounds<Pixels>)>,
|
||||
}
|
||||
@@ -9814,6 +10090,7 @@ pub fn layout_line(
|
||||
&snapshot.mode,
|
||||
text_width,
|
||||
is_row_soft_wrapped,
|
||||
&[],
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -10714,4 +10991,289 @@ mod tests {
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_merge_overlapping_ranges() {
|
||||
let base_bg = Hsla::white();
|
||||
let color1 = Hsla {
|
||||
h: 0.0,
|
||||
s: 0.5,
|
||||
l: 0.5,
|
||||
a: 0.5,
|
||||
};
|
||||
let color2 = Hsla {
|
||||
h: 120.0,
|
||||
s: 0.5,
|
||||
l: 0.5,
|
||||
a: 0.5,
|
||||
};
|
||||
|
||||
let display_point = |col| DisplayPoint::new(DisplayRow(0), col);
|
||||
let cols = |v: &Vec<(Range<DisplayPoint>, Hsla)>| -> Vec<(u32, u32)> {
|
||||
v.iter()
|
||||
.map(|(r, _)| (r.start.column(), r.end.column()))
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Test overlapping ranges blend colors
|
||||
let overlapping = vec![
|
||||
(display_point(5)..display_point(15), color1),
|
||||
(display_point(10)..display_point(20), color2),
|
||||
];
|
||||
let result = EditorElement::merge_overlapping_ranges(overlapping, base_bg);
|
||||
assert_eq!(cols(&result), vec![(5, 10), (10, 15), (15, 20)]);
|
||||
|
||||
// Test middle segment should have blended color
|
||||
let blended = Hsla::blend(Hsla::blend(base_bg, color1), color2);
|
||||
assert_eq!(result[1].1, blended);
|
||||
|
||||
// Test adjacent same-color ranges merge
|
||||
let adjacent_same = vec![
|
||||
(display_point(5)..display_point(10), color1),
|
||||
(display_point(10)..display_point(15), color1),
|
||||
];
|
||||
let result = EditorElement::merge_overlapping_ranges(adjacent_same, base_bg);
|
||||
assert_eq!(cols(&result), vec![(5, 15)]);
|
||||
|
||||
// Test contained range splits
|
||||
let contained = vec![
|
||||
(display_point(5)..display_point(20), color1),
|
||||
(display_point(10)..display_point(15), color2),
|
||||
];
|
||||
let result = EditorElement::merge_overlapping_ranges(contained, base_bg);
|
||||
assert_eq!(cols(&result), vec![(5, 10), (10, 15), (15, 20)]);
|
||||
|
||||
// Test multiple overlaps split at every boundary
|
||||
let color3 = Hsla {
|
||||
h: 240.0,
|
||||
s: 0.5,
|
||||
l: 0.5,
|
||||
a: 0.5,
|
||||
};
|
||||
let complex = vec![
|
||||
(display_point(5)..display_point(12), color1),
|
||||
(display_point(8)..display_point(16), color2),
|
||||
(display_point(10)..display_point(14), color3),
|
||||
];
|
||||
let result = EditorElement::merge_overlapping_ranges(complex, base_bg);
|
||||
assert_eq!(
|
||||
cols(&result),
|
||||
vec![(5, 8), (8, 10), (10, 12), (12, 14), (14, 16)]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_bg_segments_per_row() {
|
||||
let base_bg = Hsla::white();
|
||||
|
||||
// Case A: selection spans three display rows: row 1 [5, end), full row 2, row 3 [0, 7)
|
||||
{
|
||||
let selection_color = Hsla {
|
||||
h: 200.0,
|
||||
s: 0.5,
|
||||
l: 0.5,
|
||||
a: 0.5,
|
||||
};
|
||||
let player_color = PlayerColor {
|
||||
cursor: selection_color,
|
||||
background: selection_color,
|
||||
selection: selection_color,
|
||||
};
|
||||
|
||||
let spanning_selection = SelectionLayout {
|
||||
head: DisplayPoint::new(DisplayRow(3), 7),
|
||||
cursor_shape: CursorShape::Bar,
|
||||
is_newest: true,
|
||||
is_local: true,
|
||||
range: DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(3), 7),
|
||||
active_rows: DisplayRow(1)..DisplayRow(4),
|
||||
user_name: None,
|
||||
};
|
||||
|
||||
let selections = vec![(player_color, vec![spanning_selection])];
|
||||
let result = EditorElement::bg_segments_per_row(
|
||||
DisplayRow(0)..DisplayRow(5),
|
||||
&selections,
|
||||
&[],
|
||||
base_bg,
|
||||
);
|
||||
|
||||
assert_eq!(result.len(), 5);
|
||||
assert!(result[0].is_empty());
|
||||
assert_eq!(result[1].len(), 1);
|
||||
assert_eq!(result[2].len(), 1);
|
||||
assert_eq!(result[3].len(), 1);
|
||||
assert!(result[4].is_empty());
|
||||
|
||||
assert_eq!(result[1][0].0.start, DisplayPoint::new(DisplayRow(1), 5));
|
||||
assert_eq!(result[1][0].0.end.row(), DisplayRow(1));
|
||||
assert_eq!(result[1][0].0.end.column(), u32::MAX);
|
||||
assert_eq!(result[2][0].0.start, DisplayPoint::new(DisplayRow(2), 0));
|
||||
assert_eq!(result[2][0].0.end.row(), DisplayRow(2));
|
||||
assert_eq!(result[2][0].0.end.column(), u32::MAX);
|
||||
assert_eq!(result[3][0].0.start, DisplayPoint::new(DisplayRow(3), 0));
|
||||
assert_eq!(result[3][0].0.end, DisplayPoint::new(DisplayRow(3), 7));
|
||||
}
|
||||
|
||||
// Case B: selection ends exactly at the start of row 3, excluding row 3
|
||||
{
|
||||
let selection_color = Hsla {
|
||||
h: 120.0,
|
||||
s: 0.5,
|
||||
l: 0.5,
|
||||
a: 0.5,
|
||||
};
|
||||
let player_color = PlayerColor {
|
||||
cursor: selection_color,
|
||||
background: selection_color,
|
||||
selection: selection_color,
|
||||
};
|
||||
|
||||
let selection = SelectionLayout {
|
||||
head: DisplayPoint::new(DisplayRow(2), 0),
|
||||
cursor_shape: CursorShape::Bar,
|
||||
is_newest: true,
|
||||
is_local: true,
|
||||
range: DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(3), 0),
|
||||
active_rows: DisplayRow(1)..DisplayRow(3),
|
||||
user_name: None,
|
||||
};
|
||||
|
||||
let selections = vec![(player_color, vec![selection])];
|
||||
let result = EditorElement::bg_segments_per_row(
|
||||
DisplayRow(0)..DisplayRow(4),
|
||||
&selections,
|
||||
&[],
|
||||
base_bg,
|
||||
);
|
||||
|
||||
assert_eq!(result.len(), 4);
|
||||
assert!(result[0].is_empty());
|
||||
assert_eq!(result[1].len(), 1);
|
||||
assert_eq!(result[2].len(), 1);
|
||||
assert!(result[3].is_empty());
|
||||
|
||||
assert_eq!(result[1][0].0.start, DisplayPoint::new(DisplayRow(1), 5));
|
||||
assert_eq!(result[1][0].0.end.row(), DisplayRow(1));
|
||||
assert_eq!(result[1][0].0.end.column(), u32::MAX);
|
||||
assert_eq!(result[2][0].0.start, DisplayPoint::new(DisplayRow(2), 0));
|
||||
assert_eq!(result[2][0].0.end.row(), DisplayRow(2));
|
||||
assert_eq!(result[2][0].0.end.column(), u32::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn generate_test_run(len: usize, color: Hsla) -> TextRun {
|
||||
TextRun {
|
||||
len,
|
||||
font: gpui::font(".SystemUIFont"),
|
||||
color,
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_split_runs_by_bg_segments(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let text_color = Hsla {
|
||||
h: 210.0,
|
||||
s: 0.1,
|
||||
l: 0.4,
|
||||
a: 1.0,
|
||||
};
|
||||
let bg1 = Hsla {
|
||||
h: 30.0,
|
||||
s: 0.6,
|
||||
l: 0.8,
|
||||
a: 1.0,
|
||||
};
|
||||
let bg2 = Hsla {
|
||||
h: 200.0,
|
||||
s: 0.6,
|
||||
l: 0.2,
|
||||
a: 1.0,
|
||||
};
|
||||
let min_contrast = 45.0;
|
||||
|
||||
// Case A: single run; disjoint segments inside the run
|
||||
let runs = vec![generate_test_run(20, text_color)];
|
||||
let segs = vec![
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10),
|
||||
bg1,
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 16),
|
||||
bg2,
|
||||
),
|
||||
];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast);
|
||||
// Expected slices: [0,5) [5,10) [10,12) [12,16) [16,20)
|
||||
assert_eq!(
|
||||
out.iter().map(|r| r.len).collect::<Vec<_>>(),
|
||||
vec![5, 5, 2, 4, 4]
|
||||
);
|
||||
assert_eq!(out[0].color, text_color);
|
||||
assert_eq!(
|
||||
out[1].color,
|
||||
ensure_minimum_contrast(text_color, bg1, min_contrast)
|
||||
);
|
||||
assert_eq!(out[2].color, text_color);
|
||||
assert_eq!(
|
||||
out[3].color,
|
||||
ensure_minimum_contrast(text_color, bg2, min_contrast)
|
||||
);
|
||||
assert_eq!(out[4].color, text_color);
|
||||
|
||||
// Case B: multiple runs; segment extends to end of line (u32::MAX)
|
||||
let runs = vec![
|
||||
generate_test_run(8, text_color),
|
||||
generate_test_run(7, text_color),
|
||||
];
|
||||
let segs = vec![(
|
||||
DisplayPoint::new(DisplayRow(0), 6)..DisplayPoint::new(DisplayRow(0), u32::MAX),
|
||||
bg1,
|
||||
)];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast);
|
||||
// Expected slices across runs: [0,6) [6,8) | [0,7)
|
||||
assert_eq!(out.iter().map(|r| r.len).collect::<Vec<_>>(), vec![6, 2, 7]);
|
||||
let adjusted = ensure_minimum_contrast(text_color, bg1, min_contrast);
|
||||
assert_eq!(out[0].color, text_color);
|
||||
assert_eq!(out[1].color, adjusted);
|
||||
assert_eq!(out[2].color, adjusted);
|
||||
|
||||
// Case C: multi-byte characters
|
||||
// for text: "Hello 🌍 世界!"
|
||||
let runs = vec![
|
||||
generate_test_run(5, text_color), // "Hello"
|
||||
generate_test_run(6, text_color), // " 🌍 "
|
||||
generate_test_run(6, text_color), // "世界"
|
||||
generate_test_run(1, text_color), // "!"
|
||||
];
|
||||
// selecting "🌍 世"
|
||||
let segs = vec![(
|
||||
DisplayPoint::new(DisplayRow(0), 6)..DisplayPoint::new(DisplayRow(0), 14),
|
||||
bg1,
|
||||
)];
|
||||
let out = LineWithInvisibles::split_runs_by_bg_segments(&runs, &segs, min_contrast);
|
||||
// "Hello" | " " | "🌍 " | "世" | "界" | "!"
|
||||
assert_eq!(
|
||||
out.iter().map(|r| r.len).collect::<Vec<_>>(),
|
||||
vec![5, 1, 5, 3, 3, 1]
|
||||
);
|
||||
assert_eq!(out[0].color, text_color); // "Hello"
|
||||
assert_eq!(
|
||||
out[2].color,
|
||||
ensure_minimum_contrast(text_color, bg1, min_contrast)
|
||||
); // "🌍 "
|
||||
assert_eq!(
|
||||
out[3].color,
|
||||
ensure_minimum_contrast(text_color, bg1, min_contrast)
|
||||
); // "世"
|
||||
assert_eq!(out[4].color, text_color); // "界"
|
||||
assert_eq!(out[5].color, text_color); // "!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,18 @@ use gpui::{
|
||||
AnyElement, App, AppContext as _, Context, Entity, Hsla, ScrollHandle, Subscription, Task,
|
||||
TextStyle, WeakEntity, Window,
|
||||
};
|
||||
use language::{Bias, Buffer, BufferSnapshot, Edit};
|
||||
use itertools::Itertools;
|
||||
use language::{Bias, BufferSnapshot, Edit};
|
||||
use markdown::Markdown;
|
||||
use multi_buffer::RowInfo;
|
||||
use multi_buffer::{MultiBuffer, RowInfo};
|
||||
use project::{
|
||||
Project, ProjectItem,
|
||||
Project, ProjectItem as _,
|
||||
git_store::{GitStoreEvent, Repository, RepositoryEvent},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use sum_tree::SumTree;
|
||||
use text::BufferId;
|
||||
use workspace::Workspace;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -63,16 +65,19 @@ impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitBlame {
|
||||
project: Entity<Project>,
|
||||
buffer: Entity<Buffer>,
|
||||
struct GitBlameBuffer {
|
||||
entries: SumTree<GitBlameEntry>,
|
||||
commit_details: HashMap<Oid, ParsedCommitMessage>,
|
||||
buffer_snapshot: BufferSnapshot,
|
||||
buffer_edits: text::Subscription,
|
||||
commit_details: HashMap<Oid, ParsedCommitMessage>,
|
||||
}
|
||||
|
||||
pub struct GitBlame {
|
||||
project: Entity<Project>,
|
||||
multi_buffer: WeakEntity<MultiBuffer>,
|
||||
buffers: HashMap<BufferId, GitBlameBuffer>,
|
||||
task: Task<Result<()>>,
|
||||
focused: bool,
|
||||
generated: bool,
|
||||
changed_while_blurred: bool,
|
||||
user_triggered: bool,
|
||||
regenerate_on_edit_task: Task<Result<()>>,
|
||||
@@ -184,44 +189,44 @@ impl gpui::Global for GlobalBlameRenderer {}
|
||||
|
||||
impl GitBlame {
|
||||
pub fn new(
|
||||
buffer: Entity<Buffer>,
|
||||
multi_buffer: Entity<MultiBuffer>,
|
||||
project: Entity<Project>,
|
||||
user_triggered: bool,
|
||||
focused: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let entries = SumTree::from_item(
|
||||
GitBlameEntry {
|
||||
rows: buffer.read(cx).max_point().row + 1,
|
||||
blame: None,
|
||||
let multi_buffer_subscription = cx.subscribe(
|
||||
&multi_buffer,
|
||||
|git_blame, multi_buffer, event, cx| match event {
|
||||
multi_buffer::Event::DirtyChanged => {
|
||||
if !multi_buffer.read(cx).is_dirty(cx) {
|
||||
git_blame.generate(cx);
|
||||
}
|
||||
}
|
||||
multi_buffer::Event::ExcerptsAdded { .. }
|
||||
| multi_buffer::Event::ExcerptsEdited { .. } => git_blame.regenerate_on_edit(cx),
|
||||
_ => {}
|
||||
},
|
||||
&(),
|
||||
);
|
||||
|
||||
let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
|
||||
language::BufferEvent::DirtyChanged => {
|
||||
if !buffer.read(cx).is_dirty() {
|
||||
this.generate(cx);
|
||||
}
|
||||
}
|
||||
language::BufferEvent::Edited => {
|
||||
this.regenerate_on_edit(cx);
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
|
||||
let project_subscription = cx.subscribe(&project, {
|
||||
let buffer = buffer.clone();
|
||||
let multi_buffer = multi_buffer.downgrade();
|
||||
|
||||
move |this, _, event, cx| {
|
||||
move |git_blame, _, event, cx| {
|
||||
if let project::Event::WorktreeUpdatedEntries(_, updated) = event {
|
||||
let project_entry_id = buffer.read(cx).entry_id(cx);
|
||||
let Some(multi_buffer) = multi_buffer.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let project_entry_id = multi_buffer
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.and_then(|it| it.read(cx).entry_id(cx));
|
||||
if updated
|
||||
.iter()
|
||||
.any(|(_, entry_id, _)| project_entry_id == Some(*entry_id))
|
||||
{
|
||||
log::debug!("Updated buffers. Regenerating blame data...",);
|
||||
this.generate(cx);
|
||||
git_blame.generate(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,24 +244,17 @@ impl GitBlame {
|
||||
_ => {}
|
||||
});
|
||||
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
|
||||
let mut this = Self {
|
||||
project,
|
||||
buffer,
|
||||
buffer_snapshot,
|
||||
entries,
|
||||
buffer_edits,
|
||||
multi_buffer: multi_buffer.downgrade(),
|
||||
buffers: HashMap::default(),
|
||||
user_triggered,
|
||||
focused,
|
||||
changed_while_blurred: false,
|
||||
commit_details: HashMap::default(),
|
||||
task: Task::ready(Ok(())),
|
||||
generated: false,
|
||||
regenerate_on_edit_task: Task::ready(Ok(())),
|
||||
_regenerate_subscriptions: vec![
|
||||
buffer_subscriptions,
|
||||
multi_buffer_subscription,
|
||||
project_subscription,
|
||||
git_store_subscription,
|
||||
],
|
||||
@@ -265,56 +263,63 @@ impl GitBlame {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn repository(&self, cx: &App) -> Option<Entity<Repository>> {
|
||||
pub fn repository(&self, cx: &App, id: BufferId) -> Option<Entity<Repository>> {
|
||||
self.project
|
||||
.read(cx)
|
||||
.git_store()
|
||||
.read(cx)
|
||||
.repository_and_path_for_buffer_id(self.buffer.read(cx).remote_id(), cx)
|
||||
.repository_and_path_for_buffer_id(id, cx)
|
||||
.map(|(repo, _)| repo)
|
||||
}
|
||||
|
||||
pub fn has_generated_entries(&self) -> bool {
|
||||
self.generated
|
||||
!self.buffers.is_empty()
|
||||
}
|
||||
|
||||
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<ParsedCommitMessage> {
|
||||
self.commit_details.get(&entry.sha).cloned()
|
||||
pub fn details_for_entry(
|
||||
&self,
|
||||
buffer: BufferId,
|
||||
entry: &BlameEntry,
|
||||
) -> Option<ParsedCommitMessage> {
|
||||
self.buffers
|
||||
.get(&buffer)?
|
||||
.commit_details
|
||||
.get(&entry.sha)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn blame_for_rows<'a>(
|
||||
&'a mut self,
|
||||
rows: &'a [RowInfo],
|
||||
cx: &App,
|
||||
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
|
||||
self.sync(cx);
|
||||
|
||||
let buffer_id = self.buffer_snapshot.remote_id();
|
||||
let mut cursor = self.entries.cursor::<u32>(&());
|
||||
cx: &'a mut App,
|
||||
) -> impl Iterator<Item = Option<(BufferId, BlameEntry)>> + use<'a> {
|
||||
rows.iter().map(move |info| {
|
||||
let row = info
|
||||
.buffer_row
|
||||
.filter(|_| info.buffer_id == Some(buffer_id))?;
|
||||
cursor.seek_forward(&row, Bias::Right);
|
||||
cursor.item()?.blame.clone()
|
||||
let buffer_id = info.buffer_id?;
|
||||
self.sync(cx, buffer_id);
|
||||
|
||||
let buffer_row = info.buffer_row?;
|
||||
let mut cursor = self.buffers.get(&buffer_id)?.entries.cursor::<u32>(&());
|
||||
cursor.seek_forward(&buffer_row, Bias::Right);
|
||||
Some((buffer_id, cursor.item()?.blame.clone()?))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn max_author_length(&mut self, cx: &App) -> usize {
|
||||
self.sync(cx);
|
||||
|
||||
pub fn max_author_length(&mut self, cx: &mut App) -> usize {
|
||||
let mut max_author_length = 0;
|
||||
self.sync_all(cx);
|
||||
|
||||
for entry in self.entries.iter() {
|
||||
let author_len = entry
|
||||
.blame
|
||||
.as_ref()
|
||||
.and_then(|entry| entry.author.as_ref())
|
||||
.map(|author| author.len());
|
||||
if let Some(author_len) = author_len
|
||||
&& author_len > max_author_length
|
||||
{
|
||||
max_author_length = author_len;
|
||||
for buffer in self.buffers.values() {
|
||||
for entry in buffer.entries.iter() {
|
||||
let author_len = entry
|
||||
.blame
|
||||
.as_ref()
|
||||
.and_then(|entry| entry.author.as_ref())
|
||||
.map(|author| author.len());
|
||||
if let Some(author_len) = author_len
|
||||
&& author_len > max_author_length
|
||||
{
|
||||
max_author_length = author_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,22 +341,48 @@ impl GitBlame {
|
||||
}
|
||||
}
|
||||
|
||||
fn sync(&mut self, cx: &App) {
|
||||
let edits = self.buffer_edits.consume();
|
||||
let new_snapshot = self.buffer.read(cx).snapshot();
|
||||
fn sync_all(&mut self, cx: &mut App) {
|
||||
let Some(multi_buffer) = self.multi_buffer.upgrade() else {
|
||||
return;
|
||||
};
|
||||
multi_buffer
|
||||
.read(cx)
|
||||
.excerpt_buffer_ids()
|
||||
.into_iter()
|
||||
.for_each(|id| self.sync(cx, id));
|
||||
}
|
||||
|
||||
fn sync(&mut self, cx: &mut App, buffer_id: BufferId) {
|
||||
let Some(blame_buffer) = self.buffers.get_mut(&buffer_id) else {
|
||||
return;
|
||||
};
|
||||
let Some(buffer) = self
|
||||
.multi_buffer
|
||||
.upgrade()
|
||||
.and_then(|multi_buffer| multi_buffer.read(cx).buffer(buffer_id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let edits = blame_buffer.buffer_edits.consume();
|
||||
let new_snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
let mut row_edits = edits
|
||||
.into_iter()
|
||||
.map(|edit| {
|
||||
let old_point_range = self.buffer_snapshot.offset_to_point(edit.old.start)
|
||||
..self.buffer_snapshot.offset_to_point(edit.old.end);
|
||||
let old_point_range = blame_buffer.buffer_snapshot.offset_to_point(edit.old.start)
|
||||
..blame_buffer.buffer_snapshot.offset_to_point(edit.old.end);
|
||||
let new_point_range = new_snapshot.offset_to_point(edit.new.start)
|
||||
..new_snapshot.offset_to_point(edit.new.end);
|
||||
|
||||
if old_point_range.start.column
|
||||
== self.buffer_snapshot.line_len(old_point_range.start.row)
|
||||
== blame_buffer
|
||||
.buffer_snapshot
|
||||
.line_len(old_point_range.start.row)
|
||||
&& (new_snapshot.chars_at(edit.new.start).next() == Some('\n')
|
||||
|| self.buffer_snapshot.line_len(old_point_range.end.row) == 0)
|
||||
|| blame_buffer
|
||||
.buffer_snapshot
|
||||
.line_len(old_point_range.end.row)
|
||||
== 0)
|
||||
{
|
||||
Edit {
|
||||
old: old_point_range.start.row + 1..old_point_range.end.row + 1,
|
||||
@@ -375,7 +406,7 @@ impl GitBlame {
|
||||
.peekable();
|
||||
|
||||
let mut new_entries = SumTree::default();
|
||||
let mut cursor = self.entries.cursor::<u32>(&());
|
||||
let mut cursor = blame_buffer.entries.cursor::<u32>(&());
|
||||
|
||||
while let Some(mut edit) = row_edits.next() {
|
||||
while let Some(next_edit) = row_edits.peek() {
|
||||
@@ -433,17 +464,28 @@ impl GitBlame {
|
||||
new_entries.append(cursor.suffix(), &());
|
||||
drop(cursor);
|
||||
|
||||
self.buffer_snapshot = new_snapshot;
|
||||
self.entries = new_entries;
|
||||
blame_buffer.buffer_snapshot = new_snapshot;
|
||||
blame_buffer.entries = new_entries;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn check_invariants(&mut self, cx: &mut Context<Self>) {
|
||||
self.sync(cx);
|
||||
assert_eq!(
|
||||
self.entries.summary().rows,
|
||||
self.buffer.read(cx).max_point().row + 1
|
||||
);
|
||||
self.sync_all(cx);
|
||||
for (&id, buffer) in &self.buffers {
|
||||
assert_eq!(
|
||||
buffer.entries.summary().rows,
|
||||
self.multi_buffer
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.buffer(id)
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.max_point()
|
||||
.row
|
||||
+ 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate(&mut self, cx: &mut Context<Self>) {
|
||||
@@ -451,62 +493,105 @@ impl GitBlame {
|
||||
self.changed_while_blurred = true;
|
||||
return;
|
||||
}
|
||||
let buffer_edits = self.buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let snapshot = self.buffer.read(cx).snapshot();
|
||||
let blame = self.project.update(cx, |project, cx| {
|
||||
project.blame_buffer(&self.buffer, None, cx)
|
||||
let Some(multi_buffer) = self.multi_buffer.upgrade() else {
|
||||
return Vec::new();
|
||||
};
|
||||
multi_buffer
|
||||
.read(cx)
|
||||
.all_buffer_ids()
|
||||
.into_iter()
|
||||
.filter_map(|id| {
|
||||
let buffer = multi_buffer.read(cx).buffer(id)?;
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
|
||||
let blame_buffer = project.blame_buffer(&buffer, None, cx);
|
||||
Some((id, snapshot, buffer_edits, blame_buffer))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let provider_registry = GitHostingProviderRegistry::default_global(cx);
|
||||
|
||||
self.task = cx.spawn(async move |this, cx| {
|
||||
let result = cx
|
||||
let (result, errors) = cx
|
||||
.background_spawn({
|
||||
let snapshot = snapshot.clone();
|
||||
async move {
|
||||
let Some(Blame {
|
||||
entries,
|
||||
messages,
|
||||
remote_url,
|
||||
}) = blame.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let mut res = vec![];
|
||||
let mut errors = vec![];
|
||||
for (id, snapshot, buffer_edits, blame) in blame {
|
||||
match blame.await {
|
||||
Ok(Some(Blame {
|
||||
entries,
|
||||
messages,
|
||||
remote_url,
|
||||
})) => {
|
||||
let entries = build_blame_entry_sum_tree(
|
||||
entries,
|
||||
snapshot.max_point().row,
|
||||
);
|
||||
let commit_details = parse_commit_messages(
|
||||
messages,
|
||||
remote_url,
|
||||
provider_registry.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let entries = build_blame_entry_sum_tree(entries, snapshot.max_point().row);
|
||||
let commit_details =
|
||||
parse_commit_messages(messages, remote_url, provider_registry).await;
|
||||
|
||||
anyhow::Ok(Some((entries, commit_details)))
|
||||
res.push((
|
||||
id,
|
||||
snapshot,
|
||||
buffer_edits,
|
||||
Some(entries),
|
||||
commit_details,
|
||||
));
|
||||
}
|
||||
Ok(None) => {
|
||||
res.push((id, snapshot, buffer_edits, None, Default::default()))
|
||||
}
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
}
|
||||
(res, errors)
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(cx, |this, cx| match result {
|
||||
Ok(None) => {
|
||||
// Nothing to do, e.g. no repository found
|
||||
this.update(cx, |this, cx| {
|
||||
this.buffers.clear();
|
||||
for (id, snapshot, buffer_edits, entries, commit_details) in result {
|
||||
let Some(entries) = entries else {
|
||||
continue;
|
||||
};
|
||||
this.buffers.insert(
|
||||
id,
|
||||
GitBlameBuffer {
|
||||
buffer_edits,
|
||||
buffer_snapshot: snapshot,
|
||||
entries,
|
||||
commit_details,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(Some((entries, commit_details))) => {
|
||||
this.buffer_edits = buffer_edits;
|
||||
this.buffer_snapshot = snapshot;
|
||||
this.entries = entries;
|
||||
this.commit_details = commit_details;
|
||||
this.generated = true;
|
||||
cx.notify();
|
||||
cx.notify();
|
||||
if !errors.is_empty() {
|
||||
this.project.update(cx, |_, cx| {
|
||||
if this.user_triggered {
|
||||
log::error!("failed to get git blame data: {errors:?}");
|
||||
let notification = errors
|
||||
.into_iter()
|
||||
.format_with(",", |e, f| f(&format_args!("{:#}", e)))
|
||||
.to_string();
|
||||
cx.emit(project::Event::Toast {
|
||||
notification_id: "git-blame".into(),
|
||||
message: notification,
|
||||
});
|
||||
} else {
|
||||
// If we weren't triggered by a user, we just log errors in the background, instead of sending
|
||||
// notifications.
|
||||
log::debug!("failed to get git blame data: {errors:?}");
|
||||
}
|
||||
})
|
||||
}
|
||||
Err(error) => this.project.update(cx, |_, cx| {
|
||||
if this.user_triggered {
|
||||
log::error!("failed to get git blame data: {error:?}");
|
||||
let notification = format!("{:#}", error).trim().to_string();
|
||||
cx.emit(project::Event::Toast {
|
||||
notification_id: "git-blame".into(),
|
||||
message: notification,
|
||||
});
|
||||
} else {
|
||||
// If we weren't triggered by a user, we just log errors in the background, instead of sending
|
||||
// notifications.
|
||||
log::debug!("failed to get git blame data: {error:?}");
|
||||
}
|
||||
}),
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -520,7 +605,7 @@ impl GitBlame {
|
||||
this.update(cx, |this, cx| {
|
||||
this.generate(cx);
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,6 +744,9 @@ mod tests {
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
.into_iter()
|
||||
.map(|it| Some((buffer_id, it?)))
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -705,6 +793,7 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let blame = cx.new(|cx| GitBlame::new(buffer.clone(), project.clone(), true, true, cx));
|
||||
|
||||
@@ -785,6 +874,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id());
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let git_blame = cx.new(|cx| GitBlame::new(buffer.clone(), project, false, true, cx));
|
||||
|
||||
@@ -806,14 +896,14 @@ mod tests {
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Some(blame_entry("1b1b1b", 0..1)),
|
||||
Some(blame_entry("0d0d0d", 1..2)),
|
||||
Some(blame_entry("3a3a3a", 2..3)),
|
||||
Some((buffer_id, blame_entry("1b1b1b", 0..1))),
|
||||
Some((buffer_id, blame_entry("0d0d0d", 1..2))),
|
||||
Some((buffer_id, blame_entry("3a3a3a", 2..3))),
|
||||
None,
|
||||
None,
|
||||
Some(blame_entry("3a3a3a", 5..6)),
|
||||
Some(blame_entry("0d0d0d", 6..7)),
|
||||
Some(blame_entry("3a3a3a", 7..8)),
|
||||
Some((buffer_id, blame_entry("3a3a3a", 5..6))),
|
||||
Some((buffer_id, blame_entry("0d0d0d", 6..7))),
|
||||
Some((buffer_id, blame_entry("3a3a3a", 7..8))),
|
||||
]
|
||||
);
|
||||
// Subset of lines
|
||||
@@ -831,8 +921,8 @@ mod tests {
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Some(blame_entry("0d0d0d", 1..2)),
|
||||
Some(blame_entry("3a3a3a", 2..3)),
|
||||
Some((buffer_id, blame_entry("0d0d0d", 1..2))),
|
||||
Some((buffer_id, blame_entry("3a3a3a", 2..3))),
|
||||
None
|
||||
]
|
||||
);
|
||||
@@ -852,7 +942,7 @@ mod tests {
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Some(blame_entry("0d0d0d", 1..2)), None, None]
|
||||
vec![Some((buffer_id, blame_entry("0d0d0d", 1..2))), None, None]
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -895,6 +985,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id());
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let git_blame = cx.new(|cx| GitBlame::new(buffer.clone(), project, false, true, cx));
|
||||
|
||||
@@ -1061,8 +1152,9 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let mbuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
|
||||
|
||||
let git_blame = cx.new(|cx| GitBlame::new(buffer.clone(), project, false, true, cx));
|
||||
let git_blame = cx.new(|cx| GitBlame::new(mbuffer.clone(), project, false, true, cx));
|
||||
cx.executor().run_until_parked();
|
||||
git_blame.update(cx, |blame, cx| blame.check_invariants(cx));
|
||||
|
||||
|
||||
@@ -188,22 +188,26 @@ impl Editor {
|
||||
|
||||
pub fn scroll_hover(
|
||||
&mut self,
|
||||
amount: &ScrollAmount,
|
||||
amount: ScrollAmount,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
let selection = self.selections.newest_anchor().head();
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
|
||||
let Some(popover) = self.hover_state.info_popovers.iter().find(|popover| {
|
||||
if let Some(popover) = self.hover_state.info_popovers.iter().find(|popover| {
|
||||
popover
|
||||
.symbol_range
|
||||
.point_within_range(&TriggerPoint::Text(selection), &snapshot)
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
popover.scroll(amount, window, cx);
|
||||
true
|
||||
}) {
|
||||
popover.scroll(amount, window, cx);
|
||||
true
|
||||
} else if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
|
||||
context_menu.scroll_aside(amount, window, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_click_reveal_task(
|
||||
|
||||
@@ -896,7 +896,7 @@ impl InfoPopover {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
pub fn scroll(&self, amount: &ScrollAmount, window: &mut Window, cx: &mut Context<Editor>) {
|
||||
pub fn scroll(&self, amount: ScrollAmount, window: &mut Window, cx: &mut Context<Editor>) {
|
||||
let mut current = self.scroll_handle.offset();
|
||||
current.y -= amount.pixels(
|
||||
window.line_height(),
|
||||
|
||||
@@ -1339,7 +1339,7 @@ pub mod tests {
|
||||
let i = task_lsp_request_count.fetch_add(1, Ordering::Release) + 1;
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: lsp::Position::new(0, i),
|
||||
@@ -1449,7 +1449,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
let current_call_id =
|
||||
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
|
||||
@@ -1594,7 +1594,7 @@ pub mod tests {
|
||||
"Rust" => {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs"))
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs"))
|
||||
.unwrap(),
|
||||
);
|
||||
rs_lsp_request_count.fetch_add(1, Ordering::Release)
|
||||
@@ -1603,7 +1603,7 @@ pub mod tests {
|
||||
"Markdown" => {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/other.md"))
|
||||
lsp::Uri::from_file_path(path!("/a/other.md"))
|
||||
.unwrap(),
|
||||
);
|
||||
md_lsp_request_count.fetch_add(1, Ordering::Release)
|
||||
@@ -1789,7 +1789,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
Ok(Some(vec![
|
||||
lsp::InlayHint {
|
||||
@@ -2127,7 +2127,7 @@ pub mod tests {
|
||||
let i = lsp_request_count.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: lsp::Position::new(0, i),
|
||||
@@ -2290,7 +2290,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
|
||||
task_lsp_request_ranges.lock().push(params.range);
|
||||
@@ -2633,11 +2633,11 @@ pub mod tests {
|
||||
let task_editor_edited = Arc::clone(&closure_editor_edited);
|
||||
async move {
|
||||
let hint_text = if params.text_document.uri
|
||||
== lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
|
||||
== lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
|
||||
{
|
||||
"main hint"
|
||||
} else if params.text_document.uri
|
||||
== lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
|
||||
== lsp::Uri::from_file_path(path!("/a/other.rs")).unwrap()
|
||||
{
|
||||
"other hint"
|
||||
} else {
|
||||
@@ -2944,11 +2944,11 @@ pub mod tests {
|
||||
let task_editor_edited = Arc::clone(&closure_editor_edited);
|
||||
async move {
|
||||
let hint_text = if params.text_document.uri
|
||||
== lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
|
||||
== lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
|
||||
{
|
||||
"main hint"
|
||||
} else if params.text_document.uri
|
||||
== lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
|
||||
== lsp::Uri::from_file_path(path!("/a/other.rs")).unwrap()
|
||||
{
|
||||
"other hint"
|
||||
} else {
|
||||
@@ -3116,7 +3116,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
let query_start = params.range.start;
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
@@ -3188,7 +3188,7 @@ pub mod tests {
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(file_with_hints).unwrap(),
|
||||
lsp::Uri::from_file_path(file_with_hints).unwrap(),
|
||||
);
|
||||
|
||||
let i = lsp_request_count.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
@@ -3351,7 +3351,7 @@ pub mod tests {
|
||||
move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
Ok(Some(
|
||||
serde_json::from_value(json!([
|
||||
|
||||
@@ -289,12 +289,114 @@ pub fn previous_word_start_or_newline(map: &DisplaySnapshot, point: DisplayPoint
|
||||
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
|
||||
|
||||
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
|
||||
(classifier.kind(left) != classifier.kind(right) && !right.is_whitespace())
|
||||
(classifier.kind(left) != classifier.kind(right) && !classifier.is_whitespace(right))
|
||||
|| left == '\n'
|
||||
|| right == '\n'
|
||||
})
|
||||
}
|
||||
|
||||
/// Text movements are too greedy, making deletions too greedy too.
|
||||
/// Makes deletions more ergonomic by potentially reducing the deletion range based on its text contents:
|
||||
/// * whitespace sequences with length >= 2 stop the deletion after removal (despite movement jumping over the word behind the whitespaces)
|
||||
/// * brackets stop the deletion after removal (despite movement currently not accounting for these and jumping over)
|
||||
pub fn adjust_greedy_deletion(
|
||||
map: &DisplaySnapshot,
|
||||
delete_from: DisplayPoint,
|
||||
delete_until: DisplayPoint,
|
||||
ignore_brackets: bool,
|
||||
) -> DisplayPoint {
|
||||
if delete_from == delete_until {
|
||||
return delete_until;
|
||||
}
|
||||
let is_backward = delete_from > delete_until;
|
||||
let delete_range = if is_backward {
|
||||
map.display_point_to_point(delete_until, Bias::Left)
|
||||
.to_offset(&map.buffer_snapshot)
|
||||
..map
|
||||
.display_point_to_point(delete_from, Bias::Right)
|
||||
.to_offset(&map.buffer_snapshot)
|
||||
} else {
|
||||
map.display_point_to_point(delete_from, Bias::Left)
|
||||
.to_offset(&map.buffer_snapshot)
|
||||
..map
|
||||
.display_point_to_point(delete_until, Bias::Right)
|
||||
.to_offset(&map.buffer_snapshot)
|
||||
};
|
||||
|
||||
let trimmed_delete_range = if ignore_brackets {
|
||||
delete_range
|
||||
} else {
|
||||
let brackets_in_delete_range = map
|
||||
.buffer_snapshot
|
||||
.bracket_ranges(delete_range.clone())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flat_map(|(left_bracket, right_bracket)| {
|
||||
[
|
||||
left_bracket.start,
|
||||
left_bracket.end,
|
||||
right_bracket.start,
|
||||
right_bracket.end,
|
||||
]
|
||||
})
|
||||
.filter(|&bracket| delete_range.start < bracket && bracket < delete_range.end);
|
||||
let closest_bracket = if is_backward {
|
||||
brackets_in_delete_range.max()
|
||||
} else {
|
||||
brackets_in_delete_range.min()
|
||||
};
|
||||
|
||||
if is_backward {
|
||||
closest_bracket.unwrap_or(delete_range.start)..delete_range.end
|
||||
} else {
|
||||
delete_range.start..closest_bracket.unwrap_or(delete_range.end)
|
||||
}
|
||||
};
|
||||
|
||||
let mut whitespace_sequences = Vec::new();
|
||||
let mut current_offset = trimmed_delete_range.start;
|
||||
let mut whitespace_sequence_length = 0;
|
||||
let mut whitespace_sequence_start = 0;
|
||||
for ch in map
|
||||
.buffer_snapshot
|
||||
.text_for_range(trimmed_delete_range.clone())
|
||||
.flat_map(str::chars)
|
||||
{
|
||||
if ch.is_whitespace() {
|
||||
if whitespace_sequence_length == 0 {
|
||||
whitespace_sequence_start = current_offset;
|
||||
}
|
||||
whitespace_sequence_length += 1;
|
||||
} else {
|
||||
if whitespace_sequence_length >= 2 {
|
||||
whitespace_sequences.push((whitespace_sequence_start, current_offset));
|
||||
}
|
||||
whitespace_sequence_start = 0;
|
||||
whitespace_sequence_length = 0;
|
||||
}
|
||||
current_offset += ch.len_utf8();
|
||||
}
|
||||
if whitespace_sequence_length >= 2 {
|
||||
whitespace_sequences.push((whitespace_sequence_start, current_offset));
|
||||
}
|
||||
|
||||
let closest_whitespace_end = if is_backward {
|
||||
whitespace_sequences.last().map(|&(start, _)| start)
|
||||
} else {
|
||||
whitespace_sequences.first().map(|&(_, end)| end)
|
||||
};
|
||||
|
||||
closest_whitespace_end
|
||||
.unwrap_or_else(|| {
|
||||
if is_backward {
|
||||
trimmed_delete_range.start
|
||||
} else {
|
||||
trimmed_delete_range.end
|
||||
}
|
||||
})
|
||||
.to_display_point(map)
|
||||
}
|
||||
|
||||
/// Returns a position of the previous subword boundary, where a subword is defined as a run of
|
||||
/// word characters of the same "subkind" - where subcharacter kinds are '_' character,
|
||||
/// lowerspace characters and uppercase characters.
|
||||
|
||||
@@ -15,7 +15,7 @@ impl ScrollDirection {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
|
||||
pub enum ScrollAmount {
|
||||
// Scroll N lines (positive is towards the end of the document)
|
||||
Line(f32),
|
||||
|
||||
@@ -29,7 +29,7 @@ pub struct EditorLspTestContext {
|
||||
pub cx: EditorTestContext,
|
||||
pub lsp: lsp::FakeLanguageServer,
|
||||
pub workspace: Entity<Workspace>,
|
||||
pub buffer_lsp_url: lsp::Url,
|
||||
pub buffer_lsp_url: lsp::Uri,
|
||||
}
|
||||
|
||||
pub(crate) fn rust_lang() -> Arc<Language> {
|
||||
@@ -189,7 +189,7 @@ impl EditorLspTestContext {
|
||||
},
|
||||
lsp,
|
||||
workspace,
|
||||
buffer_lsp_url: lsp::Url::from_file_path(root.join("dir").join(file_name)).unwrap(),
|
||||
buffer_lsp_url: lsp::Uri::from_file_path(root.join("dir").join(file_name)).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ impl EditorLspTestContext {
|
||||
where
|
||||
T: 'static + request::Request,
|
||||
T::Params: 'static + Send,
|
||||
F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncApp) -> Fut,
|
||||
F: 'static + Send + FnMut(lsp::Uri, T::Params, gpui::AsyncApp) -> Fut,
|
||||
Fut: 'static + Future<Output = Result<T::Result>>,
|
||||
{
|
||||
let url = self.buffer_lsp_url.clone();
|
||||
|
||||
@@ -43,7 +43,7 @@ use language::{
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
use release_channel::ReleaseChannel;
|
||||
use remote::RemoteClient;
|
||||
use remote::{RemoteClient, RemoteConnectionOptions};
|
||||
use semantic_version::SemanticVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
@@ -117,7 +117,7 @@ pub struct ExtensionStore {
|
||||
pub wasm_host: Arc<WasmHost>,
|
||||
pub wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
pub tasks: Vec<Task<()>>,
|
||||
pub remote_clients: HashMap<String, WeakEntity<RemoteClient>>,
|
||||
pub remote_clients: HashMap<RemoteConnectionOptions, WeakEntity<RemoteClient>>,
|
||||
pub ssh_registered_tx: UnboundedSender<()>,
|
||||
}
|
||||
|
||||
@@ -1779,16 +1779,15 @@ impl ExtensionStore {
|
||||
}
|
||||
|
||||
pub fn register_remote_client(&mut self, client: Entity<RemoteClient>, cx: &mut Context<Self>) {
|
||||
let connection_options = client.read(cx).connection_options();
|
||||
let ssh_url = connection_options.ssh_url();
|
||||
let options = client.read(cx).connection_options();
|
||||
|
||||
if let Some(existing_client) = self.remote_clients.get(&ssh_url)
|
||||
if let Some(existing_client) = self.remote_clients.get(&options)
|
||||
&& existing_client.upgrade().is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.remote_clients.insert(ssh_url, client.downgrade());
|
||||
self.remote_clients.insert(options, client.downgrade());
|
||||
self.ssh_registered_tx.unbounded_send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ use collections::HashMap;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
|
||||
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi)]
|
||||
pub struct ExtensionSettings {
|
||||
/// The extensions that should be automatically installed by Zed.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, SettingsUi};
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FileFinderSettings {
|
||||
@@ -11,7 +11,7 @@ pub struct FileFinderSettings {
|
||||
pub include_ignored: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
|
||||
pub struct FileFinderSettingsContent {
|
||||
/// Whether to show file icons in the file finder.
|
||||
///
|
||||
|
||||
@@ -495,7 +495,8 @@ impl Fs for RealFs {
|
||||
};
|
||||
// todo(windows)
|
||||
// When new version of `windows-rs` release, make this operation `async`
|
||||
let path = SanitizedPath::from(path.canonicalize()?);
|
||||
let path = path.canonicalize()?;
|
||||
let path = SanitizedPath::new(&path);
|
||||
let path_string = path.to_string();
|
||||
let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path_string))?.get()?;
|
||||
file.DeleteAsync(StorageDeleteOption::Default)?.get()?;
|
||||
@@ -522,7 +523,8 @@ impl Fs for RealFs {
|
||||
|
||||
// todo(windows)
|
||||
// When new version of `windows-rs` release, make this operation `async`
|
||||
let path = SanitizedPath::from(path.canonicalize()?);
|
||||
let path = path.canonicalize()?;
|
||||
let path = SanitizedPath::new(&path);
|
||||
let path_string = path.to_string();
|
||||
let folder = StorageFolder::GetFolderFromPathAsync(&HSTRING::from(path_string))?.get()?;
|
||||
folder.DeleteAsync(StorageDeleteOption::Default)?.get()?;
|
||||
@@ -783,7 +785,7 @@ impl Fs for RealFs {
|
||||
{
|
||||
target = parent.join(target);
|
||||
if let Ok(canonical) = self.canonicalize(&target).await {
|
||||
target = SanitizedPath::from(canonical).as_path().to_path_buf();
|
||||
target = SanitizedPath::new(&canonical).as_path().to_path_buf();
|
||||
}
|
||||
}
|
||||
watcher.add(&target).ok();
|
||||
|
||||
@@ -42,7 +42,7 @@ impl Drop for FsWatcher {
|
||||
|
||||
impl Watcher for FsWatcher {
|
||||
fn add(&self, path: &std::path::Path) -> anyhow::Result<()> {
|
||||
let root_path = SanitizedPath::from(path);
|
||||
let root_path = SanitizedPath::new_arc(path);
|
||||
|
||||
let tx = self.tx.clone();
|
||||
let pending_paths = self.pending_path_events.clone();
|
||||
@@ -70,7 +70,7 @@ impl Watcher for FsWatcher {
|
||||
.paths
|
||||
.iter()
|
||||
.filter_map(|event_path| {
|
||||
let event_path = SanitizedPath::from(event_path);
|
||||
let event_path = SanitizedPath::new(event_path);
|
||||
event_path.starts_with(&root_path).then(|| PathEvent {
|
||||
path: event_path.as_path().to_path_buf(),
|
||||
kind,
|
||||
|
||||
@@ -5,7 +5,7 @@ use git::GitHostingProviderRegistry;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use settings::{Settings, SettingsStore, SettingsUi};
|
||||
use url::Url;
|
||||
use util::ResultExt as _;
|
||||
|
||||
@@ -78,7 +78,7 @@ pub struct GitHostingProviderConfig {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, SettingsUi)]
|
||||
pub struct GitHostingProviderSettings {
|
||||
/// The list of custom Git hosting providers.
|
||||
#[serde(default)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects};
|
||||
use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects, multibuffer_context_lines};
|
||||
use git::repository::{CommitDetails, CommitDiff, CommitSummary, RepoPath};
|
||||
use gpui::{
|
||||
AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter,
|
||||
@@ -195,7 +195,7 @@ impl CommitView {
|
||||
PathKey::namespaced(FILE_NAMESPACE, path),
|
||||
buffer,
|
||||
diff_hunk_ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
multibuffer_context_lines(cx),
|
||||
cx,
|
||||
);
|
||||
multibuffer.add_diff(buffer_diff, cx);
|
||||
|
||||
@@ -31,11 +31,11 @@ use git::{
|
||||
UnstageAll,
|
||||
};
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, Axis, ClickEvent, Corner,
|
||||
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext,
|
||||
ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, MouseDownEvent, Point,
|
||||
PromptLevel, ScrollStrategy, Subscription, Task, Transformation, UniformListScrollHandle,
|
||||
WeakEntity, actions, anchored, deferred, percentage, uniform_list,
|
||||
Action, AsyncApp, AsyncWindowContext, Axis, ClickEvent, Corner, DismissEvent, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, KeyContext, ListHorizontalSizingBehavior,
|
||||
ListSizingBehavior, MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy,
|
||||
Subscription, Task, UniformListScrollHandle, WeakEntity, actions, anchored, deferred,
|
||||
uniform_list,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, File};
|
||||
@@ -63,8 +63,8 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize};
|
||||
use strum::{IntoEnumIterator, VariantNames};
|
||||
use time::OffsetDateTime;
|
||||
use ui::{
|
||||
Checkbox, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu, Scrollbar,
|
||||
ScrollbarState, SplitButton, Tooltip, prelude::*,
|
||||
Checkbox, CommonAnimationExt, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize,
|
||||
PopoverMenu, Scrollbar, ScrollbarState, SplitButton, Tooltip, prelude::*,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt, maybe};
|
||||
use workspace::SERIALIZATION_THROTTLE_TIME;
|
||||
@@ -3088,13 +3088,7 @@ impl GitPanel {
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
),
|
||||
.with_rotate_animation(2),
|
||||
)
|
||||
.child(
|
||||
Label::new("Generating Commit...")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user