Compare commits
5 Commits
v0.161.2
...
drop-image
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cb301a827 | ||
|
|
859092d722 | ||
|
|
c34320c2ad | ||
|
|
efe795242f | ||
|
|
04398619f7 |
7
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
7
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
@@ -15,13 +15,6 @@ body:
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
9
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -2,7 +2,7 @@ name: Bug Report
|
||||
description: |
|
||||
Use this template for **non-crash-related** bug reports.
|
||||
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
||||
labels: ["admin read", "triage", "bug"]
|
||||
labels: ["admin read", "triage", "defect"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
@@ -38,12 +38,9 @@ body:
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<details><summary>Zed.log</summary><pre>
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
|
||||
validations:
|
||||
required: false
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
9
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Crash Report
|
||||
description: |
|
||||
Use this template for crash reports.
|
||||
labels: ["admin read", "triage", "bug", "panic / crash"]
|
||||
labels: ["admin read", "triage", "defect", "panic / crash"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
@@ -31,12 +31,9 @@ body:
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<details><summary>Zed.log</summary><pre>
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
|
||||
validations:
|
||||
required: false
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- "docs/**/*"
|
||||
- "docs/**"
|
||||
- ".github/workflows/community_*"
|
||||
|
||||
concurrency:
|
||||
@@ -260,7 +260,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p target/
|
||||
# Ignore any errors that occur while drafting release notes to not fail the build.
|
||||
script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md || true
|
||||
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
# issues, preventing 365 days from working until then.
|
||||
days-before-stale: 180
|
||||
days-before-close: 7
|
||||
any-of-issue-labels: "bug,panic / crash"
|
||||
any-of-issue-labels: "defect,panic / crash"
|
||||
operations-per-run: 1000
|
||||
ascending: true
|
||||
enable-statistics: true
|
||||
|
||||
2
.mailmap
2
.mailmap
@@ -60,8 +60,6 @@ Max Brunsfeld <maxbrunsfeld@gmail.com>
|
||||
Max Brunsfeld <maxbrunsfeld@gmail.com> <max@zed.dev>
|
||||
Max Linke <maxlinke88@gmail.com>
|
||||
Max Linke <maxlinke88@gmail.com> <kain88-de@users.noreply.github.com>
|
||||
Michael Sloan <michael@zed.dev>
|
||||
Michael Sloan <michael@zed.dev> <mgsloan@google.com>
|
||||
Mikayla Maki <mikayla@zed.dev>
|
||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
|
||||
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
|
||||
|
||||
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -2432,6 +2432,7 @@ dependencies = [
|
||||
"smol",
|
||||
"sysinfo",
|
||||
"telemetry_events",
|
||||
"tempfile",
|
||||
"text",
|
||||
"thiserror",
|
||||
"time",
|
||||
@@ -3490,6 +3491,7 @@ dependencies = [
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger 0.11.5",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
@@ -3716,7 +3718,6 @@ dependencies = [
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"ui",
|
||||
"unicode-script",
|
||||
"unicode-segmentation",
|
||||
"unindent",
|
||||
"url",
|
||||
@@ -5796,7 +5797,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"project",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"workspace",
|
||||
]
|
||||
@@ -6178,11 +6178,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-serde"
|
||||
version = "0.4.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd71aa17c4fa65e6d7536ab2728881a41f8feb2ee5841c2240516c3c3d65d8b3"
|
||||
checksum = "77b96de099fc23d5c21e05de32cc087c8326983895b7f6c242562af01f7d4c81"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
@@ -6402,7 +6403,6 @@ dependencies = [
|
||||
"pet",
|
||||
"pet-conda",
|
||||
"pet-core",
|
||||
"pet-fs",
|
||||
"pet-poetry",
|
||||
"pet-reporter",
|
||||
"project",
|
||||
@@ -7177,9 +7177,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nbformat"
|
||||
version = "0.5.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9ffb2ca556072f114bcaf2ca01dde7f1bc8a4946097dd804cb5a22d8af7d6df"
|
||||
checksum = "84f8a9ab08b34237c2c1d0504b794c2ff01c08dfc46a060d160f004a7f479c31"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -7794,7 +7794,6 @@ dependencies = [
|
||||
"project",
|
||||
"rope",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"tree-sitter-rust",
|
||||
@@ -7819,7 +7818,6 @@ dependencies = [
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"outline",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"schemars",
|
||||
@@ -9561,7 +9559,6 @@ dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"prost",
|
||||
"release_channel",
|
||||
"rpc",
|
||||
@@ -9613,7 +9610,6 @@ dependencies = [
|
||||
"settings",
|
||||
"shellexpand 2.1.2",
|
||||
"smol",
|
||||
"sysinfo",
|
||||
"telemetry_events",
|
||||
"toml 0.8.19",
|
||||
"util",
|
||||
@@ -9994,9 +9990,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "runtimelib"
|
||||
version = "0.19.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe23ba9967355bbb1be2fb9a8e51bd239ffdf9c791fad5a9b765122ee2bde2e4"
|
||||
checksum = "bc7fe3c17675445fe89de68d130be00b7115104924fbcf53a9b0a84b0283fc81"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-dispatcher",
|
||||
@@ -13131,9 +13127,9 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-script"
|
||||
version = "0.5.7"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
|
||||
checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
@@ -14777,7 +14773,6 @@ dependencies = [
|
||||
"settings",
|
||||
"smallvec",
|
||||
"sqlez",
|
||||
"strum 0.25.0",
|
||||
"task",
|
||||
"tempfile",
|
||||
"theme",
|
||||
@@ -15062,7 +15057,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.161.2"
|
||||
version = "0.161.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -372,7 +372,7 @@ linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
nbformat = "0.5.0"
|
||||
nbformat = "0.3.2"
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
@@ -381,7 +381,6 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
parking_lot = "0.12.1"
|
||||
pathdiff = "0.2"
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
|
||||
@@ -406,7 +405,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.19.0", default-features = false, features = [
|
||||
runtimelib = { version = "0.16.1", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
@@ -476,7 +475,6 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml",
|
||||
unicase = "2.6"
|
||||
unindent = "0.1.7"
|
||||
unicode-segmentation = "1.10"
|
||||
unicode-script = "0.5.7"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
|
||||
wasmparser = "0.215"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 21V12M7 12H3M7 12H11" stroke="black" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M21 19L16 19L16 14" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.99987 5.07027L7.49915 4.20467L7.99987 5.07027ZM6.04652 5.25026C5.63245 5.61573 5.59305 6.24766 5.95851 6.66173C6.32398 7.0758 6.95592 7.1152 7.36999 6.74974L6.04652 5.25026ZM11.9999 5C15.8659 5 18.9999 8.13401 18.9999 12H20.9999C20.9999 7.02944 16.9705 3 11.9999 3V5ZM18.9999 12C18.9999 14.2101 17.9768 16.1806 16.3744 17.4651L17.6254 19.0256C19.6809 17.3779 20.9999 14.8426 20.9999 12H18.9999ZM8.5006 5.93588C9.5292 5.34086 10.7232 5 11.9999 5V3C10.3623 3 8.82395 3.4383 7.49915 4.20467L8.5006 5.93588ZM7.36999 6.74974C7.71803 6.44255 8.09667 6.16954 8.5006 5.93588L7.49915 4.20467C6.9797 4.50515 6.49329 4.85593 6.04652 5.25026L7.36999 6.74974Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 979 B |
@@ -564,11 +564,9 @@
|
||||
"ctrl-alt-c": "outline_panel::CopyPath",
|
||||
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-ctrl-r": "outline_panel::RevealInFileManager",
|
||||
"space": "outline_panel::Open",
|
||||
"space": ["outline_panel::Open", { "change_selection": false }],
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"ctrl-k enter": "editor::OpenExcerptsSplit"
|
||||
"shift-up": "menu::SelectPrev"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -577,11 +577,9 @@
|
||||
"cmd-alt-c": "outline_panel::CopyPath",
|
||||
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-cmd-r": "outline_panel::RevealInFileManager",
|
||||
"space": "outline_panel::Open",
|
||||
"space": ["outline_panel::Open", { "change_selection": false }],
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"cmd-k enter": "editor::OpenExcerptsSplit"
|
||||
"shift-up": "menu::SelectPrev"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
"alt-j": ["editor::SelectNext", { "replace_newest": false }],
|
||||
"alt-shift-j": ["editor::SelectPrevious", { "replace_newest": false }],
|
||||
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
||||
"ctrl-w": "editor::SelectLargerSyntaxNode",
|
||||
"ctrl-shift-w": "editor::SelectSmallerSyntaxNode",
|
||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
||||
"shift-alt-up": "editor::MoveLineUp",
|
||||
"shift-alt-down": "editor::MoveLineDown",
|
||||
"ctrl-alt-l": "editor::Format",
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"ctrl-g": ["editor::SelectNext", { "replace_newest": false }],
|
||||
"ctrl-cmd-g": ["editor::SelectPrevious", { "replace_newest": false }],
|
||||
"cmd-/": ["editor::ToggleComments", { "advance_downwards": true }],
|
||||
"cmd-up": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-down": "editor::SelectSmallerSyntaxNode",
|
||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
||||
"shift-alt-up": "editor::MoveLineUp",
|
||||
"shift-alt-down": "editor::MoveLineDown",
|
||||
"cmd-alt-l": "editor::Format",
|
||||
@@ -58,12 +58,6 @@
|
||||
"alt-enter": "editor::ToggleCodeActions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar > Editor",
|
||||
"bindings": {
|
||||
"shift-enter": "search::SelectPrevMatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
|
||||
@@ -127,9 +127,6 @@
|
||||
"shift-h": "vim::WindowTop",
|
||||
"shift-m": "vim::WindowMiddle",
|
||||
"shift-l": "vim::WindowBottom",
|
||||
"q": "vim::ToggleRecord",
|
||||
"shift-q": "vim::ReplayLastRecording",
|
||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||
// z commands
|
||||
"z enter": ["workspace::SendKeystrokes", "z t ^"],
|
||||
"z -": ["workspace::SendKeystrokes", "z b ^"],
|
||||
@@ -140,14 +137,14 @@
|
||||
"z .": ["workspace::SendKeystrokes", "z z ^"],
|
||||
"z b": "editor::ScrollCursorBottom",
|
||||
"z a": "editor::ToggleFold",
|
||||
"z shift-a": "editor::ToggleFoldRecursive",
|
||||
"z A": "editor::ToggleFoldRecursive",
|
||||
"z c": "editor::Fold",
|
||||
"z shift-c": "editor::FoldRecursive",
|
||||
"z C": "editor::FoldRecursive",
|
||||
"z o": "editor::UnfoldLines",
|
||||
"z shift-o": "editor::UnfoldRecursive",
|
||||
"z O": "editor::UnfoldRecursive",
|
||||
"z f": "editor::FoldSelectedRanges",
|
||||
"z shift-m": "editor::FoldAll",
|
||||
"z shift-r": "editor::UnfoldAll",
|
||||
"z M": "editor::FoldAll",
|
||||
"z R": "editor::UnfoldAll",
|
||||
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
|
||||
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
|
||||
// Count support
|
||||
@@ -209,6 +206,9 @@
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"g q": ["vim::PushOperator", "Rewrap"],
|
||||
"g w": ["vim::PushOperator", "Rewrap"],
|
||||
"q": "vim::ToggleRecord",
|
||||
"shift-q": "vim::ReplayLastRecording",
|
||||
"@": ["vim::PushOperator", "ReplayRegister"],
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"insert": "vim::InsertBefore",
|
||||
@@ -234,7 +234,7 @@
|
||||
"bindings": {
|
||||
":": "vim::VisualCommand",
|
||||
"u": "vim::ConvertToLowerCase",
|
||||
"shift-u": "vim::ConvertToUpperCase",
|
||||
"U": "vim::ConvertToUpperCase",
|
||||
"o": "vim::OtherEnd",
|
||||
"shift-o": "vim::OtherEnd",
|
||||
"d": "vim::VisualDelete",
|
||||
@@ -258,8 +258,8 @@
|
||||
"g ctrl-x": ["vim::Decrement", { "step": true }],
|
||||
"shift-i": "vim::InsertBefore",
|
||||
"shift-a": "vim::InsertAfter",
|
||||
"g shift-i": "vim::VisualInsertFirstNonWhiteSpace",
|
||||
"g shift-a": "vim::VisualInsertEndOfLine",
|
||||
"g I": "vim::VisualInsertFirstNonWhiteSpace",
|
||||
"g A": "vim::VisualInsertEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
@@ -364,14 +364,12 @@
|
||||
"b": "vim::Parentheses",
|
||||
"[": "vim::SquareBrackets",
|
||||
"]": "vim::SquareBrackets",
|
||||
"r": "vim::SquareBrackets",
|
||||
"{": "vim::CurlyBrackets",
|
||||
"}": "vim::CurlyBrackets",
|
||||
"shift-b": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::AngleBrackets",
|
||||
"g": "vim::Argument"
|
||||
"a": "vim::Argument"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -68,17 +68,9 @@
|
||||
"ui_font_size": 16,
|
||||
// How much to fade out unused code.
|
||||
"unnecessary_code_fade": 0.3,
|
||||
// Active pane styling settings.
|
||||
"active_pane_modifiers": {
|
||||
// The factor to grow the active pane by. Defaults to 1.0
|
||||
// which gives the same size as all other panes.
|
||||
"magnification": 1.0,
|
||||
// Inset border size of the active pane, in pixels.
|
||||
"border_size": 0.0,
|
||||
// Opacity of the inactive panes. 0 means transparent, 1 means opaque.
|
||||
// Values are clamped to the [0.0, 1.0] range.
|
||||
"inactive_opacity": 1.0
|
||||
},
|
||||
// The factor to grow the active pane by. Defaults to 1.0
|
||||
// which gives the same size as all other panes.
|
||||
"active_pane_magnification": 1.0,
|
||||
// The direction that you want to split panes horizontally. Defaults to "up"
|
||||
"pane_split_direction_horizontal": "up",
|
||||
// The direction that you want to split panes horizontally. Defaults to "left"
|
||||
@@ -160,7 +152,7 @@
|
||||
"show_signature_help_after_edits": true,
|
||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
|
||||
// if softwrap is set to 'preferred_line_length', and will show any
|
||||
// additional guides as specified by the 'wrap_guides' setting.
|
||||
"show_wrap_guides": true,
|
||||
// Character counts at which to show wrap guides in the editor.
|
||||
@@ -182,8 +174,6 @@
|
||||
// bracket, brace, single or double quote characters.
|
||||
// For example, when you select text and type (, Zed will surround the text with ().
|
||||
"use_auto_surround": true,
|
||||
// Whether indentation of pasted content should be adjusted based on the context.
|
||||
"auto_indent_on_paste": true,
|
||||
// Controls how the editor handles the autoclosed characters.
|
||||
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
||||
// happen only for auto-inserted characters.
|
||||
@@ -485,7 +475,7 @@
|
||||
"default_width": 640,
|
||||
// Default height when the assistant is docked to the bottom.
|
||||
"default_height": 320,
|
||||
// The default model to use when creating new chats.
|
||||
// The default model to use when creating new contexts.
|
||||
"default_model": {
|
||||
// The provider to use.
|
||||
"provider": "zed.dev",
|
||||
@@ -830,6 +820,7 @@
|
||||
"tasks": {
|
||||
"variables": {}
|
||||
},
|
||||
"toolchain": { "name": "default", "path": "default" },
|
||||
// An object whose keys are language names, and whose values
|
||||
// are arrays of filenames or extensions of files that should
|
||||
// use those languages.
|
||||
|
||||
@@ -41,12 +41,10 @@ use prompts::PromptLoadingParams;
|
||||
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use slash_command::search_command::SearchSlashCommandFeatureFlag;
|
||||
use slash_command::{
|
||||
auto_command, cargo_workspace_command, context_server_command, default_command, delta_command,
|
||||
diagnostics_command, docs_command, fetch_command, file_command, now_command, project_command,
|
||||
prompt_command, search_command, selection_command, symbols_command, tab_command,
|
||||
terminal_command,
|
||||
prompt_command, search_command, symbols_command, tab_command, terminal_command,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -213,23 +211,21 @@ pub fn init(
|
||||
});
|
||||
}
|
||||
|
||||
if cx.has_flag::<SearchSlashCommandFeatureFlag>() {
|
||||
cx.spawn(|mut cx| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||
let semantic_index = SemanticDb::new(
|
||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
cx.spawn(|mut cx| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
|
||||
let semantic_index = SemanticDb::new(
|
||||
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
cx.update(|cx| cx.set_global(semantic_index))
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
cx.update(|cx| cx.set_global(semantic_index))
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
context_store::init(&client.clone().into());
|
||||
prompt_library::init(cx);
|
||||
@@ -440,7 +436,6 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
|
||||
slash_command_registry
|
||||
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(selection_command::SelectionCommand, true);
|
||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, false);
|
||||
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
|
||||
slash_command_registry.register_command(now_command::NowSlashCommand, false);
|
||||
|
||||
@@ -64,7 +64,6 @@ use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings};
|
||||
use smol::stream::StreamExt;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
borrow::Cow,
|
||||
cmp,
|
||||
ops::{ControlFlow, Range},
|
||||
@@ -443,24 +442,27 @@ impl AssistantPanel {
|
||||
);
|
||||
let _pane = cx.view().clone();
|
||||
let right_children = h_flex()
|
||||
.gap(Spacing::XSmall.rems(cx))
|
||||
.gap(Spacing::Small.rems(cx))
|
||||
.child(
|
||||
IconButton::new("new-chat", IconName::Plus)
|
||||
IconButton::new("new-context", IconName::Plus)
|
||||
.on_click(
|
||||
cx.listener(|_, _, cx| {
|
||||
cx.dispatch_action(NewContext.boxed_clone())
|
||||
}),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in("New Chat", &NewContext, &focus_handle, cx)
|
||||
Tooltip::for_action_in(
|
||||
"New Context",
|
||||
&NewContext,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
PopoverMenu::new("assistant-panel-popover-menu")
|
||||
.trigger(
|
||||
IconButton::new("menu", IconName::EllipsisVertical)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(|cx| Tooltip::text("Toggle Assistant Menu", cx)),
|
||||
IconButton::new("menu", IconName::Menu).icon_size(IconSize::Small),
|
||||
)
|
||||
.menu(move |cx| {
|
||||
let zoom_label = if _pane.read(cx).is_zoomed() {
|
||||
@@ -471,7 +473,7 @@ impl AssistantPanel {
|
||||
let focus_handle = _pane.focus_handle(cx);
|
||||
Some(ContextMenu::build(cx, move |menu, _| {
|
||||
menu.context(focus_handle.clone())
|
||||
.action("New Chat", Box::new(NewContext))
|
||||
.action("New Context", Box::new(NewContext))
|
||||
.action("History", Box::new(DeployHistory))
|
||||
.action("Prompt Library", Box::new(DeployPromptLibrary))
|
||||
.action("Configure", Box::new(ShowConfiguration))
|
||||
@@ -1080,21 +1082,7 @@ impl AssistantPanel {
|
||||
self.show_updated_summary(&context_editor, cx);
|
||||
cx.notify()
|
||||
}
|
||||
EditorEvent::Edited { .. } => {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let is_via_ssh = workspace
|
||||
.project()
|
||||
.update(cx, |project, _| project.is_via_ssh());
|
||||
|
||||
workspace
|
||||
.client()
|
||||
.telemetry()
|
||||
.log_edit_event("assistant panel", is_via_ssh);
|
||||
})
|
||||
.log_err();
|
||||
cx.emit(AssistantPanelEvent::ContextEdited)
|
||||
}
|
||||
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1509,7 +1497,7 @@ pub struct ContextEditor {
|
||||
dragged_file_worktrees: Vec<Model<Worktree>>,
|
||||
}
|
||||
|
||||
const DEFAULT_TAB_TITLE: &str = "New Chat";
|
||||
const DEFAULT_TAB_TITLE: &str = "New Context";
|
||||
const MAX_TAB_TITLE_LEN: usize = 16;
|
||||
|
||||
impl ContextEditor {
|
||||
@@ -2610,108 +2598,57 @@ impl ContextEditor {
|
||||
let context = self.context.clone();
|
||||
move |cx| {
|
||||
let message_id = MessageId(message.timestamp);
|
||||
let llm_loading = message.role == Role::Assistant
|
||||
let show_spinner = message.role == Role::Assistant
|
||||
&& message.status == MessageStatus::Pending;
|
||||
|
||||
let (label, spinner, note) = match message.role {
|
||||
Role::User => (
|
||||
Label::new("You").color(Color::Default).into_any_element(),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
let label = match message.role {
|
||||
Role::User => {
|
||||
Label::new("You").color(Color::Default).into_any_element()
|
||||
}
|
||||
Role::Assistant => {
|
||||
let base_label = Label::new("Assistant").color(Color::Info);
|
||||
let mut spinner = None;
|
||||
let mut note = None;
|
||||
let animated_label = if llm_loading {
|
||||
base_label
|
||||
let label = Label::new("Assistant").color(Color::Info);
|
||||
if show_spinner {
|
||||
label
|
||||
.with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.3, 0.9)),
|
||||
.with_easing(pulsating_between(0.4, 0.8)),
|
||||
|label, delta| label.alpha(delta),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
base_label.into_any_element()
|
||||
};
|
||||
if llm_loading {
|
||||
spinner = Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(
|
||||
percentage(delta),
|
||||
))
|
||||
},
|
||||
)
|
||||
.into_any_element(),
|
||||
);
|
||||
note = Some(
|
||||
div()
|
||||
.font(
|
||||
theme::ThemeSettings::get_global(cx)
|
||||
.buffer_font
|
||||
.clone(),
|
||||
)
|
||||
.child(
|
||||
Label::new("Press 'esc' to cancel")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.into_any_element(),
|
||||
);
|
||||
label.into_any_element()
|
||||
}
|
||||
(animated_label, spinner, note)
|
||||
}
|
||||
Role::System => (
|
||||
Label::new("System")
|
||||
.color(Color::Warning)
|
||||
.into_any_element(),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
|
||||
Role::System => Label::new("System")
|
||||
.color(Color::Warning)
|
||||
.into_any_element(),
|
||||
};
|
||||
|
||||
let sender = h_flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
ButtonLike::new("role")
|
||||
.style(ButtonStyle::Filled)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.child(label)
|
||||
.children(spinner),
|
||||
)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta(
|
||||
"Toggle message role",
|
||||
None,
|
||||
"Available roles: You (User), Assistant, System",
|
||||
let sender = ButtonLike::new("role")
|
||||
.style(ButtonStyle::Filled)
|
||||
.child(label)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta(
|
||||
"Toggle message role",
|
||||
None,
|
||||
"Available roles: You (User), Assistant, System",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let context = context.clone();
|
||||
move |_, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.cycle_message_roles(
|
||||
HashSet::from_iter(Some(message_id)),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let context = context.clone();
|
||||
move |_, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.cycle_message_roles(
|
||||
HashSet::from_iter(Some(message_id)),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
.children(note);
|
||||
}
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.id(("message_header", message_id.as_u64()))
|
||||
@@ -3036,11 +2973,97 @@ impl ContextEditor {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(creases) = selections_creases(workspace, cx) else {
|
||||
let Some(editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut creases = vec![];
|
||||
editor.update(cx, |editor, cx| {
|
||||
let selections = editor.selections.all_adjusted(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
for selection in selections {
|
||||
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
|
||||
..editor::ToOffset::to_offset(&selection.end, &buffer);
|
||||
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
|
||||
if selected_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let start_language = buffer.language_at(range.start);
|
||||
let end_language = buffer.language_at(range.end);
|
||||
let language_name = if start_language == end_language {
|
||||
start_language.map(|language| language.code_fence_block_name())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let language_name = language_name.as_deref().unwrap_or("");
|
||||
let filename = buffer
|
||||
.file_at(selection.start)
|
||||
.map(|file| file.full_path(cx));
|
||||
let text = if language_name == "markdown" {
|
||||
selected_text
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
let start_symbols = buffer
|
||||
.symbols_containing(selection.start, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
let end_symbols = buffer
|
||||
.symbols_containing(selection.end, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
|
||||
let outline_text = if let Some((start_symbols, end_symbols)) =
|
||||
start_symbols.zip(end_symbols)
|
||||
{
|
||||
Some(
|
||||
start_symbols
|
||||
.into_iter()
|
||||
.zip(end_symbols)
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a.text)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > "),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let line_comment_prefix = start_language
|
||||
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
|
||||
|
||||
let fence = codeblock_fence_for_path(
|
||||
filename.as_deref(),
|
||||
Some(selection.start.row..=selection.end.row),
|
||||
);
|
||||
|
||||
if let Some((line_comment_prefix, outline_text)) =
|
||||
line_comment_prefix.zip(outline_text)
|
||||
{
|
||||
let breadcrumb =
|
||||
format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
|
||||
format!("{fence}{breadcrumb}{selected_text}\n```")
|
||||
} else {
|
||||
format!("{fence}{selected_text}\n```")
|
||||
}
|
||||
};
|
||||
let crease_title = if let Some(path) = filename {
|
||||
let start_line = selection.start.row + 1;
|
||||
let end_line = selection.end.row + 1;
|
||||
if start_line == end_line {
|
||||
format!("{}, Line {}", path.display(), start_line)
|
||||
} else {
|
||||
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
|
||||
}
|
||||
} else {
|
||||
"Quoted selection".to_string()
|
||||
};
|
||||
creases.push((text, crease_title));
|
||||
}
|
||||
});
|
||||
if creases.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -3931,99 +3954,6 @@ fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Opti
|
||||
None
|
||||
}
|
||||
|
||||
pub fn selections_creases(
|
||||
workspace: &mut workspace::Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<Vec<(String, String)>> {
|
||||
let editor = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))?;
|
||||
|
||||
let mut creases = vec![];
|
||||
editor.update(cx, |editor, cx| {
|
||||
let selections = editor.selections.all_adjusted(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
for selection in selections {
|
||||
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
|
||||
..editor::ToOffset::to_offset(&selection.end, &buffer);
|
||||
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
|
||||
if selected_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let start_language = buffer.language_at(range.start);
|
||||
let end_language = buffer.language_at(range.end);
|
||||
let language_name = if start_language == end_language {
|
||||
start_language.map(|language| language.code_fence_block_name())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let language_name = language_name.as_deref().unwrap_or("");
|
||||
let filename = buffer
|
||||
.file_at(selection.start)
|
||||
.map(|file| file.full_path(cx));
|
||||
let text = if language_name == "markdown" {
|
||||
selected_text
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
let start_symbols = buffer
|
||||
.symbols_containing(selection.start, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
let end_symbols = buffer
|
||||
.symbols_containing(selection.end, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
|
||||
let outline_text =
|
||||
if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
|
||||
Some(
|
||||
start_symbols
|
||||
.into_iter()
|
||||
.zip(end_symbols)
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a.text)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > "),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let line_comment_prefix = start_language
|
||||
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
|
||||
|
||||
let fence = codeblock_fence_for_path(
|
||||
filename.as_deref(),
|
||||
Some(selection.start.row..=selection.end.row),
|
||||
);
|
||||
|
||||
if let Some((line_comment_prefix, outline_text)) =
|
||||
line_comment_prefix.zip(outline_text)
|
||||
{
|
||||
let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
|
||||
format!("{fence}{breadcrumb}{selected_text}\n```")
|
||||
} else {
|
||||
format!("{fence}{selected_text}\n```")
|
||||
}
|
||||
};
|
||||
let crease_title = if let Some(path) = filename {
|
||||
let start_line = selection.start.row + 1;
|
||||
let end_line = selection.end.row + 1;
|
||||
if start_line == end_line {
|
||||
format!("{}, Line {}", path.display(), start_line)
|
||||
} else {
|
||||
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
|
||||
}
|
||||
} else {
|
||||
"Quoted selection".to_string()
|
||||
};
|
||||
creases.push((text, crease_title));
|
||||
}
|
||||
});
|
||||
Some(creases)
|
||||
}
|
||||
|
||||
fn render_fold_icon_button(
|
||||
editor: WeakView<Editor>,
|
||||
icon: IconName,
|
||||
@@ -4197,21 +4127,6 @@ impl Item for ContextEditor {
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, Item::deactivated)
|
||||
}
|
||||
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
self_handle: &'a View<Self>,
|
||||
_: &'a AppContext,
|
||||
) -> Option<AnyView> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.to_any())
|
||||
} else if type_id == TypeId::of::<Editor>() {
|
||||
Some(self.editor.to_any())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchableItem for ContextEditor {
|
||||
@@ -4401,11 +4316,26 @@ impl FollowableItem for ContextEditor {
|
||||
|
||||
pub struct ContextEditorToolbarItem {
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakView<Workspace>,
|
||||
active_context_editor: Option<WeakView<ContextEditor>>,
|
||||
model_summary_editor: View<Editor>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
|
||||
}
|
||||
|
||||
fn active_editor_focus_handle(
|
||||
workspace: &WeakView<Workspace>,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> Option<FocusHandle> {
|
||||
workspace.upgrade().and_then(|workspace| {
|
||||
Some(
|
||||
workspace
|
||||
.read(cx)
|
||||
.active_item_as::<Editor>(cx)?
|
||||
.focus_handle(cx),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_inject_context_menu(
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
cx: &mut WindowContext<'_>,
|
||||
@@ -4432,6 +4362,7 @@ impl ContextEditorToolbarItem {
|
||||
) -> Self {
|
||||
Self {
|
||||
fs: workspace.app_state().fs.clone(),
|
||||
workspace: workspace.weak_handle(),
|
||||
active_context_editor: None,
|
||||
model_summary_editor,
|
||||
model_selector_menu_handle,
|
||||
@@ -4484,30 +4415,16 @@ impl ContextEditorToolbarItem {
|
||||
impl Render for ContextEditorToolbarItem {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let left_side = h_flex()
|
||||
.group("chat-title-group")
|
||||
.pl_0p5()
|
||||
.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(|cx| Tooltip::text("Regenerate Title", cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
|
||||
})),
|
||||
),
|
||||
);
|
||||
.pl_1()
|
||||
.gap_2()
|
||||
.flex_1()
|
||||
.min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
|
||||
.when(self.active_context_editor.is_some(), |left_side| {
|
||||
left_side.child(self.model_summary_editor.clone())
|
||||
});
|
||||
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let weak_self = cx.view().downgrade();
|
||||
let right_side = h_flex()
|
||||
.gap_2()
|
||||
// TODO display this in a nicer way, once we have a design for it.
|
||||
@@ -4520,6 +4437,7 @@ impl Render for ContextEditorToolbarItem {
|
||||
// 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))
|
||||
// })
|
||||
@@ -4541,13 +4459,9 @@ impl Render for ContextEditorToolbarItem {
|
||||
(Some(provider), Some(model)) => h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(
|
||||
model
|
||||
.icon()
|
||||
.unwrap_or_else(|| provider.icon()),
|
||||
)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
Icon::new(model.icon().unwrap_or_else(|| provider.icon()))
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
Label::new(model.name().0)
|
||||
@@ -4573,7 +4487,71 @@ impl Render for ContextEditorToolbarItem {
|
||||
)
|
||||
.with_handle(self.model_selector_menu_handle.clone()),
|
||||
)
|
||||
.children(self.render_remaining_tokens(cx));
|
||||
.children(self.render_remaining_tokens(cx))
|
||||
.child(
|
||||
PopoverMenu::new("context-editor-popover")
|
||||
.trigger(
|
||||
IconButton::new("context-editor-trigger", IconName::EllipsisVertical)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(|cx| Tooltip::text("Open Context Options", cx)),
|
||||
)
|
||||
.menu({
|
||||
let weak_self = weak_self.clone();
|
||||
move |cx| {
|
||||
let weak_self = weak_self.clone();
|
||||
Some(ContextMenu::build(cx, move |menu, cx| {
|
||||
let context = weak_self
|
||||
.update(cx, |this, cx| {
|
||||
active_editor_focus_handle(&this.workspace, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
menu.when_some(context, |menu, context| menu.context(context))
|
||||
.entry("Regenerate Context Title", None, {
|
||||
let weak_self = weak_self.clone();
|
||||
move |cx| {
|
||||
weak_self
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.custom_entry(
|
||||
|_| {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(Label::new("Add Context"))
|
||||
.child(Label::new("/ command").color(Color::Muted))
|
||||
.into_any()
|
||||
},
|
||||
{
|
||||
let weak_self = weak_self.clone();
|
||||
move |cx| {
|
||||
weak_self
|
||||
.update(cx, |this, cx| {
|
||||
if let Some(editor) =
|
||||
&this.active_context_editor
|
||||
{
|
||||
editor
|
||||
.update(cx, |this, cx| {
|
||||
this.slash_menu_handle
|
||||
.toggle(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
)
|
||||
.action("Add Selection", QuoteSelection.boxed_clone())
|
||||
}))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
h_flex()
|
||||
.size_full()
|
||||
@@ -4788,7 +4766,7 @@ impl ConfigurationView {
|
||||
h_flex().justify_end().child(
|
||||
Button::new(
|
||||
SharedString::from(format!("new-context-{provider_id}")),
|
||||
"Open New Chat",
|
||||
"Open new context",
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
|
||||
@@ -410,7 +410,7 @@ pub struct AssistantSettingsContentV2 {
|
||||
///
|
||||
/// Default: 320
|
||||
default_height: Option<f32>,
|
||||
/// The default model to use when creating new chats.
|
||||
/// The default model to use when creating new contexts.
|
||||
default_model: Option<LanguageModelSelection>,
|
||||
/// Additional models with which to generate alternatives when performing inline assists.
|
||||
inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
||||
@@ -498,11 +498,11 @@ pub struct LegacyAssistantSettingsContent {
|
||||
///
|
||||
/// Default: 320
|
||||
pub default_height: Option<f32>,
|
||||
/// The default OpenAI model to use when creating new chats.
|
||||
/// The default OpenAI model to use when creating new contexts.
|
||||
///
|
||||
/// Default: gpt-4-1106-preview
|
||||
pub default_open_ai_model: Option<OpenAiModel>,
|
||||
/// OpenAI API base URL to use when creating new chats.
|
||||
/// OpenAI API base URL to use when creating new contexts.
|
||||
///
|
||||
/// Default: https://api.openai.com/v1
|
||||
pub openai_api_url: Option<String>,
|
||||
|
||||
@@ -1052,9 +1052,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
|
||||
// Assume it will be a Chat request, even though that takes fewer tokens (and risks going over the limit),
|
||||
// because otherwise you see in the UI that your empty message has a bunch of tokens already used.
|
||||
let request = self.to_completion_request(RequestType::Chat, cx);
|
||||
let request = self.to_completion_request(RequestType::SuggestEdits, cx); // Conservatively assume SuggestEdits, since it takes more tokens.
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||
return;
|
||||
};
|
||||
@@ -2204,7 +2202,7 @@ impl Context {
|
||||
}
|
||||
|
||||
if let RequestType::SuggestEdits = request_type {
|
||||
if let Ok(preamble) = self.prompt_builder.generate_suggest_edits_prompt() {
|
||||
if let Ok(preamble) = self.prompt_builder.generate_workflow_prompt() {
|
||||
let last_elem_index = completion_request.messages.len();
|
||||
|
||||
completion_request
|
||||
|
||||
@@ -84,7 +84,7 @@ pub struct InlineAssistant {
|
||||
confirmed_assists: HashMap<InlineAssistId, Model<CodegenAlternative>>,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ impl InlineAssistant {
|
||||
confirmed_assists: HashMap::default(),
|
||||
prompt_history: VecDeque::default(),
|
||||
prompt_builder,
|
||||
telemetry,
|
||||
telemetry: Some(telemetry),
|
||||
fs,
|
||||
}
|
||||
}
|
||||
@@ -241,17 +241,19 @@ impl InlineAssistant {
|
||||
codegen_ranges.push(start..end);
|
||||
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
self.telemetry.report_assistant_event(AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
message_id: None,
|
||||
model: model.telemetry_id(),
|
||||
model_provider: model.provider_id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||
});
|
||||
if let Some(telemetry) = self.telemetry.as_ref() {
|
||||
telemetry.report_assistant_event(AssistantEvent {
|
||||
conversation_id: None,
|
||||
kind: AssistantKind::Inline,
|
||||
phase: AssistantPhase::Invoked,
|
||||
message_id: None,
|
||||
model: model.telemetry_id(),
|
||||
model_provider: model.provider_id().to_string(),
|
||||
response_latency: None,
|
||||
error_message: None,
|
||||
language_name: buffer.language().map(|language| language.name().to_proto()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,7 +816,7 @@ impl InlineAssistant {
|
||||
error_message: None,
|
||||
language_name: language_name.map(|name| name.to_proto()),
|
||||
},
|
||||
Some(self.telemetry.clone()),
|
||||
self.telemetry.clone(),
|
||||
cx.http_client(),
|
||||
model.api_key(cx),
|
||||
cx.background_executor(),
|
||||
@@ -1755,20 +1757,6 @@ impl PromptEditor {
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::Edited { .. } => {
|
||||
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let is_via_ssh = workspace
|
||||
.project()
|
||||
.update(cx, |project, _| project.is_via_ssh());
|
||||
|
||||
workspace
|
||||
.client()
|
||||
.telemetry()
|
||||
.log_edit_event("inline assist", is_via_ssh);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
let prompt = self.editor.read(cx).text(cx);
|
||||
if self
|
||||
.prompt_history_ix
|
||||
@@ -2302,7 +2290,7 @@ pub struct Codegen {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
is_insertion: bool,
|
||||
}
|
||||
@@ -2312,7 +2300,7 @@ impl Codegen {
|
||||
buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
@@ -2321,7 +2309,7 @@ impl Codegen {
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
false,
|
||||
Some(telemetry.clone()),
|
||||
telemetry.clone(),
|
||||
builder.clone(),
|
||||
cx,
|
||||
)
|
||||
@@ -2412,7 +2400,7 @@ impl Codegen {
|
||||
self.buffer.clone(),
|
||||
self.range.clone(),
|
||||
false,
|
||||
Some(self.telemetry.clone()),
|
||||
self.telemetry.clone(),
|
||||
self.builder.clone(),
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -310,7 +310,7 @@ impl PromptBuilder {
|
||||
.render("terminal_assistant_prompt", &context)
|
||||
}
|
||||
|
||||
pub fn generate_suggest_edits_prompt(&self) -> Result<String, RenderError> {
|
||||
pub fn generate_workflow_prompt(&self) -> Result<String, RenderError> {
|
||||
self.handlebars.lock().render("suggest_edits", &())
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ pub mod now_command;
|
||||
pub mod project_command;
|
||||
pub mod prompt_command;
|
||||
pub mod search_command;
|
||||
pub mod selection_command;
|
||||
pub mod streaming_example_command;
|
||||
pub mod symbols_command;
|
||||
pub mod tab_command;
|
||||
|
||||
@@ -21,6 +21,8 @@ use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::slash_command::diagnostics_command::collect_buffer_diagnostics;
|
||||
|
||||
pub(crate) struct FileSlashCommand;
|
||||
|
||||
impl FileSlashCommand {
|
||||
@@ -541,6 +543,8 @@ pub fn append_buffer_to_output(
|
||||
output.text.push('\n');
|
||||
|
||||
let section_ix = output.sections.len();
|
||||
collect_buffer_diagnostics(output, buffer, false);
|
||||
|
||||
output.sections.insert(
|
||||
section_ix,
|
||||
build_entry_output_section(prev_len..output.text.len(), path, false, None),
|
||||
|
||||
@@ -21,10 +21,6 @@ pub(crate) struct SearchSlashCommandFeatureFlag;
|
||||
|
||||
impl FeatureFlag for SearchSlashCommandFeatureFlag {
|
||||
const NAME: &'static str = "search-slash-command";
|
||||
|
||||
fn enabled_for_staff() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SearchSlashCommand;
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
use crate::assistant_panel::selections_creases;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
|
||||
SlashCommandOutputSection, SlashCommandResult,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use ui::{IconName, SharedString, WindowContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct SelectionCommand;
|
||||
|
||||
impl SlashCommand for SelectionCommand {
|
||||
fn name(&self) -> String {
|
||||
"selection".into()
|
||||
}
|
||||
|
||||
fn label(&self, _cx: &AppContext) -> CodeLabel {
|
||||
CodeLabel::plain(self.name(), None)
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Insert editor selection".into()
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
IconName::Quote
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
self.description()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn accepts_arguments(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakView<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let mut events = vec![];
|
||||
|
||||
let Some(creases) = workspace
|
||||
.update(cx, selections_creases)
|
||||
.unwrap_or_else(|e| {
|
||||
events.push(Err(e));
|
||||
None
|
||||
})
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("no active selection")));
|
||||
};
|
||||
|
||||
for (text, title) in creases {
|
||||
events.push(Ok(SlashCommandEvent::StartSection {
|
||||
icon: IconName::TextSnippet,
|
||||
label: SharedString::from(title),
|
||||
metadata: None,
|
||||
}));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text,
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
events.push(Ok(SlashCommandEvent::EndSection { metadata: None }));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: "\n".to_string(),
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
}
|
||||
|
||||
let result = futures::stream::iter(events).boxed();
|
||||
|
||||
Task::ready(Ok(result))
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@ use assistant_slash_command::SlashCommandRegistry;
|
||||
|
||||
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
|
||||
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
|
||||
use ui::{prelude::*, KeyBinding, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
|
||||
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
use crate::QuoteSelection;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
|
||||
@@ -31,6 +32,7 @@ enum SlashCommandEntry {
|
||||
renderer: fn(&mut WindowContext<'_>) -> AnyElement,
|
||||
on_confirm: fn(&mut WindowContext<'_>),
|
||||
},
|
||||
QuoteButton,
|
||||
}
|
||||
|
||||
impl AsRef<str> for SlashCommandEntry {
|
||||
@@ -38,6 +40,7 @@ impl AsRef<str> for SlashCommandEntry {
|
||||
match self {
|
||||
SlashCommandEntry::Info(SlashCommandInfo { name, .. })
|
||||
| SlashCommandEntry::Advert { name, .. } => name,
|
||||
SlashCommandEntry::QuoteButton => "Quote Selection",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,6 +153,9 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
SlashCommandEntry::QuoteButton => {
|
||||
cx.dispatch_action(Box::new(QuoteSelection));
|
||||
}
|
||||
SlashCommandEntry::Advert { on_confirm, .. } => {
|
||||
on_confirm(cx);
|
||||
}
|
||||
@@ -217,6 +223,40 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
),
|
||||
),
|
||||
),
|
||||
SlashCommandEntry::QuoteButton => {
|
||||
let focus = cx.focus_handle();
|
||||
let key_binding = KeyBinding::for_action_in(&QuoteSelection, &focus, cx);
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Dense)
|
||||
.selected(selected)
|
||||
.child(
|
||||
v_flex()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(IconName::Quote).size(IconSize::XSmall))
|
||||
.child(
|
||||
div().font_buffer(cx).child(
|
||||
Label::new("selection").size(LabelSize::Small),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new("Insert editor selection")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.children(key_binding.map(|kb| kb.render(cx))),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
SlashCommandEntry::Advert { renderer, .. } => Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
@@ -250,44 +290,47 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
icon: command.icon(),
|
||||
}))
|
||||
})
|
||||
.chain([SlashCommandEntry::Advert {
|
||||
name: "create-your-command".into(),
|
||||
renderer: |cx| {
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.font_buffer(cx)
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(IconName::Plus).size(IconSize::XSmall))
|
||||
.child(
|
||||
div().font_buffer(cx).child(
|
||||
Label::new("create-your-command")
|
||||
.size(LabelSize::Small),
|
||||
.chain([
|
||||
SlashCommandEntry::Advert {
|
||||
name: "create-your-command".into(),
|
||||
renderer: |cx| {
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.font_buffer(cx)
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(IconName::Plus).size(IconSize::XSmall))
|
||||
.child(
|
||||
div().font_buffer(cx).child(
|
||||
Label::new("create-your-command")
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ArrowUpRight)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Create your custom command")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ArrowUpRight)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Create your custom command")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
},
|
||||
on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
|
||||
},
|
||||
on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
|
||||
}])
|
||||
SlashCommandEntry::QuoteButton,
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let delegate = SlashCommandDelegate {
|
||||
|
||||
@@ -28,36 +28,13 @@ impl Matrix {
|
||||
self.cols = cols;
|
||||
}
|
||||
|
||||
fn swap_columns(&mut self, col1: usize, col2: usize) {
|
||||
if col1 == col2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if col1 >= self.cols {
|
||||
panic!("column out of bounds");
|
||||
}
|
||||
|
||||
if col2 >= self.cols {
|
||||
panic!("column out of bounds");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ptr = self.cells.as_mut_ptr();
|
||||
std::ptr::swap_nonoverlapping(
|
||||
ptr.add(col1 * self.rows),
|
||||
ptr.add(col2 * self.rows),
|
||||
self.rows,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, row: usize, col: usize) -> f64 {
|
||||
if row >= self.rows {
|
||||
panic!("row out of bounds")
|
||||
}
|
||||
|
||||
if col >= self.cols {
|
||||
panic!("column out of bounds")
|
||||
panic!("col out of bounds")
|
||||
}
|
||||
self.cells[col * self.rows + row]
|
||||
}
|
||||
@@ -68,7 +45,7 @@ impl Matrix {
|
||||
}
|
||||
|
||||
if col >= self.cols {
|
||||
panic!("column out of bounds")
|
||||
panic!("col out of bounds")
|
||||
}
|
||||
|
||||
self.cells[col * self.rows + row] = value;
|
||||
@@ -129,32 +106,26 @@ impl StreamingDiff {
|
||||
|
||||
pub fn push_new(&mut self, text: &str) -> Vec<CharOperation> {
|
||||
self.new.extend(text.chars());
|
||||
self.scores.swap_columns(0, self.scores.cols - 1);
|
||||
self.scores
|
||||
.resize(self.old.len() + 1, self.new.len() - self.new_text_ix + 1);
|
||||
self.equal_runs.retain(|(_i, j), _| *j == self.new_text_ix);
|
||||
self.scores.resize(self.old.len() + 1, self.new.len() + 1);
|
||||
|
||||
for j in self.new_text_ix + 1..=self.new.len() {
|
||||
let relative_j = j - self.new_text_ix;
|
||||
|
||||
self.scores
|
||||
.set(0, relative_j, j as f64 * Self::INSERTION_SCORE);
|
||||
self.scores.set(0, j, j as f64 * Self::INSERTION_SCORE);
|
||||
for i in 1..=self.old.len() {
|
||||
let insertion_score = self.scores.get(i, relative_j - 1) + Self::INSERTION_SCORE;
|
||||
let deletion_score = self.scores.get(i - 1, relative_j) + Self::DELETION_SCORE;
|
||||
let insertion_score = self.scores.get(i, j - 1) + Self::INSERTION_SCORE;
|
||||
let deletion_score = self.scores.get(i - 1, j) + Self::DELETION_SCORE;
|
||||
let equality_score = if self.old[i - 1] == self.new[j - 1] {
|
||||
let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0);
|
||||
equal_run += 1;
|
||||
self.equal_runs.insert((i, j), equal_run);
|
||||
|
||||
let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT);
|
||||
self.scores.get(i - 1, relative_j - 1) + Self::EQUALITY_BASE.powi(exponent)
|
||||
self.scores.get(i - 1, j - 1) + Self::EQUALITY_BASE.powi(exponent)
|
||||
} else {
|
||||
f64::NEG_INFINITY
|
||||
};
|
||||
|
||||
let score = insertion_score.max(deletion_score).max(equality_score);
|
||||
self.scores.set(i, relative_j, score);
|
||||
self.scores.set(i, j, score);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +133,7 @@ impl StreamingDiff {
|
||||
let mut next_old_text_ix = self.old_text_ix;
|
||||
let next_new_text_ix = self.new.len();
|
||||
for i in self.old_text_ix..=self.old.len() {
|
||||
let score = self.scores.get(i, next_new_text_ix - self.new_text_ix);
|
||||
let score = self.scores.get(i, next_new_text_ix);
|
||||
if score > max_score {
|
||||
max_score = score;
|
||||
next_old_text_ix = i;
|
||||
@@ -203,9 +174,7 @@ impl StreamingDiff {
|
||||
|
||||
let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]
|
||||
.iter()
|
||||
.max_by_key(|cell| {
|
||||
cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j - self.new_text_ix)))
|
||||
})
|
||||
.max_by_key(|cell| cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j))))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -432,9 +432,6 @@ impl AutoUpdater {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// If you are packaging Zed and need to override the place it downloads SSH remotes from,
|
||||
// you can override this function. You should also update get_remote_server_release_url to return
|
||||
// Ok(None).
|
||||
pub async fn download_remote_server_release(
|
||||
os: &str,
|
||||
arch: &str,
|
||||
@@ -485,7 +482,7 @@ impl AutoUpdater {
|
||||
release_channel: ReleaseChannel,
|
||||
version: Option<SemanticVersion>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Option<(String, String)>> {
|
||||
) -> Result<(JsonRelease, String)> {
|
||||
let this = cx.update(|cx| {
|
||||
cx.default_global::<GlobalAutoUpdate>()
|
||||
.0
|
||||
@@ -507,7 +504,7 @@ impl AutoUpdater {
|
||||
let update_request_body = build_remote_server_update_request_body(cx)?;
|
||||
let body = serde_json::to_string(&update_request_body)?;
|
||||
|
||||
Ok(Some((release.url, body)))
|
||||
Ok((release, body))
|
||||
}
|
||||
|
||||
async fn get_release(
|
||||
|
||||
@@ -44,6 +44,7 @@ sha2.workspace = true
|
||||
smol.workspace = true
|
||||
sysinfo.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
tempfile.workspace = true
|
||||
text.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
|
||||
@@ -13,7 +13,6 @@ use parking_lot::Mutex;
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
|
||||
@@ -22,7 +21,10 @@ use telemetry_events::{
|
||||
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
|
||||
SettingEvent,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use tempfile::NamedTempFile;
|
||||
#[cfg(not(debug_assertions))]
|
||||
use util::ResultExt;
|
||||
use util::TryFutureExt;
|
||||
use worktree::{UpdatedEntriesSet, WorktreeId};
|
||||
|
||||
use self::event_coalescer::EventCoalescer;
|
||||
@@ -44,7 +46,7 @@ struct TelemetryState {
|
||||
architecture: &'static str,
|
||||
events_queue: Vec<EventWrapper>,
|
||||
flush_events_task: Option<Task<()>>,
|
||||
log_file: Option<File>,
|
||||
log_file: Option<NamedTempFile>,
|
||||
is_staff: Option<bool>,
|
||||
first_event_date_time: Option<DateTime<Utc>>,
|
||||
event_coalescer: EventCoalescer,
|
||||
@@ -221,13 +223,15 @@ impl Telemetry {
|
||||
os_name: os_name(),
|
||||
app_version: release_channel::AppVersion::global(cx).to_string(),
|
||||
}));
|
||||
Self::log_file_path();
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
cx.background_executor()
|
||||
.spawn({
|
||||
let state = state.clone();
|
||||
async move {
|
||||
if let Some(tempfile) = File::create(Self::log_file_path()).log_err() {
|
||||
if let Some(tempfile) =
|
||||
NamedTempFile::new_in(paths::logs_dir().as_path()).log_err()
|
||||
{
|
||||
state.lock().log_file = Some(tempfile);
|
||||
}
|
||||
}
|
||||
@@ -276,8 +280,8 @@ impl Telemetry {
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
pub fn log_file_path() -> PathBuf {
|
||||
paths::logs_dir().join("telemetry.log")
|
||||
pub fn log_file_path(&self) -> Option<PathBuf> {
|
||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
||||
}
|
||||
|
||||
pub fn start(
|
||||
@@ -641,6 +645,7 @@ impl Telemetry {
|
||||
let mut json_bytes = Vec::new();
|
||||
|
||||
if let Some(file) = &mut this.state.lock().log_file {
|
||||
let file = file.as_file_mut();
|
||||
for event in &mut events {
|
||||
json_bytes.clear();
|
||||
serde_json::to_writer(&mut json_bytes, event)?;
|
||||
|
||||
@@ -6497,7 +6497,7 @@ async fn test_context_collaboration_with_reconnect(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client A creates a new chats.
|
||||
// Client A creates a new context.
|
||||
let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
|
||||
executor.run_until_parked();
|
||||
|
||||
|
||||
@@ -35,30 +35,14 @@ pub enum Model {
|
||||
Gpt4,
|
||||
#[serde(alias = "gpt-3.5-turbo", rename = "gpt-3.5-turbo")]
|
||||
Gpt3_5Turbo,
|
||||
#[serde(alias = "o1-preview", rename = "o1-preview-2024-09-12")]
|
||||
O1Preview,
|
||||
#[serde(alias = "o1-mini", rename = "o1-mini-2024-09-12")]
|
||||
O1Mini,
|
||||
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
|
||||
Claude3_5Sonnet,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn uses_streaming(&self) -> bool {
|
||||
match self {
|
||||
Self::Gpt4o | Self::Gpt4 | Self::Gpt3_5Turbo | Self::Claude3_5Sonnet => true,
|
||||
Self::O1Mini | Self::O1Preview => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_id(id: &str) -> Result<Self> {
|
||||
match id {
|
||||
"gpt-4o" => Ok(Self::Gpt4o),
|
||||
"gpt-4" => Ok(Self::Gpt4),
|
||||
"gpt-3.5-turbo" => Ok(Self::Gpt3_5Turbo),
|
||||
"o1-preview" => Ok(Self::O1Preview),
|
||||
"o1-mini" => Ok(Self::O1Mini),
|
||||
"claude-3-5-sonnet" => Ok(Self::Claude3_5Sonnet),
|
||||
_ => Err(anyhow!("Invalid model id: {}", id)),
|
||||
}
|
||||
}
|
||||
@@ -68,9 +52,6 @@ impl Model {
|
||||
Self::Gpt3_5Turbo => "gpt-3.5-turbo",
|
||||
Self::Gpt4 => "gpt-4",
|
||||
Self::Gpt4o => "gpt-4o",
|
||||
Self::O1Mini => "o1-mini",
|
||||
Self::O1Preview => "o1-preview",
|
||||
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,20 +60,14 @@ impl Model {
|
||||
Self::Gpt3_5Turbo => "GPT-3.5",
|
||||
Self::Gpt4 => "GPT-4",
|
||||
Self::Gpt4o => "GPT-4o",
|
||||
Self::O1Mini => "o1-mini",
|
||||
Self::O1Preview => "o1-preview",
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_token_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Gpt4o => 64000,
|
||||
Self::Gpt4 => 32768,
|
||||
Self::Gpt3_5Turbo => 12288,
|
||||
Self::O1Mini => 20000,
|
||||
Self::O1Preview => 20000,
|
||||
Self::Claude3_5Sonnet => 200_000,
|
||||
Self::Gpt4o => 128000,
|
||||
Self::Gpt4 => 8192,
|
||||
Self::Gpt3_5Turbo => 16385,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +87,7 @@ impl Request {
|
||||
Self {
|
||||
intent: true,
|
||||
n: 1,
|
||||
stream: model.uses_streaming(),
|
||||
stream: true,
|
||||
temperature: 0.1,
|
||||
model,
|
||||
messages,
|
||||
@@ -138,8 +113,7 @@ pub struct ResponseEvent {
|
||||
pub struct ResponseChoice {
|
||||
pub index: usize,
|
||||
pub finish_reason: Option<String>,
|
||||
pub delta: Option<ResponseDelta>,
|
||||
pub message: Option<ResponseDelta>,
|
||||
pub delta: ResponseDelta,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -359,23 +333,9 @@ async fn stream_completion(
|
||||
if let Some(low_speed_timeout) = low_speed_timeout {
|
||||
request_builder = request_builder.read_timeout(low_speed_timeout);
|
||||
}
|
||||
let is_streaming = request.stream;
|
||||
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
let mut response = client.send(request).await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await?;
|
||||
let body_str = std::str::from_utf8(&body)?;
|
||||
return Err(anyhow!(
|
||||
"Failed to connect to API: {} {}",
|
||||
response.status(),
|
||||
body_str
|
||||
));
|
||||
}
|
||||
|
||||
if is_streaming {
|
||||
if response.status().is_success() {
|
||||
let reader = BufReader::new(response.into_body());
|
||||
Ok(reader
|
||||
.lines()
|
||||
@@ -407,9 +367,19 @@ async fn stream_completion(
|
||||
} else {
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await?;
|
||||
let body_str = std::str::from_utf8(&body)?;
|
||||
let response: ResponseEvent = serde_json::from_str(body_str)?;
|
||||
|
||||
Ok(futures::stream::once(async move { Ok(response) }).boxed())
|
||||
let body_str = std::str::from_utf8(&body)?;
|
||||
|
||||
match serde_json::from_str::<ResponseEvent>(body_str) {
|
||||
Ok(_) => Err(anyhow!(
|
||||
"Unexpected success response while expecting an error: {}",
|
||||
body_str,
|
||||
)),
|
||||
Err(_) => Err(anyhow!(
|
||||
"Failed to connect to API: {} {}",
|
||||
response.status(),
|
||||
body_str,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ collections.workspace = true
|
||||
ctor.workspace = true
|
||||
editor.workspace = true
|
||||
env_logger.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -14,6 +14,10 @@ use editor::{
|
||||
scroll::Autoscroll,
|
||||
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
|
||||
};
|
||||
use futures::{
|
||||
channel::mpsc::{self, UnboundedSender},
|
||||
StreamExt as _,
|
||||
};
|
||||
use gpui::{
|
||||
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
|
||||
FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
|
||||
@@ -58,10 +62,11 @@ struct ProjectDiagnosticsEditor {
|
||||
summary: DiagnosticSummary,
|
||||
excerpts: Model<MultiBuffer>,
|
||||
path_states: Vec<PathState>,
|
||||
paths_to_update: BTreeSet<(ProjectPath, Option<LanguageServerId>)>,
|
||||
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
|
||||
include_warnings: bool,
|
||||
context: u32,
|
||||
update_excerpts_task: Option<Task<Result<()>>>,
|
||||
update_paths_tx: UnboundedSender<(ProjectPath, Option<LanguageServerId>)>,
|
||||
_update_excerpts_task: Task<Result<()>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
@@ -124,14 +129,14 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
|
||||
log::debug!("disk based diagnostics finished for server {language_server_id}");
|
||||
this.update_stale_excerpts(cx);
|
||||
this.enqueue_update_stale_excerpts(Some(*language_server_id));
|
||||
}
|
||||
project::Event::DiagnosticsUpdated {
|
||||
language_server_id,
|
||||
path,
|
||||
} => {
|
||||
this.paths_to_update
|
||||
.insert((path.clone(), Some(*language_server_id)));
|
||||
.insert((path.clone(), *language_server_id));
|
||||
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
||||
cx.emit(EditorEvent::TitleChanged);
|
||||
|
||||
@@ -139,7 +144,7 @@ impl ProjectDiagnosticsEditor {
|
||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
|
||||
} else {
|
||||
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
|
||||
this.update_stale_excerpts(cx);
|
||||
this.enqueue_update_stale_excerpts(Some(*language_server_id));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -166,12 +171,14 @@ impl ProjectDiagnosticsEditor {
|
||||
cx.focus(&this.focus_handle);
|
||||
}
|
||||
}
|
||||
EditorEvent::Blurred => this.update_stale_excerpts(cx),
|
||||
EditorEvent::Blurred => this.enqueue_update_stale_excerpts(None),
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let (update_excerpts_tx, mut update_excerpts_rx) = mpsc::unbounded();
|
||||
|
||||
let project = project_handle.read(cx);
|
||||
let mut this = Self {
|
||||
project: project_handle.clone(),
|
||||
@@ -184,45 +191,27 @@ impl ProjectDiagnosticsEditor {
|
||||
path_states: Default::default(),
|
||||
paths_to_update: Default::default(),
|
||||
include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||
update_excerpts_task: None,
|
||||
update_paths_tx: update_excerpts_tx,
|
||||
_update_excerpts_task: cx.spawn(move |this, mut cx| async move {
|
||||
while let Some((path, language_server_id)) = update_excerpts_rx.next().await {
|
||||
if let Some(buffer) = project_handle
|
||||
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_excerpts(path, language_server_id, buffer, cx);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}),
|
||||
_subscription: project_event_subscription,
|
||||
};
|
||||
this.update_all_excerpts(cx);
|
||||
this.enqueue_update_all_excerpts(cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn update_stale_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if self.update_excerpts_task.is_some() {
|
||||
return;
|
||||
}
|
||||
let project_handle = self.project.clone();
|
||||
self.update_excerpts_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
loop {
|
||||
let Some((path, language_server_id)) = this.update(&mut cx, |this, _| {
|
||||
let Some((path, language_server_id)) = this.paths_to_update.pop_first() else {
|
||||
this.update_excerpts_task.take();
|
||||
return None;
|
||||
};
|
||||
Some((path, language_server_id))
|
||||
})?
|
||||
else {
|
||||
break;
|
||||
};
|
||||
|
||||
if let Some(buffer) = project_handle
|
||||
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_excerpts(path, language_server_id, buffer, cx);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
fn new(
|
||||
project_handle: Model<Project>,
|
||||
workspace: WeakView<Workspace>,
|
||||
@@ -250,7 +239,7 @@ impl ProjectDiagnosticsEditor {
|
||||
|
||||
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
|
||||
self.include_warnings = !self.include_warnings;
|
||||
self.update_all_excerpts(cx);
|
||||
self.enqueue_update_all_excerpts(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -262,28 +251,37 @@ impl ProjectDiagnosticsEditor {
|
||||
|
||||
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if !self.focus_handle.is_focused(cx) && !self.editor.focus_handle(cx).is_focused(cx) {
|
||||
self.update_stale_excerpts(cx);
|
||||
self.enqueue_update_stale_excerpts(None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueue an update of all excerpts. Updates all paths that either
|
||||
/// currently have diagnostics or are currently present in this view.
|
||||
fn update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn enqueue_update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.project.update(cx, |project, cx| {
|
||||
let mut paths = project
|
||||
.diagnostic_summaries(false, cx)
|
||||
.map(|(path, _, _)| (path, None))
|
||||
.map(|(path, _, _)| path)
|
||||
.collect::<BTreeSet<_>>();
|
||||
paths.extend(
|
||||
self.path_states
|
||||
.iter()
|
||||
.map(|state| (state.path.clone(), None)),
|
||||
);
|
||||
let paths_to_update = std::mem::take(&mut self.paths_to_update);
|
||||
paths.extend(paths_to_update.into_iter().map(|(path, _)| (path, None)));
|
||||
self.paths_to_update = paths;
|
||||
paths.extend(self.path_states.iter().map(|state| state.path.clone()));
|
||||
for path in paths {
|
||||
self.update_paths_tx.unbounded_send((path, None)).unwrap();
|
||||
}
|
||||
});
|
||||
self.update_stale_excerpts(cx);
|
||||
}
|
||||
|
||||
/// Enqueue an update of the excerpts for any path whose diagnostics are known
|
||||
/// to have changed. If a language server id is passed, then only the excerpts for
|
||||
/// that language server's diagnostics will be updated. Otherwise, all stale excerpts
|
||||
/// will be refreshed.
|
||||
fn enqueue_update_stale_excerpts(&mut self, language_server_id: Option<LanguageServerId>) {
|
||||
for (path, server_id) in &self.paths_to_update {
|
||||
if language_server_id.map_or(true, |id| id == *server_id) {
|
||||
self.update_paths_tx
|
||||
.unbounded_send((path.clone(), Some(*server_id)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_excerpts(
|
||||
@@ -293,6 +291,11 @@ impl ProjectDiagnosticsEditor {
|
||||
buffer: Model<Buffer>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.paths_to_update.retain(|(path, server_id)| {
|
||||
*path != path_to_update
|
||||
|| server_to_update.map_or(false, |to_update| *server_id != to_update)
|
||||
});
|
||||
|
||||
let was_empty = self.path_states.is_empty();
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let path_ix = match self
|
||||
|
||||
@@ -800,7 +800,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
}
|
||||
|
||||
log::info!("updating mutated diagnostics view");
|
||||
mutated_view.update(cx, |view, cx| view.update_stale_excerpts(cx));
|
||||
mutated_view.update(cx, |view, _| view.enqueue_update_stale_excerpts(None));
|
||||
cx.run_until_parked();
|
||||
|
||||
log::info!("constructing reference diagnostics view");
|
||||
|
||||
@@ -14,12 +14,12 @@ impl Render for ToolbarControls {
|
||||
let mut has_stale_excerpts = false;
|
||||
let mut is_updating = false;
|
||||
|
||||
if let Some(editor) = self.diagnostics() {
|
||||
let diagnostics = editor.read(cx);
|
||||
include_warnings = diagnostics.include_warnings;
|
||||
has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
|
||||
is_updating = diagnostics.update_excerpts_task.is_some()
|
||||
|| diagnostics
|
||||
if let Some(editor) = self.editor() {
|
||||
let editor = editor.read(cx);
|
||||
include_warnings = editor.include_warnings;
|
||||
has_stale_excerpts = !editor.paths_to_update.is_empty();
|
||||
is_updating = !editor.update_paths_tx.is_empty()
|
||||
|| editor
|
||||
.project
|
||||
.read(cx)
|
||||
.language_servers_running_disk_based_diagnostics(cx)
|
||||
@@ -49,9 +49,9 @@ impl Render for ToolbarControls {
|
||||
.disabled(is_updating)
|
||||
.tooltip(move |cx| Tooltip::text("Update excerpts", cx))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(diagnostics) = this.diagnostics() {
|
||||
diagnostics.update(cx, |diagnostics, cx| {
|
||||
diagnostics.update_all_excerpts(cx);
|
||||
if let Some(editor) = this.editor() {
|
||||
editor.update(cx, |editor, _| {
|
||||
editor.enqueue_update_stale_excerpts(None);
|
||||
});
|
||||
}
|
||||
})),
|
||||
@@ -63,7 +63,7 @@ impl Render for ToolbarControls {
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip, cx))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(editor) = this.diagnostics() {
|
||||
if let Some(editor) = this.editor() {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_warnings(&Default::default(), cx);
|
||||
});
|
||||
@@ -105,7 +105,7 @@ impl ToolbarControls {
|
||||
ToolbarControls { editor: None }
|
||||
}
|
||||
|
||||
fn diagnostics(&self) -> Option<View<ProjectDiagnosticsEditor>> {
|
||||
fn editor(&self) -> Option<View<ProjectDiagnosticsEditor>> {
|
||||
self.editor.as_ref()?.upgrade()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,6 @@ tree-sitter-html = { workspace = true, optional = true }
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
unicode-segmentation.workspace = true
|
||||
unicode-script.workspace = true
|
||||
unindent = { workspace = true, optional = true }
|
||||
ui.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
@@ -1172,7 +1172,7 @@ impl Sub for DisplayPoint {
|
||||
#[serde(transparent)]
|
||||
pub struct DisplayRow(pub u32);
|
||||
|
||||
impl Add<DisplayRow> for DisplayRow {
|
||||
impl Add for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
@@ -1180,15 +1180,7 @@ impl Add<DisplayRow> for DisplayRow {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<u32> for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: u32) -> Self::Output {
|
||||
DisplayRow(self.0 + other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<DisplayRow> for DisplayRow {
|
||||
impl Sub for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
@@ -1196,14 +1188,6 @@ impl Sub<DisplayRow> for DisplayRow {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<u32> for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: u32) -> Self::Output {
|
||||
DisplayRow(self.0 - other)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayPoint {
|
||||
pub fn new(row: DisplayRow, column: u32) -> Self {
|
||||
Self(BlockPoint(Point::new(row.0, column)))
|
||||
|
||||
@@ -49,7 +49,6 @@ pub mod test;
|
||||
|
||||
use ::git::diff::DiffHunkStatus;
|
||||
pub(crate) use actions::*;
|
||||
pub use actions::{OpenExcerpts, OpenExcerptsSplit};
|
||||
use aho_corasick::AhoCorasick;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use blink_manager::BlinkManager;
|
||||
@@ -104,7 +103,6 @@ pub use proposed_changes_editor::{
|
||||
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
|
||||
};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use std::iter::Peekable;
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
|
||||
@@ -1355,22 +1353,22 @@ impl CompletionsMenu {
|
||||
// Strong matches are the ones with a high fuzzy-matcher score (the "obvious" matches)
|
||||
// and the Weak matches are the rest.
|
||||
//
|
||||
// For the strong matches, we sort by our fuzzy-finder score first and for the weak
|
||||
// matches, we prefer language-server sort_text first.
|
||||
// For the strong matches, we sort by the language-servers score first and for the weak
|
||||
// matches, we prefer our fuzzy finder first.
|
||||
//
|
||||
// The thinking behind that: we want to show strong matches first in order of relevance(fuzzy score).
|
||||
// Rest of the matches(weak) can be sorted as language-server expects.
|
||||
// The thinking behind that: it's useless to take the sort_text the language-server gives
|
||||
// us into account when it's obviously a bad match.
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum MatchScore<'a> {
|
||||
Strong {
|
||||
score: Reverse<OrderedFloat<f64>>,
|
||||
sort_text: Option<&'a str>,
|
||||
score: Reverse<OrderedFloat<f64>>,
|
||||
sort_key: (usize, &'a str),
|
||||
},
|
||||
Weak {
|
||||
sort_text: Option<&'a str>,
|
||||
score: Reverse<OrderedFloat<f64>>,
|
||||
sort_text: Option<&'a str>,
|
||||
sort_key: (usize, &'a str),
|
||||
},
|
||||
}
|
||||
@@ -1382,14 +1380,14 @@ impl CompletionsMenu {
|
||||
|
||||
if mat.score >= 0.2 {
|
||||
MatchScore::Strong {
|
||||
score,
|
||||
sort_text,
|
||||
score,
|
||||
sort_key,
|
||||
}
|
||||
} else {
|
||||
MatchScore::Weak {
|
||||
sort_text,
|
||||
score,
|
||||
sort_text,
|
||||
sort_key,
|
||||
}
|
||||
}
|
||||
@@ -7010,8 +7008,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
let tab_size = buffer.settings_at(selection.head(), cx).tab_size;
|
||||
|
||||
// Since not all lines in the selection may be at the same indent
|
||||
// level, choose the indent size that is the most common between all
|
||||
// of the lines.
|
||||
@@ -7029,7 +7025,7 @@ impl Editor {
|
||||
|
||||
let indent_size = indent_size_occurrences
|
||||
.into_iter()
|
||||
.max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
|
||||
.max_by_key(|(indent, count)| (*count, indent.len))
|
||||
.map(|(indent, _)| indent)
|
||||
.unwrap_or_default();
|
||||
let row = rows_by_indent_size[&indent_size][0];
|
||||
@@ -7055,10 +7051,6 @@ impl Editor {
|
||||
should_rewrap = true;
|
||||
}
|
||||
|
||||
if !should_rewrap {
|
||||
continue;
|
||||
}
|
||||
|
||||
if selection.is_empty() {
|
||||
'expand_upwards: while start_row > 0 {
|
||||
let prev_row = start_row - 1;
|
||||
@@ -7083,6 +7075,10 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
if !should_rewrap {
|
||||
continue;
|
||||
}
|
||||
|
||||
let start = Point::new(start_row, 0);
|
||||
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
|
||||
let selection_text = buffer.text_for_range(start..end).collect::<String>();
|
||||
@@ -7101,15 +7097,29 @@ impl Editor {
|
||||
continue;
|
||||
};
|
||||
|
||||
let unwrapped_text = lines_without_prefixes.join(" ");
|
||||
let wrap_column = buffer
|
||||
.settings_at(Point::new(start_row, 0), cx)
|
||||
.preferred_line_length as usize;
|
||||
let wrapped_text = wrap_with_prefix(
|
||||
line_prefix,
|
||||
lines_without_prefixes.join(" "),
|
||||
wrap_column,
|
||||
tab_size,
|
||||
);
|
||||
let mut wrapped_text = String::new();
|
||||
let mut current_line = line_prefix.clone();
|
||||
for word in unwrapped_text.split_whitespace() {
|
||||
if current_line.len() + word.len() >= wrap_column {
|
||||
wrapped_text.push_str(¤t_line);
|
||||
wrapped_text.push('\n');
|
||||
current_line.truncate(line_prefix.len());
|
||||
}
|
||||
|
||||
if current_line.len() > line_prefix.len() {
|
||||
current_line.push(' ');
|
||||
}
|
||||
|
||||
current_line.push_str(word);
|
||||
}
|
||||
|
||||
if !current_line.is_empty() {
|
||||
wrapped_text.push_str(¤t_line);
|
||||
}
|
||||
|
||||
let diff = TextDiff::from_lines(&selection_text, &wrapped_text);
|
||||
let mut offset = start.to_offset(&buffer);
|
||||
@@ -7270,14 +7280,9 @@ impl Editor {
|
||||
if clipboard_selections.len() != old_selections.len() {
|
||||
clipboard_selections.drain(..);
|
||||
}
|
||||
let cursor_offset = this.selections.last::<usize>(cx).head();
|
||||
let mut auto_indent_on_paste = true;
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.read(cx);
|
||||
auto_indent_on_paste =
|
||||
snapshot.settings_at(cursor_offset, cx).auto_indent_on_paste;
|
||||
|
||||
let mut start_offset = 0;
|
||||
let mut edits = Vec::new();
|
||||
let mut original_indent_columns = Vec::new();
|
||||
@@ -7316,13 +7321,9 @@ impl Editor {
|
||||
|
||||
buffer.edit(
|
||||
edits,
|
||||
if auto_indent_on_paste {
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -12540,11 +12541,11 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
|
||||
fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
|
||||
self.open_excerpts_common(true, cx)
|
||||
}
|
||||
|
||||
pub fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
|
||||
fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
|
||||
self.open_excerpts_common(false, cx)
|
||||
}
|
||||
|
||||
@@ -13046,289 +13047,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
|
||||
let tab_size = tab_size.get() as usize;
|
||||
let mut width = offset;
|
||||
|
||||
for ch in text.chars() {
|
||||
width += if ch == '\t' {
|
||||
tab_size - (width % tab_size)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
}
|
||||
|
||||
width - offset
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_string_size_with_expanded_tabs() {
|
||||
let nz = |val| NonZeroU32::new(val).unwrap();
|
||||
assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
|
||||
assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
|
||||
assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
|
||||
assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
|
||||
assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
|
||||
assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
|
||||
assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
|
||||
assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
|
||||
struct WordBreakingTokenizer<'a> {
|
||||
input: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> WordBreakingTokenizer<'a> {
|
||||
fn new(input: &'a str) -> Self {
|
||||
Self { input }
|
||||
}
|
||||
}
|
||||
|
||||
fn is_char_ideographic(ch: char) -> bool {
|
||||
use unicode_script::Script::*;
|
||||
use unicode_script::UnicodeScript;
|
||||
matches!(ch.script(), Han | Tangut | Yi)
|
||||
}
|
||||
|
||||
fn is_grapheme_ideographic(text: &str) -> bool {
|
||||
text.chars().any(is_char_ideographic)
|
||||
}
|
||||
|
||||
fn is_grapheme_whitespace(text: &str) -> bool {
|
||||
text.chars().any(|x| x.is_whitespace())
|
||||
}
|
||||
|
||||
fn should_stay_with_preceding_ideograph(text: &str) -> bool {
|
||||
text.chars().next().map_or(false, |ch| {
|
||||
matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
struct WordBreakToken<'a> {
|
||||
token: &'a str,
|
||||
grapheme_len: usize,
|
||||
is_whitespace: bool,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WordBreakingTokenizer<'a> {
|
||||
/// Yields a span, the count of graphemes in the token, and whether it was
|
||||
/// whitespace. Note that it also breaks at word boundaries.
|
||||
type Item = WordBreakToken<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
if self.input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = self.input.graphemes(true).peekable();
|
||||
let mut offset = 0;
|
||||
let mut graphemes = 0;
|
||||
if let Some(first_grapheme) = iter.next() {
|
||||
let is_whitespace = is_grapheme_whitespace(first_grapheme);
|
||||
offset += first_grapheme.len();
|
||||
graphemes += 1;
|
||||
if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
|
||||
if let Some(grapheme) = iter.peek().copied() {
|
||||
if should_stay_with_preceding_ideograph(grapheme) {
|
||||
offset += grapheme.len();
|
||||
graphemes += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut words = self.input[offset..].split_word_bound_indices().peekable();
|
||||
let mut next_word_bound = words.peek().copied();
|
||||
if next_word_bound.map_or(false, |(i, _)| i == 0) {
|
||||
next_word_bound = words.next();
|
||||
}
|
||||
while let Some(grapheme) = iter.peek().copied() {
|
||||
if next_word_bound.map_or(false, |(i, _)| i == offset) {
|
||||
break;
|
||||
};
|
||||
if is_grapheme_whitespace(grapheme) != is_whitespace {
|
||||
break;
|
||||
};
|
||||
offset += grapheme.len();
|
||||
graphemes += 1;
|
||||
iter.next();
|
||||
}
|
||||
}
|
||||
let token = &self.input[..offset];
|
||||
self.input = &self.input[offset..];
|
||||
if is_whitespace {
|
||||
Some(WordBreakToken {
|
||||
token: " ",
|
||||
grapheme_len: 1,
|
||||
is_whitespace: true,
|
||||
})
|
||||
} else {
|
||||
Some(WordBreakToken {
|
||||
token,
|
||||
grapheme_len: graphemes,
|
||||
is_whitespace: false,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word_breaking_tokenizer() {
|
||||
let tests: &[(&str, &[(&str, usize, bool)])] = &[
|
||||
("", &[]),
|
||||
(" ", &[(" ", 1, true)]),
|
||||
("Ʒ", &[("Ʒ", 1, false)]),
|
||||
("Ǽ", &[("Ǽ", 1, false)]),
|
||||
("⋑", &[("⋑", 1, false)]),
|
||||
("⋑⋑", &[("⋑⋑", 2, false)]),
|
||||
(
|
||||
"原理,进而",
|
||||
&[
|
||||
("原", 1, false),
|
||||
("理,", 2, false),
|
||||
("进", 1, false),
|
||||
("而", 1, false),
|
||||
],
|
||||
),
|
||||
(
|
||||
"hello world",
|
||||
&[("hello", 5, false), (" ", 1, true), ("world", 5, false)],
|
||||
),
|
||||
(
|
||||
"hello, world",
|
||||
&[("hello,", 6, false), (" ", 1, true), ("world", 5, false)],
|
||||
),
|
||||
(
|
||||
" hello world",
|
||||
&[
|
||||
(" ", 1, true),
|
||||
("hello", 5, false),
|
||||
(" ", 1, true),
|
||||
("world", 5, false),
|
||||
],
|
||||
),
|
||||
(
|
||||
"这是什么 \n 钢笔",
|
||||
&[
|
||||
("这", 1, false),
|
||||
("是", 1, false),
|
||||
("什", 1, false),
|
||||
("么", 1, false),
|
||||
(" ", 1, true),
|
||||
("钢", 1, false),
|
||||
("笔", 1, false),
|
||||
],
|
||||
),
|
||||
(" mutton", &[(" ", 1, true), ("mutton", 6, false)]),
|
||||
];
|
||||
|
||||
for (input, result) in tests {
|
||||
assert_eq!(
|
||||
WordBreakingTokenizer::new(input).collect::<Vec<_>>(),
|
||||
result
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|(token, grapheme_len, is_whitespace)| WordBreakToken {
|
||||
token,
|
||||
grapheme_len,
|
||||
is_whitespace,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_with_prefix(
|
||||
line_prefix: String,
|
||||
unwrapped_text: String,
|
||||
wrap_column: usize,
|
||||
tab_size: NonZeroU32,
|
||||
) -> String {
|
||||
let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
|
||||
let mut wrapped_text = String::new();
|
||||
let mut current_line = line_prefix.clone();
|
||||
|
||||
let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
|
||||
let mut current_line_len = line_prefix_len;
|
||||
for WordBreakToken {
|
||||
token,
|
||||
grapheme_len,
|
||||
is_whitespace,
|
||||
} in tokenizer
|
||||
{
|
||||
if current_line_len + grapheme_len > wrap_column && current_line_len != line_prefix_len {
|
||||
wrapped_text.push_str(current_line.trim_end());
|
||||
wrapped_text.push('\n');
|
||||
current_line.truncate(line_prefix.len());
|
||||
current_line_len = line_prefix_len;
|
||||
if !is_whitespace {
|
||||
current_line.push_str(token);
|
||||
current_line_len += grapheme_len;
|
||||
}
|
||||
} else if !is_whitespace {
|
||||
current_line.push_str(token);
|
||||
current_line_len += grapheme_len;
|
||||
} else if current_line_len != line_prefix_len {
|
||||
current_line.push(' ');
|
||||
current_line_len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !current_line.is_empty() {
|
||||
wrapped_text.push_str(¤t_line);
|
||||
}
|
||||
wrapped_text
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_with_prefix() {
|
||||
assert_eq!(
|
||||
wrap_with_prefix(
|
||||
"# ".to_string(),
|
||||
"abcdefg".to_string(),
|
||||
4,
|
||||
NonZeroU32::new(4).unwrap()
|
||||
),
|
||||
"# abcdefg"
|
||||
);
|
||||
assert_eq!(
|
||||
wrap_with_prefix(
|
||||
"".to_string(),
|
||||
"\thello world".to_string(),
|
||||
8,
|
||||
NonZeroU32::new(4).unwrap()
|
||||
),
|
||||
"hello\nworld"
|
||||
);
|
||||
assert_eq!(
|
||||
wrap_with_prefix(
|
||||
"// ".to_string(),
|
||||
"xx \nyy zz aa bb cc".to_string(),
|
||||
12,
|
||||
NonZeroU32::new(4).unwrap()
|
||||
),
|
||||
"// xx yy zz\n// aa bb cc"
|
||||
);
|
||||
assert_eq!(
|
||||
wrap_with_prefix(
|
||||
String::new(),
|
||||
"这是什么 \n 钢笔".to_string(),
|
||||
3,
|
||||
NonZeroU32::new(4).unwrap()
|
||||
),
|
||||
"这是什\n么 钢\n笔"
|
||||
);
|
||||
}
|
||||
|
||||
fn hunks_for_selections(
|
||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
selections: &[Selection<Anchor>],
|
||||
@@ -13828,7 +13546,7 @@ fn consume_contiguous_rows(
|
||||
contiguous_row_selections: &mut Vec<Selection<Point>>,
|
||||
selection: &Selection<Point>,
|
||||
display_map: &DisplaySnapshot,
|
||||
selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
|
||||
selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
|
||||
) -> (MultiBufferRow, MultiBufferRow) {
|
||||
contiguous_row_selections.push(selection.clone());
|
||||
let start_row = MultiBufferRow(selection.start.row);
|
||||
|
||||
@@ -8385,74 +8385,6 @@ async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![".".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
lsp::CompletionItem {
|
||||
label: "Range".into(),
|
||||
sort_text: Some("a".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "r".into(),
|
||||
sort_text: Some("b".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "ret".into(),
|
||||
sort_text: Some("c".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "return".into(),
|
||||
sort_text: Some("d".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "slice".into(),
|
||||
sort_text: Some("d".into()),
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.set_state("rˇ");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.show_completions(
|
||||
&ShowCompletions {
|
||||
trigger: Some("r".into()),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, _| {
|
||||
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
&["r", "ret", "Range", "return"]
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
};
|
||||
use gpui::{
|
||||
div, px, AnyElement, AsyncWindowContext, FontWeight, Hsla, InteractiveElement, IntoElement,
|
||||
MouseButton, ParentElement, Pixels, ScrollHandle, Size, Stateful, StatefulInteractiveElement,
|
||||
MouseButton, ParentElement, Pixels, ScrollHandle, Size, StatefulInteractiveElement,
|
||||
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@@ -21,7 +21,7 @@ use std::rc::Rc;
|
||||
use std::{borrow::Cow, cell::RefCell};
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, window_is_transparent, Scrollbar, ScrollbarState};
|
||||
use ui::{prelude::*, window_is_transparent};
|
||||
use util::TryFutureExt;
|
||||
pub const HOVER_DELAY_MILLIS: u64 = 350;
|
||||
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
|
||||
@@ -144,12 +144,10 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
|
||||
let blocks = vec![inlay_hover.tooltip];
|
||||
let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
|
||||
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
let hover_popover = InfoPopover {
|
||||
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
|
||||
parsed_content,
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
scroll_handle,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
keyboard_grace: Rc::new(RefCell::new(false)),
|
||||
anchor: None,
|
||||
};
|
||||
@@ -437,14 +435,12 @@ fn show_hover(
|
||||
let language = hover_result.language;
|
||||
let parsed_content =
|
||||
parse_blocks(&blocks, &language_registry, language, &mut cx).await;
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
info_popover_tasks.push((
|
||||
range.clone(),
|
||||
InfoPopover {
|
||||
symbol_range: RangeInEditor::Text(range),
|
||||
parsed_content,
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
|
||||
scroll_handle,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
|
||||
anchor: Some(anchor),
|
||||
},
|
||||
@@ -615,7 +611,7 @@ impl HoverState {
|
||||
!self.info_popovers.is_empty() || self.diagnostic_popover.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn render(
|
||||
pub fn render(
|
||||
&mut self,
|
||||
snapshot: &EditorSnapshot,
|
||||
visible_rows: Range<DisplayRow>,
|
||||
@@ -684,25 +680,25 @@ impl HoverState {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct InfoPopover {
|
||||
pub(crate) symbol_range: RangeInEditor,
|
||||
pub(crate) parsed_content: Option<View<Markdown>>,
|
||||
pub(crate) scroll_handle: ScrollHandle,
|
||||
pub(crate) scrollbar_state: ScrollbarState,
|
||||
pub(crate) keyboard_grace: Rc<RefCell<bool>>,
|
||||
pub(crate) anchor: Option<Anchor>,
|
||||
|
||||
pub struct InfoPopover {
|
||||
pub symbol_range: RangeInEditor,
|
||||
pub parsed_content: Option<View<Markdown>>,
|
||||
pub scroll_handle: ScrollHandle,
|
||||
pub keyboard_grace: Rc<RefCell<bool>>,
|
||||
pub anchor: Option<Anchor>,
|
||||
}
|
||||
|
||||
impl InfoPopover {
|
||||
pub(crate) fn render(
|
||||
&mut self,
|
||||
max_size: Size<Pixels>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> AnyElement {
|
||||
pub fn render(&mut self, max_size: Size<Pixels>, cx: &mut ViewContext<Editor>) -> AnyElement {
|
||||
let keyboard_grace = Rc::clone(&self.keyboard_grace);
|
||||
let mut d = div()
|
||||
.id("info_popover")
|
||||
.elevation_2(cx)
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.max_w(max_size.width)
|
||||
.max_h(max_size.height)
|
||||
// Prevent a mouse down/move on the popover from being propagated to the editor,
|
||||
// because that would dismiss the popover.
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
@@ -710,21 +706,11 @@ impl InfoPopover {
|
||||
let mut keyboard_grace = keyboard_grace.borrow_mut();
|
||||
*keyboard_grace = false;
|
||||
cx.stop_propagation();
|
||||
});
|
||||
})
|
||||
.p_2();
|
||||
|
||||
if let Some(markdown) = &self.parsed_content {
|
||||
d = d
|
||||
.child(
|
||||
div()
|
||||
.id("info-md-container")
|
||||
.overflow_y_scroll()
|
||||
.max_w(max_size.width)
|
||||
.max_h(max_size.height)
|
||||
.p_2()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.child(markdown.clone()),
|
||||
)
|
||||
.child(self.render_vertical_scrollbar(cx));
|
||||
d = d.child(markdown.clone());
|
||||
}
|
||||
d.into_any_element()
|
||||
}
|
||||
@@ -738,38 +724,6 @@ impl InfoPopover {
|
||||
cx.notify();
|
||||
self.scroll_handle.set_offset(current);
|
||||
}
|
||||
fn render_vertical_scrollbar(&self, cx: &mut ViewContext<Editor>) -> Stateful<Div> {
|
||||
div()
|
||||
.occlude()
|
||||
.id("info-popover-vertical-scroll")
|
||||
.on_mouse_move(cx.listener(|_, _, cx| {
|
||||
cx.notify();
|
||||
cx.stop_propagation()
|
||||
}))
|
||||
.on_hover(|_, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_any_mouse_down(|_, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_mouse_up(
|
||||
MouseButton::Left,
|
||||
cx.listener(|_, _, cx| {
|
||||
cx.stop_propagation();
|
||||
}),
|
||||
)
|
||||
.on_scroll_wheel(cx.listener(|_, _, cx| {
|
||||
cx.notify();
|
||||
}))
|
||||
.h_full()
|
||||
.absolute()
|
||||
.right_1()
|
||||
.top_1()
|
||||
.bottom_0()
|
||||
.w(px(12.))
|
||||
.cursor_default()
|
||||
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -992,15 +992,12 @@ impl SerializableItem for Editor {
|
||||
};
|
||||
|
||||
// First create the empty buffer
|
||||
let buffer = project
|
||||
.update(&mut cx, |project, cx| project.create_buffer(cx))?
|
||||
.await?;
|
||||
let buffer = project.update(&mut cx, |project, cx| {
|
||||
project.create_local_buffer("", language, cx)
|
||||
})?;
|
||||
|
||||
// Then set the text so that the dirty bit is set correctly
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
if let Some(language) = language {
|
||||
buffer.set_language(Some(language), cx);
|
||||
}
|
||||
buffer.set_text(contents, cx);
|
||||
})?;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{ops::Range, time::Duration};
|
||||
use std::ops::Range;
|
||||
|
||||
use collections::HashMap;
|
||||
use itertools::Itertools;
|
||||
@@ -36,53 +36,35 @@ impl LinkedEditingRanges {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||
|
||||
// TODO do not refresh anything at all, if the settings/capabilities do not have it enabled.
|
||||
pub(super) fn refresh_linked_ranges(
|
||||
editor: &mut Editor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<()> {
|
||||
if editor.pending_rename.is_some() {
|
||||
pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Editor>) -> Option<()> {
|
||||
if this.pending_rename.is_some() {
|
||||
return None;
|
||||
}
|
||||
let project = editor.project.as_ref()?.downgrade();
|
||||
|
||||
editor.linked_editing_range_task = Some(cx.spawn(|editor, mut cx| async move {
|
||||
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
|
||||
|
||||
let mut applicable_selections = Vec::new();
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
let selections = editor.selections.all::<usize>(cx);
|
||||
let snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
let buffer = editor.buffer.read(cx);
|
||||
for selection in selections {
|
||||
let cursor_position = selection.head();
|
||||
let start_position = snapshot.anchor_before(cursor_position);
|
||||
let end_position = snapshot.anchor_after(selection.tail());
|
||||
if start_position.buffer_id != end_position.buffer_id
|
||||
|| end_position.buffer_id.is_none()
|
||||
{
|
||||
// Throw away selections spanning multiple buffers.
|
||||
continue;
|
||||
}
|
||||
if let Some(buffer) = end_position.buffer_id.and_then(|id| buffer.buffer(id)) {
|
||||
applicable_selections.push((
|
||||
buffer,
|
||||
start_position.text_anchor,
|
||||
end_position.text_anchor,
|
||||
));
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
if applicable_selections.is_empty() {
|
||||
return None;
|
||||
let project = this.project.clone()?;
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
let buffer = this.buffer.read(cx);
|
||||
let mut applicable_selections = vec![];
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
for selection in selections {
|
||||
let cursor_position = selection.head();
|
||||
let start_position = snapshot.anchor_before(cursor_position);
|
||||
let end_position = snapshot.anchor_after(selection.tail());
|
||||
if start_position.buffer_id != end_position.buffer_id || end_position.buffer_id.is_none() {
|
||||
// Throw away selections spanning multiple buffers.
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(buffer) = end_position.buffer_id.and_then(|id| buffer.buffer(id)) {
|
||||
applicable_selections.push((
|
||||
buffer,
|
||||
start_position.text_anchor,
|
||||
end_position.text_anchor,
|
||||
));
|
||||
}
|
||||
}
|
||||
if applicable_selections.is_empty() {
|
||||
return None;
|
||||
}
|
||||
this.linked_editing_range_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let highlights = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let mut linked_edits_tasks = vec![];
|
||||
@@ -128,38 +110,37 @@ pub(super) fn refresh_linked_ranges(
|
||||
}
|
||||
linked_edits_tasks
|
||||
})
|
||||
.ok()?;
|
||||
.log_err()?;
|
||||
|
||||
let highlights = futures::future::join_all(highlights).await;
|
||||
|
||||
editor
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.linked_edit_ranges.0.clear();
|
||||
if this.pending_rename.is_some() {
|
||||
return;
|
||||
}
|
||||
for (buffer_id, ranges) in highlights.into_iter().flatten() {
|
||||
this.linked_edit_ranges
|
||||
.0
|
||||
.entry(buffer_id)
|
||||
.or_default()
|
||||
.extend(ranges);
|
||||
}
|
||||
for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
|
||||
let Some(snapshot) = this
|
||||
.buffer
|
||||
.read(cx)
|
||||
.buffer(*buffer_id)
|
||||
.map(|buffer| buffer.read(cx).snapshot())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
|
||||
}
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.linked_edit_ranges.0.clear();
|
||||
if this.pending_rename.is_some() {
|
||||
return;
|
||||
}
|
||||
for (buffer_id, ranges) in highlights.into_iter().flatten() {
|
||||
this.linked_edit_ranges
|
||||
.0
|
||||
.entry(buffer_id)
|
||||
.or_default()
|
||||
.extend(ranges);
|
||||
}
|
||||
for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
|
||||
let Some(snapshot) = this
|
||||
.buffer
|
||||
.read(cx)
|
||||
.buffer(*buffer_id)
|
||||
.map(|buffer| buffer.read(cx).snapshot())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
.ok()?;
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
|
||||
Some(())
|
||||
}));
|
||||
|
||||
@@ -135,8 +135,6 @@ impl ExtensionBuilder {
|
||||
.args(options.release.then_some("--release"))
|
||||
.arg("--target-dir")
|
||||
.arg(extension_dir.join("target"))
|
||||
// WASI builds do not work with sccache and just stuck, so disable it.
|
||||
.env("RUSTC_WRAPPER", "")
|
||||
.current_dir(extension_dir)
|
||||
.output()
|
||||
.context("failed to run `cargo`")?;
|
||||
|
||||
@@ -2,7 +2,6 @@ interface lsp {
|
||||
/// An LSP completion.
|
||||
record completion {
|
||||
label: string,
|
||||
label-details: option<completion-label-details>,
|
||||
detail: option<string>,
|
||||
kind: option<completion-kind>,
|
||||
insert-text-format: option<insert-text-format>,
|
||||
@@ -38,12 +37,6 @@ interface lsp {
|
||||
other(s32),
|
||||
}
|
||||
|
||||
/// Label details for an LSP completion.
|
||||
record completion-label-details {
|
||||
detail: option<string>,
|
||||
description: option<string>,
|
||||
}
|
||||
|
||||
/// Defines how to interpret the insert text in a completion item.
|
||||
variant insert-text-format {
|
||||
plain-text,
|
||||
|
||||
@@ -387,7 +387,6 @@ impl From<lsp::CompletionItem> for wit::Completion {
|
||||
fn from(value: lsp::CompletionItem) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
label_details: value.label_details.map(Into::into),
|
||||
detail: value.detail,
|
||||
kind: value.kind.map(Into::into),
|
||||
insert_text_format: value.insert_text_format.map(Into::into),
|
||||
@@ -395,15 +394,6 @@ impl From<lsp::CompletionItem> for wit::Completion {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lsp::CompletionItemLabelDetails> for wit::CompletionLabelDetails {
|
||||
fn from(value: lsp::CompletionItemLabelDetails) -> Self {
|
||||
Self {
|
||||
detail: value.detail,
|
||||
description: value.description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lsp::CompletionItemKind> for wit::CompletionKind {
|
||||
fn from(value: lsp::CompletionItemKind) -> Self {
|
||||
match value {
|
||||
|
||||
@@ -20,9 +20,7 @@ use wasmtime::{
|
||||
#[cfg(test)]
|
||||
pub use latest::CodeLabelSpanLiteral;
|
||||
pub use latest::{
|
||||
zed::extension::lsp::{
|
||||
Completion, CompletionKind, CompletionLabelDetails, InsertTextFormat, Symbol, SymbolKind,
|
||||
},
|
||||
zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind},
|
||||
zed::extension::slash_command::{SlashCommandArgumentCompletion, SlashCommandOutput},
|
||||
CodeLabel, CodeLabelSpan, Command, Range, SlashCommand,
|
||||
};
|
||||
@@ -78,18 +76,13 @@ impl Extension {
|
||||
version: SemanticVersion,
|
||||
component: &Component,
|
||||
) -> Result<Self> {
|
||||
if version >= latest::MIN_VERSION {
|
||||
// Note: The release channel can be used to stage a new version of the extension API.
|
||||
// We always allow the latest in tests so that the extension tests pass on release branches.
|
||||
let allow_latest_version = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => true,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => cfg!(test),
|
||||
};
|
||||
if !allow_latest_version {
|
||||
Err(anyhow!(
|
||||
"unreleased versions of the extension API can only be used on development builds of Zed"
|
||||
))?;
|
||||
}
|
||||
// Note: The release channel can be used to stage a new version of the extension API.
|
||||
let allow_latest_version = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => true,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => false,
|
||||
};
|
||||
|
||||
if allow_latest_version && version >= latest::MIN_VERSION {
|
||||
let extension =
|
||||
latest::Extension::instantiate_async(store, component, latest::linker())
|
||||
.await
|
||||
@@ -269,11 +262,7 @@ impl Extension {
|
||||
.await
|
||||
}
|
||||
Extension::V010(ext) => Ok(ext
|
||||
.call_labels_for_completions(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&completions.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
.call_labels_for_completions(store, &language_server_id.0, &completions)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
labels
|
||||
@@ -282,11 +271,7 @@ impl Extension {
|
||||
.collect()
|
||||
})),
|
||||
Extension::V006(ext) => Ok(ext
|
||||
.call_labels_for_completions(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&completions.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
.call_labels_for_completions(store, &language_server_id.0, &completions)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
labels
|
||||
@@ -310,11 +295,7 @@ impl Extension {
|
||||
.await
|
||||
}
|
||||
Extension::V010(ext) => Ok(ext
|
||||
.call_labels_for_symbols(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
.call_labels_for_symbols(store, &language_server_id.0, &symbols)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
labels
|
||||
@@ -323,11 +304,7 @@ impl Extension {
|
||||
.collect()
|
||||
})),
|
||||
Extension::V006(ext) => Ok(ext
|
||||
.call_labels_for_symbols(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
.call_labels_for_symbols(store, &language_server_id.0, &symbols)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
labels
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{latest, since_v0_1_0};
|
||||
use super::latest;
|
||||
use crate::wasm_host::WasmState;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
@@ -16,7 +16,7 @@ wasmtime::component::bindgen!({
|
||||
with: {
|
||||
"worktree": ExtensionWorktree,
|
||||
"zed:extension/github": latest::zed::extension::github,
|
||||
"zed:extension/lsp": since_v0_1_0::zed::extension::lsp,
|
||||
"zed:extension/lsp": latest::zed::extension::lsp,
|
||||
"zed:extension/nodejs": latest::zed::extension::nodejs,
|
||||
"zed:extension/platform": latest::zed::extension::platform,
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ wasmtime::component::bindgen!({
|
||||
"key-value-store": ExtensionKeyValueStore,
|
||||
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream,
|
||||
"zed:extension/github": latest::zed::extension::github,
|
||||
"zed:extension/lsp": latest::zed::extension::lsp,
|
||||
"zed:extension/nodejs": latest::zed::extension::nodejs,
|
||||
"zed:extension/platform": latest::zed::extension::platform,
|
||||
"zed:extension/slash-command": latest::zed::extension::slash_command,
|
||||
@@ -134,103 +135,6 @@ impl From<CodeLabel> for latest::CodeLabel {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::Completion> for Completion {
|
||||
fn from(value: latest::Completion) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
detail: value.detail,
|
||||
kind: value.kind.map(Into::into),
|
||||
insert_text_format: value.insert_text_format.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::lsp::CompletionKind> for lsp::CompletionKind {
|
||||
fn from(value: latest::lsp::CompletionKind) -> Self {
|
||||
match value {
|
||||
latest::lsp::CompletionKind::Text => Self::Text,
|
||||
latest::lsp::CompletionKind::Method => Self::Method,
|
||||
latest::lsp::CompletionKind::Function => Self::Function,
|
||||
latest::lsp::CompletionKind::Constructor => Self::Constructor,
|
||||
latest::lsp::CompletionKind::Field => Self::Field,
|
||||
latest::lsp::CompletionKind::Variable => Self::Variable,
|
||||
latest::lsp::CompletionKind::Class => Self::Class,
|
||||
latest::lsp::CompletionKind::Interface => Self::Interface,
|
||||
latest::lsp::CompletionKind::Module => Self::Module,
|
||||
latest::lsp::CompletionKind::Property => Self::Property,
|
||||
latest::lsp::CompletionKind::Unit => Self::Unit,
|
||||
latest::lsp::CompletionKind::Value => Self::Value,
|
||||
latest::lsp::CompletionKind::Enum => Self::Enum,
|
||||
latest::lsp::CompletionKind::Keyword => Self::Keyword,
|
||||
latest::lsp::CompletionKind::Snippet => Self::Snippet,
|
||||
latest::lsp::CompletionKind::Color => Self::Color,
|
||||
latest::lsp::CompletionKind::File => Self::File,
|
||||
latest::lsp::CompletionKind::Reference => Self::Reference,
|
||||
latest::lsp::CompletionKind::Folder => Self::Folder,
|
||||
latest::lsp::CompletionKind::EnumMember => Self::EnumMember,
|
||||
latest::lsp::CompletionKind::Constant => Self::Constant,
|
||||
latest::lsp::CompletionKind::Struct => Self::Struct,
|
||||
latest::lsp::CompletionKind::Event => Self::Event,
|
||||
latest::lsp::CompletionKind::Operator => Self::Operator,
|
||||
latest::lsp::CompletionKind::TypeParameter => Self::TypeParameter,
|
||||
latest::lsp::CompletionKind::Other(kind) => Self::Other(kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::lsp::InsertTextFormat> for lsp::InsertTextFormat {
|
||||
fn from(value: latest::lsp::InsertTextFormat) -> Self {
|
||||
match value {
|
||||
latest::lsp::InsertTextFormat::PlainText => Self::PlainText,
|
||||
latest::lsp::InsertTextFormat::Snippet => Self::Snippet,
|
||||
latest::lsp::InsertTextFormat::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::lsp::Symbol> for lsp::Symbol {
|
||||
fn from(value: latest::lsp::Symbol) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
kind: value.kind.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::lsp::SymbolKind> for lsp::SymbolKind {
|
||||
fn from(value: latest::lsp::SymbolKind) -> Self {
|
||||
match value {
|
||||
latest::lsp::SymbolKind::File => Self::File,
|
||||
latest::lsp::SymbolKind::Module => Self::Module,
|
||||
latest::lsp::SymbolKind::Namespace => Self::Namespace,
|
||||
latest::lsp::SymbolKind::Package => Self::Package,
|
||||
latest::lsp::SymbolKind::Class => Self::Class,
|
||||
latest::lsp::SymbolKind::Method => Self::Method,
|
||||
latest::lsp::SymbolKind::Property => Self::Property,
|
||||
latest::lsp::SymbolKind::Field => Self::Field,
|
||||
latest::lsp::SymbolKind::Constructor => Self::Constructor,
|
||||
latest::lsp::SymbolKind::Enum => Self::Enum,
|
||||
latest::lsp::SymbolKind::Interface => Self::Interface,
|
||||
latest::lsp::SymbolKind::Function => Self::Function,
|
||||
latest::lsp::SymbolKind::Variable => Self::Variable,
|
||||
latest::lsp::SymbolKind::Constant => Self::Constant,
|
||||
latest::lsp::SymbolKind::String => Self::String,
|
||||
latest::lsp::SymbolKind::Number => Self::Number,
|
||||
latest::lsp::SymbolKind::Boolean => Self::Boolean,
|
||||
latest::lsp::SymbolKind::Array => Self::Array,
|
||||
latest::lsp::SymbolKind::Object => Self::Object,
|
||||
latest::lsp::SymbolKind::Key => Self::Key,
|
||||
latest::lsp::SymbolKind::Null => Self::Null,
|
||||
latest::lsp::SymbolKind::EnumMember => Self::EnumMember,
|
||||
latest::lsp::SymbolKind::Struct => Self::Struct,
|
||||
latest::lsp::SymbolKind::Event => Self::Event,
|
||||
latest::lsp::SymbolKind::Operator => Self::Operator,
|
||||
latest::lsp::SymbolKind::TypeParameter => Self::TypeParameter,
|
||||
latest::lsp::SymbolKind::Other(kind) => Self::Other(kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HostKeyValueStore for WasmState {
|
||||
async fn insert(
|
||||
@@ -432,9 +336,6 @@ async fn convert_response(
|
||||
Ok(extension_response)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl lsp::Host for WasmState {}
|
||||
|
||||
#[async_trait]
|
||||
impl ExtensionImports for WasmState {
|
||||
async fn get_settings(
|
||||
|
||||
@@ -17,7 +17,6 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
|
||||
("astro", &["astro"]),
|
||||
("beancount", &["beancount"]),
|
||||
("clojure", &["bb", "clj", "cljc", "cljs", "edn"]),
|
||||
("neocmake", &["CMakeLists.txt", "cmake"]),
|
||||
("csharp", &["cs"]),
|
||||
("dart", &["dart"]),
|
||||
("dockerfile", &["Dockerfile"]),
|
||||
|
||||
@@ -29,7 +29,7 @@ const fn request_feature_url() -> &'static str {
|
||||
|
||||
fn file_bug_report_url(specs: &SystemSpecs) -> String {
|
||||
format!(
|
||||
"https://github.com/zed-industries/zed/issues/new?assignees=&labels=admin+read%2Ctriage%2Cbug&projects=&template=1_bug_report.yml&environment={}",
|
||||
"https://github.com/zed-industries/zed/issues/new?assignees=&labels=admin+read%2Ctriage%2Cdefect&projects=&template=1_bug_report.yml&environment={}",
|
||||
urlencoding::encode(&specs.to_string())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac_watcher;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux_watcher;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use git::GitHostingProviderRegistry;
|
||||
|
||||
@@ -536,21 +530,14 @@ impl Fs for RealFs {
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use fsevent::StreamFlags;
|
||||
use fsevent::{EventStream, StreamFlags};
|
||||
|
||||
let (events_tx, events_rx) = smol::channel::unbounded();
|
||||
let handles = Arc::new(parking_lot::Mutex::new(collections::BTreeMap::default()));
|
||||
let watcher = Arc::new(mac_watcher::MacWatcher::new(
|
||||
events_tx,
|
||||
Arc::downgrade(&handles),
|
||||
latency,
|
||||
));
|
||||
watcher.add(path).expect("handles can't be dropped");
|
||||
|
||||
(
|
||||
Box::pin(
|
||||
events_rx
|
||||
.map(|events| {
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
let (stream, handle) = EventStream::new(&[path], latency);
|
||||
std::thread::spawn(move || {
|
||||
stream.run(move |events| {
|
||||
smol::block_on(
|
||||
tx.send(
|
||||
events
|
||||
.into_iter()
|
||||
.map(|event| {
|
||||
@@ -568,14 +555,19 @@ impl Fs for RealFs {
|
||||
kind,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.chain(futures::stream::once(async move {
|
||||
drop(handles);
|
||||
vec![]
|
||||
})),
|
||||
),
|
||||
watcher,
|
||||
.collect(),
|
||||
),
|
||||
)
|
||||
.is_ok()
|
||||
});
|
||||
});
|
||||
|
||||
(
|
||||
Box::pin(rx.chain(futures::stream::once(async move {
|
||||
drop(handle);
|
||||
vec![]
|
||||
}))),
|
||||
Arc::new(RealWatcher {}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -588,26 +580,81 @@ impl Fs for RealFs {
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use notify::EventKind;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
|
||||
let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone()));
|
||||
let root_path = path.to_path_buf();
|
||||
|
||||
watcher.add(&path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher.
|
||||
if let Some(parent) = path.parent() {
|
||||
// watch the parent dir so we can tell when settings.json is created
|
||||
watcher.add(parent).log_err();
|
||||
}
|
||||
// Check if root path is a symlink
|
||||
let target_path = self.read_link(&path).await.ok();
|
||||
|
||||
watcher::global({
|
||||
let target_path = target_path.clone();
|
||||
|g| {
|
||||
let tx = tx.clone();
|
||||
let pending_paths = pending_paths.clone();
|
||||
g.add(move |event: ¬ify::Event| {
|
||||
let kind = match event.kind {
|
||||
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||
_ => None,
|
||||
};
|
||||
let mut paths = event
|
||||
.paths
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
if let Some(target) = target_path.clone() {
|
||||
if path.starts_with(target) {
|
||||
return Some(PathEvent {
|
||||
path: path.clone(),
|
||||
kind,
|
||||
});
|
||||
}
|
||||
} else if path.starts_with(&root_path) {
|
||||
return Some(PathEvent {
|
||||
path: path.clone(),
|
||||
kind,
|
||||
});
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !paths.is_empty() {
|
||||
paths.sort();
|
||||
let mut pending_paths = pending_paths.lock();
|
||||
if pending_paths.is_empty() {
|
||||
tx.try_send(()).ok();
|
||||
}
|
||||
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, |a, b| {
|
||||
a.path.cmp(&b.path)
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
|
||||
let watcher = Arc::new(RealWatcher {});
|
||||
|
||||
watcher.add(path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher.
|
||||
|
||||
// Check if path is a symlink and follow the target parent
|
||||
if let Some(target) = self.read_link(&path).await.ok() {
|
||||
if let Some(target) = target_path {
|
||||
watcher.add(&target).ok();
|
||||
if let Some(parent) = target.parent() {
|
||||
watcher.add(parent).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
// watch the parent dir so we can tell when settings.json is created
|
||||
if let Some(parent) = path.parent() {
|
||||
watcher.add(parent).log_err();
|
||||
}
|
||||
|
||||
(
|
||||
Box::pin(rx.filter_map({
|
||||
let watcher = watcher.clone();
|
||||
@@ -737,6 +784,23 @@ impl Watcher for RealWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl Watcher for RealWatcher {
|
||||
fn add(&self, path: &Path) -> Result<()> {
|
||||
use notify::Watcher;
|
||||
Ok(watcher::global(|w| {
|
||||
w.inotify
|
||||
.lock()
|
||||
.watch(path, notify::RecursiveMode::NonRecursive)
|
||||
})??)
|
||||
}
|
||||
|
||||
fn remove(&self, path: &Path) -> Result<()> {
|
||||
use notify::Watcher;
|
||||
Ok(watcher::global(|w| w.inotify.lock().unwatch(path))??)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct FakeFs {
|
||||
// Use an unfair lock to ensure tests are deterministic.
|
||||
@@ -2020,3 +2084,49 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod watcher {
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct GlobalWatcher {
|
||||
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
|
||||
pub(super) inotify: Mutex<notify::INotifyWatcher>,
|
||||
pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
|
||||
}
|
||||
|
||||
impl GlobalWatcher {
|
||||
pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) {
|
||||
self.watchers.lock().push(Box::new(cb))
|
||||
}
|
||||
}
|
||||
|
||||
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> =
|
||||
OnceLock::new();
|
||||
|
||||
fn handle_event(event: Result<notify::Event, notify::Error>) {
|
||||
let Some(event) = event.log_err() else { return };
|
||||
global::<()>(move |watcher| {
|
||||
for f in watcher.watchers.lock().iter() {
|
||||
f(&event)
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
|
||||
let result = INOTIFY_INSTANCE.get_or_init(|| {
|
||||
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
|
||||
inotify: Mutex::new(file_watcher),
|
||||
watchers: Default::default(),
|
||||
})
|
||||
});
|
||||
match result {
|
||||
Ok(g) => Ok(f(g)),
|
||||
Err(e) => Err(anyhow::anyhow!("{}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
use notify::EventKind;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{PathEvent, PathEventKind, Watcher};
|
||||
|
||||
pub struct LinuxWatcher {
|
||||
tx: smol::channel::Sender<()>,
|
||||
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
|
||||
}
|
||||
|
||||
impl LinuxWatcher {
|
||||
pub fn new(
|
||||
tx: smol::channel::Sender<()>,
|
||||
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tx,
|
||||
pending_path_events,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for LinuxWatcher {
|
||||
fn add(&self, path: &std::path::Path) -> gpui::Result<()> {
|
||||
let root_path = path.to_path_buf();
|
||||
|
||||
let tx = self.tx.clone();
|
||||
let pending_paths = self.pending_path_events.clone();
|
||||
|
||||
use notify::Watcher;
|
||||
|
||||
global({
|
||||
|g| {
|
||||
g.add(move |event: ¬ify::Event| {
|
||||
let kind = match event.kind {
|
||||
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||
_ => None,
|
||||
};
|
||||
let mut path_events = event
|
||||
.paths
|
||||
.iter()
|
||||
.filter_map(|event_path| {
|
||||
event_path.starts_with(&root_path).then(|| PathEvent {
|
||||
path: event_path.clone(),
|
||||
kind,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !path_events.is_empty() {
|
||||
path_events.sort();
|
||||
let mut pending_paths = pending_paths.lock();
|
||||
if pending_paths.is_empty() {
|
||||
tx.try_send(()).ok();
|
||||
}
|
||||
util::extend_sorted(
|
||||
&mut *pending_paths,
|
||||
path_events,
|
||||
usize::MAX,
|
||||
|a, b| a.path.cmp(&b.path),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
})?;
|
||||
|
||||
global(|g| {
|
||||
g.inotify
|
||||
.lock()
|
||||
.watch(path, notify::RecursiveMode::NonRecursive)
|
||||
})??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, path: &std::path::Path) -> gpui::Result<()> {
|
||||
use notify::Watcher;
|
||||
Ok(global(|w| w.inotify.lock().unwatch(path))??)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalWatcher {
|
||||
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
|
||||
pub(super) inotify: Mutex<notify::INotifyWatcher>,
|
||||
pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
|
||||
}
|
||||
|
||||
impl GlobalWatcher {
|
||||
pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) {
|
||||
self.watchers.lock().push(Box::new(cb))
|
||||
}
|
||||
}
|
||||
|
||||
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> = OnceLock::new();
|
||||
|
||||
fn handle_event(event: Result<notify::Event, notify::Error>) {
|
||||
let Some(event) = event.log_err() else { return };
|
||||
global::<()>(move |watcher| {
|
||||
for f in watcher.watchers.lock().iter() {
|
||||
f(&event)
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
|
||||
let result = INOTIFY_INSTANCE.get_or_init(|| {
|
||||
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
|
||||
inotify: Mutex::new(file_watcher),
|
||||
watchers: Default::default(),
|
||||
})
|
||||
});
|
||||
match result {
|
||||
Ok(g) => Ok(f(g)),
|
||||
Err(e) => Err(anyhow::anyhow!("{}", e)),
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
use crate::Watcher;
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::{BTreeMap, Bound};
|
||||
use fsevent::EventStream;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Weak,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub struct MacWatcher {
|
||||
events_tx: smol::channel::Sender<Vec<fsevent::Event>>,
|
||||
handles: Weak<Mutex<BTreeMap<PathBuf, fsevent::Handle>>>,
|
||||
latency: Duration,
|
||||
}
|
||||
|
||||
impl MacWatcher {
|
||||
pub fn new(
|
||||
events_tx: smol::channel::Sender<Vec<fsevent::Event>>,
|
||||
handles: Weak<Mutex<BTreeMap<PathBuf, fsevent::Handle>>>,
|
||||
latency: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
events_tx,
|
||||
handles,
|
||||
latency,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for MacWatcher {
|
||||
fn add(&self, path: &Path) -> Result<()> {
|
||||
let handles = self
|
||||
.handles
|
||||
.upgrade()
|
||||
.context("unable to watch path, receiver dropped")?;
|
||||
let mut handles = handles.lock();
|
||||
|
||||
// Return early if an ancestor of this path was already being watched.
|
||||
if let Some((watched_path, _)) = handles
|
||||
.range::<Path, _>((Bound::Unbounded, Bound::Included(path)))
|
||||
.next_back()
|
||||
{
|
||||
if path.starts_with(watched_path) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let (stream, handle) = EventStream::new(&[path], self.latency);
|
||||
let tx = self.events_tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
|
||||
});
|
||||
handles.insert(path.into(), handle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, path: &Path) -> gpui::Result<()> {
|
||||
let handles = self
|
||||
.handles
|
||||
.upgrade()
|
||||
.context("unable to remove path, receiver dropped")?;
|
||||
|
||||
let mut handles = handles.lock();
|
||||
handles.remove(path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -45,8 +45,6 @@ pub trait GitRepository: Send + Sync {
|
||||
fn branch_exits(&self, _: &str) -> Result<bool>;
|
||||
|
||||
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
|
||||
|
||||
fn path(&self) -> PathBuf;
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn GitRepository {
|
||||
@@ -85,11 +83,6 @@ impl GitRepository for RealGitRepository {
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> PathBuf {
|
||||
let repo = self.repository.lock();
|
||||
repo.path().into()
|
||||
}
|
||||
|
||||
fn load_index_text(&self, relative_file_path: &Path) -> Option<String> {
|
||||
fn logic(repo: &git2::Repository, relative_file_path: &Path) -> Result<Option<String>> {
|
||||
const STAGE_NORMAL: i32 = 0;
|
||||
@@ -283,11 +276,6 @@ impl GitRepository for FakeGitRepository {
|
||||
None
|
||||
}
|
||||
|
||||
fn path(&self) -> PathBuf {
|
||||
let state = self.state.lock();
|
||||
state.path.clone()
|
||||
}
|
||||
|
||||
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus> {
|
||||
let state = self.state.lock();
|
||||
let mut entries = state
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use editor::{Editor, ToPoint};
|
||||
use gpui::{AppContext, Subscription, Task, View, WeakView};
|
||||
use gpui::{AppContext, Subscription, View, WeakView};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use std::{fmt::Write, time::Duration};
|
||||
use std::fmt::Write;
|
||||
use text::{Point, Selection};
|
||||
use ui::{
|
||||
div, Button, ButtonCommon, Clickable, FluentBuilder, IntoElement, LabelSize, ParentElement,
|
||||
@@ -23,7 +23,6 @@ pub struct CursorPosition {
|
||||
position: Option<Point>,
|
||||
selected_count: SelectionStats,
|
||||
workspace: WeakView<Workspace>,
|
||||
update_position: Task<()>,
|
||||
_observe_active_editor: Option<Subscription>,
|
||||
}
|
||||
|
||||
@@ -33,61 +32,40 @@ impl CursorPosition {
|
||||
position: None,
|
||||
selected_count: Default::default(),
|
||||
workspace: workspace.weak_handle(),
|
||||
update_position: Task::ready(()),
|
||||
_observe_active_editor: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_position(
|
||||
&mut self,
|
||||
editor: View<Editor>,
|
||||
debounce: Option<Duration>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let editor = editor.downgrade();
|
||||
self.update_position = cx.spawn(|cursor_position, mut cx| async move {
|
||||
if let Some(debounce) = debounce {
|
||||
cx.background_executor().timer(debounce).await;
|
||||
}
|
||||
fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
cursor_position.update(cx, |cursor_position, cx| {
|
||||
cursor_position.selected_count = SelectionStats::default();
|
||||
cursor_position.selected_count.selections = editor.selections.count();
|
||||
let mut last_selection = None::<Selection<usize>>;
|
||||
for selection in editor.selections.all::<usize>(cx) {
|
||||
cursor_position.selected_count.characters += buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.map(|t| t.chars().count())
|
||||
.sum::<usize>();
|
||||
if last_selection
|
||||
.as_ref()
|
||||
.map_or(true, |last_selection| selection.id > last_selection.id)
|
||||
{
|
||||
last_selection = Some(selection);
|
||||
}
|
||||
}
|
||||
for selection in editor.selections.all::<Point>(cx) {
|
||||
if selection.end != selection.start {
|
||||
cursor_position.selected_count.lines +=
|
||||
(selection.end.row - selection.start.row) as usize;
|
||||
if selection.end.column != 0 {
|
||||
cursor_position.selected_count.lines += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor_position.position =
|
||||
last_selection.map(|s| s.head().to_point(&buffer));
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten();
|
||||
self.selected_count = Default::default();
|
||||
self.selected_count.selections = editor.selections.count();
|
||||
let mut last_selection: Option<Selection<usize>> = None;
|
||||
for selection in editor.selections.all::<usize>(cx) {
|
||||
self.selected_count.characters += buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.map(|t| t.chars().count())
|
||||
.sum::<usize>();
|
||||
if last_selection
|
||||
.as_ref()
|
||||
.map_or(true, |last_selection| selection.id > last_selection.id)
|
||||
{
|
||||
last_selection = Some(selection);
|
||||
}
|
||||
}
|
||||
for selection in editor.selections.all::<Point>(cx) {
|
||||
if selection.end != selection.start {
|
||||
self.selected_count.lines += (selection.end.row - selection.start.row) as usize;
|
||||
if selection.end.column != 0 {
|
||||
self.selected_count.lines += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.position = last_selection.map(|s| s.head().to_point(&buffer));
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn write_position(&self, text: &mut String, cx: &AppContext) {
|
||||
@@ -176,8 +154,6 @@ impl Render for CursorPosition {
|
||||
}
|
||||
}
|
||||
|
||||
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||
|
||||
impl StatusItemView for CursorPosition {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
@@ -185,11 +161,8 @@ impl StatusItemView for CursorPosition {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
|
||||
self._observe_active_editor =
|
||||
Some(cx.observe(&editor, |cursor_position, editor, cx| {
|
||||
Self::update_position(cursor_position, editor, Some(UPDATE_DEBOUNCE), cx)
|
||||
}));
|
||||
self.update_position(editor, None, cx);
|
||||
self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
|
||||
self.update_position(editor, cx);
|
||||
} else {
|
||||
self.position = None;
|
||||
self._observe_active_editor = None;
|
||||
|
||||
@@ -229,7 +229,7 @@ mod tests {
|
||||
use indoc::indoc;
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::sync::Arc;
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
#[gpui::test]
|
||||
@@ -379,7 +379,6 @@ mod tests {
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert_eq!(
|
||||
&SelectionStats {
|
||||
@@ -398,7 +397,6 @@ mod tests {
|
||||
);
|
||||
});
|
||||
editor.update(cx, |editor, cx| editor.select_all(&SelectAll, cx));
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert_eq!(
|
||||
&SelectionStats {
|
||||
|
||||
@@ -136,7 +136,7 @@ pub struct GenerateContentResponse {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GenerateContentCandidate {
|
||||
pub index: Option<usize>,
|
||||
pub index: usize,
|
||||
pub content: Content,
|
||||
pub finish_reason: Option<String>,
|
||||
pub finish_message: Option<String>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::SharedString;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::HashMap;
|
||||
pub use no_action::{is_no_action, NoAction};
|
||||
pub use no_action::NoAction;
|
||||
use serde_json::json;
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
@@ -321,12 +321,6 @@ macro_rules! __impl_action {
|
||||
|
||||
mod no_action {
|
||||
use crate as gpui;
|
||||
use std::any::Any as _;
|
||||
|
||||
actions!(zed, [NoAction]);
|
||||
|
||||
/// Returns whether or not this action represents a removed key binding.
|
||||
pub fn is_no_action(action: &dyn gpui::Action) -> bool {
|
||||
action.as_any().type_id() == (NoAction {}).type_id()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ mod context;
|
||||
pub use binding::*;
|
||||
pub use context::*;
|
||||
|
||||
use crate::{is_no_action, Action, Keystroke};
|
||||
use crate::{Action, Keystroke, NoAction};
|
||||
use collections::HashMap;
|
||||
use smallvec::SmallVec;
|
||||
use std::any::TypeId;
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
/// An opaque identifier of which version of the keymap is currently active.
|
||||
/// The keymap's version is changed whenever bindings are added or removed.
|
||||
@@ -19,7 +19,6 @@ pub struct KeymapVersion(usize);
|
||||
pub struct Keymap {
|
||||
bindings: Vec<KeyBinding>,
|
||||
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
|
||||
no_action_binding_indices: Vec<usize>,
|
||||
version: KeymapVersion,
|
||||
}
|
||||
|
||||
@@ -40,14 +39,10 @@ impl Keymap {
|
||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||
for binding in bindings {
|
||||
let action_id = binding.action().as_any().type_id();
|
||||
if is_no_action(&*binding.action) {
|
||||
self.no_action_binding_indices.push(self.bindings.len());
|
||||
} else {
|
||||
self.binding_indices_by_action_id
|
||||
.entry(action_id)
|
||||
.or_default()
|
||||
.push(self.bindings.len());
|
||||
}
|
||||
self.binding_indices_by_action_id
|
||||
.entry(action_id)
|
||||
.or_default()
|
||||
.push(self.bindings.len());
|
||||
self.bindings.push(binding);
|
||||
}
|
||||
|
||||
@@ -58,7 +53,6 @@ impl Keymap {
|
||||
pub fn clear(&mut self) {
|
||||
self.bindings.clear();
|
||||
self.binding_indices_by_action_id.clear();
|
||||
self.no_action_binding_indices.clear();
|
||||
self.version.0 += 1;
|
||||
}
|
||||
|
||||
@@ -73,39 +67,12 @@ impl Keymap {
|
||||
action: &'a dyn Action,
|
||||
) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
|
||||
let action_id = action.type_id();
|
||||
let binding_indices = self
|
||||
.binding_indices_by_action_id
|
||||
self.binding_indices_by_action_id
|
||||
.get(&action_id)
|
||||
.map_or(&[] as _, SmallVec::as_slice)
|
||||
.iter();
|
||||
|
||||
binding_indices.filter_map(|ix| {
|
||||
let binding = &self.bindings[*ix];
|
||||
if !binding.action().partial_eq(action) {
|
||||
return None;
|
||||
}
|
||||
|
||||
for null_ix in &self.no_action_binding_indices {
|
||||
if null_ix > ix {
|
||||
let null_binding = &self.bindings[*null_ix];
|
||||
if null_binding.keystrokes == binding.keystrokes {
|
||||
let null_binding_matches =
|
||||
match (&null_binding.context_predicate, &binding.context_predicate) {
|
||||
(None, _) => true,
|
||||
(Some(_), None) => false,
|
||||
(Some(null_predicate), Some(predicate)) => {
|
||||
null_predicate.is_superset(predicate)
|
||||
}
|
||||
};
|
||||
if null_binding_matches {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(binding)
|
||||
})
|
||||
.iter()
|
||||
.map(|ix| &self.bindings[*ix])
|
||||
.filter(move |binding| binding.action().partial_eq(action))
|
||||
}
|
||||
|
||||
/// all bindings for input returns all bindings that might match the input
|
||||
@@ -167,7 +134,7 @@ impl Keymap {
|
||||
let bindings = bindings
|
||||
.into_iter()
|
||||
.map_while(|(binding, _)| {
|
||||
if is_no_action(&*binding.action) {
|
||||
if binding.action.as_any().type_id() == (NoAction {}).type_id() {
|
||||
None
|
||||
} else {
|
||||
Some(binding)
|
||||
@@ -195,7 +162,7 @@ impl Keymap {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate as gpui;
|
||||
use gpui::{actions, NoAction};
|
||||
use gpui::actions;
|
||||
|
||||
actions!(
|
||||
keymap_test,
|
||||
@@ -274,31 +241,4 @@ mod tests {
|
||||
.0
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bindings_for_action() {
|
||||
let bindings = [
|
||||
KeyBinding::new("ctrl-a", ActionAlpha {}, Some("pane")),
|
||||
KeyBinding::new("ctrl-b", ActionBeta {}, Some("editor && mode == full")),
|
||||
KeyBinding::new("ctrl-c", ActionGamma {}, Some("workspace")),
|
||||
KeyBinding::new("ctrl-a", NoAction {}, Some("pane && active")),
|
||||
KeyBinding::new("ctrl-b", NoAction {}, Some("editor")),
|
||||
];
|
||||
|
||||
let mut keymap = Keymap::default();
|
||||
keymap.add_bindings(bindings.clone());
|
||||
|
||||
assert_bindings(&keymap, &ActionAlpha {}, &["ctrl-a"]);
|
||||
assert_bindings(&keymap, &ActionBeta {}, &[]);
|
||||
assert_bindings(&keymap, &ActionGamma {}, &["ctrl-c"]);
|
||||
|
||||
#[track_caller]
|
||||
fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
|
||||
let actual = keymap
|
||||
.bindings_for_action(action)
|
||||
.map(|binding| binding.keystrokes[0].unparse())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected, "{:?}", action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,30 +269,6 @@ impl KeyBindingContextPredicate {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether or not this predicate matches all possible contexts matched by
|
||||
/// the other predicate.
|
||||
pub fn is_superset(&self, other: &Self) -> bool {
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let KeyBindingContextPredicate::Or(left, right) = self {
|
||||
return left.is_superset(other) || right.is_superset(other);
|
||||
}
|
||||
|
||||
match other {
|
||||
KeyBindingContextPredicate::Child(_, child) => self.is_superset(child),
|
||||
KeyBindingContextPredicate::And(left, right) => {
|
||||
self.is_superset(left) || self.is_superset(right)
|
||||
}
|
||||
KeyBindingContextPredicate::Identifier(_) => false,
|
||||
KeyBindingContextPredicate::Equal(_, _) => false,
|
||||
KeyBindingContextPredicate::NotEqual(_, _) => false,
|
||||
KeyBindingContextPredicate::Not(_) => false,
|
||||
KeyBindingContextPredicate::Or(_, _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
|
||||
type Op = fn(
|
||||
KeyBindingContextPredicate,
|
||||
@@ -583,27 +559,4 @@ mod tests {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_superset() {
|
||||
assert_is_superset("editor", "editor", true);
|
||||
assert_is_superset("editor", "workspace", false);
|
||||
|
||||
assert_is_superset("editor", "editor && vim_mode", true);
|
||||
assert_is_superset("editor", "mode == full && editor", true);
|
||||
assert_is_superset("editor && mode == full", "editor", false);
|
||||
|
||||
assert_is_superset("editor", "something > editor", true);
|
||||
assert_is_superset("editor", "editor > menu", false);
|
||||
|
||||
assert_is_superset("foo || bar || baz", "bar", true);
|
||||
assert_is_superset("foo || bar || baz", "quux", false);
|
||||
|
||||
#[track_caller]
|
||||
fn assert_is_superset(a: &str, b: &str, result: bool) {
|
||||
let a = KeyBindingContextPredicate::parse(a).unwrap();
|
||||
let b = KeyBindingContextPredicate::parse(b).unwrap();
|
||||
assert_eq!(a.is_superset(&b), result, "({a:?}).is_superset({b:?})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ use smallvec::SmallVec;
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Cursor;
|
||||
use std::ops;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
@@ -548,6 +549,41 @@ pub(crate) trait PlatformAtlas: Send + Sync {
|
||||
key: &AtlasKey,
|
||||
build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
|
||||
) -> Result<Option<AtlasTile>>;
|
||||
fn remove(&self, key: &AtlasKey);
|
||||
}
|
||||
|
||||
struct AtlasTextureList<T> {
|
||||
textures: Vec<Option<T>>,
|
||||
free_list: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<T> Default for AtlasTextureList<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
textures: Vec::default(),
|
||||
free_list: Vec::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Index<usize> for AtlasTextureList<T> {
|
||||
type Output = Option<T>;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.textures[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AtlasTextureList<T> {
|
||||
#[allow(unused)]
|
||||
fn drain(&mut self) -> std::vec::Drain<Option<T>> {
|
||||
self.free_list.clear();
|
||||
self.textures.drain(..)
|
||||
}
|
||||
|
||||
fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
|
||||
self.textures.iter_mut().flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
|
||||
Point, Size,
|
||||
platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds,
|
||||
DevicePixels, PlatformAtlas, Point, Size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use blade_graphics as gpu;
|
||||
@@ -67,7 +67,7 @@ impl BladeAtlas {
|
||||
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
||||
let mut lock = self.0.lock();
|
||||
let textures = &mut lock.storage[texture_kind];
|
||||
for texture in textures {
|
||||
for texture in textures.iter_mut() {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
@@ -130,19 +130,48 @@ impl PlatformAtlas for BladeAtlas {
|
||||
Ok(Some(tile))
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, key: &AtlasKey) {
|
||||
let mut lock = self.0.lock();
|
||||
|
||||
let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mut texture) = texture_slot.take() {
|
||||
texture.decrement_ref_count();
|
||||
if texture.is_unreferenced() {
|
||||
lock.storage[id.kind]
|
||||
.free_list
|
||||
.push(texture.id.index as usize);
|
||||
texture.destroy(&lock.gpu);
|
||||
} else {
|
||||
*texture_slot = Some(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BladeAtlasState {
|
||||
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
|
||||
let textures = &mut self.storage[texture_kind];
|
||||
textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
.unwrap_or_else(|| {
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size).unwrap()
|
||||
})
|
||||
{
|
||||
let textures = &mut self.storage[texture_kind];
|
||||
|
||||
if let Some(tile) = textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
{
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size).unwrap()
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
@@ -198,21 +227,30 @@ impl BladeAtlasState {
|
||||
},
|
||||
);
|
||||
|
||||
let textures = &mut self.storage[kind];
|
||||
let texture_list = &mut self.storage[kind];
|
||||
let index = texture_list.free_list.pop();
|
||||
|
||||
let atlas_texture = BladeAtlasTexture {
|
||||
id: AtlasTextureId {
|
||||
index: textures.len() as u32,
|
||||
index: index.unwrap_or(texture_list.textures.len()) as u32,
|
||||
kind,
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
format,
|
||||
raw,
|
||||
raw_view,
|
||||
live_atlas_keys: 0,
|
||||
};
|
||||
|
||||
self.initializations.push(atlas_texture.id);
|
||||
textures.push(atlas_texture);
|
||||
textures.last_mut().unwrap()
|
||||
|
||||
if let Some(ix) = index {
|
||||
texture_list.textures[ix] = Some(atlas_texture);
|
||||
texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
|
||||
} else {
|
||||
texture_list.textures.push(Some(atlas_texture));
|
||||
texture_list.textures.last_mut().unwrap().as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
|
||||
@@ -258,13 +296,13 @@ impl BladeAtlasState {
|
||||
|
||||
#[derive(Default)]
|
||||
struct BladeAtlasStorage {
|
||||
monochrome_textures: Vec<BladeAtlasTexture>,
|
||||
polychrome_textures: Vec<BladeAtlasTexture>,
|
||||
path_textures: Vec<BladeAtlasTexture>,
|
||||
monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
path_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
}
|
||||
|
||||
impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
|
||||
type Output = Vec<BladeAtlasTexture>;
|
||||
type Output = AtlasTextureList<BladeAtlasTexture>;
|
||||
fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
|
||||
match kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
@@ -292,19 +330,19 @@ impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
||||
};
|
||||
&textures[id.index as usize]
|
||||
textures[id.index as usize].as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl BladeAtlasStorage {
|
||||
fn destroy(&mut self, gpu: &gpu::Context) {
|
||||
for mut texture in self.monochrome_textures.drain(..) {
|
||||
for mut texture in self.monochrome_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
for mut texture in self.polychrome_textures.drain(..) {
|
||||
for mut texture in self.polychrome_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
for mut texture in self.path_textures.drain(..) {
|
||||
for mut texture in self.path_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
}
|
||||
@@ -316,6 +354,7 @@ struct BladeAtlasTexture {
|
||||
raw: gpu::Texture,
|
||||
raw_view: gpu::TextureView,
|
||||
format: gpu::TextureFormat,
|
||||
live_atlas_keys: u32,
|
||||
}
|
||||
|
||||
impl BladeAtlasTexture {
|
||||
@@ -334,6 +373,7 @@ impl BladeAtlasTexture {
|
||||
size,
|
||||
},
|
||||
};
|
||||
self.live_atlas_keys += 1;
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
@@ -345,6 +385,14 @@ impl BladeAtlasTexture {
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
self.format.block_info().size
|
||||
}
|
||||
|
||||
fn decrement_ref_count(&mut self) {
|
||||
self.live_atlas_keys -= 1;
|
||||
}
|
||||
|
||||
fn is_unreferenced(&mut self) -> bool {
|
||||
self.live_atlas_keys == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<DevicePixels>> for etagere::Size {
|
||||
|
||||
@@ -36,9 +36,11 @@ struct CosmicTextSystemState {
|
||||
|
||||
impl CosmicTextSystem {
|
||||
pub(crate) fn new() -> Self {
|
||||
// todo(linux) make font loading non-blocking
|
||||
let mut font_system = FontSystem::new();
|
||||
|
||||
// todo(linux) make font loading non-blocking
|
||||
font_system.db_mut().load_system_fonts();
|
||||
|
||||
Self(RwLock::new(CosmicTextSystemState {
|
||||
font_system,
|
||||
swash_cache: SwashCache::new(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
|
||||
Point, Size,
|
||||
platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds,
|
||||
DevicePixels, PlatformAtlas, Point, Size,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::FxHashMap;
|
||||
@@ -42,7 +42,7 @@ impl MetalAtlas {
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut lock.path_textures,
|
||||
};
|
||||
for texture in textures {
|
||||
for texture in textures.iter_mut() {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
@@ -50,9 +50,9 @@ impl MetalAtlas {
|
||||
|
||||
struct MetalAtlasState {
|
||||
device: AssertSend<Device>,
|
||||
monochrome_textures: Vec<MetalAtlasTexture>,
|
||||
polychrome_textures: Vec<MetalAtlasTexture>,
|
||||
path_textures: Vec<MetalAtlasTexture>,
|
||||
monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
path_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||
}
|
||||
|
||||
@@ -78,6 +78,38 @@ impl PlatformAtlas for MetalAtlas {
|
||||
Ok(Some(tile))
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, key: &AtlasKey) {
|
||||
let mut lock = self.0.lock();
|
||||
let Some(id) = lock.tiles_by_key.get(key).map(|v| v.texture_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let textures = match id.kind {
|
||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut lock.polychrome_textures,
|
||||
};
|
||||
|
||||
let Some(texture_slot) = textures
|
||||
.textures
|
||||
.iter_mut()
|
||||
.find(|texture| texture.as_ref().is_some_and(|v| v.id == id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mut texture) = texture_slot.take() {
|
||||
texture.decrement_ref_count();
|
||||
|
||||
if texture.is_unreferenced() {
|
||||
textures.free_list.push(id.index as usize);
|
||||
lock.tiles_by_key.remove(key);
|
||||
} else {
|
||||
*texture_slot = Some(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetalAtlasState {
|
||||
@@ -86,20 +118,24 @@ impl MetalAtlasState {
|
||||
size: Size<DevicePixels>,
|
||||
texture_kind: AtlasTextureKind,
|
||||
) -> Option<AtlasTile> {
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
{
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
|
||||
textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
.or_else(|| {
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size)
|
||||
})
|
||||
if let Some(tile) = textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
{
|
||||
return Some(tile);
|
||||
}
|
||||
}
|
||||
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size)
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
@@ -140,21 +176,31 @@ impl MetalAtlasState {
|
||||
texture_descriptor.set_usage(usage);
|
||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
|
||||
let textures = match kind {
|
||||
let texture_list = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
|
||||
let index = texture_list.free_list.pop();
|
||||
|
||||
let atlas_texture = MetalAtlasTexture {
|
||||
id: AtlasTextureId {
|
||||
index: textures.len() as u32,
|
||||
index: index.unwrap_or(texture_list.textures.len()) as u32,
|
||||
kind,
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
metal_texture: AssertSend(metal_texture),
|
||||
live_atlas_keys: 0,
|
||||
};
|
||||
textures.push(atlas_texture);
|
||||
textures.last_mut().unwrap()
|
||||
|
||||
if let Some(ix) = index {
|
||||
texture_list.textures[ix] = Some(atlas_texture);
|
||||
texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
|
||||
} else {
|
||||
texture_list.textures.push(Some(atlas_texture));
|
||||
texture_list.textures.last_mut().unwrap().as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
|
||||
@@ -163,7 +209,7 @@ impl MetalAtlasState {
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
||||
};
|
||||
&textures[id.index as usize]
|
||||
textures[id.index as usize].as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +217,7 @@ struct MetalAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
metal_texture: AssertSend<metal::Texture>,
|
||||
live_atlas_keys: u32,
|
||||
}
|
||||
|
||||
impl MetalAtlasTexture {
|
||||
@@ -189,6 +236,7 @@ impl MetalAtlasTexture {
|
||||
},
|
||||
padding: 0,
|
||||
};
|
||||
self.live_atlas_keys += 1;
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
@@ -215,6 +263,14 @@ impl MetalAtlasTexture {
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement_ref_count(&mut self) {
|
||||
self.live_atlas_keys -= 1;
|
||||
}
|
||||
|
||||
fn is_unreferenced(&mut self) -> bool {
|
||||
self.live_atlas_keys == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<DevicePixels>> for etagere::Size {
|
||||
|
||||
@@ -294,10 +294,8 @@ impl MacPlatform {
|
||||
Some(crate::OsAction::Copy) => selector("copy:"),
|
||||
Some(crate::OsAction::Paste) => selector("paste:"),
|
||||
Some(crate::OsAction::SelectAll) => selector("selectAll:"),
|
||||
// "undo:" and "redo:" are always disabled in our case, as
|
||||
// we don't have a NSTextView/NSTextField to enable them on.
|
||||
Some(crate::OsAction::Undo) => selector("handleGPUIMenuItem:"),
|
||||
Some(crate::OsAction::Redo) => selector("handleGPUIMenuItem:"),
|
||||
Some(crate::OsAction::Undo) => selector("undo:"),
|
||||
Some(crate::OsAction::Redo) => selector("redo:"),
|
||||
None => selector("handleGPUIMenuItem:"),
|
||||
};
|
||||
|
||||
@@ -393,7 +391,7 @@ impl MacPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
fn os_version() -> Result<SemanticVersion> {
|
||||
fn os_version(&self) -> Result<SemanticVersion> {
|
||||
unsafe {
|
||||
let process_info = NSProcessInfo::processInfo(nil);
|
||||
let version = process_info.operatingSystemVersion();
|
||||
@@ -587,7 +585,7 @@ impl Platform for MacPlatform {
|
||||
// API only available post Monterey
|
||||
// https://developer.apple.com/documentation/appkit/nsworkspace/3753004-setdefaultapplicationaturl
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
if Self::os_version().ok() < Some(SemanticVersion::new(12, 0, 0)) {
|
||||
if self.os_version().ok() < Some(SemanticVersion::new(12, 0, 0)) {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"macOS 12.0 or later is required to register URL schemes"
|
||||
)));
|
||||
@@ -697,36 +695,7 @@ impl Platform for MacPlatform {
|
||||
if response == NSModalResponse::NSModalResponseOk {
|
||||
let url = panel.URL();
|
||||
if url.isFileURL() == YES {
|
||||
result = ns_url_to_path(panel.URL()).ok().map(|mut result| {
|
||||
let Some(filename) = result.file_name() else {
|
||||
return result;
|
||||
};
|
||||
let chunks = filename
|
||||
.as_bytes()
|
||||
.split(|&b| b == b'.')
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// https://github.com/zed-industries/zed/issues/16969
|
||||
// Workaround a bug in macOS Sequoia that adds an extra file-extension
|
||||
// sometimes. e.g. `a.sql` becomes `a.sql.s` or `a.txtx` becomes `a.txtx.txt`
|
||||
//
|
||||
// This is conditional on OS version because I'd like to get rid of it, so that
|
||||
// you can manually create a file called `a.sql.s`. That said it seems better
|
||||
// to break that use-case than breaking `a.sql`.
|
||||
if chunks.len() == 3 && chunks[1].starts_with(chunks[2]) {
|
||||
if Self::os_version()
|
||||
.is_ok_and(|v| v >= SemanticVersion::new(15, 0, 0))
|
||||
{
|
||||
let new_filename = OsStr::from_bytes(
|
||||
&filename.as_bytes()
|
||||
[..chunks[0].len() + 1 + chunks[1].len()],
|
||||
)
|
||||
.to_owned();
|
||||
result.set_file_name(&new_filename);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
result = ns_url_to_path(panel.URL()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -339,4 +339,9 @@ impl PlatformAtlas for TestAtlas {
|
||||
|
||||
Ok(Some(state.tiles[key].clone()))
|
||||
}
|
||||
|
||||
fn remove(&self, key: &AtlasKey) {
|
||||
let mut state = self.0.lock();
|
||||
state.tiles.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2689,6 +2689,20 @@ impl<'a> WindowContext<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes an image from the sprite atlas.
|
||||
pub fn drop_image(&mut self, data: Arc<RenderImage>) -> Result<()> {
|
||||
for frame_index in 0..data.frame_count() {
|
||||
let params = RenderImageParams {
|
||||
image_id: data.id,
|
||||
frame_index,
|
||||
};
|
||||
|
||||
self.window.sprite_atlas.remove(¶ms.clone().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which
|
||||
/// layout is being requested, along with the layout ids of any children. This method is called during
|
||||
|
||||
@@ -15,10 +15,9 @@ doctest = false
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
db.workspace = true
|
||||
file_icons.workspace = true
|
||||
gpui.workspace = true
|
||||
project.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
file_icons.workspace = true
|
||||
ui.workspace = true
|
||||
settings.workspace = true
|
||||
workspace.workspace = true
|
||||
project.workspace = true
|
||||
|
||||
@@ -6,7 +6,6 @@ use gpui::{
|
||||
WindowContext,
|
||||
};
|
||||
use persistence::IMAGE_VIEWER;
|
||||
use theme::Theme;
|
||||
use ui::prelude::*;
|
||||
|
||||
use file_icons::FileIcons;
|
||||
@@ -14,8 +13,8 @@ use project::{Project, ProjectEntryId, ProjectPath};
|
||||
use settings::Settings;
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
use workspace::{
|
||||
item::{BreadcrumbText, Item, ProjectItem, SerializableItem, TabContentParams},
|
||||
ItemId, ItemSettings, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
|
||||
item::{Item, ProjectItem, SerializableItem, TabContentParams},
|
||||
ItemId, ItemSettings, Pane, Workspace, WorkspaceId,
|
||||
};
|
||||
|
||||
const IMAGE_VIEWER_KIND: &str = "ImageView";
|
||||
@@ -24,7 +23,6 @@ pub struct ImageItem {
|
||||
id: ProjectEntryId,
|
||||
path: PathBuf,
|
||||
project_path: ProjectPath,
|
||||
project: Model<Project>,
|
||||
}
|
||||
|
||||
impl project::Item for ImageItem {
|
||||
@@ -58,7 +56,6 @@ impl project::Item for ImageItem {
|
||||
.id;
|
||||
|
||||
cx.new_model(|_| ImageItem {
|
||||
project,
|
||||
path: abs_path,
|
||||
project_path: path,
|
||||
id,
|
||||
@@ -121,19 +118,6 @@ impl Item for ImageView {
|
||||
.map(Icon::from_path)
|
||||
}
|
||||
|
||||
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||
ToolbarItemLocation::PrimaryLeft
|
||||
}
|
||||
|
||||
fn breadcrumbs(&self, _theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
|
||||
let text = breadcrumbs_text_for_image(self.image.read(cx), cx);
|
||||
Some(vec![BreadcrumbText {
|
||||
text,
|
||||
highlights: None,
|
||||
font: None,
|
||||
}])
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
@@ -149,25 +133,6 @@ impl Item for ImageView {
|
||||
}
|
||||
}
|
||||
|
||||
fn breadcrumbs_text_for_image(image: &ImageItem, cx: &AppContext) -> String {
|
||||
let path = &image.project_path.path;
|
||||
let project = image.project.read(cx);
|
||||
|
||||
if project.visible_worktrees(cx).count() <= 1 {
|
||||
return path.to_string_lossy().to_string();
|
||||
}
|
||||
|
||||
project
|
||||
.worktree_for_entry(image.id, cx)
|
||||
.map(|worktree| {
|
||||
PathBuf::from(worktree.read(cx).root_name())
|
||||
.join(path)
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
})
|
||||
.unwrap_or_else(|| path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
impl SerializableItem for ImageView {
|
||||
fn serialized_item_kind() -> &'static str {
|
||||
IMAGE_VIEWER_KIND
|
||||
@@ -210,7 +175,6 @@ impl SerializableItem for ImageView {
|
||||
id,
|
||||
path: image_path,
|
||||
project_path,
|
||||
project,
|
||||
});
|
||||
|
||||
Ok(cx.new_view(|cx| ImageView {
|
||||
|
||||
@@ -46,7 +46,6 @@ use std::{
|
||||
future::Future,
|
||||
iter::{self, Iterator, Peekable},
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
@@ -4326,13 +4325,6 @@ impl IndentSize {
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn len_with_expanded_tabs(&self, tab_size: NonZeroU32) -> usize {
|
||||
match self.kind {
|
||||
IndentKind::Space => self.len as usize,
|
||||
IndentKind::Tab => self.len as usize * tab_size.get() as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -2120,8 +2120,8 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
|
||||
},
|
||||
],
|
||||
disabled_scopes_by_bracket_ix: vec![
|
||||
Vec::new(), //
|
||||
vec!["string".into(), "comment".into()], // single quotes disabled
|
||||
Vec::new(), //
|
||||
vec!["string".into()],
|
||||
],
|
||||
},
|
||||
overrides: [(
|
||||
@@ -2142,7 +2142,6 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
|
||||
r#"
|
||||
(jsx_element) @element
|
||||
(string) @string
|
||||
(comment) @comment.inclusive
|
||||
[
|
||||
(jsx_opening_element)
|
||||
(jsx_closing_element)
|
||||
@@ -2156,7 +2155,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
|
||||
a["b"] = <C d="e">
|
||||
<F></F>
|
||||
{ g() }
|
||||
</C>; // a comment
|
||||
</C>;
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
@@ -2171,14 +2170,6 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
|
||||
&[true, true]
|
||||
);
|
||||
|
||||
let comment_config = snapshot
|
||||
.language_scope_at(text.find("comment").unwrap() + "comment".len())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
comment_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
|
||||
&[true, false]
|
||||
);
|
||||
|
||||
let string_config = snapshot
|
||||
.language_scope_at(text.find("b\"").unwrap())
|
||||
.unwrap();
|
||||
|
||||
@@ -591,9 +591,6 @@ pub struct LanguageConfig {
|
||||
/// the indentation level for a new line.
|
||||
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
|
||||
pub auto_indent_using_last_non_empty_line: bool,
|
||||
// Whether indentation of pasted content should be adjusted based on the context.
|
||||
#[serde(default)]
|
||||
pub auto_indent_on_paste: Option<bool>,
|
||||
/// A regex that is used to determine whether the indentation level should be
|
||||
/// increased in the following line.
|
||||
#[serde(default, deserialize_with = "deserialize_regex")]
|
||||
@@ -721,7 +718,6 @@ impl Default for LanguageConfig {
|
||||
matcher: LanguageMatcher::default(),
|
||||
brackets: Default::default(),
|
||||
auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
|
||||
auto_indent_on_paste: None,
|
||||
increase_indent_pattern: Default::default(),
|
||||
decrease_indent_pattern: Default::default(),
|
||||
autoclose_before: Default::default(),
|
||||
@@ -945,14 +941,7 @@ struct RunnableConfig {
|
||||
|
||||
struct OverrideConfig {
|
||||
query: Query,
|
||||
values: HashMap<u32, OverrideEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OverrideEntry {
|
||||
name: String,
|
||||
range_is_inclusive: bool,
|
||||
value: LanguageConfigOverride,
|
||||
values: HashMap<u32, (String, LanguageConfigOverride)>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@@ -1272,66 +1261,58 @@ impl Language {
|
||||
};
|
||||
|
||||
let mut override_configs_by_id = HashMap::default();
|
||||
for (ix, mut name) in query.capture_names().iter().copied().enumerate() {
|
||||
let mut range_is_inclusive = false;
|
||||
if name.starts_with('_') {
|
||||
continue;
|
||||
}
|
||||
if let Some(prefix) = name.strip_suffix(".inclusive") {
|
||||
name = prefix;
|
||||
range_is_inclusive = true;
|
||||
}
|
||||
|
||||
let value = self.config.overrides.get(name).cloned().unwrap_or_default();
|
||||
for server_name in &value.opt_into_language_servers {
|
||||
if !self
|
||||
.config
|
||||
.scope_opt_in_language_servers
|
||||
.contains(server_name)
|
||||
{
|
||||
util::debug_panic!("Server {server_name:?} has been opted-in by scope {name:?} but has not been marked as an opt-in server");
|
||||
for (ix, name) in query.capture_names().iter().enumerate() {
|
||||
if !name.starts_with('_') {
|
||||
let value = self.config.overrides.remove(*name).unwrap_or_default();
|
||||
for server_name in &value.opt_into_language_servers {
|
||||
if !self
|
||||
.config
|
||||
.scope_opt_in_language_servers
|
||||
.contains(server_name)
|
||||
{
|
||||
util::debug_panic!("Server {server_name:?} has been opted-in by scope {name:?} but has not been marked as an opt-in server");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override_configs_by_id.insert(
|
||||
ix as u32,
|
||||
OverrideEntry {
|
||||
name: name.to_string(),
|
||||
range_is_inclusive,
|
||||
value,
|
||||
},
|
||||
);
|
||||
override_configs_by_id.insert(ix as u32, (name.to_string(), value));
|
||||
}
|
||||
}
|
||||
|
||||
let referenced_override_names = self.config.overrides.keys().chain(
|
||||
self.config
|
||||
.brackets
|
||||
.disabled_scopes_by_bracket_ix
|
||||
.iter()
|
||||
.flatten(),
|
||||
);
|
||||
if !self.config.overrides.is_empty() {
|
||||
let keys = self.config.overrides.keys().collect::<Vec<_>>();
|
||||
Err(anyhow!(
|
||||
"language {:?} has overrides in config not in query: {keys:?}",
|
||||
self.config.name
|
||||
))?;
|
||||
}
|
||||
|
||||
for referenced_name in referenced_override_names {
|
||||
for disabled_scope_name in self
|
||||
.config
|
||||
.brackets
|
||||
.disabled_scopes_by_bracket_ix
|
||||
.iter()
|
||||
.flatten()
|
||||
{
|
||||
if !override_configs_by_id
|
||||
.values()
|
||||
.any(|entry| entry.name == *referenced_name)
|
||||
.any(|(scope_name, _)| scope_name == disabled_scope_name)
|
||||
{
|
||||
Err(anyhow!(
|
||||
"language {:?} has overrides in config not in query: {referenced_name:?}",
|
||||
"language {:?} has overrides in config not in query: {disabled_scope_name:?}",
|
||||
self.config.name
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
for entry in override_configs_by_id.values_mut() {
|
||||
entry.value.disabled_bracket_ixs = self
|
||||
for (name, override_config) in override_configs_by_id.values_mut() {
|
||||
override_config.disabled_bracket_ixs = self
|
||||
.config
|
||||
.brackets
|
||||
.disabled_scopes_by_bracket_ix
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(ix, disabled_scope_names)| {
|
||||
if disabled_scope_names.contains(&entry.name) {
|
||||
if disabled_scope_names.contains(name) {
|
||||
Some(ix as u16)
|
||||
} else {
|
||||
None
|
||||
@@ -1549,14 +1530,14 @@ impl LanguageScope {
|
||||
let id = self.override_id?;
|
||||
let grammar = self.language.grammar.as_ref()?;
|
||||
let override_config = grammar.override_config.as_ref()?;
|
||||
override_config.values.get(&id).map(|e| e.name.as_str())
|
||||
override_config.values.get(&id).map(|e| e.0.as_str())
|
||||
}
|
||||
|
||||
fn config_override(&self) -> Option<&LanguageConfigOverride> {
|
||||
let id = self.override_id?;
|
||||
let grammar = self.language.grammar.as_ref()?;
|
||||
let override_config = grammar.override_config.as_ref()?;
|
||||
override_config.values.get(&id).map(|e| &e.value)
|
||||
override_config.values.get(&id).map(|e| &e.1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -965,7 +965,6 @@ impl LanguageRegistryState {
|
||||
tab_size: language.config.tab_size,
|
||||
hard_tabs: language.config.hard_tabs,
|
||||
soft_wrap: language.config.soft_wrap,
|
||||
auto_indent_on_paste: language.config.auto_indent_on_paste,
|
||||
..Default::default()
|
||||
}
|
||||
.clone(),
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::Result;
|
||||
use collections::{HashMap, HashSet};
|
||||
use core::slice;
|
||||
use ec4rs::{
|
||||
property::{FinalNewline, IndentSize, IndentStyle, TabWidth, TrimTrailingWs},
|
||||
property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
|
||||
Properties as EditorconfigProperties,
|
||||
};
|
||||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
@@ -125,8 +125,6 @@ pub struct LanguageSettings {
|
||||
/// Whether to use additional LSP queries to format (and amend) the code after
|
||||
/// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
pub use_on_type_format: bool,
|
||||
/// Whether indentation of pasted content should be adjusted based on the context.
|
||||
pub auto_indent_on_paste: bool,
|
||||
// Controls how the editor handles the autoclosed characters.
|
||||
pub always_treat_brackets_as_autoclosed: bool,
|
||||
/// Which code actions to run on save
|
||||
@@ -362,10 +360,6 @@ pub struct LanguageSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub linked_edits: Option<bool>,
|
||||
/// Whether indentation of pasted content should be adjusted based on the context.
|
||||
///
|
||||
/// Default: true
|
||||
pub auto_indent_on_paste: Option<bool>,
|
||||
/// Task configuration for this language.
|
||||
///
|
||||
/// Default: {}
|
||||
@@ -876,6 +870,10 @@ impl AllLanguageSettings {
|
||||
}
|
||||
|
||||
fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
|
||||
let max_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
|
||||
MaxLineLen::Value(u) => Some(u as u32),
|
||||
MaxLineLen::Off => None,
|
||||
});
|
||||
let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
|
||||
IndentSize::Value(u) => NonZeroU32::new(u as u32),
|
||||
IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
|
||||
@@ -898,6 +896,13 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr
|
||||
TrimTrailingWs::Value(b) => b,
|
||||
})
|
||||
.ok();
|
||||
let preferred_line_length = max_line_length;
|
||||
let soft_wrap = if max_line_length.is_some() {
|
||||
Some(SoftWrap::PreferredLineLength)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
fn merge<T>(target: &mut T, value: Option<T>) {
|
||||
if let Some(value) = value {
|
||||
*target = value;
|
||||
@@ -913,6 +918,8 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr
|
||||
&mut settings.ensure_final_newline_on_save,
|
||||
ensure_final_newline_on_save,
|
||||
);
|
||||
merge(&mut settings.preferred_line_length, preferred_line_length);
|
||||
merge(&mut settings.soft_wrap, soft_wrap);
|
||||
}
|
||||
|
||||
/// The kind of an inlay hint.
|
||||
@@ -1125,7 +1132,6 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
|
||||
merge(&mut settings.use_autoclose, src.use_autoclose);
|
||||
merge(&mut settings.use_auto_surround, src.use_auto_surround);
|
||||
merge(&mut settings.use_on_type_format, src.use_on_type_format);
|
||||
merge(&mut settings.auto_indent_on_paste, src.auto_indent_on_paste);
|
||||
merge(
|
||||
&mut settings.always_treat_brackets_as_autoclosed,
|
||||
src.always_treat_brackets_as_autoclosed,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::{BufferSnapshot, Point, ToPoint};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{BackgroundExecutor, HighlightStyle};
|
||||
use gpui::{relative, AppContext, BackgroundExecutor, HighlightStyle, StyledText, TextStyle};
|
||||
use settings::Settings;
|
||||
use std::ops::Range;
|
||||
use theme::{color_alpha, ActiveTheme, ThemeSettings};
|
||||
|
||||
/// An outline of all the symbols contained in a buffer.
|
||||
#[derive(Debug)]
|
||||
@@ -191,6 +193,42 @@ impl<T> Outline<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_item<T>(
|
||||
outline_item: &OutlineItem<T>,
|
||||
match_ranges: impl IntoIterator<Item = Range<usize>>,
|
||||
cx: &AppContext,
|
||||
) -> StyledText {
|
||||
let highlight_style = HighlightStyle {
|
||||
background_color: Some(color_alpha(cx.theme().colors().text_accent, 0.3)),
|
||||
..Default::default()
|
||||
};
|
||||
let custom_highlights = match_ranges
|
||||
.into_iter()
|
||||
.map(|range| (range, highlight_style));
|
||||
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
|
||||
// TODO: We probably shouldn't need to build a whole new text style here
|
||||
// but I'm not sure how to get the current one and modify it.
|
||||
// Before this change TextStyle::default() was used here, which was giving us the wrong font and text color.
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_features: settings.buffer_font.features.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(1.),
|
||||
..Default::default()
|
||||
};
|
||||
let highlights = gpui::combine_highlights(
|
||||
custom_highlights,
|
||||
outline_item.highlight_ranges.iter().cloned(),
|
||||
);
|
||||
|
||||
StyledText::new(outline_item.text.clone()).with_highlights(&text_style, highlights)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1520,24 +1520,18 @@ impl<'a> SyntaxLayer<'a> {
|
||||
let config = self.language.grammar.as_ref()?.override_config.as_ref()?;
|
||||
|
||||
let mut query_cursor = QueryCursorHandle::new();
|
||||
query_cursor.set_byte_range(offset.saturating_sub(1)..offset.saturating_add(1));
|
||||
query_cursor.set_byte_range(offset..offset);
|
||||
|
||||
let mut smallest_match: Option<(u32, Range<usize>)> = None;
|
||||
for mat in query_cursor.matches(&config.query, self.node(), text) {
|
||||
for capture in mat.captures {
|
||||
let Some(override_entry) = config.values.get(&capture.index) else {
|
||||
if !config.values.contains_key(&capture.index) {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
let range = capture.node.byte_range();
|
||||
if override_entry.range_is_inclusive {
|
||||
if offset < range.start || offset > range.end {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if offset <= range.start || offset >= range.end {
|
||||
continue;
|
||||
}
|
||||
if offset <= range.start || offset >= range.end {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((_, smallest_range)) = &smallest_match {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::{AsyncAppContext, SharedString};
|
||||
use settings::WorktreeId;
|
||||
|
||||
@@ -24,11 +23,7 @@ pub struct Toolchain {
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait ToolchainLister: Send + Sync {
|
||||
async fn list(
|
||||
&self,
|
||||
worktree_root: PathBuf,
|
||||
project_env: Option<HashMap<String, String>>,
|
||||
) -> ToolchainList;
|
||||
async fn list(&self, _: PathBuf) -> ToolchainList;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
||||
@@ -30,7 +30,6 @@ use crate::{
|
||||
};
|
||||
use crate::{LanguageModelCompletionEvent, LanguageModelProviderState};
|
||||
|
||||
use super::anthropic::count_anthropic_tokens;
|
||||
use super::open_ai::count_open_ai_tokens;
|
||||
|
||||
const PROVIDER_ID: &str = "copilot_chat";
|
||||
@@ -180,19 +179,13 @@ impl LanguageModel for CopilotChatLanguageModel {
|
||||
request: LanguageModelRequest,
|
||||
cx: &AppContext,
|
||||
) -> BoxFuture<'static, Result<usize>> {
|
||||
match self.model {
|
||||
CopilotChatModel::Claude3_5Sonnet => count_anthropic_tokens(request, cx),
|
||||
_ => {
|
||||
let model = match self.model {
|
||||
CopilotChatModel::Gpt4o => open_ai::Model::FourOmni,
|
||||
CopilotChatModel::Gpt4 => open_ai::Model::Four,
|
||||
CopilotChatModel::Gpt3_5Turbo => open_ai::Model::ThreePointFiveTurbo,
|
||||
CopilotChatModel::O1Preview | CopilotChatModel::O1Mini => open_ai::Model::Four,
|
||||
CopilotChatModel::Claude3_5Sonnet => unreachable!(),
|
||||
};
|
||||
count_open_ai_tokens(request, model, cx)
|
||||
}
|
||||
}
|
||||
let model = match self.model {
|
||||
CopilotChatModel::Gpt4o => open_ai::Model::FourOmni,
|
||||
CopilotChatModel::Gpt4 => open_ai::Model::Four,
|
||||
CopilotChatModel::Gpt3_5Turbo => open_ai::Model::ThreePointFiveTurbo,
|
||||
};
|
||||
|
||||
count_open_ai_tokens(request, model, cx)
|
||||
}
|
||||
|
||||
fn stream_completion(
|
||||
@@ -216,8 +209,7 @@ impl LanguageModel for CopilotChatLanguageModel {
|
||||
}
|
||||
}
|
||||
|
||||
let copilot_request = self.to_copilot_chat_request(request);
|
||||
let is_streaming = copilot_request.stream;
|
||||
let request = self.to_copilot_chat_request(request);
|
||||
let Ok(low_speed_timeout) = cx.update(|cx| {
|
||||
AllLanguageModelSettings::get_global(cx)
|
||||
.copilot_chat
|
||||
@@ -228,31 +220,16 @@ impl LanguageModel for CopilotChatLanguageModel {
|
||||
|
||||
let request_limiter = self.request_limiter.clone();
|
||||
let future = cx.spawn(|cx| async move {
|
||||
let response = CopilotChat::stream_completion(copilot_request, low_speed_timeout, cx);
|
||||
let response = CopilotChat::stream_completion(request, low_speed_timeout, cx);
|
||||
request_limiter.stream(async move {
|
||||
let response = response.await?;
|
||||
let stream = response
|
||||
.filter_map(move |response| async move {
|
||||
.filter_map(|response| async move {
|
||||
match response {
|
||||
Ok(result) => {
|
||||
let choice = result.choices.first();
|
||||
match choice {
|
||||
Some(choice) if !is_streaming => {
|
||||
match &choice.message {
|
||||
Some(msg) => Some(Ok(msg.content.clone().unwrap_or_default())),
|
||||
None => Some(Err(anyhow::anyhow!(
|
||||
"The Copilot Chat API returned a response with no message content"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Some(choice) => {
|
||||
match &choice.delta {
|
||||
Some(delta) => Some(Ok(delta.content.clone().unwrap_or_default())),
|
||||
None => Some(Err(anyhow::anyhow!(
|
||||
"The Copilot Chat API returned a response with no delta content"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Some(choice) => Some(Ok(choice.delta.content.clone().unwrap_or_default())),
|
||||
None => Some(Err(anyhow::anyhow!(
|
||||
"The Copilot Chat API returned a response with no choices, but hadn't finished the message yet. Please try again."
|
||||
))),
|
||||
|
||||
@@ -47,7 +47,6 @@ lsp.workspace = true
|
||||
node_runtime.workspace = true
|
||||
paths.workspace = true
|
||||
pet.workspace = true
|
||||
pet-fs.workspace = true
|
||||
pet-core.workspace = true
|
||||
pet-conda.workspace = true
|
||||
pet-poetry.workspace = true
|
||||
|
||||
@@ -7,7 +7,5 @@ first_line_pattern = '^#!.*\b(?:ash|bash|dash|sh|zsh)\b'
|
||||
brackets = [
|
||||
{ start = "[", end = "]", close = true, newline = false },
|
||||
{ start = "(", end = ")", close = true, newline = false },
|
||||
{ start = "{", end = "}", close = true, newline = false },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(comment) @comment.inclusive
|
||||
(comment) @comment
|
||||
(string_literal) @string
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(comment) @comment.inclusive
|
||||
(comment) @comment
|
||||
(string_literal) @string
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(comment) @comment.inclusive
|
||||
(comment) @comment
|
||||
(string_value) @string
|
||||
|
||||
@@ -408,8 +408,6 @@ fn adjust_runs(
|
||||
pub(crate) struct GoContextProvider;
|
||||
|
||||
const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE"));
|
||||
const GO_MODULE_ROOT_TASK_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("GO_MODULE_ROOT"));
|
||||
const GO_SUBTEST_NAME_TASK_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("GO_SUBTEST_NAME"));
|
||||
|
||||
@@ -449,33 +447,15 @@ impl ContextProvider for GoContextProvider {
|
||||
(GO_PACKAGE_TASK_VARIABLE.clone(), package_name.to_string())
|
||||
});
|
||||
|
||||
let go_module_root_variable = local_abs_path
|
||||
.as_deref()
|
||||
.and_then(|local_abs_path| local_abs_path.parent())
|
||||
.map(|buffer_dir| {
|
||||
// Walk dirtree up until getting the first go.mod file
|
||||
let module_dir = buffer_dir
|
||||
.ancestors()
|
||||
.find(|dir| dir.join("go.mod").is_file())
|
||||
.map(|dir| dir.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| ".".to_string());
|
||||
|
||||
(GO_MODULE_ROOT_TASK_VARIABLE.clone(), module_dir)
|
||||
});
|
||||
|
||||
let _subtest_name = variables.get(&VariableName::Custom(Cow::Borrowed("_subtest_name")));
|
||||
|
||||
let go_subtest_variable = extract_subtest_name(_subtest_name.unwrap_or(""))
|
||||
.map(|subtest_name| (GO_SUBTEST_NAME_TASK_VARIABLE.clone(), subtest_name));
|
||||
|
||||
Ok(TaskVariables::from_iter(
|
||||
[
|
||||
go_package_variable,
|
||||
go_subtest_variable,
|
||||
go_module_root_variable,
|
||||
]
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
[go_package_variable, go_subtest_variable]
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -489,7 +469,6 @@ impl ContextProvider for GoContextProvider {
|
||||
} else {
|
||||
Some("$ZED_DIRNAME".to_string())
|
||||
};
|
||||
let module_cwd = Some(GO_MODULE_ROOT_TASK_VARIABLE.template_value());
|
||||
|
||||
Some(TaskTemplates(vec![
|
||||
TaskTemplate {
|
||||
@@ -519,7 +498,7 @@ impl ContextProvider for GoContextProvider {
|
||||
label: "go test ./...".into(),
|
||||
command: "go".into(),
|
||||
args: vec!["test".into(), "./...".into()],
|
||||
cwd: module_cwd.clone(),
|
||||
cwd: package_cwd.clone(),
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
@@ -570,21 +549,6 @@ impl ContextProvider for GoContextProvider {
|
||||
tags: vec!["go-main".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: format!("go generate {}", GO_PACKAGE_TASK_VARIABLE.template_value()),
|
||||
command: "go".into(),
|
||||
args: vec!["generate".into()],
|
||||
cwd: package_cwd.clone(),
|
||||
tags: vec!["go-generate".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "go generate ./...".into(),
|
||||
command: "go".into(),
|
||||
args: vec!["generate".into(), "./...".into()],
|
||||
cwd: module_cwd.clone(),
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(comment) @comment.inclusive
|
||||
(comment) @comment
|
||||
[
|
||||
(interpreted_string_literal)
|
||||
(raw_string_literal)
|
||||
|
||||
@@ -7,13 +7,6 @@
|
||||
(#set! tag go-test)
|
||||
)
|
||||
|
||||
; `go:generate` comments
|
||||
(
|
||||
((comment) @_comment @run
|
||||
(#match? @_comment "^//go:generate"))
|
||||
(#set! tag go-generate)
|
||||
)
|
||||
|
||||
; `t.Run`
|
||||
(
|
||||
(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(comment) @comment.inclusive
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(string)
|
||||
|
||||
@@ -12,7 +12,5 @@ brackets = [
|
||||
{ start = "`", end = "`", close = false, newline = false },
|
||||
]
|
||||
|
||||
auto_indent_on_paste = false
|
||||
auto_indent_using_last_non_empty_line = false
|
||||
tab_size = 2
|
||||
prettier_parser_name = "markdown"
|
||||
|
||||
@@ -11,13 +11,11 @@ use language::ToolchainLister;
|
||||
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use pet_core::os_environment::Environment;
|
||||
use pet_core::python_environment::PythonEnvironmentKind;
|
||||
use pet_core::Configuration;
|
||||
use project::lsp_store::language_server_settings;
|
||||
use serde_json::Value;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
@@ -382,13 +380,8 @@ fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl ToolchainLister for PythonToolchainProvider {
|
||||
async fn list(
|
||||
&self,
|
||||
worktree_root: PathBuf,
|
||||
project_env: Option<HashMap<String, String>>,
|
||||
) -> ToolchainList {
|
||||
let env = project_env.unwrap_or_default();
|
||||
let environment = EnvironmentApi::from_env(&env);
|
||||
async fn list(&self, worktree_root: PathBuf) -> ToolchainList {
|
||||
let environment = pet_core::os_environment::EnvironmentApi::new();
|
||||
let locators = pet::locators::create_locators(
|
||||
Arc::new(pet_conda::Conda::from(&environment)),
|
||||
Arc::new(pet_poetry::Poetry::from(&environment)),
|
||||
@@ -434,78 +427,6 @@ impl ToolchainLister for PythonToolchainProvider {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnvironmentApi<'a> {
|
||||
global_search_locations: Arc<Mutex<Vec<PathBuf>>>,
|
||||
project_env: &'a HashMap<String, String>,
|
||||
pet_env: pet_core::os_environment::EnvironmentApi,
|
||||
}
|
||||
|
||||
impl<'a> EnvironmentApi<'a> {
|
||||
pub fn from_env(project_env: &'a HashMap<String, String>) -> Self {
|
||||
let paths = project_env
|
||||
.get("PATH")
|
||||
.map(|p| std::env::split_paths(p).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
EnvironmentApi {
|
||||
global_search_locations: Arc::new(Mutex::new(paths)),
|
||||
project_env,
|
||||
pet_env: pet_core::os_environment::EnvironmentApi::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn user_home(&self) -> Option<PathBuf> {
|
||||
self.project_env
|
||||
.get("HOME")
|
||||
.or_else(|| self.project_env.get("USERPROFILE"))
|
||||
.map(|home| pet_fs::path::norm_case(PathBuf::from(home)))
|
||||
.or_else(|| self.pet_env.get_user_home())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> pet_core::os_environment::Environment for EnvironmentApi<'a> {
|
||||
fn get_user_home(&self) -> Option<PathBuf> {
|
||||
self.user_home()
|
||||
}
|
||||
|
||||
fn get_root(&self) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_env_var(&self, key: String) -> Option<String> {
|
||||
self.project_env
|
||||
.get(&key)
|
||||
.cloned()
|
||||
.or_else(|| self.pet_env.get_env_var(key))
|
||||
}
|
||||
|
||||
fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
|
||||
if self.global_search_locations.lock().unwrap().is_empty() {
|
||||
let mut paths =
|
||||
std::env::split_paths(&self.get_env_var("PATH".to_string()).unwrap_or_default())
|
||||
.collect::<Vec<PathBuf>>();
|
||||
|
||||
log::trace!("Env PATH: {:?}", paths);
|
||||
for p in self.pet_env.get_know_global_search_locations() {
|
||||
if !paths.contains(&p) {
|
||||
paths.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
let mut paths = paths
|
||||
.into_iter()
|
||||
.filter(|p| p.exists())
|
||||
.collect::<Vec<PathBuf>>();
|
||||
|
||||
self.global_search_locations
|
||||
.lock()
|
||||
.unwrap()
|
||||
.append(&mut paths);
|
||||
}
|
||||
self.global_search_locations.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(comment) @comment.inclusive
|
||||
(comment) @comment
|
||||
(string) @string
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
[
|
||||
(line_comment)
|
||||
(block_comment)
|
||||
] @comment.inclusive
|
||||
] @comment
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(comment) @comment.inclusive
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(string)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(comment) @comment.inclusive
|
||||
(comment) @comment
|
||||
(string) @string
|
||||
|
||||
@@ -24,16 +24,11 @@ pub struct VtslsLspAdapter {
|
||||
}
|
||||
|
||||
impl VtslsLspAdapter {
|
||||
const PACKAGE_NAME: &'static str = "@vtsls/language-server";
|
||||
const SERVER_PATH: &'static str = "node_modules/@vtsls/language-server/bin/vtsls.js";
|
||||
|
||||
const TYPESCRIPT_PACKAGE_NAME: &'static str = "typescript";
|
||||
const TYPESCRIPT_TSDK_PATH: &'static str = "node_modules/typescript/lib";
|
||||
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
VtslsLspAdapter { node }
|
||||
}
|
||||
|
||||
async fn tsdk_path(adapter: &Arc<dyn LspAdapterDelegate>) -> &'static str {
|
||||
let is_yarn = adapter
|
||||
.read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js"))
|
||||
@@ -43,7 +38,7 @@ impl VtslsLspAdapter {
|
||||
if is_yarn {
|
||||
".yarn/sdks/typescript/lib"
|
||||
} else {
|
||||
Self::TYPESCRIPT_TSDK_PATH
|
||||
"node_modules/typescript/lib"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +49,6 @@ struct TypeScriptVersions {
|
||||
}
|
||||
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("vtsls");
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for VtslsLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
@@ -96,41 +90,32 @@ impl LspAdapter for VtslsLspAdapter {
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<TypeScriptVersions>().unwrap();
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
let package_name = "typescript";
|
||||
|
||||
let mut packages_to_install = Vec::new();
|
||||
|
||||
if self
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(
|
||||
Self::PACKAGE_NAME,
|
||||
package_name,
|
||||
&server_path,
|
||||
&container_dir,
|
||||
&latest_version.server_version,
|
||||
)
|
||||
.await
|
||||
{
|
||||
packages_to_install.push((Self::PACKAGE_NAME, latest_version.server_version.as_str()));
|
||||
}
|
||||
|
||||
if self
|
||||
.node
|
||||
.should_install_npm_package(
|
||||
Self::TYPESCRIPT_PACKAGE_NAME,
|
||||
&container_dir.join(Self::TYPESCRIPT_TSDK_PATH),
|
||||
&container_dir,
|
||||
&latest_version.typescript_version,
|
||||
)
|
||||
.await
|
||||
{
|
||||
packages_to_install.push((
|
||||
Self::TYPESCRIPT_PACKAGE_NAME,
|
||||
latest_version.typescript_version.as_str(),
|
||||
));
|
||||
}
|
||||
)
|
||||
.await;
|
||||
|
||||
self.node
|
||||
.npm_install_packages(&container_dir, &packages_to_install)
|
||||
.await?;
|
||||
if should_install_language_server {
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[
|
||||
(package_name, latest_version.typescript_version.as_str()),
|
||||
(
|
||||
"@vtsls/language-server",
|
||||
latest_version.server_version.as_str(),
|
||||
),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
|
||||
@@ -10,7 +10,6 @@ brackets = [
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string"] },
|
||||
]
|
||||
|
||||
auto_indent_on_paste = false
|
||||
auto_indent_using_last_non_empty_line = false
|
||||
increase_indent_pattern = ":\\s*[|>]?\\s*$"
|
||||
prettier_parser_name = "yaml"
|
||||
|
||||
@@ -297,7 +297,7 @@ impl MarkdownPreviewView {
|
||||
|
||||
let subscription = cx.subscribe(&editor, |this, editor, event: &EditorEvent, cx| {
|
||||
match event {
|
||||
EditorEvent::Edited { .. } | EditorEvent::DirtyChanged => {
|
||||
EditorEvent::Edited { .. } => {
|
||||
this.parse_markdown_from_active_editor(true, cx);
|
||||
}
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::markdown_elements::{
|
||||
ParsedMarkdownTableRow, ParsedMarkdownText,
|
||||
};
|
||||
use gpui::{
|
||||
div, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element,
|
||||
ElementId, HighlightStyle, Hsla, InteractiveText, IntoElement, Keystroke, Length, Modifiers,
|
||||
div, px, rems, AbsoluteLength, AnyElement, DefiniteLength, Div, Element, ElementId,
|
||||
HighlightStyle, Hsla, InteractiveText, IntoElement, Keystroke, Length, Modifiers,
|
||||
ParentElement, SharedString, Styled, StyledText, TextStyle, WeakView, WindowContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
@@ -16,9 +16,8 @@ use std::{
|
||||
};
|
||||
use theme::{ActiveTheme, SyntaxTheme, ThemeSettings};
|
||||
use ui::{
|
||||
h_flex, relative, v_flex, Checkbox, Clickable, FluentBuilder, IconButton, IconName, IconSize,
|
||||
InteractiveElement, LinkPreview, Selection, StatefulInteractiveElement, StyledExt, Tooltip,
|
||||
VisibleOnHover,
|
||||
h_flex, relative, v_flex, Checkbox, FluentBuilder, InteractiveElement, LinkPreview, Selection,
|
||||
StatefulInteractiveElement, Tooltip,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -370,16 +369,6 @@ fn render_markdown_code_block(
|
||||
StyledText::new(parsed.contents.clone())
|
||||
};
|
||||
|
||||
let copy_block_button = IconButton::new("copy-code", IconName::Copy)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let contents = parsed.contents.clone();
|
||||
move |_, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(contents.to_string()));
|
||||
}
|
||||
})
|
||||
.visible_on_hover("markdown-block");
|
||||
|
||||
cx.with_common_p(div())
|
||||
.font_family(cx.buffer_font_family.clone())
|
||||
.px_3()
|
||||
@@ -387,14 +376,6 @@ fn render_markdown_code_block(
|
||||
.bg(cx.code_block_background_color)
|
||||
.rounded_md()
|
||||
.child(body)
|
||||
.child(
|
||||
div()
|
||||
.h_flex()
|
||||
.absolute()
|
||||
.right_1()
|
||||
.top_1()
|
||||
.child(copy_block_button),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
|
||||
@@ -161,10 +161,6 @@ impl NodeRuntime {
|
||||
directory: &Path,
|
||||
packages: &[(&str, &str)],
|
||||
) -> Result<()> {
|
||||
if packages.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let packages: Vec<_> = packages
|
||||
.iter()
|
||||
.map(|(name, version)| format!("{name}@{version}"))
|
||||
|
||||
@@ -19,7 +19,6 @@ gpui.workspace = true
|
||||
language.workspace = true
|
||||
ordered-float.workspace = true
|
||||
picker.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user