Compare commits
1 Commits
thread3
...
debug-shel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7eb12d540 |
4
.github/workflows/ci.yml
vendored
@@ -460,10 +460,8 @@ jobs:
|
||||
RET_CODE=0
|
||||
# Always check style
|
||||
[[ "${{ needs.style.result }}" != 'success' ]] && { RET_CODE=1; echo "style tests failed"; }
|
||||
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
|
||||
|
||||
if [[ "${{ needs.job_spec.outputs.run_docs }}" == "true" ]]; then
|
||||
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
|
||||
fi
|
||||
# Only check test jobs if they were supposed to run
|
||||
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
|
||||
[[ "${{ needs.workspace_hack.result }}" != 'success' ]] && { RET_CODE=1; echo "Workspace Hack failed"; }
|
||||
|
||||
12
.github/workflows/eval.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
types: [synchronize, reopened, labeled]
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -25,6 +25,16 @@ env:
|
||||
ZED_EVAL_TELEMETRY: 1
|
||||
|
||||
jobs:
|
||||
# This is a no-op job that we run to prevent GitHub from marking the workflow
|
||||
# as failed for PRs that don't have the `run-eval` label.
|
||||
noop:
|
||||
name: No-op
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- name: No-op
|
||||
run: echo "Nothing to do"
|
||||
|
||||
run_eval:
|
||||
timeout-minutes: 60
|
||||
name: Run Agent Eval
|
||||
|
||||
38
Cargo.lock
generated
@@ -78,8 +78,6 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"log",
|
||||
"markdown",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
@@ -4157,7 +4155,6 @@ dependencies = [
|
||||
"paths",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"task",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
@@ -4311,7 +4308,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
"bitflags 2.9.0",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
@@ -9018,7 +9014,6 @@ dependencies = [
|
||||
"collections",
|
||||
"copilot",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
@@ -9242,7 +9237,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libwebrtc"
|
||||
version = "0.3.10"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"jni",
|
||||
@@ -9322,7 +9317,7 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||
[[package]]
|
||||
name = "livekit"
|
||||
version = "0.7.8"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
@@ -9345,7 +9340,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-api"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
@@ -9369,7 +9364,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-protocol"
|
||||
version = "0.3.9"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"livekit-runtime",
|
||||
@@ -9386,7 +9381,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "livekit-runtime"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -14556,12 +14551,12 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"smallvec",
|
||||
"streaming-iterator",
|
||||
"tree-sitter",
|
||||
"tree-sitter-json",
|
||||
"unindent",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
"zlog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15528,18 +15523,6 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
|
||||
|
||||
[[package]]
|
||||
name = "svg_preview"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"editor",
|
||||
"file_icons",
|
||||
"gpui",
|
||||
"ui",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.15.3"
|
||||
@@ -16051,6 +16034,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"log",
|
||||
"palette",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
@@ -16881,7 +16865,8 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tree-sitter-python"
|
||||
version = "0.23.6"
|
||||
source = "git+https://github.com/zed-industries/tree-sitter-python?rev=218fcbf3fda3d029225f3dec005cb497d111b35e#218fcbf3fda3d029225f3dec005cb497d111b35e"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
@@ -18297,7 +18282,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "webrtc-sys"
|
||||
version = "0.3.7"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxx",
|
||||
@@ -18310,7 +18295,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "webrtc-sys-build"
|
||||
version = "0.3.6"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4#d2eade7a6b15d6dbdb38ba12a1ff7bf07fcebba4"
|
||||
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=80bb8f4c9112789f7c24cc98d8423010977806a6#80bb8f4c9112789f7c24cc98d8423010977806a6"
|
||||
dependencies = [
|
||||
"fs2",
|
||||
"regex",
|
||||
@@ -20035,7 +20020,6 @@ dependencies = [
|
||||
"snippet_provider",
|
||||
"snippets_ui",
|
||||
"supermaven",
|
||||
"svg_preview",
|
||||
"sysinfo",
|
||||
"tab_switcher",
|
||||
"task",
|
||||
|
||||
@@ -95,7 +95,6 @@ members = [
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/svg_preview",
|
||||
"crates/migrator",
|
||||
"crates/mistral",
|
||||
"crates/multi_buffer",
|
||||
@@ -305,7 +304,6 @@ lmstudio = { path = "crates/lmstudio" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
markdown = { path = "crates/markdown" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
svg_preview = { path = "crates/svg_preview" }
|
||||
media = { path = "crates/media" }
|
||||
menu = { path = "crates/menu" }
|
||||
migrator = { path = "crates/migrator" }
|
||||
@@ -597,7 +595,7 @@ tree-sitter-html = "0.23"
|
||||
tree-sitter-jsdoc = "0.23"
|
||||
tree-sitter-json = "0.24"
|
||||
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||
tree-sitter-python = { git = "https://github.com/zed-industries/tree-sitter-python", rev = "218fcbf3fda3d029225f3dec005cb497d111b35e" }
|
||||
tree-sitter-python = "0.23"
|
||||
tree-sitter-regex = "0.24"
|
||||
tree-sitter-ruby = "0.23"
|
||||
tree-sitter-rust = "0.24"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.88-bookworm as builder
|
||||
FROM rust:1.87-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down10-icon lucide-arrow-down-1-0"><path d="m3 16 4 4 4-4"/><path d="M7 20V4"/><path d="M17 10V4h-2"/><path d="M15 10h4"/><rect x="15" y="14" width="4" height="6" ry="2"/></svg>
|
||||
|
Before Width: | Height: | Size: 386 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.75776 5.50003H8.49988C8.70769 5.50003 8.89518 5.62971 8.95455 5.82346C9.04049 6.01876 8.9858 6.23906 8.82956 6.37656L4.82971 9.87643C4.65315 10.0295 4.39488 10.042 4.20614 9.90455C4.01724 9.76705 3.94849 9.51706 4.04052 9.30301L5.24219 6.49999H3.48601C3.2918 6.49999 3.10524 6.37031 3.03197 6.17657C2.9587 5.98126 3.014 5.76096 3.1708 5.62346L7.17018 2.12375C7.34674 1.97001 7.60454 1.95829 7.7936 2.09547C7.98265 2.23275 8.0514 2.48218 7.95922 2.69695L6.75776 5.50003Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 601 B |
@@ -1,12 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 3L7 4" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 4L10 3" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.002 6V5.51658C5.98992 5.32067 6.03266 5.12502 6.12762 4.94143C6.22259 4.75784 6.36781 4.59012 6.55453 4.44839C6.74125 4.30666 6.9656 4.19386 7.21403 4.1168C7.46246 4.03973 7.72983 4 8 4C8.27017 4 8.53754 4.03973 8.78597 4.1168C9.0344 4.19386 9.25875 4.30666 9.44547 4.44839C9.63219 4.59012 9.77741 4.75784 9.87238 4.94143C9.96734 5.12502 10.0101 5.32067 9.998 5.51658V6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 13C6.35 13 5 11.5462 5 9.76923V8.15385C5 7.58261 5.21071 7.03477 5.58579 6.63085C5.96086 6.22692 6.46957 6 7 6H9C9.53043 6 10.0391 6.22692 10.4142 6.63085C10.7893 7.03477 11 7.58261 11 8.15385V9.76923C11 11.5462 9.65 13 8 13Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 6.16663C3.90652 6.06663 3 5.21663 3 4.16663" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 9H3" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 13C3 11.95 3.89474 11.05 5 11" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 4C13 5.05 12.0857 5.9 11 6" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 9H11" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 11C12.1053 11.05 13 11.95 13 13" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.84265 10.7778C4.39206 11.6001 5.17295 12.241 6.08658 12.6194C7.00021 12.9978 8.00555 13.0969 8.97545 12.9039C9.94535 12.711 10.8363 12.2348 11.5355 11.5355C12.2348 10.8363 12.711 9.94535 12.9039 8.97545C13.0969 8.00555 12.9978 7.00021 12.6194 6.08658C12.241 5.17295 11.6001 4.39206 10.7778 3.84265C9.9556 3.29324 8.9889 3 8 3C6.60219 3.00526 5.26054 3.55068 4.25556 4.52222L3 5.77778" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 3V6H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 685 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 5L11 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 409 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scroll-text-icon lucide-scroll-text"><path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/></svg>
|
||||
|
Before Width: | Height: | Size: 441 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-split-icon lucide-split"><path d="M16 3h5v5"/><path d="M8 3H3v5"/><path d="M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3"/><path d="m15 9 6-6"/></svg>
|
||||
|
Before Width: | Height: | Size: 345 B |
@@ -244,7 +244,6 @@
|
||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-enter": "agent::ContinueThread",
|
||||
"super-ctrl-b": "agent::ToggleBurnMode",
|
||||
"alt-enter": "agent::ContinueWithBurnMode"
|
||||
}
|
||||
},
|
||||
@@ -491,27 +490,13 @@
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
"ctrl-\\": "pane::SplitRight",
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||
"alt-.": "editor::GoToHunk",
|
||||
"alt-,": "editor::GoToPreviousHunk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == md",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == svg",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-k v": "svg::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "svg::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
@@ -919,9 +904,7 @@
|
||||
"context": "BreakpointList",
|
||||
"bindings": {
|
||||
"space": "debugger::ToggleEnableBreakpoint",
|
||||
"backspace": "debugger::UnsetBreakpoint",
|
||||
"left": "debugger::PreviousBreakpointProperty",
|
||||
"right": "debugger::NextBreakpointProperty"
|
||||
"backspace": "debugger::UnsetBreakpoint"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -283,7 +283,6 @@
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-alt-e": "agent::RemoveAllContext",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-ctrl-b": "agent::ToggleBurnMode",
|
||||
"cmd-shift-enter": "agent::ContinueThread",
|
||||
"alt-enter": "agent::ContinueWithBurnMode"
|
||||
}
|
||||
@@ -545,23 +544,9 @@
|
||||
"cmd-k r": "editor::RevealInFileManager",
|
||||
"cmd-k p": "editor::CopyPath",
|
||||
"cmd-\\": "pane::SplitRight",
|
||||
"ctrl-cmd-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == md",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k v": "markdown::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "markdown::OpenPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == svg",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-k v": "svg::OpenPreviewToTheSide",
|
||||
"cmd-shift-v": "svg::OpenPreview"
|
||||
"cmd-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-cmd-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -602,7 +587,6 @@
|
||||
"alt-cmd-o": ["projects::OpenRecent", { "create_new_window": false }],
|
||||
"ctrl-cmd-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }],
|
||||
"ctrl-cmd-shift-o": ["projects::OpenRemote", { "from_existing_connection": true, "create_new_window": false }],
|
||||
"cmd-ctrl-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"cmd-s": "workspace::Save",
|
||||
"cmd-k s": "workspace::SaveWithoutFormat",
|
||||
@@ -620,7 +604,6 @@
|
||||
"cmd-8": ["workspace::ActivatePane", 7],
|
||||
"cmd-9": ["workspace::ActivatePane", 8],
|
||||
"cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-alt-b": "workspace::ToggleRightDock",
|
||||
"cmd-r": "workspace::ToggleRightDock",
|
||||
"cmd-j": "workspace::ToggleBottomDock",
|
||||
"alt-cmd-y": "workspace::CloseAllDocks",
|
||||
@@ -980,9 +963,7 @@
|
||||
"context": "BreakpointList",
|
||||
"bindings": {
|
||||
"space": "debugger::ToggleEnableBreakpoint",
|
||||
"backspace": "debugger::UnsetBreakpoint",
|
||||
"left": "debugger::PreviousBreakpointProperty",
|
||||
"right": "debugger::NextBreakpointProperty"
|
||||
"backspace": "debugger::UnsetBreakpoint"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -59,8 +59,7 @@
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -59,8 +59,7 @@
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines", // join-line
|
||||
"alt-q": "editor::Rewrap" // fill-paragraph
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -42,7 +42,6 @@ itertools.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
log.workspace = true
|
||||
markdown.workspace = true
|
||||
paths.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
@@ -73,7 +72,6 @@ gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod agent2;
|
||||
pub mod agent_profile;
|
||||
pub mod context;
|
||||
pub mod context_server_tool;
|
||||
@@ -6,17 +5,15 @@ pub mod context_store;
|
||||
pub mod history_store;
|
||||
pub mod thread;
|
||||
pub mod thread_store;
|
||||
mod zed_agent;
|
||||
pub mod tool_use;
|
||||
|
||||
pub use agent2::*;
|
||||
pub use context::{AgentContext, ContextId, ContextLoadResult};
|
||||
pub use context_store::ContextStore;
|
||||
pub use thread::{
|
||||
LastRestoreCheckpoint, Message, MessageCrease, MessageSegment, Thread, ThreadError,
|
||||
ThreadEvent, ThreadFeedback, ThreadTitle, TokenUsageRatio,
|
||||
LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
|
||||
ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio,
|
||||
};
|
||||
pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
|
||||
pub use zed_agent::*;
|
||||
|
||||
pub fn init(cx: &mut gpui::App) {
|
||||
thread_store::init(cx);
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use assistant_tool::{Tool, ToolResultOutput};
|
||||
use futures::{channel::oneshot, future::BoxFuture, stream::BoxStream};
|
||||
use gpui::SharedString;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
|
||||
)]
|
||||
pub struct ThreadId(SharedString);
|
||||
|
||||
impl ThreadId {
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ThreadId {
|
||||
fn from(value: &str) -> Self {
|
||||
ThreadId(SharedString::from(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ThreadId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct MessageId(pub usize);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AgentThreadToolCallId(SharedString);
|
||||
|
||||
pub enum AgentThreadResponseEvent {
|
||||
Text(String),
|
||||
Thinking(String),
|
||||
ToolCallChunk {
|
||||
id: AgentThreadToolCallId,
|
||||
tool: Arc<dyn Tool>,
|
||||
input: serde_json::Value,
|
||||
},
|
||||
ToolCall {
|
||||
id: AgentThreadToolCallId,
|
||||
tool: Arc<dyn Tool>,
|
||||
input: serde_json::Value,
|
||||
response_tx: oneshot::Sender<Result<ToolResultOutput>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub enum AgentThreadMessage {
|
||||
User {
|
||||
id: MessageId,
|
||||
chunks: Vec<AgentThreadUserMessageChunk>,
|
||||
},
|
||||
Assistant {
|
||||
id: MessageId,
|
||||
chunks: Vec<AgentThreadAssistantMessageChunk>,
|
||||
},
|
||||
}
|
||||
|
||||
pub enum AgentThreadUserMessageChunk {
|
||||
Text(String),
|
||||
// here's where we would put mentions, etc.
|
||||
}
|
||||
|
||||
pub enum AgentThreadAssistantMessageChunk {
|
||||
Text(String),
|
||||
Thinking(String),
|
||||
ToolResult {
|
||||
id: AgentThreadToolCallId,
|
||||
tool: Arc<dyn Tool>,
|
||||
input: serde_json::Value,
|
||||
output: Result<ToolResultOutput>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct AgentThreadResponse {
|
||||
pub user_message_id: MessageId,
|
||||
pub assistant_message_id: MessageId,
|
||||
pub events: BoxStream<'static, Result<AgentThreadResponseEvent>>,
|
||||
}
|
||||
|
||||
pub trait Agent {
|
||||
fn create_thread() -> BoxFuture<'static, Result<Arc<dyn AgentThread>>>;
|
||||
fn list_threads();
|
||||
fn load_thread(&self, thread_id: ThreadId) -> BoxFuture<'static, Result<Arc<dyn AgentThread>>>;
|
||||
}
|
||||
|
||||
pub trait AgentThread {
|
||||
fn id(&self) -> ThreadId;
|
||||
fn title(&self) -> BoxFuture<'static, Result<String>>;
|
||||
fn summary(&self) -> BoxFuture<'static, Result<String>>;
|
||||
fn messages(&self) -> BoxFuture<'static, Result<Vec<AgentThreadMessage>>>;
|
||||
fn truncate(&self, message_id: MessageId) -> BoxFuture<'static, Result<()>>;
|
||||
fn edit(
|
||||
&self,
|
||||
message_id: MessageId,
|
||||
content: Vec<AgentThreadUserMessageChunk>,
|
||||
max_iterations: usize,
|
||||
) -> BoxFuture<'static, Result<AgentThreadResponse>>;
|
||||
fn send(
|
||||
&self,
|
||||
content: Vec<AgentThreadUserMessageChunk>,
|
||||
max_iterations: usize,
|
||||
) -> BoxFuture<'static, Result<AgentThreadResponse>>;
|
||||
}
|
||||
@@ -581,7 +581,7 @@ impl ThreadContextHandle {
|
||||
}
|
||||
|
||||
pub fn title(&self, cx: &App) -> SharedString {
|
||||
self.thread.read(cx).title().or_default()
|
||||
self.thread.read(cx).summary().or_default()
|
||||
}
|
||||
|
||||
fn load(self, cx: &App) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
|
||||
@@ -589,7 +589,7 @@ impl ThreadContextHandle {
|
||||
let text = Thread::wait_for_detailed_summary_or_text(&self.thread, cx).await?;
|
||||
let title = self
|
||||
.thread
|
||||
.read_with(cx, |thread, _cx| thread.title().or_default())
|
||||
.read_with(cx, |thread, _cx| thread.summary().or_default())
|
||||
.ok()?;
|
||||
let context = AgentContext::Thread(ThreadContext {
|
||||
title,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::{
|
||||
MessageId, ThreadId,
|
||||
context::{
|
||||
AgentContextHandle, AgentContextKey, ContextId, ContextKind, DirectoryContextHandle,
|
||||
FetchedUrlContext, FileContextHandle, ImageContext, RulesContextHandle,
|
||||
SelectionContextHandle, SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
|
||||
},
|
||||
thread::Thread,
|
||||
thread::{MessageId, Thread, ThreadId},
|
||||
thread_store::ThreadStore,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
@@ -72,8 +71,7 @@ impl ContextStore {
|
||||
) -> Vec<AgentContextHandle> {
|
||||
let existing_context = thread
|
||||
.messages()
|
||||
.iter()
|
||||
.take_while(|message| exclude_messages_from_id.is_none_or(|id| message.id() != id))
|
||||
.take_while(|message| exclude_messages_from_id.is_none_or(|id| message.id != id))
|
||||
.flat_map(|message| {
|
||||
message
|
||||
.loaded_context
|
||||
@@ -443,7 +441,7 @@ impl ContextStore {
|
||||
match context {
|
||||
AgentContextHandle::Thread(thread_context) => {
|
||||
self.context_thread_ids
|
||||
.remove(&thread_context.thread.read(cx).id());
|
||||
.remove(thread_context.thread.read(cx).id());
|
||||
}
|
||||
AgentContextHandle::TextThread(text_thread_context) => {
|
||||
if let Some(path) = text_thread_context.context.read(cx).path() {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
MessageId, ThreadId,
|
||||
context_server_tool::ContextServerTool,
|
||||
thread::{DetailedSummaryState, ExceededWindowError, ProjectSnapshot, Thread},
|
||||
thread::{
|
||||
DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
|
||||
},
|
||||
};
|
||||
use agent_settings::{AgentProfileId, CompletionMode};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
@@ -399,17 +400,35 @@ impl ThreadStore {
|
||||
self.threads.iter()
|
||||
}
|
||||
|
||||
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Thread>>> {
|
||||
todo!()
|
||||
// cx.new(|cx| {
|
||||
// Thread::new(
|
||||
// self.project.clone(),
|
||||
// self.tools.clone(),
|
||||
// self.prompt_builder.clone(),
|
||||
// self.project_context.clone(),
|
||||
// cx,
|
||||
// )
|
||||
// })
|
||||
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
|
||||
cx.new(|cx| {
|
||||
Thread::new(
|
||||
self.project.clone(),
|
||||
self.tools.clone(),
|
||||
self.prompt_builder.clone(),
|
||||
self.project_context.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_thread_from_serialized(
|
||||
&mut self,
|
||||
serialized: SerializedThread,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<Thread> {
|
||||
cx.new(|cx| {
|
||||
Thread::deserialize(
|
||||
ThreadId::new(),
|
||||
serialized,
|
||||
self.project.clone(),
|
||||
self.tools.clone(),
|
||||
self.prompt_builder.clone(),
|
||||
self.project_context.clone(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_thread(
|
||||
@@ -428,53 +447,51 @@ impl ThreadStore {
|
||||
.await?
|
||||
.with_context(|| format!("no thread found with ID: {id:?}"))?;
|
||||
|
||||
todo!();
|
||||
// let thread = this.update_in(cx, |this, window, cx| {
|
||||
// cx.new(|cx| {
|
||||
// Thread::deserialize(
|
||||
// id.clone(),
|
||||
// thread,
|
||||
// this.project.clone(),
|
||||
// this.tools.clone(),
|
||||
// this.prompt_builder.clone(),
|
||||
// this.project_context.clone(),
|
||||
// Some(window),
|
||||
// cx,
|
||||
// )
|
||||
// })
|
||||
// })?;
|
||||
// Ok(thread)
|
||||
let thread = this.update_in(cx, |this, window, cx| {
|
||||
cx.new(|cx| {
|
||||
Thread::deserialize(
|
||||
id.clone(),
|
||||
thread,
|
||||
this.project.clone(),
|
||||
this.tools.clone(),
|
||||
this.prompt_builder.clone(),
|
||||
this.project_context.clone(),
|
||||
Some(window),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(thread)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save_thread(&self, thread: &Entity<Thread>, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
todo!()
|
||||
// let (metadata, serialized_thread) =
|
||||
// thread.update(cx, |thread, cx| (thread.id().clone(), thread.serialize(cx)));
|
||||
let (metadata, serialized_thread) =
|
||||
thread.update(cx, |thread, cx| (thread.id().clone(), thread.serialize(cx)));
|
||||
|
||||
// let database_future = ThreadsDatabase::global_future(cx);
|
||||
// cx.spawn(async move |this, cx| {
|
||||
// let serialized_thread = serialized_thread.await?;
|
||||
// let database = database_future.await.map_err(|err| anyhow!(err))?;
|
||||
// database.save_thread(metadata, serialized_thread).await?;
|
||||
let database_future = ThreadsDatabase::global_future(cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let serialized_thread = serialized_thread.await?;
|
||||
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
||||
database.save_thread(metadata, serialized_thread).await?;
|
||||
|
||||
// this.update(cx, |this, cx| this.reload(cx))?.await
|
||||
// })
|
||||
this.update(cx, |this, cx| this.reload(cx))?.await
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
todo!()
|
||||
// let id = id.clone();
|
||||
// let database_future = ThreadsDatabase::global_future(cx);
|
||||
// cx.spawn(async move |this, cx| {
|
||||
// let database = database_future.await.map_err(|err| anyhow!(err))?;
|
||||
// database.delete_thread(id.clone()).await?;
|
||||
let id = id.clone();
|
||||
let database_future = ThreadsDatabase::global_future(cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let database = database_future.await.map_err(|err| anyhow!(err))?;
|
||||
database.delete_thread(id.clone()).await?;
|
||||
|
||||
// this.update(cx, |this, cx| {
|
||||
// this.threads.retain(|thread| thread.id != id);
|
||||
// cx.notify();
|
||||
// })
|
||||
// })
|
||||
this.update(cx, |this, cx| {
|
||||
this.threads.retain(|thread| thread.id != id);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reload(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
@@ -1050,7 +1067,7 @@ impl ThreadsDatabase {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{MessageId, thread::DetailedSummaryState};
|
||||
use crate::thread::{DetailedSummaryState, MessageId};
|
||||
use chrono::Utc;
|
||||
use language_model::{Role, TokenUsage};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
567
crates/agent/src/tool_use.rs
Normal file
@@ -0,0 +1,567 @@
|
||||
use crate::{
|
||||
thread::{MessageId, PromptId, ThreadId},
|
||||
thread_store::SerializedMessage,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_tool::{
|
||||
AnyToolCard, Tool, ToolResultContent, ToolResultOutput, ToolUseStatus, ToolWorkingSet,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use futures::{FutureExt as _, future::Shared};
|
||||
use gpui::{App, Entity, SharedString, Task, Window};
|
||||
use icons::IconName;
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelRequest, LanguageModelToolResult,
|
||||
LanguageModelToolResultContent, LanguageModelToolUse, LanguageModelToolUseId, Role,
|
||||
};
|
||||
use project::Project;
|
||||
use std::sync::Arc;
|
||||
use util::truncate_lines_to_byte_limit;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
pub name: SharedString,
|
||||
pub ui_text: SharedString,
|
||||
pub status: ToolUseStatus,
|
||||
pub input: serde_json::Value,
|
||||
pub icon: icons::IconName,
|
||||
pub needs_confirmation: bool,
|
||||
}
|
||||
|
||||
pub struct ToolUseState {
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
|
||||
tool_results: HashMap<LanguageModelToolUseId, LanguageModelToolResult>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
tool_result_cards: HashMap<LanguageModelToolUseId, AnyToolCard>,
|
||||
tool_use_metadata_by_id: HashMap<LanguageModelToolUseId, ToolUseMetadata>,
|
||||
}
|
||||
|
||||
impl ToolUseState {
|
||||
pub fn new(tools: Entity<ToolWorkingSet>) -> Self {
|
||||
Self {
|
||||
tools,
|
||||
tool_uses_by_assistant_message: HashMap::default(),
|
||||
tool_results: HashMap::default(),
|
||||
pending_tool_uses_by_id: HashMap::default(),
|
||||
tool_result_cards: HashMap::default(),
|
||||
tool_use_metadata_by_id: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a [`ToolUseState`] from the given list of [`SerializedMessage`]s.
|
||||
///
|
||||
/// Accepts a function to filter the tools that should be used to populate the state.
|
||||
///
|
||||
/// If `window` is `None` (e.g., when in headless mode or when running evals),
|
||||
/// tool cards won't be deserialized
|
||||
pub fn from_serialized_messages(
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
messages: &[SerializedMessage],
|
||||
project: Entity<Project>,
|
||||
window: Option<&mut Window>, // None in headless mode
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let mut this = Self::new(tools);
|
||||
let mut tool_names_by_id = HashMap::default();
|
||||
let mut window = window;
|
||||
|
||||
for message in messages {
|
||||
match message.role {
|
||||
Role::Assistant => {
|
||||
if !message.tool_uses.is_empty() {
|
||||
let tool_uses = message
|
||||
.tool_uses
|
||||
.iter()
|
||||
.map(|tool_use| LanguageModelToolUse {
|
||||
id: tool_use.id.clone(),
|
||||
name: tool_use.name.clone().into(),
|
||||
raw_input: tool_use.input.to_string(),
|
||||
input: tool_use.input.clone(),
|
||||
is_input_complete: true,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tool_names_by_id.extend(
|
||||
tool_uses
|
||||
.iter()
|
||||
.map(|tool_use| (tool_use.id.clone(), tool_use.name.clone())),
|
||||
);
|
||||
|
||||
this.tool_uses_by_assistant_message
|
||||
.insert(message.id, tool_uses);
|
||||
|
||||
for tool_result in &message.tool_results {
|
||||
let tool_use_id = tool_result.tool_use_id.clone();
|
||||
let Some(tool_use) = tool_names_by_id.get(&tool_use_id) else {
|
||||
log::warn!("no tool name found for tool use: {tool_use_id:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
this.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name: tool_use.clone(),
|
||||
is_error: tool_result.is_error,
|
||||
content: tool_result.content.clone(),
|
||||
output: tool_result.output.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(window) = &mut window {
|
||||
if let Some(tool) = this.tools.read(cx).tool(tool_use, cx) {
|
||||
if let Some(output) = tool_result.output.clone() {
|
||||
if let Some(card) = tool.deserialize_card(
|
||||
output,
|
||||
project.clone(),
|
||||
window,
|
||||
cx,
|
||||
) {
|
||||
this.tool_result_cards.insert(tool_use_id, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Role::System | Role::User => {}
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn cancel_pending(&mut self) -> Vec<PendingToolUse> {
|
||||
let mut cancelled_tool_uses = Vec::new();
|
||||
self.pending_tool_uses_by_id
|
||||
.retain(|tool_use_id, tool_use| {
|
||||
if matches!(tool_use.status, PendingToolUseStatus::Error { .. }) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let content = "Tool canceled by user".into();
|
||||
self.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name: tool_use.name.clone(),
|
||||
content,
|
||||
output: None,
|
||||
is_error: true,
|
||||
},
|
||||
);
|
||||
cancelled_tool_uses.push(tool_use.clone());
|
||||
false
|
||||
});
|
||||
cancelled_tool_uses
|
||||
}
|
||||
|
||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> {
|
||||
let Some(tool_uses_for_message) = &self.tool_uses_by_assistant_message.get(&id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut tool_uses = Vec::new();
|
||||
|
||||
for tool_use in tool_uses_for_message.iter() {
|
||||
let tool_result = self.tool_results.get(&tool_use.id);
|
||||
|
||||
let status = (|| {
|
||||
if let Some(tool_result) = tool_result {
|
||||
let content = tool_result
|
||||
.content
|
||||
.to_str()
|
||||
.map(|str| str.to_owned().into())
|
||||
.unwrap_or_default();
|
||||
|
||||
return if tool_result.is_error {
|
||||
ToolUseStatus::Error(content)
|
||||
} else {
|
||||
ToolUseStatus::Finished(content)
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(pending_tool_use) = self.pending_tool_uses_by_id.get(&tool_use.id) {
|
||||
match pending_tool_use.status {
|
||||
PendingToolUseStatus::Idle => ToolUseStatus::Pending,
|
||||
PendingToolUseStatus::NeedsConfirmation { .. } => {
|
||||
ToolUseStatus::NeedsConfirmation
|
||||
}
|
||||
PendingToolUseStatus::Running { .. } => ToolUseStatus::Running,
|
||||
PendingToolUseStatus::Error(ref err) => {
|
||||
ToolUseStatus::Error(err.clone().into())
|
||||
}
|
||||
PendingToolUseStatus::InputStillStreaming => {
|
||||
ToolUseStatus::InputStillStreaming
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ToolUseStatus::Pending
|
||||
}
|
||||
})();
|
||||
|
||||
let (icon, needs_confirmation) =
|
||||
if let Some(tool) = self.tools.read(cx).tool(&tool_use.name, cx) {
|
||||
(tool.icon(), tool.needs_confirmation(&tool_use.input, cx))
|
||||
} else {
|
||||
(IconName::Cog, false)
|
||||
};
|
||||
|
||||
tool_uses.push(ToolUse {
|
||||
id: tool_use.id.clone(),
|
||||
name: tool_use.name.clone().into(),
|
||||
ui_text: self.tool_ui_label(
|
||||
&tool_use.name,
|
||||
&tool_use.input,
|
||||
tool_use.is_input_complete,
|
||||
cx,
|
||||
),
|
||||
input: tool_use.input.clone(),
|
||||
status,
|
||||
icon,
|
||||
needs_confirmation,
|
||||
})
|
||||
}
|
||||
|
||||
tool_uses
|
||||
}
|
||||
|
||||
pub fn tool_ui_label(
|
||||
&self,
|
||||
tool_name: &str,
|
||||
input: &serde_json::Value,
|
||||
is_input_complete: bool,
|
||||
cx: &App,
|
||||
) -> SharedString {
|
||||
if let Some(tool) = self.tools.read(cx).tool(tool_name, cx) {
|
||||
if is_input_complete {
|
||||
tool.ui_text(input).into()
|
||||
} else {
|
||||
tool.still_streaming_ui_text(input).into()
|
||||
}
|
||||
} else {
|
||||
format!("Unknown tool {tool_name:?}").into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool_results_for_message(
|
||||
&self,
|
||||
assistant_message_id: MessageId,
|
||||
) -> Vec<&LanguageModelToolResult> {
|
||||
let Some(tool_uses) = self
|
||||
.tool_uses_by_assistant_message
|
||||
.get(&assistant_message_id)
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
tool_uses
|
||||
.iter()
|
||||
.filter_map(|tool_use| self.tool_results.get(&tool_use.id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn message_has_tool_results(&self, assistant_message_id: MessageId) -> bool {
|
||||
self.tool_uses_by_assistant_message
|
||||
.get(&assistant_message_id)
|
||||
.map_or(false, |results| !results.is_empty())
|
||||
}
|
||||
|
||||
pub fn tool_result(
|
||||
&self,
|
||||
tool_use_id: &LanguageModelToolUseId,
|
||||
) -> Option<&LanguageModelToolResult> {
|
||||
self.tool_results.get(tool_use_id)
|
||||
}
|
||||
|
||||
pub fn tool_result_card(&self, tool_use_id: &LanguageModelToolUseId) -> Option<&AnyToolCard> {
|
||||
self.tool_result_cards.get(tool_use_id)
|
||||
}
|
||||
|
||||
pub fn insert_tool_result_card(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
card: AnyToolCard,
|
||||
) {
|
||||
self.tool_result_cards.insert(tool_use_id, card);
|
||||
}
|
||||
|
||||
pub fn request_tool_use(
|
||||
&mut self,
|
||||
assistant_message_id: MessageId,
|
||||
tool_use: LanguageModelToolUse,
|
||||
metadata: ToolUseMetadata,
|
||||
cx: &App,
|
||||
) -> Arc<str> {
|
||||
let tool_uses = self
|
||||
.tool_uses_by_assistant_message
|
||||
.entry(assistant_message_id)
|
||||
.or_default();
|
||||
|
||||
let mut existing_tool_use_found = false;
|
||||
|
||||
for existing_tool_use in tool_uses.iter_mut() {
|
||||
if existing_tool_use.id == tool_use.id {
|
||||
*existing_tool_use = tool_use.clone();
|
||||
existing_tool_use_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !existing_tool_use_found {
|
||||
tool_uses.push(tool_use.clone());
|
||||
}
|
||||
|
||||
let status = if tool_use.is_input_complete {
|
||||
self.tool_use_metadata_by_id
|
||||
.insert(tool_use.id.clone(), metadata);
|
||||
|
||||
PendingToolUseStatus::Idle
|
||||
} else {
|
||||
PendingToolUseStatus::InputStillStreaming
|
||||
};
|
||||
|
||||
let ui_text: Arc<str> = self
|
||||
.tool_ui_label(
|
||||
&tool_use.name,
|
||||
&tool_use.input,
|
||||
tool_use.is_input_complete,
|
||||
cx,
|
||||
)
|
||||
.into();
|
||||
|
||||
let may_perform_edits = self
|
||||
.tools
|
||||
.read(cx)
|
||||
.tool(&tool_use.name, cx)
|
||||
.is_some_and(|tool| tool.may_perform_edits());
|
||||
|
||||
self.pending_tool_uses_by_id.insert(
|
||||
tool_use.id.clone(),
|
||||
PendingToolUse {
|
||||
assistant_message_id,
|
||||
id: tool_use.id,
|
||||
name: tool_use.name.clone(),
|
||||
ui_text: ui_text.clone(),
|
||||
input: tool_use.input,
|
||||
may_perform_edits,
|
||||
status,
|
||||
},
|
||||
);
|
||||
|
||||
ui_text
|
||||
}
|
||||
|
||||
pub fn run_pending_tool(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
ui_text: SharedString,
|
||||
task: Task<()>,
|
||||
) {
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.ui_text = ui_text.into();
|
||||
tool_use.status = PendingToolUseStatus::Running {
|
||||
_task: task.shared(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn confirm_tool_use(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
ui_text: impl Into<Arc<str>>,
|
||||
input: serde_json::Value,
|
||||
request: Arc<LanguageModelRequest>,
|
||||
tool: Arc<dyn Tool>,
|
||||
) {
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
let ui_text = ui_text.into();
|
||||
tool_use.ui_text = ui_text.clone();
|
||||
let confirmation = Confirmation {
|
||||
tool_use_id,
|
||||
input,
|
||||
request,
|
||||
tool,
|
||||
ui_text,
|
||||
};
|
||||
tool_use.status = PendingToolUseStatus::NeedsConfirmation(Arc::new(confirmation));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
output: Result<ToolResultOutput>,
|
||||
configured_model: Option<&ConfiguredModel>,
|
||||
) -> Option<PendingToolUse> {
|
||||
let metadata = self.tool_use_metadata_by_id.remove(&tool_use_id);
|
||||
|
||||
telemetry::event!(
|
||||
"Agent Tool Finished",
|
||||
model = metadata
|
||||
.as_ref()
|
||||
.map(|metadata| metadata.model.telemetry_id()),
|
||||
model_provider = metadata
|
||||
.as_ref()
|
||||
.map(|metadata| metadata.model.provider_id().to_string()),
|
||||
thread_id = metadata.as_ref().map(|metadata| metadata.thread_id.clone()),
|
||||
prompt_id = metadata.as_ref().map(|metadata| metadata.prompt_id.clone()),
|
||||
tool_name,
|
||||
success = output.is_ok()
|
||||
);
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
let tool_result = output.content;
|
||||
const BYTES_PER_TOKEN_ESTIMATE: usize = 3;
|
||||
|
||||
let old_use = self.pending_tool_uses_by_id.remove(&tool_use_id);
|
||||
|
||||
// Protect from overly large output
|
||||
let tool_output_limit = configured_model
|
||||
.map(|model| model.model.max_token_count() as usize * BYTES_PER_TOKEN_ESTIMATE)
|
||||
.unwrap_or(usize::MAX);
|
||||
|
||||
let content = match tool_result {
|
||||
ToolResultContent::Text(text) => {
|
||||
let text = if text.len() < tool_output_limit {
|
||||
text
|
||||
} else {
|
||||
let truncated = truncate_lines_to_byte_limit(&text, tool_output_limit);
|
||||
format!(
|
||||
"Tool result too long. The first {} bytes:\n\n{}",
|
||||
truncated.len(),
|
||||
truncated
|
||||
)
|
||||
};
|
||||
LanguageModelToolResultContent::Text(text.into())
|
||||
}
|
||||
ToolResultContent::Image(language_model_image) => {
|
||||
if language_model_image.estimate_tokens() < tool_output_limit {
|
||||
LanguageModelToolResultContent::Image(language_model_image)
|
||||
} else {
|
||||
self.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name,
|
||||
content: "Tool responded with an image that would exceeded the remaining tokens".into(),
|
||||
is_error: true,
|
||||
output: None,
|
||||
},
|
||||
);
|
||||
|
||||
return old_use;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name,
|
||||
content,
|
||||
is_error: false,
|
||||
output: output.output,
|
||||
},
|
||||
);
|
||||
|
||||
old_use
|
||||
}
|
||||
Err(err) => {
|
||||
self.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name,
|
||||
content: LanguageModelToolResultContent::Text(err.to_string().into()),
|
||||
is_error: true,
|
||||
output: None,
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Error(err.to_string().into());
|
||||
}
|
||||
|
||||
self.pending_tool_uses_by_id.get(&tool_use_id).cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_tool_results(&self, assistant_message_id: MessageId) -> bool {
|
||||
self.tool_uses_by_assistant_message
|
||||
.contains_key(&assistant_message_id)
|
||||
}
|
||||
|
||||
pub fn tool_results(
|
||||
&self,
|
||||
assistant_message_id: MessageId,
|
||||
) -> impl Iterator<Item = (&LanguageModelToolUse, Option<&LanguageModelToolResult>)> {
|
||||
self.tool_uses_by_assistant_message
|
||||
.get(&assistant_message_id)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|tool_use| (tool_use, self.tool_results.get(&tool_use.id)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
/// The ID of the Assistant message in which the tool use was requested.
|
||||
#[allow(unused)]
|
||||
pub assistant_message_id: MessageId,
|
||||
pub name: Arc<str>,
|
||||
pub ui_text: Arc<str>,
|
||||
pub input: serde_json::Value,
|
||||
pub status: PendingToolUseStatus,
|
||||
pub may_perform_edits: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Confirmation {
|
||||
pub tool_use_id: LanguageModelToolUseId,
|
||||
pub input: serde_json::Value,
|
||||
pub ui_text: Arc<str>,
|
||||
pub request: Arc<LanguageModelRequest>,
|
||||
pub tool: Arc<dyn Tool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PendingToolUseStatus {
|
||||
InputStillStreaming,
|
||||
Idle,
|
||||
NeedsConfirmation(Arc<Confirmation>),
|
||||
Running { _task: Shared<Task<()>> },
|
||||
Error(#[allow(unused)] Arc<str>),
|
||||
}
|
||||
|
||||
impl PendingToolUseStatus {
|
||||
pub fn is_idle(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::Idle)
|
||||
}
|
||||
|
||||
pub fn is_error(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::Error(_))
|
||||
}
|
||||
|
||||
pub fn needs_confirmation(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::NeedsConfirmation { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToolUseMetadata {
|
||||
pub model: Arc<dyn LanguageModel>,
|
||||
pub thread_id: ThreadId,
|
||||
pub prompt_id: PromptId,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub struct ZedAgentThread {}
|
||||
@@ -748,7 +748,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: colors.element_selection_background,
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
link: TextStyleRefinement {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||
underline: Some(UnderlineStyle {
|
||||
|
||||
@@ -5,8 +5,7 @@ use anyhow::Result;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot,
|
||||
SelectionEffects, ToPoint,
|
||||
Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, ToPoint,
|
||||
actions::{GoToHunk, GoToPreviousHunk},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
@@ -172,9 +171,15 @@ impl AgentDiffPane {
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::fit()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
selections
|
||||
.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +216,7 @@ impl AgentDiffPane {
|
||||
}
|
||||
|
||||
fn update_title(&mut self, cx: &mut Context<Self>) {
|
||||
let new_title = self.thread.read(cx).title().unwrap_or("Agent Changes");
|
||||
let new_title = self.thread.read(cx).summary().unwrap_or("Agent Changes");
|
||||
if new_title != self.title {
|
||||
self.title = new_title;
|
||||
cx.emit(EditorEvent::TitleChanged);
|
||||
@@ -237,7 +242,7 @@ impl AgentDiffPane {
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
}
|
||||
@@ -411,7 +416,7 @@ fn update_editor_selection(
|
||||
};
|
||||
|
||||
if let Some(target_hunk) = target_hunk {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
let next_hunk_start = target_hunk.multi_buffer_range().start;
|
||||
selections.select_anchor_ranges([next_hunk_start..next_hunk_start]);
|
||||
})
|
||||
@@ -461,7 +466,7 @@ impl Item for AgentDiffPane {
|
||||
}
|
||||
|
||||
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
|
||||
let summary = self.thread.read(cx).title().unwrap_or("Agent Changes");
|
||||
let summary = self.thread.read(cx).summary().unwrap_or("Agent Changes");
|
||||
Label::new(format!("Review: {}", summary))
|
||||
.color(if params.selected {
|
||||
Color::Default
|
||||
@@ -1369,11 +1374,12 @@ impl AgentDiff {
|
||||
| ThreadEvent::MessageDeleted(_)
|
||||
| ThreadEvent::SummaryGenerated
|
||||
| ThreadEvent::SummaryChanged
|
||||
| ThreadEvent::UsePendingTools { .. }
|
||||
| ThreadEvent::ToolFinished { .. }
|
||||
| ThreadEvent::CheckpointChanged
|
||||
| ThreadEvent::ToolConfirmationNeeded
|
||||
| ThreadEvent::ToolUseLimitReached
|
||||
| ThreadEvent::CancelEditing
|
||||
| ThreadEvent::RetriesFailed { .. }
|
||||
| ThreadEvent::ProfileChanged => {}
|
||||
}
|
||||
}
|
||||
@@ -1537,7 +1543,7 @@ impl AgentDiff {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
Some(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
@@ -1799,10 +1805,7 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let thread = thread_store
|
||||
.update(cx, |store, cx| store.create_thread(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
||||
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
|
||||
|
||||
let (workspace, cx) =
|
||||
@@ -1864,7 +1867,7 @@ mod tests {
|
||||
|
||||
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
});
|
||||
});
|
||||
@@ -1967,10 +1970,7 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let thread = thread_store
|
||||
.update(cx, |store, cx| store.create_thread(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
||||
let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
|
||||
|
||||
let (workspace, cx) =
|
||||
@@ -2123,7 +2123,7 @@ mod tests {
|
||||
|
||||
// Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end.
|
||||
editor1.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ use language_model::{ConfiguredModel, LanguageModelRegistry};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
|
||||
use ui::{PopoverMenuHandle, Tooltip, prelude::*};
|
||||
|
||||
pub struct AgentModelSelector {
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
@@ -45,7 +45,7 @@ impl AgentModelSelector {
|
||||
let registry = LanguageModelRegistry::read_global(cx);
|
||||
if let Some(provider) = registry.provider(&model.provider_id())
|
||||
{
|
||||
thread.set_model(
|
||||
thread.set_configured_model(
|
||||
Some(ConfiguredModel {
|
||||
provider,
|
||||
model: model.clone(),
|
||||
@@ -94,35 +94,20 @@ impl Render for AgentModelSelector {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let model = self.selector.read(cx).delegate.active_model(cx);
|
||||
let model_name = model
|
||||
.as_ref()
|
||||
.map(|model| model.model.name().0)
|
||||
.unwrap_or_else(|| SharedString::from("No model selected"));
|
||||
let provider_icon = model
|
||||
.as_ref()
|
||||
.map(|model| model.provider.icon())
|
||||
.unwrap_or_else(|| IconName::Ai);
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.child(
|
||||
Icon::new(provider_icon)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.ml_0p5(),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
Button::new("active-model", model_name)
|
||||
.label_size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.icon(IconName::ChevronDown)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
|
||||
@@ -4,7 +4,6 @@ mod agent_diff;
|
||||
mod agent_model_selector;
|
||||
mod agent_panel;
|
||||
mod buffer_codegen;
|
||||
mod burn_mode_tooltip;
|
||||
mod context_picker;
|
||||
mod context_server_configuration;
|
||||
mod context_strip;
|
||||
@@ -12,6 +11,7 @@ mod debug;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod language_model_selector;
|
||||
mod max_mode_tooltip;
|
||||
mod message_editor;
|
||||
mod profile_selector;
|
||||
mod slash_command;
|
||||
@@ -48,7 +48,7 @@ pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
|
||||
pub use text_thread_editor::AgentPanelDelegate;
|
||||
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
||||
|
||||
actions!(
|
||||
@@ -121,7 +121,7 @@ pub(crate) enum ModelUsageContext {
|
||||
impl ModelUsageContext {
|
||||
pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
||||
match self {
|
||||
Self::Thread(thread) => thread.read(cx).model(),
|
||||
Self::Thread(thread) => thread.read(cx).configured_model(),
|
||||
Self::InlineAssistant => {
|
||||
LanguageModelRegistry::read_global(cx).inline_assistant_model()
|
||||
}
|
||||
@@ -157,7 +157,6 @@ pub fn init(
|
||||
agent::init(cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
||||
TextThreadEditor::init(cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(
|
||||
|
||||
@@ -1094,9 +1094,15 @@ mod tests {
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||
use rand::prelude::*;
|
||||
use serde::Serialize;
|
||||
use settings::SettingsStore;
|
||||
use std::{future, sync::Arc};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DummyCompletionRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -661,7 +661,7 @@ fn recent_context_picker_entries(
|
||||
|
||||
let active_thread_id = workspace
|
||||
.panel::<AgentPanel>(cx)
|
||||
.and_then(|panel| Some(panel.read(cx).active_thread(cx)?.read(cx).id()));
|
||||
.and_then(|panel| Some(panel.read(cx).active_thread()?.read(cx).id()));
|
||||
|
||||
if let Some((thread_store, text_thread_store)) = thread_store
|
||||
.and_then(|store| store.upgrade())
|
||||
@@ -670,7 +670,7 @@ fn recent_context_picker_entries(
|
||||
let mut threads = unordered_thread_entries(thread_store, text_thread_store, cx)
|
||||
.filter(|(_, thread)| match thread {
|
||||
ThreadContextEntry::Thread { id, .. } => {
|
||||
Some(id) != active_thread_id.as_ref() && !current_threads.contains(id)
|
||||
Some(id) != active_thread_id && !current_threads.contains(id)
|
||||
}
|
||||
ThreadContextEntry::Context { .. } => true,
|
||||
})
|
||||
@@ -930,8 +930,8 @@ impl MentionLink {
|
||||
format!(
|
||||
"[@{} ({}-{})]({}:{}:{}-{})",
|
||||
file_name,
|
||||
line_range.start + 1,
|
||||
line_range.end + 1,
|
||||
line_range.start,
|
||||
line_range.end,
|
||||
Self::SELECTION,
|
||||
full_path,
|
||||
line_range.start,
|
||||
|
||||
@@ -161,7 +161,7 @@ impl ContextStrip {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
|
||||
|
||||
if let Some(active_thread) = panel.active_thread(cx) {
|
||||
if let Some(active_thread) = panel.active_thread() {
|
||||
let weak_active_thread = active_thread.downgrade();
|
||||
|
||||
let active_thread = active_thread.read(cx);
|
||||
@@ -169,13 +169,13 @@ impl ContextStrip {
|
||||
if self
|
||||
.context_store
|
||||
.read(cx)
|
||||
.includes_thread(&active_thread.id())
|
||||
.includes_thread(active_thread.id())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SuggestedContext::Thread {
|
||||
name: active_thread.title().or_default(),
|
||||
name: active_thread.summary().or_default(),
|
||||
thread: weak_active_thread,
|
||||
})
|
||||
} else if let Some(active_context_editor) = panel.active_context_editor() {
|
||||
|
||||
@@ -18,7 +18,6 @@ use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, HashSet, VecDeque, hash_map};
|
||||
use editor::SelectionEffects;
|
||||
use editor::{
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
|
||||
MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||
@@ -1160,7 +1159,7 @@ impl InlineAssistant {
|
||||
|
||||
let position = assist.range.start;
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_anchor_ranges([position..position])
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use gpui::{Context, FontWeight, IntoElement, Render, Window};
|
||||
use ui::{prelude::*, tooltip_container};
|
||||
|
||||
pub struct BurnModeTooltip {
|
||||
pub struct MaxModeTooltip {
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl BurnModeTooltip {
|
||||
impl MaxModeTooltip {
|
||||
pub fn new() -> Self {
|
||||
Self { selected: false }
|
||||
}
|
||||
@@ -16,7 +16,7 @@ impl BurnModeTooltip {
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for BurnModeTooltip {
|
||||
impl Render for MaxModeTooltip {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (icon, color) = if self.selected {
|
||||
(IconName::ZedBurnModeOn, Color::Error)
|
||||
@@ -387,14 +387,25 @@ impl MessageEditor {
|
||||
|
||||
thread
|
||||
.update(cx, |thread, cx| {
|
||||
todo!();
|
||||
// thread.send(
|
||||
// user_message,
|
||||
// loaded_context,
|
||||
// checkpoint.ok(),
|
||||
// user_message_creases,
|
||||
// cx,
|
||||
// );
|
||||
thread.insert_user_message(
|
||||
user_message,
|
||||
loaded_context,
|
||||
checkpoint.ok(),
|
||||
user_message_creases,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
|
||||
thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.advance_prompt_id();
|
||||
thread.send_to_model(
|
||||
model,
|
||||
CompletionIntent::UserPrompt,
|
||||
Some(window_handle),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
@@ -564,7 +575,7 @@ impl MessageEditor {
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let thread = self.thread.read(cx);
|
||||
let model = thread.configured_model();
|
||||
if !model?.model.supports_burn_mode() {
|
||||
if !model?.model.supports_max_mode() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ impl Render for ProfileSelector {
|
||||
.map(|profile| profile.name.clone())
|
||||
.unwrap_or_else(|| "Unknown".into());
|
||||
|
||||
let configured_model = self.thread.read(cx).model().or_else(|| {
|
||||
let configured_model = self.thread.read(cx).configured_model().or_else(|| {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
model_registry.default_model()
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
burn_mode_tooltip::BurnModeTooltip,
|
||||
language_model_selector::{
|
||||
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||
},
|
||||
max_mode_tooltip::MaxModeTooltip,
|
||||
};
|
||||
use agent_settings::{AgentSettings, CompletionMode};
|
||||
use anyhow::Result;
|
||||
@@ -21,6 +21,7 @@ use editor::{
|
||||
BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
|
||||
RenderBlock, ToDisplayPoint,
|
||||
},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use editor::{FoldPlaceholder, display_map::CreaseId};
|
||||
use fs::Fs;
|
||||
@@ -68,7 +69,7 @@ use workspace::{
|
||||
searchable::{Direction, SearchableItemHandle},
|
||||
};
|
||||
use workspace::{
|
||||
Save, Toast, Workspace,
|
||||
Save, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
item::{self, FollowableItem, Item, ItemHandle},
|
||||
notifications::NotificationId,
|
||||
pane,
|
||||
@@ -388,7 +389,7 @@ impl TextThreadEditor {
|
||||
cursor..cursor
|
||||
};
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
|
||||
selections.select_ranges([new_selection])
|
||||
});
|
||||
});
|
||||
@@ -448,7 +449,8 @@ impl TextThreadEditor {
|
||||
if let Some(command) = self.slash_commands.command(name, cx) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| s.try_cancel());
|
||||
editor
|
||||
.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel());
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let newest_cursor = editor.selections.newest::<Point>(cx).head();
|
||||
if newest_cursor.column > 0
|
||||
@@ -1581,7 +1583,7 @@ impl TextThreadEditor {
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.transact(window, cx, |this, window, cx| {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select(selections);
|
||||
});
|
||||
this.insert("", window, cx);
|
||||
@@ -2073,12 +2075,12 @@ impl TextThreadEditor {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let context = self.context().read(cx);
|
||||
let active_model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.model)?;
|
||||
if !active_model.supports_burn_mode() {
|
||||
if !active_model.supports_max_mode() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -2105,7 +2107,7 @@ impl TextThreadEditor {
|
||||
});
|
||||
}))
|
||||
.tooltip(move |_window, cx| {
|
||||
cx.new(|_| BurnModeTooltip::new().selected(burn_mode_enabled))
|
||||
cx.new(|_| MaxModeTooltip::new().selected(burn_mode_enabled))
|
||||
.into()
|
||||
})
|
||||
.into_any_element(),
|
||||
@@ -2120,21 +2122,12 @@ impl TextThreadEditor {
|
||||
let active_model = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.model);
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
let model_name = match active_model {
|
||||
Some(model) => model.name().0,
|
||||
None => SharedString::from("No model selected"),
|
||||
};
|
||||
|
||||
let active_provider = LanguageModelRegistry::read_global(cx)
|
||||
.default_model()
|
||||
.map(|default| default.provider);
|
||||
let provider_icon = match active_provider {
|
||||
Some(provider) => provider.icon(),
|
||||
None => IconName::Ai,
|
||||
};
|
||||
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
|
||||
PickerPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
@@ -2142,16 +2135,10 @@ impl TextThreadEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Icon::new(provider_icon)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.ml_0p5(),
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
@@ -2588,7 +2575,7 @@ impl Render for TextThreadEditor {
|
||||
};
|
||||
|
||||
let language_model_selector = self.language_model_selector_menu_handle.clone();
|
||||
let burn_mode_toggle = self.render_burn_mode_toggle(cx);
|
||||
let max_mode_toggle = self.render_max_mode_toggle(cx);
|
||||
|
||||
v_flex()
|
||||
.key_context("ContextEditor")
|
||||
@@ -2643,7 +2630,7 @@ impl Render for TextThreadEditor {
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(self.render_inject_context_menu(cx))
|
||||
.when_some(burn_mode_toggle, |this, element| this.child(element)),
|
||||
.when_some(max_mode_toggle, |this, element| this.child(element)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -2937,6 +2924,13 @@ impl FollowableItem for TextThreadEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextEditorToolbarItem {
|
||||
active_context_editor: Option<WeakEntity<TextThreadEditor>>,
|
||||
model_summary_editor: Entity<Editor>,
|
||||
}
|
||||
|
||||
impl ContextEditorToolbarItem {}
|
||||
|
||||
pub fn render_remaining_tokens(
|
||||
context_editor: &Entity<TextThreadEditor>,
|
||||
cx: &App,
|
||||
@@ -2989,6 +2983,98 @@ pub fn render_remaining_tokens(
|
||||
)
|
||||
}
|
||||
|
||||
impl Render for ContextEditorToolbarItem {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let left_side = h_flex()
|
||||
.group("chat-title-group")
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.flex_grow()
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.when(self.active_context_editor.is_some(), |left_side| {
|
||||
left_side.child(self.model_summary_editor.clone())
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div().visible_on_hover("chat-title-group").child(
|
||||
IconButton::new("regenerate-context", IconName::RefreshTitle)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::text("Regenerate Title"))
|
||||
.on_click(cx.listener(move |_, _, _window, cx| {
|
||||
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
let right_side = h_flex()
|
||||
.gap_2()
|
||||
// TODO display this in a nicer way, once we have a design for it.
|
||||
// .children({
|
||||
// let project = self
|
||||
// .workspace
|
||||
// .upgrade()
|
||||
// .map(|workspace| workspace.read(cx).project().downgrade());
|
||||
//
|
||||
// let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
|
||||
// project.and_then(|project| db.remaining_summaries(&project, cx))
|
||||
// });
|
||||
// scan_items_remaining
|
||||
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
|
||||
// })
|
||||
.children(
|
||||
self.active_context_editor
|
||||
.as_ref()
|
||||
.and_then(|editor| editor.upgrade())
|
||||
.and_then(|editor| render_remaining_tokens(&editor, cx)),
|
||||
);
|
||||
|
||||
h_flex()
|
||||
.px_0p5()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(left_side)
|
||||
.child(right_side)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolbarItemView for ContextEditorToolbarItem {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
self.active_context_editor = active_pane_item
|
||||
.and_then(|item| item.act_as::<TextThreadEditor>(cx))
|
||||
.map(|editor| editor.downgrade());
|
||||
cx.notify();
|
||||
if self.active_context_editor.is_none() {
|
||||
ToolbarItemLocation::Hidden
|
||||
} else {
|
||||
ToolbarItemLocation::PrimaryRight
|
||||
}
|
||||
}
|
||||
|
||||
fn pane_focus_update(
|
||||
&mut self,
|
||||
_pane_focused: bool,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
|
||||
|
||||
pub enum ContextEditorToolbarItemEvent {
|
||||
RegenerateSummary,
|
||||
}
|
||||
impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
|
||||
|
||||
enum PendingSlashCommand {}
|
||||
|
||||
fn invoked_slash_command_fold_placeholder(
|
||||
@@ -3154,7 +3240,6 @@ pub fn make_lsp_adapter_delegate(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use editor::SelectionEffects;
|
||||
use fs::FakeFs;
|
||||
use gpui::{App, TestAppContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
@@ -3380,9 +3465,7 @@ mod tests {
|
||||
) {
|
||||
context_editor.update_in(cx, |context_editor, window, cx| {
|
||||
context_editor.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([range])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([range]));
|
||||
});
|
||||
|
||||
context_editor.copy(&Default::default(), window, cx);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
mod agent_notification;
|
||||
mod animated_label;
|
||||
mod burn_mode_tooltip;
|
||||
mod context_pill;
|
||||
mod max_mode_tooltip;
|
||||
mod onboarding_modal;
|
||||
pub mod preview;
|
||||
mod upsell;
|
||||
|
||||
pub use agent_notification::*;
|
||||
pub use animated_label::*;
|
||||
pub use burn_mode_tooltip::*;
|
||||
pub use context_pill::*;
|
||||
pub use max_mode_tooltip::*;
|
||||
pub use onboarding_modal::*;
|
||||
|
||||
@@ -498,7 +498,7 @@ impl AddedContext {
|
||||
render_hover: {
|
||||
let thread = handle.thread.clone();
|
||||
Some(Rc::new(move |_, cx| {
|
||||
let text = thread.read(cx).latest_detailed_summary_or_text(cx);
|
||||
let text = thread.read(cx).latest_detailed_summary_or_text();
|
||||
ContextPillHover::new_text(text.clone(), cx).into()
|
||||
}))
|
||||
},
|
||||
|
||||
@@ -2346,13 +2346,13 @@ impl AssistantContext {
|
||||
completion_request.messages.push(request_message);
|
||||
}
|
||||
}
|
||||
let supports_burn_mode = if let Some(model) = model {
|
||||
model.supports_burn_mode()
|
||||
let supports_max_mode = if let Some(model) = model {
|
||||
model.supports_max_mode()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if supports_burn_mode {
|
||||
if supports_max_mode {
|
||||
completion_request.mode = Some(self.completion_mode.into());
|
||||
}
|
||||
completion_request
|
||||
|
||||
@@ -74,7 +74,7 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
.slice(section.range.to_offset(&context_buffer)),
|
||||
);
|
||||
file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
|
||||
std::slice::from_ref(&metadata.path),
|
||||
&[metadata.path.clone()],
|
||||
context_slash_command_output_sections,
|
||||
context_buffer.clone(),
|
||||
workspace.clone(),
|
||||
|
||||
@@ -10,7 +10,7 @@ use assistant_tool::{
|
||||
ToolUseStatus,
|
||||
};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey};
|
||||
use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey, scroll::Autoscroll};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task,
|
||||
@@ -823,7 +823,7 @@ impl ToolCard for EditFileToolCard {
|
||||
let first_hunk_start =
|
||||
first_hunk.multi_buffer_range().start;
|
||||
editor.change_selections(
|
||||
Default::default(),
|
||||
Some(Autoscroll::fit()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
@@ -1065,7 +1065,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,7 +691,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: text_style.clone(),
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use auto_update::AutoUpdater;
|
||||
use client::proto::UpdateNotification;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use gpui::{App, Context, DismissEvent, Entity, Window, actions, prelude::*};
|
||||
use gpui::{App, Context, DismissEvent, Entity, SharedString, Window, actions, prelude::*};
|
||||
use http_client::HttpClient;
|
||||
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
@@ -94,6 +94,7 @@ fn view_release_notes_locally(
|
||||
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let tab_content = Some(SharedString::from(body.title.to_string()));
|
||||
let editor = cx.new(|cx| {
|
||||
Editor::for_multibuffer(buffer, Some(project), window, cx)
|
||||
});
|
||||
@@ -104,6 +105,7 @@ fn view_release_notes_locally(
|
||||
editor,
|
||||
workspace_handle,
|
||||
language_registry,
|
||||
tab_content,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -1867,7 +1867,7 @@ mod tests {
|
||||
let hunk = diff.hunks(&buffer, cx).next().unwrap();
|
||||
|
||||
let new_index_text = diff
|
||||
.stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
|
||||
.stage_or_unstage_hunks(true, &[hunk.clone()], &buffer, true, cx)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
assert_eq!(new_index_text, buffer_text);
|
||||
|
||||
@@ -734,8 +734,8 @@ impl Database {
|
||||
users.push(proto::User {
|
||||
id: user.id.to_proto(),
|
||||
avatar_url: format!(
|
||||
"https://avatars.githubusercontent.com/u/{}?s=128&v=4",
|
||||
user.github_user_id
|
||||
"https://github.com/{}.png?size=128",
|
||||
user.github_login
|
||||
),
|
||||
github_login: user.github_login,
|
||||
name: user.name,
|
||||
|
||||
@@ -76,10 +76,7 @@ async fn test_purge_old_embeddings(cx: &mut gpui::TestAppContext) {
|
||||
db.purge_old_embeddings().await.unwrap();
|
||||
|
||||
// Try to retrieve the purged embeddings
|
||||
let retrieved_embeddings = db
|
||||
.get_embeddings(model, std::slice::from_ref(&digest))
|
||||
.await
|
||||
.unwrap();
|
||||
let retrieved_embeddings = db.get_embeddings(model, &[digest.clone()]).await.unwrap();
|
||||
assert!(
|
||||
retrieved_embeddings.is_empty(),
|
||||
"Old embeddings should have been purged"
|
||||
|
||||
@@ -179,7 +179,7 @@ struct Session {
|
||||
}
|
||||
|
||||
impl Session {
|
||||
async fn db(&self) -> tokio::sync::MutexGuard<'_, DbHandle> {
|
||||
async fn db(&self) -> tokio::sync::MutexGuard<DbHandle> {
|
||||
#[cfg(test)]
|
||||
tokio::task::yield_now().await;
|
||||
let guard = self.db.lock().await;
|
||||
@@ -1037,7 +1037,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn snapshot(self: &Arc<Self>) -> ServerSnapshot<'_> {
|
||||
pub async fn snapshot(self: &Arc<Self>) -> ServerSnapshot {
|
||||
ServerSnapshot {
|
||||
connection_pool: ConnectionPoolGuard {
|
||||
guard: self.connection_pool.lock(),
|
||||
|
||||
@@ -178,7 +178,7 @@ async fn test_channel_notes_participant_indices(
|
||||
channel_view_a.update_in(cx_a, |notes, window, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.insert("a", window, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![0..1]);
|
||||
});
|
||||
});
|
||||
@@ -188,7 +188,7 @@ async fn test_channel_notes_participant_indices(
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&Default::default(), window, cx);
|
||||
editor.insert("b", window, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![1..2]);
|
||||
});
|
||||
});
|
||||
@@ -198,7 +198,7 @@ async fn test_channel_notes_participant_indices(
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&Default::default(), window, cx);
|
||||
editor.insert("c", window, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![2..3]);
|
||||
});
|
||||
});
|
||||
@@ -273,12 +273,12 @@ async fn test_channel_notes_participant_indices(
|
||||
.unwrap();
|
||||
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![0..1]);
|
||||
});
|
||||
});
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![2..3]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use editor::{
|
||||
DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, SelectionEffects,
|
||||
DocumentColorsRenderMode, Editor, EditorSettings, RowInfo,
|
||||
actions::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
|
||||
ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
|
||||
@@ -348,9 +348,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
|
||||
// Type a completion trigger character as the guest.
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(".", window, cx);
|
||||
});
|
||||
cx_b.focus(&editor_b);
|
||||
@@ -463,9 +461,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
// Now we do a second completion, this time to ensure that documentation/snippets are
|
||||
// resolved
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([46..46])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([46..46]));
|
||||
editor.handle_input("; a", window, cx);
|
||||
editor.handle_input(".", window, cx);
|
||||
});
|
||||
@@ -617,7 +613,7 @@ async fn test_collaborating_with_code_actions(
|
||||
|
||||
// Move cursor to a location that contains code actions.
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
|
||||
});
|
||||
});
|
||||
@@ -821,9 +817,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
|
||||
// Move cursor to a location that can be renamed.
|
||||
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([7..7])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
|
||||
editor.rename(&Rename, window, cx).unwrap()
|
||||
});
|
||||
|
||||
@@ -869,9 +863,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
editor.cancel(&editor::actions::Cancel, window, cx);
|
||||
});
|
||||
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([7..8])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
|
||||
editor.rename(&Rename, window, cx).unwrap()
|
||||
});
|
||||
|
||||
@@ -1372,9 +1364,7 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
cx_a.focus(&editor_a);
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(">", window, cx);
|
||||
});
|
||||
|
||||
@@ -1470,9 +1460,7 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
cx_b.focus(&editor_b);
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(":", window, cx);
|
||||
});
|
||||
|
||||
@@ -1709,9 +1697,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
|
||||
let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13].clone())
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
|
||||
editor.handle_input(":", window, cx);
|
||||
});
|
||||
cx_b.focus(&editor_b);
|
||||
@@ -1732,9 +1718,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
|
||||
let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input("a change to increment both buffers' versions", window, cx);
|
||||
});
|
||||
cx_a.focus(&editor_a);
|
||||
@@ -2137,9 +2121,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
||||
});
|
||||
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13].clone())
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
|
||||
editor.handle_input(":", window, cx);
|
||||
});
|
||||
color_request_handle.next().await.unwrap();
|
||||
|
||||
@@ -6,7 +6,7 @@ use collab_ui::{
|
||||
channel_view::ChannelView,
|
||||
notifications::project_shared_notification::ProjectSharedNotification,
|
||||
};
|
||||
use editor::{Editor, MultiBuffer, PathKey, SelectionEffects};
|
||||
use editor::{Editor, MultiBuffer, PathKey};
|
||||
use gpui::{
|
||||
AppContext as _, BackgroundExecutor, BorrowAppContext, Entity, SharedString, TestAppContext,
|
||||
VisualContext, VisualTestContext, point,
|
||||
@@ -376,9 +376,7 @@ async fn test_basic_following(
|
||||
|
||||
// Changes to client A's editor are reflected on client B.
|
||||
editor_a1.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([1..1, 2..2])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2]));
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
executor.run_until_parked();
|
||||
@@ -395,9 +393,7 @@ async fn test_basic_following(
|
||||
editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
|
||||
|
||||
editor_a1.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([3..3])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
|
||||
editor.set_scroll_position(point(0., 100.), window, cx);
|
||||
});
|
||||
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -1651,9 +1647,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
|
||||
// b should follow a to position 1
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([1..1])
|
||||
})
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]))
|
||||
});
|
||||
cx_a.executor()
|
||||
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -1673,9 +1667,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
|
||||
// b should not follow a to position 2
|
||||
editor_a.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([2..2])
|
||||
})
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]))
|
||||
});
|
||||
cx_a.executor()
|
||||
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
|
||||
@@ -1976,7 +1968,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.insert("Hello from A.", window, cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![3..4]);
|
||||
});
|
||||
});
|
||||
@@ -2117,7 +2109,7 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut
|
||||
workspace.add_item_to_center(Box::new(editor.clone()) as _, window, cx)
|
||||
});
|
||||
editor.update_in(cx_a, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::row_range(4..4)]);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -7,8 +7,8 @@ use client::{
|
||||
};
|
||||
use collections::HashMap;
|
||||
use editor::{
|
||||
CollaborationHub, DisplayPoint, Editor, EditorEvent, SelectionEffects,
|
||||
display_map::ToDisplayPoint, scroll::Autoscroll,
|
||||
CollaborationHub, DisplayPoint, Editor, EditorEvent, display_map::ToDisplayPoint,
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
AnyView, App, ClipboardItem, Context, Entity, EventEmitter, Focusable, Pixels, Point, Render,
|
||||
@@ -260,16 +260,9 @@ impl ChannelView {
|
||||
.find(|item| &Channel::slug(&item.text).to_lowercase() == &position)
|
||||
{
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::focused()),
|
||||
window,
|
||||
cx,
|
||||
|s| {
|
||||
s.replace_cursors_with(|map| {
|
||||
vec![item.range.start.to_display_point(map)]
|
||||
})
|
||||
},
|
||||
)
|
||||
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
|
||||
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)])
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -698,16 +698,16 @@ async fn stream_completion(
|
||||
completion_url: Arc<str>,
|
||||
request: Request,
|
||||
) -> Result<BoxStream<'static, Result<ResponseEvent>>> {
|
||||
let is_vision_request = request.messages.iter().any(|message| match message {
|
||||
ChatMessage::User { content }
|
||||
| ChatMessage::Assistant { content, .. }
|
||||
| ChatMessage::Tool { content, .. } => {
|
||||
matches!(content, ChatMessageContent::Multipart(parts) if parts.iter().any(|part| matches!(part, ChatMessagePart::Image { .. })))
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
let is_vision_request = request.messages.last().map_or(false, |message| match message {
|
||||
ChatMessage::User { content }
|
||||
| ChatMessage::Assistant { content, .. }
|
||||
| ChatMessage::Tool { content, .. } => {
|
||||
matches!(content, ChatMessageContent::Multipart(parts) if parts.iter().any(|part| matches!(part, ChatMessagePart::Image { .. })))
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let mut request_builder = HttpRequest::builder()
|
||||
let request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(completion_url.as_ref())
|
||||
.header(
|
||||
@@ -719,12 +719,8 @@ async fn stream_completion(
|
||||
)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Copilot-Integration-Id", "vscode-chat");
|
||||
|
||||
if is_vision_request {
|
||||
request_builder =
|
||||
request_builder.header("Copilot-Vision-Request", is_vision_request.to_string());
|
||||
}
|
||||
.header("Copilot-Integration-Id", "vscode-chat")
|
||||
.header("Copilot-Vision-Request", is_vision_request.to_string());
|
||||
|
||||
let is_streaming = request.stream;
|
||||
|
||||
|
||||
@@ -264,8 +264,7 @@ fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b:
|
||||
mod tests {
|
||||
use super::*;
|
||||
use editor::{
|
||||
Editor, ExcerptRange, MultiBuffer, SelectionEffects,
|
||||
test::editor_lsp_test_context::EditorLspTestContext,
|
||||
Editor, ExcerptRange, MultiBuffer, test::editor_lsp_test_context::EditorLspTestContext,
|
||||
};
|
||||
use fs::FakeFs;
|
||||
use futures::StreamExt;
|
||||
@@ -479,7 +478,7 @@ mod tests {
|
||||
// Reset the editor to verify how suggestions behave when tabbing on leading indentation.
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.set_text("fn foo() {\n \n}", window, cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
|
||||
});
|
||||
});
|
||||
@@ -768,7 +767,7 @@ mod tests {
|
||||
);
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
// Ensure copilot suggestions are shown for the first excerpt.
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
|
||||
});
|
||||
editor.next_edit_prediction(&Default::default(), window, cx);
|
||||
@@ -794,7 +793,7 @@ mod tests {
|
||||
);
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
// Move to another excerpt, ensuring the suggestion gets cleared.
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
|
||||
});
|
||||
assert!(!editor.has_active_inline_completion());
|
||||
@@ -1020,7 +1019,7 @@ mod tests {
|
||||
);
|
||||
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
|
||||
});
|
||||
editor.refresh_inline_completion(true, false, window, cx);
|
||||
@@ -1030,7 +1029,7 @@ mod tests {
|
||||
assert!(copilot_requests.try_next().is_err());
|
||||
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(5, 0)..Point::new(5, 0)])
|
||||
});
|
||||
editor.refresh_inline_completion(true, false, window, cx);
|
||||
|
||||
@@ -33,7 +33,6 @@ log.workspace = true
|
||||
paths.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
shlex.workspace = true
|
||||
task.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
@@ -5,7 +5,7 @@ use gpui::AsyncApp;
|
||||
use serde_json::Value;
|
||||
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
|
||||
use task::DebugRequest;
|
||||
use util::{ResultExt, maybe};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::*;
|
||||
|
||||
@@ -72,24 +72,6 @@ impl JsDebugAdapter {
|
||||
|
||||
let mut configuration = task_definition.config.clone();
|
||||
if let Some(configuration) = configuration.as_object_mut() {
|
||||
maybe!({
|
||||
configuration
|
||||
.get("type")
|
||||
.filter(|value| value == &"node-terminal")?;
|
||||
let command = configuration.get("command")?.as_str()?.to_owned();
|
||||
let mut args = shlex::split(&command)?.into_iter();
|
||||
let program = args.next()?;
|
||||
configuration.insert("program".to_owned(), program.into());
|
||||
configuration.insert(
|
||||
"args".to_owned(),
|
||||
args.map(Value::from).collect::<Vec<_>>().into(),
|
||||
);
|
||||
configuration.insert("console".to_owned(), "externalTerminal".into());
|
||||
Some(())
|
||||
});
|
||||
|
||||
configuration.entry("type").and_modify(normalize_task_type);
|
||||
|
||||
if let Some(program) = configuration
|
||||
.get("program")
|
||||
.cloned()
|
||||
@@ -114,6 +96,7 @@ impl JsDebugAdapter {
|
||||
.entry("cwd")
|
||||
.or_insert(delegate.worktree_root_path().to_string_lossy().into());
|
||||
|
||||
configuration.entry("type").and_modify(normalize_task_type);
|
||||
configuration
|
||||
.entry("console")
|
||||
.or_insert("externalTerminal".into());
|
||||
@@ -529,7 +512,7 @@ fn normalize_task_type(task_type: &mut Value) {
|
||||
};
|
||||
|
||||
let new_name = match task_type_str {
|
||||
"node" | "pwa-node" | "node-terminal" => "pwa-node",
|
||||
"node" | "pwa-node" => "pwa-node",
|
||||
"chrome" | "pwa-chrome" => "pwa-chrome",
|
||||
"edge" | "msedge" | "pwa-edge" | "pwa-msedge" => "pwa-msedge",
|
||||
_ => task_type_str,
|
||||
|
||||
@@ -28,7 +28,6 @@ test-support = [
|
||||
[dependencies]
|
||||
alacritty_terminal.workspace = true
|
||||
anyhow.workspace = true
|
||||
bitflags.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
|
||||
@@ -100,13 +100,7 @@ impl DebugPanel {
|
||||
sessions: vec![],
|
||||
active_session: None,
|
||||
focus_handle,
|
||||
breakpoint_list: BreakpointList::new(
|
||||
None,
|
||||
workspace.weak_handle(),
|
||||
&project,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
breakpoint_list: BreakpointList::new(None, workspace.weak_handle(), &project, cx),
|
||||
project,
|
||||
workspace: workspace.weak_handle(),
|
||||
context_menu: None,
|
||||
|
||||
@@ -697,13 +697,8 @@ impl RunningState {
|
||||
)
|
||||
});
|
||||
|
||||
let breakpoint_list = BreakpointList::new(
|
||||
Some(session.clone()),
|
||||
workspace.clone(),
|
||||
&project,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let breakpoint_list =
|
||||
BreakpointList::new(Some(session.clone()), workspace.clone(), &project, cx);
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.on_app_quit(move |this, cx| {
|
||||
|
||||
@@ -5,11 +5,11 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use dap::{Capabilities, ExceptionBreakpointsFilter};
|
||||
use dap::ExceptionBreakpointsFilter;
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
Action, AppContext, ClickEvent, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy,
|
||||
Stateful, Task, UniformListScrollHandle, WeakEntity, actions, uniform_list,
|
||||
Action, AppContext, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful,
|
||||
Task, UniformListScrollHandle, WeakEntity, uniform_list,
|
||||
};
|
||||
use language::Point;
|
||||
use project::{
|
||||
@@ -21,20 +21,16 @@ use project::{
|
||||
worktree_store::WorktreeStore,
|
||||
};
|
||||
use ui::{
|
||||
ActiveTheme, AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div,
|
||||
Divider, FluentBuilder as _, Icon, IconButton, IconName, IconSize, Indicator,
|
||||
InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement,
|
||||
Render, RenderOnce, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement,
|
||||
Styled, Toggleable, Tooltip, Window, div, h_flex, px, v_flex,
|
||||
AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div, FluentBuilder as _,
|
||||
Icon, IconButton, IconName, IconSize, Indicator, InteractiveElement, IntoElement, Label,
|
||||
LabelCommon, LabelSize, ListItem, ParentElement, Render, Scrollbar, ScrollbarState,
|
||||
SharedString, StatefulInteractiveElement, Styled, Toggleable, Tooltip, Window, div, h_flex, px,
|
||||
v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
|
||||
|
||||
actions!(
|
||||
debugger,
|
||||
[PreviousBreakpointProperty, NextBreakpointProperty]
|
||||
);
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum SelectedBreakpointKind {
|
||||
Source,
|
||||
@@ -52,8 +48,6 @@ pub(crate) struct BreakpointList {
|
||||
focus_handle: FocusHandle,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
selected_ix: Option<usize>,
|
||||
input: Entity<Editor>,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
}
|
||||
|
||||
impl Focusable for BreakpointList {
|
||||
@@ -62,19 +56,11 @@ impl Focusable for BreakpointList {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum ActiveBreakpointStripMode {
|
||||
Log,
|
||||
Condition,
|
||||
HitCondition,
|
||||
}
|
||||
|
||||
impl BreakpointList {
|
||||
pub(crate) fn new(
|
||||
session: Option<Entity<Session>>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: &Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let project = project.read(cx);
|
||||
@@ -84,7 +70,7 @@ impl BreakpointList {
|
||||
let scroll_handle = UniformListScrollHandle::new();
|
||||
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||
|
||||
cx.new(|cx| Self {
|
||||
cx.new(|_| Self {
|
||||
breakpoint_store,
|
||||
worktree_store,
|
||||
scrollbar_state,
|
||||
@@ -96,28 +82,17 @@ impl BreakpointList {
|
||||
focus_handle,
|
||||
scroll_handle,
|
||||
selected_ix: None,
|
||||
input: cx.new(|cx| Editor::single_line(window, cx)),
|
||||
strip_mode: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn edit_line_breakpoint(
|
||||
&self,
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
row: u32,
|
||||
action: BreakpointEditAction,
|
||||
cx: &mut App,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
Self::edit_line_breakpoint_inner(&self.breakpoint_store, path, row, action, cx);
|
||||
}
|
||||
fn edit_line_breakpoint_inner(
|
||||
breakpoint_store: &Entity<BreakpointStore>,
|
||||
path: Arc<Path>,
|
||||
row: u32,
|
||||
action: BreakpointEditAction,
|
||||
cx: &mut App,
|
||||
) {
|
||||
breakpoint_store.update(cx, |breakpoint_store, cx| {
|
||||
self.breakpoint_store.update(cx, |breakpoint_store, cx| {
|
||||
if let Some((buffer, breakpoint)) = breakpoint_store.breakpoint_at_row(&path, row, cx) {
|
||||
breakpoint_store.toggle_breakpoint(buffer, breakpoint, action, cx);
|
||||
} else {
|
||||
@@ -173,63 +148,16 @@ impl BreakpointList {
|
||||
})
|
||||
}
|
||||
|
||||
fn set_active_breakpoint_property(
|
||||
&mut self,
|
||||
prop: ActiveBreakpointStripMode,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.strip_mode = Some(prop);
|
||||
let placeholder = match prop {
|
||||
ActiveBreakpointStripMode::Log => "Set Log Message",
|
||||
ActiveBreakpointStripMode::Condition => "Set Condition",
|
||||
ActiveBreakpointStripMode::HitCondition => "Set Hit Condition",
|
||||
};
|
||||
let mut is_exception_breakpoint = true;
|
||||
let active_value = self.selected_ix.and_then(|ix| {
|
||||
self.breakpoints.get(ix).and_then(|bp| {
|
||||
if let BreakpointEntryKind::LineBreakpoint(bp) = &bp.kind {
|
||||
is_exception_breakpoint = false;
|
||||
match prop {
|
||||
ActiveBreakpointStripMode::Log => bp.breakpoint.message.clone(),
|
||||
ActiveBreakpointStripMode::Condition => bp.breakpoint.condition.clone(),
|
||||
ActiveBreakpointStripMode::HitCondition => {
|
||||
bp.breakpoint.hit_condition.clone()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
self.input.update(cx, |this, cx| {
|
||||
this.set_placeholder_text(placeholder, cx);
|
||||
this.set_read_only(is_exception_breakpoint);
|
||||
this.set_text(active_value.as_deref().unwrap_or(""), window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn select_ix(&mut self, ix: Option<usize>, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
|
||||
self.selected_ix = ix;
|
||||
if let Some(ix) = ix {
|
||||
self.scroll_handle
|
||||
.scroll_to_item(ix, ScrollStrategy::Center);
|
||||
}
|
||||
if let Some(mode) = self.strip_mode {
|
||||
self.set_active_breakpoint_property(mode, window, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let ix = match self.selected_ix {
|
||||
_ if self.breakpoints.len() == 0 => None,
|
||||
None => Some(0),
|
||||
@@ -241,21 +169,15 @@ impl BreakpointList {
|
||||
}
|
||||
}
|
||||
};
|
||||
self.select_ix(ix, window, cx);
|
||||
self.select_ix(ix, cx);
|
||||
}
|
||||
|
||||
fn select_previous(
|
||||
&mut self,
|
||||
_: &menu::SelectPrevious,
|
||||
window: &mut Window,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let ix = match self.selected_ix {
|
||||
_ if self.breakpoints.len() == 0 => None,
|
||||
None => Some(self.breakpoints.len() - 1),
|
||||
@@ -267,105 +189,37 @@ impl BreakpointList {
|
||||
}
|
||||
}
|
||||
};
|
||||
self.select_ix(ix, window, cx);
|
||||
self.select_ix(ix, cx);
|
||||
}
|
||||
|
||||
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
fn select_first(
|
||||
&mut self,
|
||||
_: &menu::SelectFirst,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let ix = if self.breakpoints.len() > 0 {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.select_ix(ix, window, cx);
|
||||
self.select_ix(ix, cx);
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let ix = if self.breakpoints.len() > 0 {
|
||||
Some(self.breakpoints.len() - 1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.select_ix(ix, window, cx);
|
||||
self.select_ix(ix, cx);
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
self.focus_handle.focus(window);
|
||||
} else if self.strip_mode.is_some() {
|
||||
self.strip_mode.take();
|
||||
cx.notify();
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mode) = self.strip_mode {
|
||||
let handle = self.input.focus_handle(cx);
|
||||
if handle.is_focused(window) {
|
||||
// Go back to the main strip. Save the result as well.
|
||||
let text = self.input.read(cx).text(cx);
|
||||
|
||||
match mode {
|
||||
ActiveBreakpointStripMode::Log => match &entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
Self::edit_line_breakpoint_inner(
|
||||
&self.breakpoint_store,
|
||||
line_breakpoint.breakpoint.path.clone(),
|
||||
line_breakpoint.breakpoint.row,
|
||||
BreakpointEditAction::EditLogMessage(Arc::from(text)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
ActiveBreakpointStripMode::Condition => match &entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
Self::edit_line_breakpoint_inner(
|
||||
&self.breakpoint_store,
|
||||
line_breakpoint.breakpoint.path.clone(),
|
||||
line_breakpoint.breakpoint.row,
|
||||
BreakpointEditAction::EditCondition(Arc::from(text)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
ActiveBreakpointStripMode::HitCondition => match &entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
Self::edit_line_breakpoint_inner(
|
||||
&self.breakpoint_store,
|
||||
line_breakpoint.breakpoint.path.clone(),
|
||||
line_breakpoint.breakpoint.row,
|
||||
BreakpointEditAction::EditHitCondition(Arc::from(text)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
self.focus_handle.focus(window);
|
||||
} else {
|
||||
handle.focus(window);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
match &mut entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
let path = line_breakpoint.breakpoint.path.clone();
|
||||
@@ -379,18 +233,12 @@ impl BreakpointList {
|
||||
fn toggle_enable_breakpoint(
|
||||
&mut self,
|
||||
_: &ToggleEnableBreakpoint,
|
||||
window: &mut Window,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {
|
||||
return;
|
||||
};
|
||||
if self.strip_mode.is_some() {
|
||||
if self.input.focus_handle(cx).contains_focused(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match &mut entry.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
@@ -431,50 +279,6 @@ impl BreakpointList {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn previous_breakpoint_property(
|
||||
&mut self,
|
||||
_: &PreviousBreakpointProperty,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let next_mode = match self.strip_mode {
|
||||
Some(ActiveBreakpointStripMode::Log) => None,
|
||||
Some(ActiveBreakpointStripMode::Condition) => Some(ActiveBreakpointStripMode::Log),
|
||||
Some(ActiveBreakpointStripMode::HitCondition) => {
|
||||
Some(ActiveBreakpointStripMode::Condition)
|
||||
}
|
||||
None => Some(ActiveBreakpointStripMode::HitCondition),
|
||||
};
|
||||
if let Some(mode) = next_mode {
|
||||
self.set_active_breakpoint_property(mode, window, cx);
|
||||
} else {
|
||||
self.strip_mode.take();
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
fn next_breakpoint_property(
|
||||
&mut self,
|
||||
_: &NextBreakpointProperty,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let next_mode = match self.strip_mode {
|
||||
Some(ActiveBreakpointStripMode::Log) => Some(ActiveBreakpointStripMode::Condition),
|
||||
Some(ActiveBreakpointStripMode::Condition) => {
|
||||
Some(ActiveBreakpointStripMode::HitCondition)
|
||||
}
|
||||
Some(ActiveBreakpointStripMode::HitCondition) => None,
|
||||
None => Some(ActiveBreakpointStripMode::Log),
|
||||
};
|
||||
if let Some(mode) = next_mode {
|
||||
self.set_active_breakpoint_property(mode, window, cx);
|
||||
} else {
|
||||
self.strip_mode.take();
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {
|
||||
@@ -490,31 +294,20 @@ impl BreakpointList {
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_list(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let selected_ix = self.selected_ix;
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let supported_breakpoint_properties = self
|
||||
.session
|
||||
.as_ref()
|
||||
.map(|session| SupportedBreakpointProperties::from(session.read(cx).capabilities()))
|
||||
.unwrap_or_else(SupportedBreakpointProperties::empty);
|
||||
let strip_mode = self.strip_mode;
|
||||
uniform_list(
|
||||
"breakpoint-list",
|
||||
self.breakpoints.len(),
|
||||
cx.processor(move |this, range: Range<usize>, _, _| {
|
||||
cx.processor(move |this, range: Range<usize>, window, cx| {
|
||||
range
|
||||
.clone()
|
||||
.zip(&mut this.breakpoints[range])
|
||||
.map(|(ix, breakpoint)| {
|
||||
breakpoint
|
||||
.render(
|
||||
strip_mode,
|
||||
supported_breakpoint_properties,
|
||||
ix,
|
||||
Some(ix) == selected_ix,
|
||||
focus_handle.clone(),
|
||||
)
|
||||
.render(ix, focus_handle.clone(), window, cx)
|
||||
.toggle_state(Some(ix) == selected_ix)
|
||||
.into_any_element()
|
||||
})
|
||||
.collect()
|
||||
@@ -650,6 +443,7 @@ impl BreakpointList {
|
||||
|
||||
impl Render for BreakpointList {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
||||
// let old_len = self.breakpoints.len();
|
||||
let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx);
|
||||
self.breakpoints.clear();
|
||||
let weak = cx.weak_entity();
|
||||
@@ -729,46 +523,15 @@ impl Render for BreakpointList {
|
||||
.on_action(cx.listener(Self::select_previous))
|
||||
.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_last))
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::toggle_enable_breakpoint))
|
||||
.on_action(cx.listener(Self::unset_breakpoint))
|
||||
.on_action(cx.listener(Self::next_breakpoint_property))
|
||||
.on_action(cx.listener(Self::previous_breakpoint_property))
|
||||
.size_full()
|
||||
.m_0p5()
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.child(self.render_list(cx))
|
||||
.children(self.render_vertical_scrollbar(cx)),
|
||||
)
|
||||
.when_some(self.strip_mode, |this, _| {
|
||||
this.child(Divider::horizontal()).child(
|
||||
h_flex()
|
||||
// .w_full()
|
||||
.m_0p5()
|
||||
.p_0p5()
|
||||
.border_1()
|
||||
.rounded_sm()
|
||||
.when(
|
||||
self.input.focus_handle(cx).contains_focused(window, cx),
|
||||
|this| {
|
||||
let colors = cx.theme().colors();
|
||||
let border = if self.input.read(cx).read_only(cx) {
|
||||
colors.border_disabled
|
||||
} else {
|
||||
colors.border_focused
|
||||
};
|
||||
this.border_color(border)
|
||||
},
|
||||
)
|
||||
.child(self.input.clone()),
|
||||
)
|
||||
})
|
||||
.child(self.render_list(window, cx))
|
||||
.children(self.render_vertical_scrollbar(cx))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct LineBreakpoint {
|
||||
name: SharedString,
|
||||
@@ -780,10 +543,7 @@ struct LineBreakpoint {
|
||||
impl LineBreakpoint {
|
||||
fn render(
|
||||
&mut self,
|
||||
props: SupportedBreakpointProperties,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
ix: usize,
|
||||
is_selected: bool,
|
||||
focus_handle: FocusHandle,
|
||||
weak: WeakEntity<BreakpointList>,
|
||||
) -> ListItem {
|
||||
@@ -834,16 +594,15 @@ impl LineBreakpoint {
|
||||
})
|
||||
.child(Indicator::icon(Icon::new(icon_name)).color(Color::Debugger))
|
||||
.on_mouse_down(MouseButton::Left, move |_, _, _| {});
|
||||
|
||||
ListItem::new(SharedString::from(format!(
|
||||
"breakpoint-ui-item-{:?}/{}:{}",
|
||||
self.dir, self.name, self.line
|
||||
)))
|
||||
.on_click({
|
||||
let weak = weak.clone();
|
||||
move |_, window, cx| {
|
||||
move |_, _, cx| {
|
||||
weak.update(cx, |breakpoint_list, cx| {
|
||||
breakpoint_list.select_ix(Some(ix), window, cx);
|
||||
breakpoint_list.select_ix(Some(ix), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -854,26 +613,21 @@ impl LineBreakpoint {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.mr_4()
|
||||
.py_0p5()
|
||||
v_flex()
|
||||
.py_1()
|
||||
.gap_1()
|
||||
.min_h(px(26.))
|
||||
.justify_between()
|
||||
.justify_center()
|
||||
.id(SharedString::from(format!(
|
||||
"breakpoint-ui-on-click-go-to-line-{:?}/{}:{}",
|
||||
self.dir, self.name, self.line
|
||||
)))
|
||||
.on_click({
|
||||
let weak = weak.clone();
|
||||
move |_, window, cx| {
|
||||
weak.update(cx, |breakpoint_list, cx| {
|
||||
breakpoint_list.select_ix(Some(ix), window, cx);
|
||||
breakpoint_list.go_to_line_breakpoint(path.clone(), row, window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
.on_click(move |_, window, cx| {
|
||||
weak.update(cx, |breakpoint_list, cx| {
|
||||
breakpoint_list.select_ix(Some(ix), cx);
|
||||
breakpoint_list.go_to_line_breakpoint(path.clone(), row, window, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.cursor_pointer()
|
||||
.child(
|
||||
@@ -890,20 +644,8 @@ impl LineBreakpoint {
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel)
|
||||
})),
|
||||
)
|
||||
.child(BreakpointOptionsStrip {
|
||||
props,
|
||||
breakpoint: BreakpointEntry {
|
||||
kind: BreakpointEntryKind::LineBreakpoint(self.clone()),
|
||||
weak: weak,
|
||||
},
|
||||
is_selected,
|
||||
focus_handle,
|
||||
strip_mode,
|
||||
index: ix,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.toggle_state(is_selected)
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -916,10 +658,7 @@ struct ExceptionBreakpoint {
|
||||
impl ExceptionBreakpoint {
|
||||
fn render(
|
||||
&mut self,
|
||||
props: SupportedBreakpointProperties,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
ix: usize,
|
||||
is_selected: bool,
|
||||
focus_handle: FocusHandle,
|
||||
list: WeakEntity<BreakpointList>,
|
||||
) -> ListItem {
|
||||
@@ -930,15 +669,15 @@ impl ExceptionBreakpoint {
|
||||
};
|
||||
let id = SharedString::from(&self.id);
|
||||
let is_enabled = self.is_enabled;
|
||||
let weak = list.clone();
|
||||
|
||||
ListItem::new(SharedString::from(format!(
|
||||
"exception-breakpoint-ui-item-{}",
|
||||
self.id
|
||||
)))
|
||||
.on_click({
|
||||
let list = list.clone();
|
||||
move |_, window, cx| {
|
||||
list.update(cx, |list, cx| list.select_ix(Some(ix), window, cx))
|
||||
move |_, _, cx| {
|
||||
list.update(cx, |list, cx| list.select_ix(Some(ix), cx))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
@@ -952,21 +691,18 @@ impl ExceptionBreakpoint {
|
||||
"exception-breakpoint-ui-item-{}-click-handler",
|
||||
self.id
|
||||
)))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
if is_enabled {
|
||||
"Disable Exception Breakpoint"
|
||||
} else {
|
||||
"Enable Exception Breakpoint"
|
||||
},
|
||||
&ToggleEnableBreakpoint,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
if is_enabled {
|
||||
"Disable Exception Breakpoint"
|
||||
} else {
|
||||
"Enable Exception Breakpoint"
|
||||
},
|
||||
&ToggleEnableBreakpoint,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let list = list.clone();
|
||||
@@ -986,40 +722,21 @@ impl ExceptionBreakpoint {
|
||||
.child(Indicator::icon(Icon::new(IconName::Flame)).color(color)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.mr_4()
|
||||
.py_0p5()
|
||||
.justify_between()
|
||||
v_flex()
|
||||
.py_1()
|
||||
.gap_1()
|
||||
.min_h(px(26.))
|
||||
.justify_center()
|
||||
.id(("exception-breakpoint-label", ix))
|
||||
.child(
|
||||
v_flex()
|
||||
.py_1()
|
||||
.gap_1()
|
||||
.min_h(px(26.))
|
||||
.justify_center()
|
||||
.id(("exception-breakpoint-label", ix))
|
||||
.child(
|
||||
Label::new(self.data.label.clone())
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||
)
|
||||
.when_some(self.data.description.clone(), |el, description| {
|
||||
el.tooltip(Tooltip::text(description))
|
||||
}),
|
||||
Label::new(self.data.label.clone())
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||
)
|
||||
.child(BreakpointOptionsStrip {
|
||||
props,
|
||||
breakpoint: BreakpointEntry {
|
||||
kind: BreakpointEntryKind::ExceptionBreakpoint(self.clone()),
|
||||
weak: weak,
|
||||
},
|
||||
is_selected,
|
||||
focus_handle,
|
||||
strip_mode,
|
||||
index: ix,
|
||||
.when_some(self.data.description.clone(), |el, description| {
|
||||
el.tooltip(Tooltip::text(description))
|
||||
}),
|
||||
)
|
||||
.toggle_state(is_selected)
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -1037,264 +754,18 @@ struct BreakpointEntry {
|
||||
impl BreakpointEntry {
|
||||
fn render(
|
||||
&mut self,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
props: SupportedBreakpointProperties,
|
||||
ix: usize,
|
||||
is_selected: bool,
|
||||
focus_handle: FocusHandle,
|
||||
_: &mut Window,
|
||||
_: &mut App,
|
||||
) -> ListItem {
|
||||
match &mut self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => line_breakpoint.render(
|
||||
props,
|
||||
strip_mode,
|
||||
ix,
|
||||
is_selected,
|
||||
focus_handle,
|
||||
self.weak.clone(),
|
||||
),
|
||||
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => exception_breakpoint
|
||||
.render(
|
||||
props.for_exception_breakpoints(),
|
||||
strip_mode,
|
||||
ix,
|
||||
is_selected,
|
||||
focus_handle,
|
||||
self.weak.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> SharedString {
|
||||
match &self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => format!(
|
||||
"source-breakpoint-control-strip-{:?}:{}",
|
||||
line_breakpoint.breakpoint.path, line_breakpoint.breakpoint.row
|
||||
)
|
||||
.into(),
|
||||
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => format!(
|
||||
"exception-breakpoint-control-strip--{}",
|
||||
exception_breakpoint.id
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_log(&self) -> bool {
|
||||
match &self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
line_breakpoint.breakpoint.message.is_some()
|
||||
line_breakpoint.render(ix, focus_handle, self.weak.clone())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_condition(&self) -> bool {
|
||||
match &self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
line_breakpoint.breakpoint.condition.is_some()
|
||||
}
|
||||
// We don't support conditions on exception breakpoints
|
||||
BreakpointEntryKind::ExceptionBreakpoint(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_hit_condition(&self) -> bool {
|
||||
match &self.kind {
|
||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||
line_breakpoint.breakpoint.hit_condition.is_some()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SupportedBreakpointProperties: u32 {
|
||||
const LOG = 1 << 0;
|
||||
const CONDITION = 1 << 1;
|
||||
const HIT_CONDITION = 1 << 2;
|
||||
// Conditions for exceptions can be set only when exception filters are supported.
|
||||
const EXCEPTION_FILTER_OPTIONS = 1 << 3;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Capabilities> for SupportedBreakpointProperties {
|
||||
fn from(caps: &Capabilities) -> Self {
|
||||
let mut this = Self::empty();
|
||||
for (prop, offset) in [
|
||||
(caps.supports_log_points, Self::LOG),
|
||||
(caps.supports_conditional_breakpoints, Self::CONDITION),
|
||||
(
|
||||
caps.supports_hit_conditional_breakpoints,
|
||||
Self::HIT_CONDITION,
|
||||
),
|
||||
(
|
||||
caps.supports_exception_options,
|
||||
Self::EXCEPTION_FILTER_OPTIONS,
|
||||
),
|
||||
] {
|
||||
if prop.unwrap_or_default() {
|
||||
this.insert(offset);
|
||||
}
|
||||
}
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl SupportedBreakpointProperties {
|
||||
fn for_exception_breakpoints(self) -> Self {
|
||||
// TODO: we don't yet support conditions for exception breakpoints at the data layer, hence all props are disabled here.
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
#[derive(IntoElement)]
|
||||
struct BreakpointOptionsStrip {
|
||||
props: SupportedBreakpointProperties,
|
||||
breakpoint: BreakpointEntry,
|
||||
is_selected: bool,
|
||||
focus_handle: FocusHandle,
|
||||
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl BreakpointOptionsStrip {
|
||||
fn is_toggled(&self, expected_mode: ActiveBreakpointStripMode) -> bool {
|
||||
self.is_selected && self.strip_mode == Some(expected_mode)
|
||||
}
|
||||
fn on_click_callback(
|
||||
&self,
|
||||
mode: ActiveBreakpointStripMode,
|
||||
) -> impl for<'a> Fn(&ClickEvent, &mut Window, &'a mut App) + use<> {
|
||||
let list = self.breakpoint.weak.clone();
|
||||
let ix = self.index;
|
||||
move |_, window, cx| {
|
||||
list.update(cx, |this, cx| {
|
||||
if this.strip_mode != Some(mode) {
|
||||
this.set_active_breakpoint_property(mode, window, cx);
|
||||
} else if this.selected_ix == Some(ix) {
|
||||
this.strip_mode.take();
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
fn add_border(
|
||||
&self,
|
||||
kind: ActiveBreakpointStripMode,
|
||||
available: bool,
|
||||
window: &Window,
|
||||
cx: &App,
|
||||
) -> impl Fn(Div) -> Div {
|
||||
move |this: Div| {
|
||||
// Avoid layout shifts in case there's no colored border
|
||||
let this = this.border_2().rounded_sm();
|
||||
if self.is_selected && self.strip_mode == Some(kind) {
|
||||
let theme = cx.theme().colors();
|
||||
if self.focus_handle.is_focused(window) {
|
||||
this.border_color(theme.border_selected)
|
||||
} else {
|
||||
this.border_color(theme.border_disabled)
|
||||
}
|
||||
} else if !available {
|
||||
this.border_color(cx.theme().colors().border_disabled)
|
||||
} else {
|
||||
this
|
||||
BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => {
|
||||
exception_breakpoint.render(ix, focus_handle, self.weak.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl RenderOnce for BreakpointOptionsStrip {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let id = self.breakpoint.id();
|
||||
let supports_logs = self.props.contains(SupportedBreakpointProperties::LOG);
|
||||
let supports_condition = self
|
||||
.props
|
||||
.contains(SupportedBreakpointProperties::CONDITION);
|
||||
let supports_hit_condition = self
|
||||
.props
|
||||
.contains(SupportedBreakpointProperties::HIT_CONDITION);
|
||||
let has_logs = self.breakpoint.has_log();
|
||||
let has_condition = self.breakpoint.has_condition();
|
||||
let has_hit_condition = self.breakpoint.has_hit_condition();
|
||||
let style_for_toggle = |mode, is_enabled| {
|
||||
if is_enabled && self.strip_mode == Some(mode) && self.is_selected {
|
||||
ui::ButtonStyle::Filled
|
||||
} else {
|
||||
ui::ButtonStyle::Subtle
|
||||
}
|
||||
};
|
||||
let color_for_toggle = |is_enabled| {
|
||||
if is_enabled {
|
||||
ui::Color::Default
|
||||
} else {
|
||||
ui::Color::Muted
|
||||
}
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
div() .map(self.add_border(ActiveBreakpointStripMode::Log, supports_logs, window, cx))
|
||||
.child(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("{id}-log-toggle")),
|
||||
IconName::ScrollText,
|
||||
)
|
||||
.style(style_for_toggle(ActiveBreakpointStripMode::Log, has_logs))
|
||||
.icon_color(color_for_toggle(has_logs))
|
||||
.disabled(!supports_logs)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Log))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Log)).tooltip(|window, cx| Tooltip::with_meta("Set Log Message", None, "Set log message to display (instead of stopping) when a breakpoint is hit", window, cx))
|
||||
)
|
||||
.when(!has_logs && !self.is_selected, |this| this.invisible()),
|
||||
)
|
||||
.child(
|
||||
div().map(self.add_border(
|
||||
ActiveBreakpointStripMode::Condition,
|
||||
supports_condition,
|
||||
window, cx
|
||||
))
|
||||
.child(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("{id}-condition-toggle")),
|
||||
IconName::SplitAlt,
|
||||
)
|
||||
.style(style_for_toggle(
|
||||
ActiveBreakpointStripMode::Condition,
|
||||
has_condition
|
||||
))
|
||||
.icon_color(color_for_toggle(has_condition))
|
||||
.disabled(!supports_condition)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Condition))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Condition))
|
||||
.tooltip(|window, cx| Tooltip::with_meta("Set Condition", None, "Set condition to evaluate when a breakpoint is hit. Program execution will stop only when the condition is met", window, cx))
|
||||
)
|
||||
.when(!has_condition && !self.is_selected, |this| this.invisible()),
|
||||
)
|
||||
.child(
|
||||
div() .map(self.add_border(
|
||||
ActiveBreakpointStripMode::HitCondition,
|
||||
supports_hit_condition,window, cx
|
||||
))
|
||||
.child(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("{id}-hit-condition-toggle")),
|
||||
IconName::ArrowDown10,
|
||||
)
|
||||
.style(style_for_toggle(
|
||||
ActiveBreakpointStripMode::HitCondition,
|
||||
has_hit_condition,
|
||||
))
|
||||
.icon_color(color_for_toggle(has_hit_condition))
|
||||
.disabled(!supports_hit_condition)
|
||||
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::HitCondition))
|
||||
.on_click(self.on_click_callback(ActiveBreakpointStripMode::HitCondition)).tooltip(|window, cx| Tooltip::with_meta("Set Hit Condition", None, "Set expression that controls how many hits of the breakpoint are ignored.", window, cx))
|
||||
)
|
||||
.when(!has_hit_condition && !self.is_selected, |this| {
|
||||
this.invisible()
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use collections::HashMap;
|
||||
use dap::StackFrameId;
|
||||
use editor::{
|
||||
Anchor, Bias, DebugStackFrameLine, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer,
|
||||
RowHighlightOptions, SelectionEffects, ToPoint, scroll::Autoscroll,
|
||||
RowHighlightOptions, ToPoint, scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
AnyView, App, AppContext, Entity, EventEmitter, Focusable, IntoElement, Render, SharedString,
|
||||
@@ -99,11 +99,10 @@ impl StackTraceView {
|
||||
if frame_anchor.excerpt_id
|
||||
!= editor.selections.newest_anchor().head().excerpt_id
|
||||
{
|
||||
let effects = SelectionEffects::scroll(
|
||||
Autoscroll::center().for_anchor(frame_anchor),
|
||||
);
|
||||
let auto_scroll =
|
||||
Some(Autoscroll::center().for_anchor(frame_anchor));
|
||||
|
||||
editor.change_selections(effects, window, cx, |selections| {
|
||||
editor.change_selections(auto_scroll, window, cx, |selections| {
|
||||
let selection_id = selections.new_selection_id();
|
||||
|
||||
let selection = Selection {
|
||||
|
||||
@@ -4,6 +4,7 @@ use editor::{
|
||||
Anchor, Editor, EditorSnapshot, ToOffset,
|
||||
display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle},
|
||||
hover_popover::diagnostics_markdown_style,
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{AppContext, Entity, Focusable, WeakEntity};
|
||||
use language::{BufferId, Diagnostic, DiagnosticEntry};
|
||||
@@ -310,7 +311,7 @@ impl DiagnosticBlock {
|
||||
let range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
|
||||
|
||||
editor.unfold_ranges(&[range.start..range.end], true, false, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_ranges([range.start..range.start]);
|
||||
});
|
||||
window.focus(&editor.focus_handle(cx));
|
||||
|
||||
@@ -12,6 +12,7 @@ use diagnostic_renderer::DiagnosticBlock;
|
||||
use editor::{
|
||||
DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
|
||||
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use futures::future::join_all;
|
||||
use gpui::{
|
||||
@@ -625,7 +626,7 @@ impl ProjectDiagnosticsEditor {
|
||||
if let Some(anchor_range) = anchor_ranges.first() {
|
||||
let range_to_select = anchor_range.start..anchor_range.start;
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_anchor_ranges([range_to_select]);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -388,6 +388,7 @@ actions!(
|
||||
RestartLanguageServer,
|
||||
RevealInFileManager,
|
||||
ReverseLines,
|
||||
RevertFile,
|
||||
ReloadFile,
|
||||
Rewrap,
|
||||
RunFlycheck,
|
||||
|
||||
@@ -966,22 +966,10 @@ impl DisplaySnapshot {
|
||||
.and_then(|id| id.style(&editor_style.syntax));
|
||||
|
||||
if let Some(chunk_highlight) = chunk.highlight_style {
|
||||
// For color inlays, blend the color with the editor background
|
||||
let mut processed_highlight = chunk_highlight;
|
||||
if chunk.is_inlay {
|
||||
if let Some(inlay_color) = chunk_highlight.color {
|
||||
// Only blend if the color has transparency (alpha < 1.0)
|
||||
if inlay_color.a < 1.0 {
|
||||
let blended_color = editor_style.background.blend(inlay_color);
|
||||
processed_highlight.color = Some(blended_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(highlight_style) = highlight_style.as_mut() {
|
||||
highlight_style.highlight(processed_highlight);
|
||||
highlight_style.highlight(chunk_highlight);
|
||||
} else {
|
||||
highlight_style = Some(processed_highlight);
|
||||
highlight_style = Some(chunk_highlight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -327,9 +327,9 @@ impl<'a> Iterator for InlayChunks<'a> {
|
||||
InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
|
||||
InlayId::Color(_) => match inlay.color {
|
||||
Some(color) => {
|
||||
let mut style = self.highlight_styles.inlay_hint.unwrap_or_default();
|
||||
let style = self.highlight_styles.inlay_hint.get_or_insert_default();
|
||||
style.color = Some(color);
|
||||
Some(style)
|
||||
Some(*style)
|
||||
}
|
||||
None => self.highlight_styles.inlay_hint,
|
||||
},
|
||||
|
||||
@@ -10051,7 +10051,7 @@ fn compute_auto_height_layout(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
Editor, MultiBuffer, SelectionEffects,
|
||||
Editor, MultiBuffer,
|
||||
display_map::{BlockPlacement, BlockProperties},
|
||||
editor_tests::{init_test, update_test_language_settings},
|
||||
};
|
||||
@@ -10176,7 +10176,7 @@ mod tests {
|
||||
window
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.cursor_shape = CursorShape::Block;
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([
|
||||
Point::new(0, 0)..Point::new(1, 0),
|
||||
Point::new(3, 2)..Point::new(3, 3),
|
||||
|
||||
@@ -1257,7 +1257,7 @@ mod tests {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let anchor_range = snapshot.anchor_before(selection_range.start)
|
||||
..snapshot.anchor_after(selection_range.end);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
editor.change_selections(Some(crate::Autoscroll::fit()), window, cx, |s| {
|
||||
s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
EditorSnapshot, GlobalDiagnosticRenderer, Hover,
|
||||
display_map::{InlayOffset, ToDisplayPoint, invisibles::is_invisible},
|
||||
hover_links::{InlayHighlight, RangeInEditor},
|
||||
scroll::ScrollAmount,
|
||||
scroll::{Autoscroll, ScrollAmount},
|
||||
};
|
||||
use anyhow::Context as _;
|
||||
use gpui::{
|
||||
@@ -648,7 +648,7 @@ pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
..Default::default()
|
||||
},
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
selection_background_color: { cx.theme().players().local().selection },
|
||||
heading: StyleRefinement::default()
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.text_base()
|
||||
@@ -697,7 +697,7 @@ pub fn diagnostics_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||
..Default::default()
|
||||
},
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().colors().element_selection_background,
|
||||
selection_background_color: { cx.theme().players().local().selection },
|
||||
height_is_multiple_of_line_height: true,
|
||||
heading: StyleRefinement::default()
|
||||
.font_weight(FontWeight::BOLD)
|
||||
@@ -746,7 +746,7 @@ pub fn open_markdown_url(link: SharedString, window: &mut Window, cx: &mut App)
|
||||
};
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(
|
||||
Default::default(),
|
||||
Some(Autoscroll::fit()),
|
||||
window,
|
||||
cx,
|
||||
|selections| {
|
||||
|
||||
@@ -956,7 +956,7 @@ fn fetch_and_update_hints(
|
||||
.update(cx, |editor, cx| {
|
||||
if got_throttled {
|
||||
let query_not_around_visible_range = match editor
|
||||
.visible_excerpts(None, cx)
|
||||
.excerpts_for_inlay_hints_query(None, cx)
|
||||
.remove(&query.excerpt_id)
|
||||
{
|
||||
Some((_, _, current_visible_range)) => {
|
||||
@@ -1302,7 +1302,6 @@ fn apply_hint_update(
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use crate::SelectionEffects;
|
||||
use crate::editor_tests::update_test_language_settings;
|
||||
use crate::scroll::ScrollAmount;
|
||||
use crate::{ExcerptRange, scroll::Autoscroll, test::editor_lsp_test_context::rust_lang};
|
||||
@@ -1385,9 +1384,7 @@ pub mod tests {
|
||||
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input("some change", window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -1701,9 +1698,7 @@ pub mod tests {
|
||||
|
||||
rs_editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input("some rs change", window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -1738,9 +1733,7 @@ pub mod tests {
|
||||
|
||||
md_editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input("some md change", window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -2162,9 +2155,7 @@ pub mod tests {
|
||||
] {
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(change_after_opening, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -2208,9 +2199,7 @@ pub mod tests {
|
||||
edits.push(cx.spawn(|mut cx| async move {
|
||||
task_editor
|
||||
.update(&mut cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([13..13])
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(async_later_change, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -2458,12 +2447,9 @@ pub mod tests {
|
||||
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_ranges([selection_in_cached_range..selection_in_cached_range]),
|
||||
);
|
||||
editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
|
||||
s.select_ranges([selection_in_cached_range..selection_in_cached_range])
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_millis(
|
||||
@@ -2525,7 +2511,9 @@ pub mod tests {
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> Range<Point> {
|
||||
let ranges = editor
|
||||
.update(cx, |editor, _window, cx| editor.visible_excerpts(None, cx))
|
||||
.update(cx, |editor, _window, cx| {
|
||||
editor.excerpts_for_inlay_hints_query(None, cx)
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
ranges.len(),
|
||||
@@ -2724,24 +2712,15 @@ pub mod tests {
|
||||
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::Next),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]),
|
||||
);
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::Next),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]),
|
||||
);
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::Next),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]),
|
||||
);
|
||||
editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
|
||||
s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
|
||||
});
|
||||
editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
|
||||
s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
|
||||
});
|
||||
editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
|
||||
s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
@@ -2766,12 +2745,9 @@ pub mod tests {
|
||||
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::Next),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]),
|
||||
);
|
||||
editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
|
||||
s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_millis(
|
||||
@@ -2802,12 +2778,9 @@ pub mod tests {
|
||||
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::Next),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]),
|
||||
);
|
||||
editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
|
||||
s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_millis(
|
||||
@@ -2839,7 +2812,7 @@ pub mod tests {
|
||||
editor_edited.store(true, Ordering::Release);
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
|
||||
});
|
||||
editor.handle_input("++++more text++++", window, cx);
|
||||
@@ -3157,7 +3130,7 @@ pub mod tests {
|
||||
cx.executor().run_until_parked();
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
})
|
||||
})
|
||||
@@ -3439,7 +3412,7 @@ pub mod tests {
|
||||
cx.executor().run_until_parked();
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -778,7 +778,7 @@ impl Item for Editor {
|
||||
|
||||
fn deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let selection = self.selections.newest_anchor();
|
||||
self.push_to_nav_history(selection.head(), None, true, false, cx);
|
||||
self.push_to_nav_history(selection.head(), None, true, cx);
|
||||
}
|
||||
|
||||
fn workspace_deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -1352,7 +1352,7 @@ impl ProjectItem for Editor {
|
||||
cx,
|
||||
);
|
||||
if !restoration_data.selections.is_empty() {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges(clip_ranges(&restoration_data.selections, &snapshot));
|
||||
});
|
||||
}
|
||||
@@ -1521,7 +1521,7 @@ impl SearchableItem for Editor {
|
||||
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
||||
let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
|
||||
let snapshot = &self.snapshot(window, cx).buffer_snapshot;
|
||||
let selection = self.selections.newest_adjusted(cx);
|
||||
let selection = self.selections.newest::<usize>(cx);
|
||||
|
||||
match setting {
|
||||
SeedQuerySetting::Never => String::new(),
|
||||
@@ -1558,7 +1558,7 @@ impl SearchableItem for Editor {
|
||||
) {
|
||||
self.unfold_ranges(&[matches[index].clone()], false, true, cx);
|
||||
let range = self.range_for_match(&matches[index]);
|
||||
self.change_selections(Default::default(), window, cx, |s| {
|
||||
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
})
|
||||
}
|
||||
@@ -1570,7 +1570,7 @@ impl SearchableItem for Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.unfold_ranges(matches, false, false, cx);
|
||||
self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
self.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges(matches.iter().cloned())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -843,7 +843,7 @@ mod jsx_tag_autoclose_tests {
|
||||
let mut cx = EditorTestContext::for_editor(editor, cx).await;
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select(vec![
|
||||
Selection::from_offset(4),
|
||||
Selection::from_offset(9),
|
||||
|
||||
@@ -3,10 +3,10 @@ use std::{cmp, ops::Range};
|
||||
use collections::HashMap;
|
||||
use futures::future::join_all;
|
||||
use gpui::{Hsla, Rgba};
|
||||
use itertools::Itertools;
|
||||
use language::point_from_lsp;
|
||||
use lsp::LanguageServerId;
|
||||
use multi_buffer::Anchor;
|
||||
use project::{DocumentColor, lsp_store::ColorFetchStrategy};
|
||||
use project::DocumentColor;
|
||||
use settings::Settings as _;
|
||||
use text::{Bias, BufferId, OffsetRangeExt as _};
|
||||
use ui::{App, Context, Window};
|
||||
@@ -19,7 +19,6 @@ use crate::{
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct LspColorData {
|
||||
cache_version_used: usize,
|
||||
colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>,
|
||||
inlay_colors: HashMap<InlayId, usize>,
|
||||
render_mode: DocumentColorsRenderMode,
|
||||
@@ -28,7 +27,6 @@ pub(super) struct LspColorData {
|
||||
impl LspColorData {
|
||||
pub fn new(cx: &App) -> Self {
|
||||
Self {
|
||||
cache_version_used: 0,
|
||||
colors: Vec::new(),
|
||||
inlay_colors: HashMap::default(),
|
||||
render_mode: EditorSettings::get_global(cx).lsp_document_colors,
|
||||
@@ -124,7 +122,7 @@ impl LspColorData {
|
||||
impl Editor {
|
||||
pub(super) fn refresh_colors(
|
||||
&mut self,
|
||||
ignore_cache: bool,
|
||||
for_server_id: Option<LanguageServerId>,
|
||||
buffer_id: Option<BufferId>,
|
||||
_: &Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -143,41 +141,29 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let visible_buffers = self
|
||||
.visible_excerpts(None, cx)
|
||||
.into_values()
|
||||
.map(|(buffer, ..)| buffer)
|
||||
.filter(|editor_buffer| {
|
||||
buffer_id.is_none_or(|buffer_id| buffer_id == editor_buffer.read(cx).remote_id())
|
||||
})
|
||||
.unique_by(|buffer| buffer.read(cx).remote_id())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let all_colors_task = project.read(cx).lsp_store().update(cx, |lsp_store, cx| {
|
||||
visible_buffers
|
||||
self.buffer()
|
||||
.update(cx, |multi_buffer, cx| {
|
||||
multi_buffer
|
||||
.all_buffers()
|
||||
.into_iter()
|
||||
.filter(|editor_buffer| {
|
||||
buffer_id.is_none_or(|buffer_id| {
|
||||
buffer_id == editor_buffer.read(cx).remote_id()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.into_iter()
|
||||
.filter_map(|buffer| {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let fetch_strategy = if ignore_cache {
|
||||
ColorFetchStrategy::IgnoreCache
|
||||
} else {
|
||||
ColorFetchStrategy::UseCache {
|
||||
known_cache_version: self
|
||||
.colors
|
||||
.as_ref()
|
||||
.map(|colors| colors.cache_version_used),
|
||||
}
|
||||
};
|
||||
let colors_task = lsp_store.document_colors(fetch_strategy, buffer, cx)?;
|
||||
let colors_task = lsp_store.document_colors(for_server_id, buffer, cx)?;
|
||||
Some(async move { (buffer_id, colors_task.await) })
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
cx.spawn(async move |editor, cx| {
|
||||
let all_colors = join_all(all_colors_task).await;
|
||||
if all_colors.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Ok((multi_buffer_snapshot, editor_excerpts)) = editor.update(cx, |editor, cx| {
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let editor_excerpts = multi_buffer_snapshot.excerpts().fold(
|
||||
@@ -201,7 +187,6 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut cache_version = None;
|
||||
let mut new_editor_colors = Vec::<(Range<Anchor>, DocumentColor)>::new();
|
||||
for (buffer_id, colors) in all_colors {
|
||||
let Some(excerpts) = editor_excerpts.get(&buffer_id) else {
|
||||
@@ -209,8 +194,7 @@ impl Editor {
|
||||
};
|
||||
match colors {
|
||||
Ok(colors) => {
|
||||
cache_version = colors.cache_version;
|
||||
for color in colors.colors {
|
||||
for color in colors {
|
||||
let color_start = point_from_lsp(color.lsp_range.start);
|
||||
let color_end = point_from_lsp(color.lsp_range.end);
|
||||
|
||||
@@ -353,9 +337,6 @@ impl Editor {
|
||||
}
|
||||
|
||||
let mut updated = colors.set_colors(new_color_inlays);
|
||||
if let Some(cache_version) = cache_version {
|
||||
colors.cache_version_used = cache_version;
|
||||
}
|
||||
if colors.render_mode == DocumentColorsRenderMode::Inlay
|
||||
&& (!colors_splice.to_insert.is_empty()
|
||||
|| !colors_splice.to_remove.is_empty())
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor,
|
||||
EvaluateSelectedText, FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation,
|
||||
GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionEffects,
|
||||
SelectionExt, ToDisplayPoint, ToggleCodeActions,
|
||||
GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionExt,
|
||||
ToDisplayPoint, ToggleCodeActions,
|
||||
actions::{Format, FormatSelections},
|
||||
selections_collection::SelectionsCollection,
|
||||
};
|
||||
@@ -177,7 +177,7 @@ pub fn deploy_context_menu(
|
||||
let anchor = buffer.anchor_before(point.to_point(&display_map));
|
||||
if !display_ranges(&display_map, &editor.selections).any(|r| r.contains(&point)) {
|
||||
// Move the cursor to the clicked location so that dispatched actions make sense
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.clear_disjoint();
|
||||
s.set_pending_anchor_range(anchor..anchor, SelectMode::Character);
|
||||
});
|
||||
@@ -275,10 +275,10 @@ pub fn deploy_context_menu(
|
||||
cx,
|
||||
),
|
||||
None => {
|
||||
let character_size = editor.character_dimensions(window);
|
||||
let character_size = editor.character_size(window);
|
||||
let menu_position = MenuPosition::PinnedToEditor {
|
||||
source: source_anchor,
|
||||
offset: gpui::point(character_size.em_width, character_size.line_height),
|
||||
offset: gpui::point(character_size.width, character_size.height),
|
||||
};
|
||||
Some(MouseContextMenu::new(
|
||||
editor,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SelectionEffects, SemanticsProvider};
|
||||
use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider};
|
||||
use buffer_diff::BufferDiff;
|
||||
use collections::HashSet;
|
||||
use futures::{channel::mpsc, future::join_all};
|
||||
@@ -213,9 +213,7 @@ impl ProposedChangesEditor {
|
||||
|
||||
self.buffer_entries = buffer_entries;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
selections.refresh()
|
||||
});
|
||||
editor.change_selections(None, window, cx, |selections| selections.refresh());
|
||||
editor.buffer.update(cx, |buffer, cx| {
|
||||
for diff in new_diffs {
|
||||
buffer.add_diff(diff, cx)
|
||||
|
||||
@@ -487,9 +487,8 @@ impl Editor {
|
||||
if opened_first_time {
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
editor
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
editor.refresh_colors(false, None, window, cx);
|
||||
.update(cx, |editor, cx| {
|
||||
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
@@ -600,7 +599,6 @@ impl Editor {
|
||||
);
|
||||
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
self.refresh_colors(false, None, window, cx);
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<f32> {
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{rc::Rc, sync::LazyLock};
|
||||
|
||||
pub use crate::rust_analyzer_ext::expand_macro_recursively;
|
||||
use crate::{
|
||||
DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects,
|
||||
DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer,
|
||||
display_map::{
|
||||
Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot,
|
||||
ToDisplayPoint,
|
||||
@@ -93,9 +93,7 @@ pub fn select_ranges(
|
||||
) {
|
||||
let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
|
||||
assert_eq!(editor.text(cx), unmarked_text);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges(text_ranges)
|
||||
});
|
||||
editor.change_selections(None, window, cx, |s| s.select_ranges(text_ranges));
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
AnchorRangeExt, DisplayPoint, Editor, MultiBuffer, RowExt,
|
||||
AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, RowExt,
|
||||
display_map::{HighlightKey, ToDisplayPoint},
|
||||
};
|
||||
use buffer_diff::DiffHunkStatusKind;
|
||||
@@ -362,7 +362,7 @@ impl EditorTestContext {
|
||||
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||
self.editor.update_in(&mut self.cx, |editor, window, cx| {
|
||||
editor.set_text(unmarked_text, window, cx);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_ranges(selection_ranges)
|
||||
})
|
||||
});
|
||||
@@ -379,7 +379,7 @@ impl EditorTestContext {
|
||||
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||
self.editor.update_in(&mut self.cx, |editor, window, cx| {
|
||||
assert_eq!(editor.text(cx), unmarked_text);
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select_ranges(selection_ranges)
|
||||
})
|
||||
});
|
||||
|
||||
@@ -221,9 +221,6 @@ impl ExampleContext {
|
||||
ThreadEvent::ShowError(thread_error) => {
|
||||
tx.try_send(Err(anyhow!(thread_error.clone()))).ok();
|
||||
}
|
||||
ThreadEvent::RetriesFailed { .. } => {
|
||||
// Ignore retries failed events
|
||||
}
|
||||
ThreadEvent::Stopped(reason) => match reason {
|
||||
Ok(StopReason::EndTurn) => {
|
||||
tx.close_channel();
|
||||
|
||||
@@ -259,36 +259,6 @@ async fn copy_extension_resources(
|
||||
}
|
||||
}
|
||||
|
||||
if !manifest.debug_adapters.is_empty() {
|
||||
for (debug_adapter, entry) in &manifest.debug_adapters {
|
||||
let schema_path = entry.schema_path.clone().unwrap_or_else(|| {
|
||||
PathBuf::from("debug_adapter_schemas".to_owned())
|
||||
.join(debug_adapter.as_ref())
|
||||
.with_extension("json")
|
||||
});
|
||||
let parent = schema_path
|
||||
.parent()
|
||||
.with_context(|| format!("invalid empty schema path for {debug_adapter}"))?;
|
||||
fs::create_dir_all(output_dir.join(parent))?;
|
||||
copy_recursive(
|
||||
fs.as_ref(),
|
||||
&extension_path.join(&schema_path),
|
||||
&output_dir.join(&schema_path),
|
||||
CopyOptions {
|
||||
overwrite: true,
|
||||
ignore_if_exists: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to copy debug adapter schema '{}'",
|
||||
schema_path.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
|
||||
("r", &["r", "R"]),
|
||||
("racket", &["rkt"]),
|
||||
("rescript", &["res", "resi"]),
|
||||
("rst", &["rst"]),
|
||||
("ruby", &["rb", "erb"]),
|
||||
("scheme", &["scm"]),
|
||||
("scss", &["scss"]),
|
||||
|
||||
@@ -74,7 +74,7 @@ impl FakeGitRepository {
|
||||
impl GitRepository for FakeGitRepository {
|
||||
fn reload_index(&self) {}
|
||||
|
||||
fn load_index_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>> {
|
||||
fn load_index_text(&self, path: RepoPath) -> BoxFuture<Option<String>> {
|
||||
async {
|
||||
self.with_state_async(false, move |state| {
|
||||
state
|
||||
@@ -89,7 +89,7 @@ impl GitRepository for FakeGitRepository {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn load_committed_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>> {
|
||||
fn load_committed_text(&self, path: RepoPath) -> BoxFuture<Option<String>> {
|
||||
async {
|
||||
self.with_state_async(false, move |state| {
|
||||
state
|
||||
@@ -108,7 +108,7 @@ impl GitRepository for FakeGitRepository {
|
||||
&self,
|
||||
_commit: String,
|
||||
_cx: AsyncApp,
|
||||
) -> BoxFuture<'_, Result<git::repository::CommitDiff>> {
|
||||
) -> BoxFuture<Result<git::repository::CommitDiff>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ impl GitRepository for FakeGitRepository {
|
||||
path: RepoPath,
|
||||
content: Option<String>,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, anyhow::Result<()>> {
|
||||
) -> BoxFuture<anyhow::Result<()>> {
|
||||
self.with_state_async(true, move |state| {
|
||||
if let Some(message) = &state.simulated_index_write_error_message {
|
||||
anyhow::bail!("{message}");
|
||||
@@ -134,7 +134,7 @@ impl GitRepository for FakeGitRepository {
|
||||
None
|
||||
}
|
||||
|
||||
fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<'_, Result<Vec<Option<String>>>> {
|
||||
fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<Result<Vec<Option<String>>>> {
|
||||
self.with_state_async(false, |state| {
|
||||
Ok(revs
|
||||
.into_iter()
|
||||
@@ -143,7 +143,7 @@ impl GitRepository for FakeGitRepository {
|
||||
})
|
||||
}
|
||||
|
||||
fn show(&self, commit: String) -> BoxFuture<'_, Result<CommitDetails>> {
|
||||
fn show(&self, commit: String) -> BoxFuture<Result<CommitDetails>> {
|
||||
async {
|
||||
Ok(CommitDetails {
|
||||
sha: commit.into(),
|
||||
@@ -158,7 +158,7 @@ impl GitRepository for FakeGitRepository {
|
||||
_commit: String,
|
||||
_mode: ResetMode,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>> {
|
||||
) -> BoxFuture<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ impl GitRepository for FakeGitRepository {
|
||||
_commit: String,
|
||||
_paths: Vec<RepoPath>,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>> {
|
||||
) -> BoxFuture<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -179,11 +179,11 @@ impl GitRepository for FakeGitRepository {
|
||||
self.common_dir_path.clone()
|
||||
}
|
||||
|
||||
fn merge_message(&self) -> BoxFuture<'_, Option<String>> {
|
||||
fn merge_message(&self) -> BoxFuture<Option<String>> {
|
||||
async move { None }.boxed()
|
||||
}
|
||||
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'_, Result<GitStatus>> {
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<Result<GitStatus>> {
|
||||
let workdir_path = self.dot_git_path.parent().unwrap();
|
||||
|
||||
// Load gitignores
|
||||
@@ -314,7 +314,7 @@ impl GitRepository for FakeGitRepository {
|
||||
async move { result? }.boxed()
|
||||
}
|
||||
|
||||
fn branches(&self) -> BoxFuture<'_, Result<Vec<Branch>>> {
|
||||
fn branches(&self) -> BoxFuture<Result<Vec<Branch>>> {
|
||||
self.with_state_async(false, move |state| {
|
||||
let current_branch = &state.current_branch_name;
|
||||
Ok(state
|
||||
@@ -330,21 +330,21 @@ impl GitRepository for FakeGitRepository {
|
||||
})
|
||||
}
|
||||
|
||||
fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
|
||||
fn change_branch(&self, name: String) -> BoxFuture<Result<()>> {
|
||||
self.with_state_async(true, |state| {
|
||||
state.current_branch_name = Some(name);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
|
||||
fn create_branch(&self, name: String) -> BoxFuture<Result<()>> {
|
||||
self.with_state_async(true, move |state| {
|
||||
state.branches.insert(name.to_owned());
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture<'_, Result<git::blame::Blame>> {
|
||||
fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture<Result<git::blame::Blame>> {
|
||||
self.with_state_async(false, move |state| {
|
||||
state
|
||||
.blames
|
||||
@@ -358,7 +358,7 @@ impl GitRepository for FakeGitRepository {
|
||||
&self,
|
||||
_paths: Vec<RepoPath>,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>> {
|
||||
) -> BoxFuture<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -366,7 +366,7 @@ impl GitRepository for FakeGitRepository {
|
||||
&self,
|
||||
_paths: Vec<RepoPath>,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>> {
|
||||
) -> BoxFuture<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ impl GitRepository for FakeGitRepository {
|
||||
_name_and_email: Option<(gpui::SharedString, gpui::SharedString)>,
|
||||
_options: CommitOptions,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>> {
|
||||
) -> BoxFuture<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ impl GitRepository for FakeGitRepository {
|
||||
_askpass: AskPassDelegate,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
_cx: AsyncApp,
|
||||
) -> BoxFuture<'_, Result<git::repository::RemoteCommandOutput>> {
|
||||
) -> BoxFuture<Result<git::repository::RemoteCommandOutput>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ impl GitRepository for FakeGitRepository {
|
||||
_askpass: AskPassDelegate,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
_cx: AsyncApp,
|
||||
) -> BoxFuture<'_, Result<git::repository::RemoteCommandOutput>> {
|
||||
) -> BoxFuture<Result<git::repository::RemoteCommandOutput>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -409,19 +409,19 @@ impl GitRepository for FakeGitRepository {
|
||||
_askpass: AskPassDelegate,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
_cx: AsyncApp,
|
||||
) -> BoxFuture<'_, Result<git::repository::RemoteCommandOutput>> {
|
||||
) -> BoxFuture<Result<git::repository::RemoteCommandOutput>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_remotes(&self, _branch: Option<String>) -> BoxFuture<'_, Result<Vec<Remote>>> {
|
||||
fn get_remotes(&self, _branch: Option<String>) -> BoxFuture<Result<Vec<Remote>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn check_for_pushed_commit(&self) -> BoxFuture<'_, Result<Vec<gpui::SharedString>>> {
|
||||
fn check_for_pushed_commit(&self) -> BoxFuture<Result<Vec<gpui::SharedString>>> {
|
||||
future::ready(Ok(Vec::new())).boxed()
|
||||
}
|
||||
|
||||
fn diff(&self, _diff: git::repository::DiffType) -> BoxFuture<'_, Result<String>> {
|
||||
fn diff(&self, _diff: git::repository::DiffType) -> BoxFuture<Result<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -429,10 +429,7 @@ impl GitRepository for FakeGitRepository {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn restore_checkpoint(
|
||||
&self,
|
||||
_checkpoint: GitRepositoryCheckpoint,
|
||||
) -> BoxFuture<'_, Result<()>> {
|
||||
fn restore_checkpoint(&self, _checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -440,7 +437,7 @@ impl GitRepository for FakeGitRepository {
|
||||
&self,
|
||||
_left: GitRepositoryCheckpoint,
|
||||
_right: GitRepositoryCheckpoint,
|
||||
) -> BoxFuture<'_, Result<bool>> {
|
||||
) -> BoxFuture<Result<bool>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -448,7 +445,7 @@ impl GitRepository for FakeGitRepository {
|
||||
&self,
|
||||
_base_checkpoint: GitRepositoryCheckpoint,
|
||||
_target_checkpoint: GitRepositoryCheckpoint,
|
||||
) -> BoxFuture<'_, Result<String>> {
|
||||
) -> BoxFuture<Result<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,25 +303,25 @@ pub trait GitRepository: Send + Sync {
|
||||
/// Returns the contents of an entry in the repository's index, or None if there is no entry for the given path.
|
||||
///
|
||||
/// Also returns `None` for symlinks.
|
||||
fn load_index_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>>;
|
||||
fn load_index_text(&self, path: RepoPath) -> BoxFuture<Option<String>>;
|
||||
|
||||
/// Returns the contents of an entry in the repository's HEAD, or None if HEAD does not exist or has no entry for the given path.
|
||||
///
|
||||
/// Also returns `None` for symlinks.
|
||||
fn load_committed_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>>;
|
||||
fn load_committed_text(&self, path: RepoPath) -> BoxFuture<Option<String>>;
|
||||
|
||||
fn set_index_text(
|
||||
&self,
|
||||
path: RepoPath,
|
||||
content: Option<String>,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, anyhow::Result<()>>;
|
||||
) -> BoxFuture<anyhow::Result<()>>;
|
||||
|
||||
/// Returns the URL of the remote with the given name.
|
||||
fn remote_url(&self, name: &str) -> Option<String>;
|
||||
|
||||
/// Resolve a list of refs to SHAs.
|
||||
fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<'_, Result<Vec<Option<String>>>>;
|
||||
fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<Result<Vec<Option<String>>>>;
|
||||
|
||||
fn head_sha(&self) -> BoxFuture<'_, Option<String>> {
|
||||
async move {
|
||||
@@ -335,33 +335,33 @@ pub trait GitRepository: Send + Sync {
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn merge_message(&self) -> BoxFuture<'_, Option<String>>;
|
||||
fn merge_message(&self) -> BoxFuture<Option<String>>;
|
||||
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'_, Result<GitStatus>>;
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<Result<GitStatus>>;
|
||||
|
||||
fn branches(&self) -> BoxFuture<'_, Result<Vec<Branch>>>;
|
||||
fn branches(&self) -> BoxFuture<Result<Vec<Branch>>>;
|
||||
|
||||
fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>>;
|
||||
fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>>;
|
||||
fn change_branch(&self, name: String) -> BoxFuture<Result<()>>;
|
||||
fn create_branch(&self, name: String) -> BoxFuture<Result<()>>;
|
||||
|
||||
fn reset(
|
||||
&self,
|
||||
commit: String,
|
||||
mode: ResetMode,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>>;
|
||||
) -> BoxFuture<Result<()>>;
|
||||
|
||||
fn checkout_files(
|
||||
&self,
|
||||
commit: String,
|
||||
paths: Vec<RepoPath>,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>>;
|
||||
) -> BoxFuture<Result<()>>;
|
||||
|
||||
fn show(&self, commit: String) -> BoxFuture<'_, Result<CommitDetails>>;
|
||||
fn show(&self, commit: String) -> BoxFuture<Result<CommitDetails>>;
|
||||
|
||||
fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>>;
|
||||
fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result<crate::blame::Blame>>;
|
||||
fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<Result<CommitDiff>>;
|
||||
fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<Result<crate::blame::Blame>>;
|
||||
|
||||
/// Returns the absolute path to the repository. For worktrees, this will be the path to the
|
||||
/// worktree's gitdir within the main repository (typically `.git/worktrees/<name>`).
|
||||
@@ -376,7 +376,7 @@ pub trait GitRepository: Send + Sync {
|
||||
&self,
|
||||
paths: Vec<RepoPath>,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>>;
|
||||
) -> BoxFuture<Result<()>>;
|
||||
/// Updates the index to match HEAD at the given paths.
|
||||
///
|
||||
/// If any of the paths were previously staged but do not exist in HEAD, they will be removed from the index.
|
||||
@@ -384,7 +384,7 @@ pub trait GitRepository: Send + Sync {
|
||||
&self,
|
||||
paths: Vec<RepoPath>,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>>;
|
||||
) -> BoxFuture<Result<()>>;
|
||||
|
||||
fn commit(
|
||||
&self,
|
||||
@@ -392,7 +392,7 @@ pub trait GitRepository: Send + Sync {
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
options: CommitOptions,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
) -> BoxFuture<'_, Result<()>>;
|
||||
) -> BoxFuture<Result<()>>;
|
||||
|
||||
fn push(
|
||||
&self,
|
||||
@@ -404,7 +404,7 @@ pub trait GitRepository: Send + Sync {
|
||||
// This method takes an AsyncApp to ensure it's invoked on the main thread,
|
||||
// otherwise git-credentials-manager won't work.
|
||||
cx: AsyncApp,
|
||||
) -> BoxFuture<'_, Result<RemoteCommandOutput>>;
|
||||
) -> BoxFuture<Result<RemoteCommandOutput>>;
|
||||
|
||||
fn pull(
|
||||
&self,
|
||||
@@ -415,7 +415,7 @@ pub trait GitRepository: Send + Sync {
|
||||
// This method takes an AsyncApp to ensure it's invoked on the main thread,
|
||||
// otherwise git-credentials-manager won't work.
|
||||
cx: AsyncApp,
|
||||
) -> BoxFuture<'_, Result<RemoteCommandOutput>>;
|
||||
) -> BoxFuture<Result<RemoteCommandOutput>>;
|
||||
|
||||
fn fetch(
|
||||
&self,
|
||||
@@ -425,35 +425,35 @@ pub trait GitRepository: Send + Sync {
|
||||
// This method takes an AsyncApp to ensure it's invoked on the main thread,
|
||||
// otherwise git-credentials-manager won't work.
|
||||
cx: AsyncApp,
|
||||
) -> BoxFuture<'_, Result<RemoteCommandOutput>>;
|
||||
) -> BoxFuture<Result<RemoteCommandOutput>>;
|
||||
|
||||
fn get_remotes(&self, branch_name: Option<String>) -> BoxFuture<'_, Result<Vec<Remote>>>;
|
||||
fn get_remotes(&self, branch_name: Option<String>) -> BoxFuture<Result<Vec<Remote>>>;
|
||||
|
||||
/// returns a list of remote branches that contain HEAD
|
||||
fn check_for_pushed_commit(&self) -> BoxFuture<'_, Result<Vec<SharedString>>>;
|
||||
fn check_for_pushed_commit(&self) -> BoxFuture<Result<Vec<SharedString>>>;
|
||||
|
||||
/// Run git diff
|
||||
fn diff(&self, diff: DiffType) -> BoxFuture<'_, Result<String>>;
|
||||
fn diff(&self, diff: DiffType) -> BoxFuture<Result<String>>;
|
||||
|
||||
/// Creates a checkpoint for the repository.
|
||||
fn checkpoint(&self) -> BoxFuture<'static, Result<GitRepositoryCheckpoint>>;
|
||||
|
||||
/// Resets to a previously-created checkpoint.
|
||||
fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<'_, Result<()>>;
|
||||
fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>>;
|
||||
|
||||
/// Compares two checkpoints, returning true if they are equal
|
||||
fn compare_checkpoints(
|
||||
&self,
|
||||
left: GitRepositoryCheckpoint,
|
||||
right: GitRepositoryCheckpoint,
|
||||
) -> BoxFuture<'_, Result<bool>>;
|
||||
) -> BoxFuture<Result<bool>>;
|
||||
|
||||
/// Computes a diff between two checkpoints.
|
||||
fn diff_checkpoints(
|
||||
&self,
|
||||
base_checkpoint: GitRepositoryCheckpoint,
|
||||
target_checkpoint: GitRepositoryCheckpoint,
|
||||
) -> BoxFuture<'_, Result<String>>;
|
||||
) -> BoxFuture<Result<String>>;
|
||||
}
|
||||
|
||||
pub enum DiffType {
|
||||
@@ -1032,39 +1032,32 @@ impl GitRepository for RealGitRepository {
|
||||
|
||||
fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
|
||||
let repo = self.repository.clone();
|
||||
let working_directory = self.working_directory();
|
||||
let git_binary_path = self.git_binary_path.clone();
|
||||
let executor = self.executor.clone();
|
||||
let branch = self.executor.spawn(async move {
|
||||
let repo = repo.lock();
|
||||
let branch = if let Ok(branch) = repo.find_branch(&name, BranchType::Local) {
|
||||
branch
|
||||
} else if let Ok(revision) = repo.find_branch(&name, BranchType::Remote) {
|
||||
let (_, branch_name) = name.split_once("/").context("Unexpected branch format")?;
|
||||
let revision = revision.get();
|
||||
let branch_commit = revision.peel_to_commit()?;
|
||||
let mut branch = repo.branch(&branch_name, &branch_commit, false)?;
|
||||
branch.set_upstream(Some(&name))?;
|
||||
branch
|
||||
} else {
|
||||
anyhow::bail!("Branch not found");
|
||||
};
|
||||
|
||||
Ok(branch
|
||||
.name()?
|
||||
.context("cannot checkout anonymous branch")?
|
||||
.to_string())
|
||||
});
|
||||
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let branch = branch.await?;
|
||||
let repo = repo.lock();
|
||||
let branch = if let Ok(branch) = repo.find_branch(&name, BranchType::Local) {
|
||||
branch
|
||||
} else if let Ok(revision) = repo.find_branch(&name, BranchType::Remote) {
|
||||
let (_, branch_name) =
|
||||
name.split_once("/").context("Unexpected branch format")?;
|
||||
let revision = revision.get();
|
||||
let branch_commit = revision.peel_to_commit()?;
|
||||
let mut branch = repo.branch(&branch_name, &branch_commit, false)?;
|
||||
branch.set_upstream(Some(&name))?;
|
||||
branch
|
||||
} else {
|
||||
anyhow::bail!("Branch not found");
|
||||
};
|
||||
|
||||
GitBinary::new(git_binary_path, working_directory?, executor)
|
||||
.run(&["checkout", &branch])
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(())
|
||||
let revision = branch.get();
|
||||
let as_tree = revision.peel_to_tree()?;
|
||||
repo.checkout_tree(as_tree.as_object(), None)?;
|
||||
repo.set_head(
|
||||
revision
|
||||
.name()
|
||||
.context("Branch name could not be retrieved")?,
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
@@ -2275,7 +2268,7 @@ mod tests {
|
||||
|
||||
impl RealGitRepository {
|
||||
/// Force a Git garbage collection on the repository.
|
||||
fn gc(&self) -> BoxFuture<'_, Result<()>> {
|
||||
fn gc(&self) -> BoxFuture<Result<()>> {
|
||||
let working_directory = self.working_directory();
|
||||
let git_binary_path = self.git_binary_path.clone();
|
||||
let executor = self.executor.clone();
|
||||
|
||||
@@ -245,7 +245,7 @@ impl PickerDelegate for BranchListDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Select branch…".into()
|
||||
"Select branch...".into()
|
||||
}
|
||||
|
||||
fn editor_position(&self) -> PickerEditorPosition {
|
||||
@@ -439,43 +439,44 @@ impl PickerDelegate for BranchListDelegate {
|
||||
})
|
||||
.unwrap_or_else(|| (None, None));
|
||||
|
||||
let branch_name = if entry.is_new {
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Plus)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Label::new(format!("Create branch \"{}\"…", entry.branch.name()))
|
||||
.single_line()
|
||||
.truncate(),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
HighlightedLabel::new(entry.branch.name().to_owned(), entry.positions.clone())
|
||||
.truncate()
|
||||
.into_any_element()
|
||||
};
|
||||
|
||||
Some(
|
||||
ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
|
||||
.inset(true)
|
||||
.spacing(match self.style {
|
||||
BranchListStyle::Modal => ListItemSpacing::default(),
|
||||
BranchListStyle::Popover => ListItemSpacing::ExtraDense,
|
||||
})
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_6()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.flex_shrink()
|
||||
.overflow_x_hidden()
|
||||
.child(branch_name)
|
||||
.when_some(commit_time, |label, commit_time| {
|
||||
label.child(
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(div().flex_shrink().overflow_x_hidden().child(
|
||||
if entry.is_new {
|
||||
Label::new(format!(
|
||||
"Create branch \"{}\"…",
|
||||
entry.branch.name()
|
||||
))
|
||||
.single_line()
|
||||
.into_any_element()
|
||||
} else {
|
||||
HighlightedLabel::new(
|
||||
entry.branch.name().to_owned(),
|
||||
entry.positions.clone(),
|
||||
)
|
||||
.truncate()
|
||||
.into_any_element()
|
||||
},
|
||||
))
|
||||
.when_some(commit_time, |el, commit_time| {
|
||||
el.child(
|
||||
Label::new(commit_time)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
|
||||
@@ -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};
|
||||
use git::repository::{CommitDetails, CommitDiff, CommitSummary, RepoPath};
|
||||
use gpui::{
|
||||
AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter,
|
||||
@@ -154,7 +154,7 @@ impl CommitView {
|
||||
});
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.disable_header_for_buffer(metadata_buffer_id.unwrap(), cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![0..0]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,6 +83,7 @@ actions!(
|
||||
FocusEditor,
|
||||
FocusChanges,
|
||||
ToggleFillCoAuthors,
|
||||
GenerateCommitMessage
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use anyhow::Result;
|
||||
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
|
||||
use collections::HashSet;
|
||||
use editor::{
|
||||
Editor, EditorEvent, SelectionEffects,
|
||||
Editor, EditorEvent,
|
||||
actions::{GoToHunk, GoToPreviousHunk},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
@@ -255,14 +255,9 @@ impl ProjectDiff {
|
||||
fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::focused()),
|
||||
window,
|
||||
cx,
|
||||
|s| {
|
||||
s.select_ranges([position..position]);
|
||||
},
|
||||
)
|
||||
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
|
||||
s.select_ranges([position..position]);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
self.pending_scroll = Some(path_key);
|
||||
@@ -468,7 +463,7 @@ impl ProjectDiff {
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if was_empty {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
// TODO select the very beginning (possibly inside a deletion)
|
||||
selections.select_ranges([0..0])
|
||||
});
|
||||
|
||||
@@ -2,8 +2,8 @@ pub mod cursor_position;
|
||||
|
||||
use cursor_position::{LineIndicatorFormat, UserCaretPosition};
|
||||
use editor::{
|
||||
Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, SelectionEffects, ToOffset, ToPoint,
|
||||
actions::Tab, scroll::Autoscroll,
|
||||
Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, ToOffset, ToPoint, actions::Tab,
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Styled,
|
||||
@@ -249,12 +249,9 @@ impl GoToLine {
|
||||
let Some(start) = self.anchor_from_query(&snapshot, cx) else {
|
||||
return;
|
||||
};
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_anchor_ranges([start..start]),
|
||||
);
|
||||
editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
|
||||
s.select_anchor_ranges([start..start])
|
||||
});
|
||||
editor.focus_handle(cx).focus(window);
|
||||
cx.notify()
|
||||
});
|
||||
|
||||
@@ -48,8 +48,6 @@ macro_rules! actions {
|
||||
/// actions!(editor, [MoveUp, MoveDown, MoveLeft, MoveRight, Newline]);
|
||||
/// ```
|
||||
///
|
||||
/// Registering the actions with the same name will result in a panic during `App` creation.
|
||||
///
|
||||
/// # Derive Macro
|
||||
///
|
||||
/// More complex data types can also be actions, by using the derive macro for `Action`:
|
||||
@@ -282,27 +280,14 @@ impl ActionRegistry {
|
||||
}
|
||||
|
||||
fn insert_action(&mut self, action: MacroActionData) {
|
||||
let name = action.name;
|
||||
if self.by_name.contains_key(name) {
|
||||
panic!(
|
||||
"Action with name `{name}` already registered \
|
||||
(might be registered in `#[action(deprecated_aliases = [...])]`."
|
||||
);
|
||||
}
|
||||
self.by_name.insert(
|
||||
name,
|
||||
action.name,
|
||||
ActionData {
|
||||
build: action.build,
|
||||
json_schema: action.json_schema,
|
||||
},
|
||||
);
|
||||
for &alias in action.deprecated_aliases {
|
||||
if self.by_name.contains_key(alias) {
|
||||
panic!(
|
||||
"Action with name `{alias}` already registered. \
|
||||
`{alias}` is specified in `#[action(deprecated_aliases = [...])]` for action `{name}`."
|
||||
);
|
||||
}
|
||||
self.by_name.insert(
|
||||
alias,
|
||||
ActionData {
|
||||
@@ -310,13 +295,14 @@ impl ActionRegistry {
|
||||
json_schema: action.json_schema,
|
||||
},
|
||||
);
|
||||
self.deprecated_aliases.insert(alias, name);
|
||||
self.deprecated_aliases.insert(alias, action.name);
|
||||
self.all_names.push(alias);
|
||||
}
|
||||
self.names_by_type_id.insert(action.type_id, name);
|
||||
self.all_names.push(name);
|
||||
self.names_by_type_id.insert(action.type_id, action.name);
|
||||
self.all_names.push(action.name);
|
||||
if let Some(deprecation_msg) = action.deprecation_message {
|
||||
self.deprecation_messages.insert(name, deprecation_msg);
|
||||
self.deprecation_messages
|
||||
.insert(action.name, deprecation_msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,32 @@ impl<T: ?Sized> DerefMut for ArenaBox<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArenaRef<T: ?Sized>(ArenaBox<T>);
|
||||
|
||||
impl<T: ?Sized> From<ArenaBox<T>> for ArenaRef<T> {
|
||||
fn from(value: ArenaBox<T>) -> Self {
|
||||
ArenaRef(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for ArenaRef<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(ArenaBox {
|
||||
ptr: self.0.ptr,
|
||||
valid: self.0.valid.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for ArenaRef<T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.deref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
@@ -432,7 +432,7 @@ mod tests {
|
||||
actions!(
|
||||
test_only,
|
||||
[
|
||||
H, I, J, K, L, M, N, // Don't wrap, test the trailing comma
|
||||
A, B, C, D, E, F, G, // Don't wrap, test the trailing comma
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ pub unsafe fn new_renderer(
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for RawWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
let view = NonNull::new(self.view).unwrap();
|
||||
let handle = rwh::AppKitWindowHandle::new(view);
|
||||
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
impl rwh::HasDisplayHandle for RawWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
let handle = rwh::AppKitDisplayHandle::new();
|
||||
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
|
||||
@@ -252,11 +252,11 @@ impl Drop for WaylandWindow {
|
||||
}
|
||||
|
||||
impl WaylandWindow {
|
||||
fn borrow(&self) -> Ref<'_, WaylandWindowState> {
|
||||
fn borrow(&self) -> Ref<WaylandWindowState> {
|
||||
self.0.state.borrow()
|
||||
}
|
||||
|
||||
fn borrow_mut(&self) -> RefMut<'_, WaylandWindowState> {
|
||||
fn borrow_mut(&self) -> RefMut<WaylandWindowState> {
|
||||
self.0.state.borrow_mut()
|
||||
}
|
||||
|
||||
@@ -699,14 +699,12 @@ impl WaylandWindowStatePtr {
|
||||
}
|
||||
}
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
if event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
|
||||
if let Some(key_char) = &event.keystroke.key_char {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some(mut input_handler) = state.input_handler.take() {
|
||||
drop(state);
|
||||
input_handler.replace_text_in_range(None, key_char);
|
||||
self.state.borrow_mut().input_handler = Some(input_handler);
|
||||
}
|
||||
if let Some(key_char) = &event.keystroke.key_char {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some(mut input_handler) = state.input_handler.take() {
|
||||
drop(state);
|
||||
input_handler.replace_text_in_range(None, key_char);
|
||||
self.state.borrow_mut().input_handler = Some(input_handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ pub(crate) struct X11WindowStatePtr {
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for RawWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
let Some(non_zero) = NonZeroU32::new(self.window_id) else {
|
||||
log::error!("RawWindow.window_id zero when getting window handle.");
|
||||
return Err(rwh::HandleError::Unavailable);
|
||||
@@ -299,7 +299,7 @@ impl rwh::HasWindowHandle for RawWindow {
|
||||
}
|
||||
}
|
||||
impl rwh::HasDisplayHandle for RawWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
let Some(non_zero) = NonNull::new(self.connection) else {
|
||||
log::error!("Null RawWindow.connection when getting display handle.");
|
||||
return Err(rwh::HandleError::Unavailable);
|
||||
@@ -310,12 +310,12 @@ impl rwh::HasDisplayHandle for RawWindow {
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for X11Window {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
impl rwh::HasDisplayHandle for X11Window {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -679,6 +679,26 @@ impl X11WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to an X11 window which destroys it on Drop.
|
||||
pub struct X11WindowHandle {
|
||||
id: xproto::Window,
|
||||
xcb: Rc<XCBConnection>,
|
||||
}
|
||||
|
||||
impl Drop for X11WindowHandle {
|
||||
fn drop(&mut self) {
|
||||
maybe!({
|
||||
check_reply(
|
||||
|| "X11 DestroyWindow failed while dropping X11WindowHandle.",
|
||||
self.xcb.destroy_window(self.id),
|
||||
)?;
|
||||
xcb_flush(&self.xcb);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct X11Window(pub X11WindowStatePtr);
|
||||
|
||||
impl Drop for X11Window {
|
||||
@@ -962,17 +982,14 @@ impl X11WindowStatePtr {
|
||||
}
|
||||
}
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
// only allow shift modifier when inserting text
|
||||
if event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some(mut input_handler) = state.input_handler.take() {
|
||||
if let Some(key_char) = &event.keystroke.key_char {
|
||||
drop(state);
|
||||
input_handler.replace_text_in_range(None, key_char);
|
||||
state = self.state.borrow_mut();
|
||||
}
|
||||
state.input_handler = Some(input_handler);
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some(mut input_handler) = state.input_handler.take() {
|
||||
if let Some(key_char) = &event.keystroke.key_char {
|
||||
drop(state);
|
||||
input_handler.replace_text_in_range(None, key_char);
|
||||
state = self.state.borrow_mut();
|
||||
}
|
||||
state.input_handler = Some(input_handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,10 @@ use crate::{
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
appkit::{
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber12_0, NSApplication, NSBackingStoreBuffered,
|
||||
NSColor, NSEvent, NSEventModifierFlags, NSFilenamesPboardType, NSPasteboard, NSScreen,
|
||||
NSView, NSViewHeightSizable, NSViewWidthSizable, NSVisualEffectMaterial,
|
||||
NSVisualEffectState, NSVisualEffectView, NSWindow, NSWindowButton,
|
||||
NSWindowCollectionBehavior, NSWindowOcclusionState, NSWindowOrderingMode,
|
||||
NSWindowStyleMask, NSWindowTitleVisibility,
|
||||
NSApplication, NSBackingStoreBuffered, NSColor, NSEvent, NSEventModifierFlags,
|
||||
NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable,
|
||||
NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
|
||||
NSWindowOcclusionState, NSWindowStyleMask, NSWindowTitleVisibility,
|
||||
},
|
||||
base::{id, nil},
|
||||
foundation::{
|
||||
@@ -55,7 +53,6 @@ const WINDOW_STATE_IVAR: &str = "windowState";
|
||||
static mut WINDOW_CLASS: *const Class = ptr::null();
|
||||
static mut PANEL_CLASS: *const Class = ptr::null();
|
||||
static mut VIEW_CLASS: *const Class = ptr::null();
|
||||
static mut BLURRED_VIEW_CLASS: *const Class = ptr::null();
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
|
||||
@@ -244,20 +241,6 @@ unsafe fn build_classes() {
|
||||
}
|
||||
decl.register()
|
||||
};
|
||||
BLURRED_VIEW_CLASS = {
|
||||
let mut decl = ClassDecl::new("BlurredView", class!(NSVisualEffectView)).unwrap();
|
||||
unsafe {
|
||||
decl.add_method(
|
||||
sel!(initWithFrame:),
|
||||
blurred_view_init_with_frame as extern "C" fn(&Object, Sel, NSRect) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(updateLayer),
|
||||
blurred_view_update_layer as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
decl.register()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +335,6 @@ struct MacWindowState {
|
||||
executor: ForegroundExecutor,
|
||||
native_window: id,
|
||||
native_view: NonNull<Object>,
|
||||
blurred_view: Option<id>,
|
||||
display_link: Option<DisplayLink>,
|
||||
renderer: renderer::Renderer,
|
||||
request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
|
||||
@@ -618,9 +600,8 @@ impl MacWindow {
|
||||
setReleasedWhenClosed: NO
|
||||
];
|
||||
|
||||
let content_view = native_window.contentView();
|
||||
let native_view: id = msg_send![VIEW_CLASS, alloc];
|
||||
let native_view = NSView::initWithFrame_(native_view, NSView::bounds(content_view));
|
||||
let native_view = NSView::init(native_view);
|
||||
assert!(!native_view.is_null());
|
||||
|
||||
let mut window = Self(Arc::new(Mutex::new(MacWindowState {
|
||||
@@ -628,7 +609,6 @@ impl MacWindow {
|
||||
executor,
|
||||
native_window,
|
||||
native_view: NonNull::new_unchecked(native_view),
|
||||
blurred_view: None,
|
||||
display_link: None,
|
||||
renderer: renderer::new_renderer(
|
||||
renderer_context,
|
||||
@@ -703,11 +683,11 @@ impl MacWindow {
|
||||
// itself and break the association with its context.
|
||||
native_view.setWantsLayer(YES);
|
||||
let _: () = msg_send![
|
||||
native_view,
|
||||
setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
|
||||
native_view,
|
||||
setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
|
||||
];
|
||||
|
||||
content_view.addSubview_(native_view.autorelease());
|
||||
native_window.setContentView_(native_view.autorelease());
|
||||
native_window.makeFirstResponder_(native_view);
|
||||
|
||||
match kind {
|
||||
@@ -1055,57 +1035,28 @@ impl PlatformWindow for MacWindow {
|
||||
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
||||
let mut this = self.0.as_ref().lock();
|
||||
this.renderer
|
||||
.update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
|
||||
|
||||
let opaque = background_appearance == WindowBackgroundAppearance::Opaque;
|
||||
this.renderer.update_transparency(!opaque);
|
||||
let blur_radius = if background_appearance == WindowBackgroundAppearance::Blurred {
|
||||
80
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let opaque = (background_appearance == WindowBackgroundAppearance::Opaque).to_objc();
|
||||
|
||||
unsafe {
|
||||
this.native_window.setOpaque_(opaque as BOOL);
|
||||
let background_color = if opaque {
|
||||
this.native_window.setOpaque_(opaque);
|
||||
// Shadows for transparent windows cause artifacts and performance issues
|
||||
this.native_window.setHasShadow_(opaque);
|
||||
let clear_color = if opaque == YES {
|
||||
NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 1f64)
|
||||
} else {
|
||||
// Not using `+[NSColor clearColor]` to avoid broken shadow.
|
||||
NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 0.0001)
|
||||
NSColor::clearColor(nil)
|
||||
};
|
||||
this.native_window.setBackgroundColor_(background_color);
|
||||
|
||||
if NSAppKitVersionNumber < NSAppKitVersionNumber12_0 {
|
||||
// Whether `-[NSVisualEffectView respondsToSelector:@selector(_updateProxyLayer)]`.
|
||||
// On macOS Catalina/Big Sur `NSVisualEffectView` doesn’t own concrete sublayers
|
||||
// but uses a `CAProxyLayer`. Use the legacy WindowServer API.
|
||||
let blur_radius = if background_appearance == WindowBackgroundAppearance::Blurred {
|
||||
80
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let window_number = this.native_window.windowNumber();
|
||||
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, blur_radius);
|
||||
} else {
|
||||
// On newer macOS `NSVisualEffectView` manages the effect layer directly. Using it
|
||||
// could have a better performance (it downsamples the backdrop) and more control
|
||||
// over the effect layer.
|
||||
if background_appearance != WindowBackgroundAppearance::Blurred {
|
||||
if let Some(blur_view) = this.blurred_view {
|
||||
NSView::removeFromSuperview(blur_view);
|
||||
this.blurred_view = None;
|
||||
}
|
||||
} else if this.blurred_view == None {
|
||||
let content_view = this.native_window.contentView();
|
||||
let frame = NSView::bounds(content_view);
|
||||
let mut blur_view: id = msg_send![BLURRED_VIEW_CLASS, alloc];
|
||||
blur_view = NSView::initWithFrame_(blur_view, frame);
|
||||
blur_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
|
||||
|
||||
let _: () = msg_send![
|
||||
content_view,
|
||||
addSubview: blur_view
|
||||
positioned: NSWindowOrderingMode::NSWindowBelow
|
||||
relativeTo: nil
|
||||
];
|
||||
this.blurred_view = Some(blur_view.autorelease());
|
||||
}
|
||||
}
|
||||
this.native_window.setBackgroundColor_(clear_color);
|
||||
let window_number = this.native_window.windowNumber();
|
||||
CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, blur_radius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1812,12 +1763,7 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
|
||||
let new_size = Size::<Pixels>::from(size);
|
||||
let old_size = unsafe {
|
||||
let old_frame: NSRect = msg_send![this, frame];
|
||||
Size::<Pixels>::from(old_frame.size)
|
||||
};
|
||||
|
||||
if old_size == new_size {
|
||||
if lock.content_size() == new_size {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2202,75 +2148,3 @@ unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
|
||||
screen_number as CGDirectDisplayID
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn blurred_view_init_with_frame(this: &Object, _: Sel, frame: NSRect) -> id {
|
||||
unsafe {
|
||||
let view = msg_send![super(this, class!(NSVisualEffectView)), initWithFrame: frame];
|
||||
// Use a colorless semantic material. The default value `AppearanceBased`, though not
|
||||
// manually set, is deprecated.
|
||||
NSVisualEffectView::setMaterial_(view, NSVisualEffectMaterial::Selection);
|
||||
NSVisualEffectView::setState_(view, NSVisualEffectState::Active);
|
||||
view
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn blurred_view_update_layer(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
let _: () = msg_send![super(this, class!(NSVisualEffectView)), updateLayer];
|
||||
let layer: id = msg_send![this, layer];
|
||||
if !layer.is_null() {
|
||||
remove_layer_background(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn remove_layer_background(layer: id) {
|
||||
unsafe {
|
||||
let _: () = msg_send![layer, setBackgroundColor:nil];
|
||||
|
||||
let class_name: id = msg_send![layer, className];
|
||||
if class_name.isEqualToString("CAChameleonLayer") {
|
||||
// Remove the desktop tinting effect.
|
||||
let _: () = msg_send![layer, setHidden: YES];
|
||||
return;
|
||||
}
|
||||
|
||||
let filters: id = msg_send![layer, filters];
|
||||
if !filters.is_null() {
|
||||
// Remove the increased saturation.
|
||||
// The effect of a `CAFilter` or `CIFilter` is determined by its name, and the
|
||||
// `description` reflects its name and some parameters. Currently `NSVisualEffectView`
|
||||
// uses a `CAFilter` named "colorSaturate". If one day they switch to `CIFilter`, the
|
||||
// `description` will still contain "Saturat" ("... inputSaturation = ...").
|
||||
let test_string: id = NSString::alloc(nil).init_str("Saturat").autorelease();
|
||||
let count = NSArray::count(filters);
|
||||
for i in 0..count {
|
||||
let description: id = msg_send![filters.objectAtIndex(i), description];
|
||||
let hit: BOOL = msg_send![description, containsString: test_string];
|
||||
if hit == NO {
|
||||
continue;
|
||||
}
|
||||
|
||||
let all_indices = NSRange {
|
||||
location: 0,
|
||||
length: count,
|
||||
};
|
||||
let indices: id = msg_send![class!(NSMutableIndexSet), indexSet];
|
||||
let _: () = msg_send![indices, addIndexesInRange: all_indices];
|
||||
let _: () = msg_send![indices, removeIndex:i];
|
||||
let filtered: id = msg_send![filters, objectsAtIndexes: indices];
|
||||
let _: () = msg_send![layer, setFilters: filtered];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let sublayers: id = msg_send![layer, sublayers];
|
||||
if !sublayers.is_null() {
|
||||
let count = NSArray::count(sublayers);
|
||||
for i in 0..count {
|
||||
let sublayer = sublayers.objectAtIndex(i);
|
||||
remove_layer_background(sublayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||