Compare commits
69 Commits
v0.168.2
...
refactor-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a55062dd1 | ||
|
|
1ef638d802 | ||
|
|
fcb03989d2 | ||
|
|
2a9fa0e2dc | ||
|
|
3ae6aa0e4d | ||
|
|
7506c0385b | ||
|
|
570e6c80a8 | ||
|
|
94ee2e1811 | ||
|
|
299ae92ffb | ||
|
|
de08e47e5b | ||
|
|
8151dc7696 | ||
|
|
5f1eee3c66 | ||
|
|
9613084f59 | ||
|
|
72057e5716 | ||
|
|
e25789893d | ||
|
|
b46b261f11 | ||
|
|
71a0eb3b13 | ||
|
|
dd75f85ecf | ||
|
|
b1a6e2427f | ||
|
|
bbe6bf9caf | ||
|
|
53cfb578e8 | ||
|
|
e6fe12d0e1 | ||
|
|
04cf19d49a | ||
|
|
4bd5f0d355 | ||
|
|
fdbf3d0f25 | ||
|
|
a1ef1d3f76 | ||
|
|
04518b11bc | ||
|
|
e4eef725de | ||
|
|
7c7eb98e64 | ||
|
|
7826d79a40 | ||
|
|
bb24c085be | ||
|
|
11ec25aedb | ||
|
|
39af06085a | ||
|
|
a49e394e51 | ||
|
|
e5c3d5d626 | ||
|
|
1aba459a0a | ||
|
|
8c253af451 | ||
|
|
3c207209cb | ||
|
|
663f5244ca | ||
|
|
0599f0fcb6 | ||
|
|
6e2b6258b1 | ||
|
|
82492d74a8 | ||
|
|
16ead69052 | ||
|
|
a53be7b4be | ||
|
|
a79def005d | ||
|
|
2d431e9b51 | ||
|
|
f9df8c1729 | ||
|
|
898064e6b4 | ||
|
|
8cb397cf6c | ||
|
|
374c298bd5 | ||
|
|
0e75ca8603 | ||
|
|
2c2ca9e370 | ||
|
|
3cf5ab16a9 | ||
|
|
53e1ab3c64 | ||
|
|
d0c4c0c240 | ||
|
|
20c0d72fe4 | ||
|
|
d5f058d6e2 | ||
|
|
fbef8c2b3b | ||
|
|
b009e72121 | ||
|
|
f55a3629b0 | ||
|
|
3ac0aef211 | ||
|
|
59b5b9af90 | ||
|
|
b3e36c93b4 | ||
|
|
5447821715 | ||
|
|
665717da9a | ||
|
|
28d1d2d939 | ||
|
|
44af405fb0 | ||
|
|
c11bde7bf4 | ||
|
|
0d423a7b37 |
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -35,7 +35,12 @@ jobs:
|
||||
- name: Check for non-docs changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
|
||||
if [ "${{ github.event_name }}" == "merge_group" ]; then
|
||||
# When we're running in a merge queue, never assume that the changes
|
||||
# are docs-only, as there could be other PRs in the group that
|
||||
# contain non-docs changes.
|
||||
echo "docs_only=false" >> $GITHUB_OUTPUT
|
||||
elif git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
|
||||
echo "docs_only=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "docs_only=true" >> $GITHUB_OUTPUT
|
||||
@@ -175,7 +180,7 @@ jobs:
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
|
||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "buildjet"
|
||||
@@ -216,7 +221,7 @@ jobs:
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
|
||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "buildjet"
|
||||
@@ -247,7 +252,7 @@ jobs:
|
||||
|
||||
- name: Cache dependencies
|
||||
if: needs.check_docs_only.outputs.docs_only == 'false'
|
||||
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
|
||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "github"
|
||||
|
||||
2
.github/workflows/publish_extension_cli.yml
vendored
2
.github/workflows/publish_extension_cli.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
clean: false
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
|
||||
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-provider: "github"
|
||||
|
||||
194
Cargo.lock
generated
194
Cargo.lock
generated
@@ -245,7 +245,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"thiserror 1.0.69",
|
||||
"util",
|
||||
]
|
||||
@@ -435,7 +435,7 @@ dependencies = [
|
||||
"similar",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"telemetry_events",
|
||||
"terminal",
|
||||
"terminal_view",
|
||||
@@ -494,7 +494,6 @@ dependencies = [
|
||||
"project",
|
||||
"proto",
|
||||
"rand 0.8.5",
|
||||
"release_channel",
|
||||
"rope",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2152,7 +2151,7 @@ dependencies = [
|
||||
"cap-primitives",
|
||||
"cap-std",
|
||||
"io-lifetimes 2.0.4",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2180,7 +2179,7 @@ dependencies = [
|
||||
"ipnet",
|
||||
"maybe-owned",
|
||||
"rustix 0.38.42",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
@@ -2275,7 +2274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2716,7 +2715,7 @@ dependencies = [
|
||||
"settings",
|
||||
"sha2",
|
||||
"sqlx",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"subtle",
|
||||
"supermaven_api",
|
||||
"telemetry_events",
|
||||
@@ -2785,7 +2784,8 @@ dependencies = [
|
||||
name = "collections"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rustc-hash 1.1.0",
|
||||
"indexmap",
|
||||
"rustc-hash 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2994,7 +2994,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"task",
|
||||
"theme",
|
||||
"ui",
|
||||
@@ -4145,7 +4145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4812,7 +4812,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4"
|
||||
dependencies = [
|
||||
"io-lifetimes 2.0.4",
|
||||
"rustix 0.38.42",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5102,7 +5102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
@@ -5194,6 +5194,7 @@ dependencies = [
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"workspace",
|
||||
"worktree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5274,7 +5275,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5374,7 +5375,7 @@ dependencies = [
|
||||
"slotmap",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"sum_tree",
|
||||
"taffy",
|
||||
"thiserror 1.0.69",
|
||||
@@ -5434,7 +5435,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -5453,7 +5454,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.2.0",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -6232,27 +6233,16 @@ dependencies = [
|
||||
"heed",
|
||||
"html_to_markdown",
|
||||
"http_client",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.0"
|
||||
@@ -6388,7 +6378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
|
||||
dependencies = [
|
||||
"io-lifetimes 2.0.4",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6775,7 +6765,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"ui",
|
||||
"util",
|
||||
]
|
||||
@@ -6821,7 +6811,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"telemetry_events",
|
||||
"theme",
|
||||
"thiserror 1.0.69",
|
||||
@@ -6999,7 +6989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7741,7 +7731,7 @@ dependencies = [
|
||||
"cfg_aliases 0.1.1",
|
||||
"codespan-reporting",
|
||||
"hexf-parse",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"rustc-hash 1.1.0",
|
||||
"spirv",
|
||||
@@ -8373,7 +8363,7 @@ checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"hashbrown 0.15.2",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@@ -8478,7 +8468,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9328,7 +9318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9511,7 +9501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"quick-xml 0.32.0",
|
||||
"serde",
|
||||
"time",
|
||||
@@ -9829,7 +9819,7 @@ dependencies = [
|
||||
"file_icons",
|
||||
"git",
|
||||
"gpui",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap",
|
||||
"language",
|
||||
"menu",
|
||||
"pretty_assertions",
|
||||
@@ -9933,7 +9923,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
|
||||
dependencies = [
|
||||
"bytes 1.9.0",
|
||||
"heck 0.5.0",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"multimap 0.10.0",
|
||||
"once_cell",
|
||||
@@ -9966,7 +9956,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
@@ -10148,14 +10138,14 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2 0.5.8",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -10892,7 +10882,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"tracing",
|
||||
"util",
|
||||
"zstd",
|
||||
@@ -11048,7 +11038,7 @@ dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.14",
|
||||
"once_cell",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11223,7 +11213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -11296,9 +11286,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b24d72a69e89762982c29af249542b06c59fa131f87cc9d5b94be1f692b427a"
|
||||
checksum = "0dbcf83248860dc632c46c7e81a221e041b50d0006191756cb001d9e8afc60a9"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
@@ -11314,7 +11304,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"strum 0.26.3",
|
||||
"strum",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"tracing",
|
||||
@@ -11324,9 +11314,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm-macros"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0497f4fd82ecb2a222bea5319b9048f8ab58d4e734d095b062987acbcdeecdda"
|
||||
checksum = "49ce6f08134f3681b1ca92185b96fac898f26d9b4f5538d13f7032ef243d14b2"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
@@ -11556,11 +11546,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.133"
|
||||
version = "1.0.134"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
|
||||
dependencies = [
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
@@ -11573,7 +11563,7 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540"
|
||||
dependencies = [
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
@@ -12146,7 +12136,7 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"hashlink 0.9.1",
|
||||
"hex",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
@@ -12370,7 +12360,7 @@ dependencies = [
|
||||
"settings",
|
||||
"simplelog",
|
||||
"story",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"theme",
|
||||
"title_bar",
|
||||
"ui",
|
||||
@@ -12430,26 +12420,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
@@ -12768,7 +12752,7 @@ dependencies = [
|
||||
"fd-lock",
|
||||
"io-lifetimes 2.0.4",
|
||||
"rustix 0.38.42",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
@@ -12900,7 +12884,7 @@ dependencies = [
|
||||
"fastrand 2.3.0",
|
||||
"once_cell",
|
||||
"rustix 0.38.42",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13025,7 +13009,7 @@ dependencies = [
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap",
|
||||
"log",
|
||||
"palette",
|
||||
"parking_lot",
|
||||
@@ -13037,7 +13021,7 @@ dependencies = [
|
||||
"serde_json_lenient",
|
||||
"serde_repr",
|
||||
"settings",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"util",
|
||||
"uuid",
|
||||
]
|
||||
@@ -13060,7 +13044,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"gpui",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap",
|
||||
"log",
|
||||
"palette",
|
||||
"rust-embed",
|
||||
@@ -13069,7 +13053,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"simplelog",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"theme",
|
||||
"vscode_theme",
|
||||
]
|
||||
@@ -13497,7 +13481,7 @@ version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -14000,7 +13984,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smallvec",
|
||||
"story",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"theme",
|
||||
"ui_macros",
|
||||
"windows 0.58.0",
|
||||
@@ -14029,9 +14013,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
@@ -14579,7 +14563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
@@ -14608,7 +14592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
@@ -14621,7 +14605,7 @@ dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bitflags 2.6.0",
|
||||
"hashbrown 0.14.5",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"semver",
|
||||
"serde",
|
||||
]
|
||||
@@ -14651,7 +14635,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"encoding_rs",
|
||||
"hashbrown 0.14.5",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"libc",
|
||||
"libm",
|
||||
"log",
|
||||
@@ -14772,7 +14756,7 @@ dependencies = [
|
||||
"cranelift-bitset",
|
||||
"cranelift-entity",
|
||||
"gimli 0.29.0",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"object",
|
||||
"postcard",
|
||||
@@ -14902,7 +14886,7 @@ checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.4.1",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"wit-parser 0.215.0",
|
||||
]
|
||||
|
||||
@@ -15203,7 +15187,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15652,7 +15636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15699,7 +15683,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck 0.4.1",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
@@ -15727,7 +15711,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.6.0",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -15746,7 +15730,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
@@ -15764,7 +15748,7 @@ checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap 2.7.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
@@ -15820,7 +15804,7 @@ dependencies = [
|
||||
"settings",
|
||||
"smallvec",
|
||||
"sqlez",
|
||||
"strum 0.25.0",
|
||||
"strum",
|
||||
"task",
|
||||
"tempfile",
|
||||
"theme",
|
||||
@@ -16204,7 +16188,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.168.2"
|
||||
version = "0.169.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -16336,24 +16320,9 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_astro"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_clojure"
|
||||
version = "0.0.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_csharp"
|
||||
version = "0.0.2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -16372,13 +16341,6 @@ dependencies = [
|
||||
"zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_elm"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_emmet"
|
||||
version = "0.0.3"
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -149,12 +149,9 @@ members = [
|
||||
# Extensions
|
||||
#
|
||||
|
||||
"extensions/astro",
|
||||
"extensions/clojure",
|
||||
"extensions/csharp",
|
||||
"extensions/deno",
|
||||
"extensions/elixir",
|
||||
"extensions/elm",
|
||||
"extensions/emmet",
|
||||
"extensions/erlang",
|
||||
"extensions/glsl",
|
||||
@@ -392,7 +389,7 @@ hyper = "0.14"
|
||||
http = "1.1"
|
||||
ignore = "0.4.22"
|
||||
image = "0.25.1"
|
||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
indexmap = { version = "2.7.0", features = ["serde"] }
|
||||
indoc = "2"
|
||||
itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
@@ -443,9 +440,10 @@ runtimelib = { version = "0.24.0", default-features = false, features = [
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
rustc-hash = "2.1.0"
|
||||
rustls = "0.21.12"
|
||||
rustls-native-certs = "0.8.0"
|
||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
|
||||
semver = "1.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
@@ -465,7 +463,7 @@ smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "1.2"
|
||||
sqlformat = "0.2"
|
||||
strsim = "0.11"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
strum = { version = "0.26.0", features = ["derive"] }
|
||||
subtle = "2.5.0"
|
||||
sys-locale = "0.3.1"
|
||||
sysinfo = "0.31.0"
|
||||
|
||||
@@ -435,6 +435,13 @@
|
||||
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ApplicationMenu",
|
||||
"bindings": {
|
||||
"left": ["app_menu::NavigateApplicationMenuInDirection", "Left"],
|
||||
"right": ["app_menu::NavigateApplicationMenuInDirection", "Right"]
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
{
|
||||
"context": "Editor",
|
||||
|
||||
@@ -4,55 +4,70 @@
|
||||
// from the command palette.
|
||||
[
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-g": "editor::Cancel",
|
||||
"ctrl-shift-g": "go_to_line::Toggle",
|
||||
//"ctrl-space": "editor::SetMark",
|
||||
"ctrl-x u": "editor::Undo",
|
||||
"ctrl-x ctrl-u": "editor::Redo",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-p": "editor::MoveUp",
|
||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"alt-f": "editor::MoveToNextSubwordEnd",
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-d": "editor::Delete",
|
||||
"alt-d": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-w": "editor::Cut",
|
||||
"alt-w": "editor::Copy",
|
||||
"ctrl-y": "editor::Paste",
|
||||
"ctrl-_": "editor::Undo",
|
||||
"ctrl-v": "editor::MovePageDown",
|
||||
"alt-v": "editor::MovePageUp",
|
||||
"ctrl-x ]": "editor::MoveToEnd",
|
||||
"ctrl-x [": "editor::MoveToBeginning",
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
|
||||
"ctrl-s": "buffer_search::Deploy",
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle",
|
||||
"ctrl-shift-r": "editor::Rename"
|
||||
"ctrl-g": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-x k": "pane::CloseActiveItem",
|
||||
"ctrl-x ctrl-c": "workspace::CloseWindow",
|
||||
"ctrl-x o": "workspace::ActivateNextPane",
|
||||
"ctrl-x b": "tab_switcher::Toggle",
|
||||
"ctrl-x 0": "pane::CloseActiveItem",
|
||||
"ctrl-x 1": "pane::CloseInactiveItems",
|
||||
"ctrl-x 2": "pane::SplitVertical",
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle",
|
||||
"ctrl-x ctrl-s": "workspace::Save",
|
||||
"ctrl-x ctrl-w": "workspace::SaveAs",
|
||||
"ctrl-x s": "workspace::SaveAll",
|
||||
"shift shift": "file_finder::Toggle"
|
||||
"ctrl-g": "editor::Cancel",
|
||||
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
|
||||
"alt-g g": "go_to_line::Toggle", // goto-line
|
||||
"alt-g alt-g": "go_to_line::Toggle", // goto-line
|
||||
//"ctrl-space": "editor::SetMark",
|
||||
"ctrl-f": "editor::MoveRight", // forward-char
|
||||
"ctrl-b": "editor::MoveLeft", // backward-char
|
||||
"ctrl-n": "editor::MoveDown", // next-line
|
||||
"ctrl-p": "editor::MoveUp", // previous-line
|
||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
|
||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
"alt-l": "editor::ConvertToLowerCase", // downcase-word
|
||||
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
|
||||
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-x ctrl-;": "editor::ToggleComments",
|
||||
"alt-.": "editor::GoToDefinition", // xref-find-definitions
|
||||
"alt-,": "pane::GoBack", // xref-pop-marker-stack
|
||||
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
|
||||
"ctrl-d": "editor::Delete", // delete-char
|
||||
"alt-d": "editor::DeleteToNextWordEnd", // kill-word
|
||||
"ctrl-k": "editor::KillRingCut", // kill-line
|
||||
"ctrl-w": "editor::Cut", // kill-region
|
||||
"alt-w": "editor::Copy", // kill-ring-save
|
||||
"ctrl-y": "editor::KillRingYank", // yank
|
||||
"ctrl-_": "editor::Undo", // undo
|
||||
"ctrl-/": "editor::Undo", // undo
|
||||
"ctrl-x u": "editor::Undo", // undo
|
||||
"ctrl-v": "editor::MovePageDown", // scroll-up
|
||||
"alt-v": "editor::MovePageUp", // scroll-down
|
||||
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
|
||||
"ctrl-x ]": "editor::MoveToEnd", // end-of-buffer
|
||||
"alt-<": "editor::MoveToBeginning", // beginning-of-buffer
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace && !Terminal",
|
||||
"bindings": {
|
||||
"ctrl-x ctrl-c": "workspace::CloseWindow", // kill-emacs
|
||||
"ctrl-x o": "workspace::ActivateNextPane", // other-window
|
||||
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
|
||||
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
|
||||
"ctrl-x 1": "pane::CloseInactiveItems", // delete-other-windows
|
||||
"ctrl-x 2": "pane::SplitDown", // split-window-below
|
||||
"ctrl-x 3": "pane::SplitRight", // split-window-right
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
|
||||
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
|
||||
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
|
||||
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,55 +4,70 @@
|
||||
// from the command palette.
|
||||
[
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-g": "editor::Cancel",
|
||||
"ctrl-shift-g": "go_to_line::Toggle",
|
||||
//"ctrl-space": "editor::SetMark",
|
||||
"ctrl-x u": "editor::Undo",
|
||||
"ctrl-x ctrl-u": "editor::Redo",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-p": "editor::MoveUp",
|
||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"alt-f": "editor::MoveToNextSubwordEnd",
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-d": "editor::Delete",
|
||||
"alt-d": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-w": "editor::Cut",
|
||||
"alt-w": "editor::Copy",
|
||||
"ctrl-y": "editor::Paste",
|
||||
"ctrl-_": "editor::Undo",
|
||||
"ctrl-v": "editor::MovePageDown",
|
||||
"alt-v": "editor::MovePageUp",
|
||||
"ctrl-x ]": "editor::MoveToEnd",
|
||||
"ctrl-x [": "editor::MoveToBeginning",
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
|
||||
"ctrl-s": "buffer_search::Deploy",
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle",
|
||||
"ctrl-shift-r": "editor::Rename"
|
||||
"ctrl-g": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-x k": "pane::CloseActiveItem",
|
||||
"ctrl-x ctrl-c": "workspace::CloseWindow",
|
||||
"ctrl-x o": "workspace::ActivateNextPane",
|
||||
"ctrl-x b": "tab_switcher::Toggle",
|
||||
"ctrl-x 0": "pane::CloseActiveItem",
|
||||
"ctrl-x 1": "pane::CloseInactiveItems",
|
||||
"ctrl-x 2": "pane::SplitVertical",
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle",
|
||||
"ctrl-x ctrl-s": "workspace::Save",
|
||||
"ctrl-x ctrl-w": "workspace::SaveAs",
|
||||
"ctrl-x s": "workspace::SaveAll",
|
||||
"shift shift": "file_finder::Toggle"
|
||||
"ctrl-g": "editor::Cancel",
|
||||
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
|
||||
"alt-g g": "go_to_line::Toggle", // goto-line
|
||||
"alt-g alt-g": "go_to_line::Toggle", // goto-line
|
||||
//"ctrl-space": "editor::SetMark",
|
||||
"ctrl-f": "editor::MoveRight", // forward-char
|
||||
"ctrl-b": "editor::MoveLeft", // backward-char
|
||||
"ctrl-n": "editor::MoveDown", // next-line
|
||||
"ctrl-p": "editor::MoveUp", // previous-line
|
||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
|
||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
|
||||
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
|
||||
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
|
||||
"alt-u": "editor::ConvertToUpperCase", // upcase-word
|
||||
"alt-l": "editor::ConvertToLowerCase", // downcase-word
|
||||
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
|
||||
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-x ctrl-;": "editor::ToggleComments",
|
||||
"alt-.": "editor::GoToDefinition", // xref-find-definitions
|
||||
"alt-,": "pane::GoBack", // xref-pop-marker-stack
|
||||
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
|
||||
"ctrl-d": "editor::Delete", // delete-char
|
||||
"alt-d": "editor::DeleteToNextWordEnd", // kill-word
|
||||
"ctrl-k": "editor::KillRingCut", // kill-line
|
||||
"ctrl-w": "editor::Cut", // kill-region
|
||||
"alt-w": "editor::Copy", // kill-ring-save
|
||||
"ctrl-y": "editor::KillRingYank", // yank
|
||||
"ctrl-_": "editor::Undo", // undo
|
||||
"ctrl-/": "editor::Undo", // undo
|
||||
"ctrl-x u": "editor::Undo", // undo
|
||||
"ctrl-v": "editor::MovePageDown", // scroll-up
|
||||
"alt-v": "editor::MovePageUp", // scroll-down
|
||||
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
|
||||
"ctrl-x ]": "editor::MoveToEnd", // end-of-buffer
|
||||
"alt-<": "editor::MoveToBeginning", // beginning-of-buffer
|
||||
"alt->": "editor::MoveToEnd", // end-of-buffer
|
||||
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
|
||||
"ctrl-s": "buffer_search::Deploy", // isearch-forward
|
||||
"alt-^": "editor::JoinLines" // join-line
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace && !Terminal",
|
||||
"bindings": {
|
||||
"ctrl-x ctrl-c": "workspace::CloseWindow", // kill-emacs
|
||||
"ctrl-x o": "workspace::ActivateNextPane", // other-window
|
||||
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
|
||||
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
|
||||
"ctrl-x 1": "pane::CloseInactiveItems", // delete-other-windows
|
||||
"ctrl-x 2": "pane::SplitDown", // split-window-below
|
||||
"ctrl-x 3": "pane::SplitRight", // split-window-right
|
||||
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
|
||||
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
|
||||
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
|
||||
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -256,8 +256,13 @@
|
||||
"search_results": true,
|
||||
// Whether to show selected symbol occurrences in the scrollbar.
|
||||
"selected_symbol": true,
|
||||
// Whether to show diagnostic indicators in the scrollbar.
|
||||
"diagnostics": true,
|
||||
// Which diagnostic indicators to show in the scrollbar:
|
||||
// - "none" or false: do not show diagnostics
|
||||
// - "error": show only errors
|
||||
// - "warning": show only errors and warnings
|
||||
// - "information": show only errors, warnings, and information
|
||||
// - "all" or true: show all diagnostics
|
||||
"diagnostics": "all",
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
"axes": {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
@@ -741,7 +746,7 @@
|
||||
// Delay is restarted with every cursor movement.
|
||||
// "delay_ms": 600
|
||||
//
|
||||
// Whether or not do display the git commit summary on the same line.
|
||||
// Whether or not to display the git commit summary on the same line.
|
||||
// "show_commit_summary": false
|
||||
//
|
||||
// The minimum column number to show the inline blame information at
|
||||
|
||||
@@ -30,6 +30,8 @@ pub enum Model {
|
||||
#[default]
|
||||
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
|
||||
Claude3_5Sonnet,
|
||||
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
|
||||
Claude3_5Haiku,
|
||||
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
||||
Claude3Opus,
|
||||
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
|
||||
@@ -48,6 +50,8 @@ pub enum Model {
|
||||
cache_configuration: Option<AnthropicModelCacheConfiguration>,
|
||||
max_output_tokens: Option<u32>,
|
||||
default_temperature: Option<f32>,
|
||||
#[serde(default)]
|
||||
extra_beta_headers: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -55,6 +59,8 @@ impl Model {
|
||||
pub fn from_id(id: &str) -> Result<Self> {
|
||||
if id.starts_with("claude-3-5-sonnet") {
|
||||
Ok(Self::Claude3_5Sonnet)
|
||||
} else if id.starts_with("claude-3-5-haiku") {
|
||||
Ok(Self::Claude3_5Haiku)
|
||||
} else if id.starts_with("claude-3-opus") {
|
||||
Ok(Self::Claude3Opus)
|
||||
} else if id.starts_with("claude-3-sonnet") {
|
||||
@@ -69,6 +75,7 @@ impl Model {
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
||||
Model::Claude3Opus => "claude-3-opus-latest",
|
||||
Model::Claude3Sonnet => "claude-3-sonnet-latest",
|
||||
Model::Claude3Haiku => "claude-3-haiku-latest",
|
||||
@@ -79,6 +86,7 @@ impl Model {
|
||||
pub fn display_name(&self) -> &str {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
|
||||
Self::Claude3Opus => "Claude 3 Opus",
|
||||
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
||||
Self::Claude3Haiku => "Claude 3 Haiku",
|
||||
@@ -90,11 +98,13 @@ impl Model {
|
||||
|
||||
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet | Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
|
||||
min_total_token: 2_048,
|
||||
should_speculate: true,
|
||||
max_cache_anchors: 4,
|
||||
}),
|
||||
Self::Claude3_5Sonnet | Self::Claude3_5Haiku | Self::Claude3Haiku => {
|
||||
Some(AnthropicModelCacheConfiguration {
|
||||
min_total_token: 2_048,
|
||||
should_speculate: true,
|
||||
max_cache_anchors: 4,
|
||||
})
|
||||
}
|
||||
Self::Custom {
|
||||
cache_configuration,
|
||||
..
|
||||
@@ -106,6 +116,7 @@ impl Model {
|
||||
pub fn max_token_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
| Self::Claude3Haiku => 200_000,
|
||||
@@ -116,7 +127,7 @@ impl Model {
|
||||
pub fn max_output_tokens(&self) -> u32 {
|
||||
match self {
|
||||
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
|
||||
Self::Claude3_5Sonnet => 8_192,
|
||||
Self::Claude3_5Sonnet | Self::Claude3_5Haiku => 8_192,
|
||||
Self::Custom {
|
||||
max_output_tokens, ..
|
||||
} => max_output_tokens.unwrap_or(4_096),
|
||||
@@ -126,6 +137,7 @@ impl Model {
|
||||
pub fn default_temperature(&self) -> f32 {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
| Self::Claude3Haiku => 1.0,
|
||||
@@ -136,6 +148,24 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn beta_headers(&self) -> String {
|
||||
let mut headers = vec!["prompt-caching-2024-07-31".to_string()];
|
||||
|
||||
if let Self::Custom {
|
||||
extra_beta_headers, ..
|
||||
} = self
|
||||
{
|
||||
headers.extend(
|
||||
extra_beta_headers
|
||||
.iter()
|
||||
.filter(|header| !header.trim().is_empty())
|
||||
.cloned(),
|
||||
);
|
||||
}
|
||||
|
||||
headers.join(",")
|
||||
}
|
||||
|
||||
pub fn tool_model_id(&self) -> &str {
|
||||
if let Self::Custom {
|
||||
tool_override: Some(tool_override),
|
||||
@@ -156,11 +186,12 @@ pub async fn complete(
|
||||
request: Request,
|
||||
) -> Result<Response, AnthropicError> {
|
||||
let uri = format!("{api_url}/v1/messages");
|
||||
let model = Model::from_id(&request.model)?;
|
||||
let request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Anthropic-Version", "2023-06-01")
|
||||
.header("Anthropic-Beta", "prompt-caching-2024-07-31")
|
||||
.header("Anthropic-Beta", model.beta_headers())
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
@@ -271,14 +302,12 @@ pub async fn stream_completion_with_rate_limit_info(
|
||||
stream: true,
|
||||
};
|
||||
let uri = format!("{api_url}/v1/messages");
|
||||
let model = Model::from_id(&request.base.model)?;
|
||||
let request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Anthropic-Version", "2023-06-01")
|
||||
.header(
|
||||
"Anthropic-Beta",
|
||||
"tools-2024-04-04,prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
|
||||
)
|
||||
.header("Anthropic-Beta", model.beta_headers())
|
||||
.header("X-Api-Key", api_key)
|
||||
.header("Content-Type", "application/json");
|
||||
let serialized_request =
|
||||
|
||||
@@ -37,7 +37,7 @@ pub use prompts::PromptBuilder;
|
||||
use prompts::PromptLoadingParams;
|
||||
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use slash_command::search_command::SearchSlashCommandFeatureFlag;
|
||||
use slash_command::{
|
||||
auto_command, cargo_workspace_command, default_command, delta_command, diagnostics_command,
|
||||
@@ -199,16 +199,6 @@ pub fn init(
|
||||
AssistantSettings::register(cx);
|
||||
SlashCommandSettings::register(cx);
|
||||
|
||||
// TODO: remove this when 0.148.0 is released.
|
||||
if AssistantSettings::get_global(cx).using_outdated_settings_version {
|
||||
update_settings_file::<AssistantSettings>(fs.clone(), cx, {
|
||||
let fs = fs.clone();
|
||||
|content, cx| {
|
||||
content.update_file(fs, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cx.spawn(|mut cx| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
|
||||
@@ -122,7 +122,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
terminal_panel.asssistant_enabled(settings.enabled, cx);
|
||||
terminal_panel.set_assistant_enabled(settings.enabled, cx);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
@@ -3,18 +3,12 @@ use std::sync::Arc;
|
||||
use ::open_ai::Model as OpenAiModel;
|
||||
use anthropic::Model as AnthropicModel;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, Pixels};
|
||||
use language_model::{CloudModel, LanguageModel};
|
||||
use language_models::{
|
||||
provider::open_ai, AllLanguageModelSettings, AnthropicSettingsContent,
|
||||
AnthropicSettingsContentV1, OllamaSettingsContent, OpenAiSettingsContent,
|
||||
OpenAiSettingsContentV1, VersionedAnthropicSettingsContent, VersionedOpenAiSettingsContent,
|
||||
};
|
||||
use ollama::Model as OllamaModel;
|
||||
use schemars::{schema::Schema, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -106,96 +100,6 @@ impl AssistantSettingsContent {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_file(&mut self, fs: Arc<dyn Fs>, cx: &AppContext) {
|
||||
if let AssistantSettingsContent::Versioned(settings) = self {
|
||||
if let VersionedAssistantSettingsContent::V1(settings) = settings {
|
||||
if let Some(provider) = settings.provider.clone() {
|
||||
match provider {
|
||||
AssistantProviderContentV1::Anthropic { api_url, .. } => {
|
||||
update_settings_file::<AllLanguageModelSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |content, _| {
|
||||
if content.anthropic.is_none() {
|
||||
content.anthropic =
|
||||
Some(AnthropicSettingsContent::Versioned(
|
||||
VersionedAnthropicSettingsContent::V1(
|
||||
AnthropicSettingsContentV1 {
|
||||
api_url,
|
||||
available_models: None,
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
AssistantProviderContentV1::Ollama { api_url, .. } => {
|
||||
update_settings_file::<AllLanguageModelSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |content, _| {
|
||||
if content.ollama.is_none() {
|
||||
content.ollama = Some(OllamaSettingsContent {
|
||||
api_url,
|
||||
available_models: None,
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
AssistantProviderContentV1::OpenAi {
|
||||
api_url,
|
||||
available_models,
|
||||
..
|
||||
} => update_settings_file::<AllLanguageModelSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |content, _| {
|
||||
if content.openai.is_none() {
|
||||
let available_models = available_models.map(|models| {
|
||||
models
|
||||
.into_iter()
|
||||
.filter_map(|model| match model {
|
||||
OpenAiModel::Custom {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
max_completion_tokens: None,
|
||||
} => Some(open_ai::AvailableModel {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
max_completion_tokens: None,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
content.openai = Some(OpenAiSettingsContent::Versioned(
|
||||
VersionedOpenAiSettingsContent::V1(
|
||||
OpenAiSettingsContentV1 {
|
||||
api_url,
|
||||
available_models,
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*self = AssistantSettingsContent::Versioned(VersionedAssistantSettingsContent::V2(
|
||||
self.upgrade(),
|
||||
));
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> AssistantSettingsContentV2 {
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||
@@ -534,6 +438,7 @@ fn merge<T>(target: &mut T, value: Option<T>) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fs::Fs;
|
||||
use gpui::{ReadGlobal, TestAppContext};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -133,7 +133,7 @@ impl InlineAssistant {
|
||||
};
|
||||
let enabled = AssistantSettings::get_global(cx).enabled;
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.asssistant_enabled(enabled, cx)
|
||||
terminal_panel.set_assistant_enabled(enabled, cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
@@ -228,19 +228,20 @@ impl InlineAssistant {
|
||||
let newest_selection = newest_selection.unwrap();
|
||||
|
||||
let mut codegen_ranges = Vec::new();
|
||||
for (excerpt_id, buffer, buffer_range) in
|
||||
snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
|
||||
for (excerpt, buffer_range) in
|
||||
snapshot.disjoint_ranges_to_buffer_ranges(selections.iter().map(|selection| {
|
||||
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
|
||||
}))
|
||||
{
|
||||
let buffer = excerpt.buffer();
|
||||
let start = Anchor {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
text_anchor: buffer.anchor_before(buffer_range.start),
|
||||
};
|
||||
let end = Anchor {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
text_anchor: buffer.anchor_after(buffer_range.end),
|
||||
};
|
||||
codegen_ranges.push(start..end);
|
||||
@@ -797,10 +798,12 @@ impl InlineAssistant {
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
let language_name = assist.editor.upgrade().and_then(|editor| {
|
||||
let multibuffer = editor.read(cx).buffer().read(cx);
|
||||
let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx);
|
||||
let multibuffer_snapshot = multibuffer.snapshot(cx);
|
||||
let mut ranges =
|
||||
multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone());
|
||||
ranges
|
||||
.first()
|
||||
.and_then(|(buffer, _, _)| buffer.read(cx).language())
|
||||
.next()
|
||||
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||
.map(|language| language.name())
|
||||
});
|
||||
report_assistant_event(
|
||||
@@ -2615,26 +2618,30 @@ impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
||||
|
||||
impl CodegenAlternative {
|
||||
pub fn new(
|
||||
buffer: Model<MultiBuffer>,
|
||||
multi_buffer: Model<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
active: bool,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
let snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||
|
||||
let (old_buffer, _, _) = buffer
|
||||
.read(cx)
|
||||
.range_to_buffer_ranges(range.clone(), cx)
|
||||
.pop()
|
||||
// TODO: Could be made more efficient by using a reverse iterator.
|
||||
let (old_excerpt, _) = snapshot
|
||||
.range_to_buffer_ranges(range.clone())
|
||||
.last()
|
||||
.unwrap();
|
||||
let old_buffer = cx.new_model(|cx| {
|
||||
let old_buffer = old_buffer.read(cx);
|
||||
let text = old_buffer.as_rope().clone();
|
||||
let line_ending = old_buffer.line_ending();
|
||||
let language = old_buffer.language().cloned();
|
||||
let language_registry = old_buffer.language_registry();
|
||||
let text = old_excerpt.buffer().as_rope().clone();
|
||||
let line_ending = old_excerpt.buffer().line_ending();
|
||||
let language = old_excerpt.buffer().language().cloned();
|
||||
let language_registry = multi_buffer
|
||||
.read(cx)
|
||||
.buffer(old_excerpt.buffer_id())
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.language_registry();
|
||||
|
||||
let mut buffer = Buffer::local_normalized(text, line_ending, cx);
|
||||
buffer.set_language(language, cx);
|
||||
@@ -2645,7 +2652,7 @@ impl CodegenAlternative {
|
||||
});
|
||||
|
||||
Self {
|
||||
buffer: buffer.clone(),
|
||||
buffer: multi_buffer.clone(),
|
||||
old_buffer,
|
||||
edit_position: None,
|
||||
message_id: None,
|
||||
@@ -2656,7 +2663,7 @@ impl CodegenAlternative {
|
||||
generation: Task::ready(()),
|
||||
diff: Diff::default(),
|
||||
telemetry,
|
||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||
_subscription: cx.subscribe(&multi_buffer, Self::handle_buffer_event),
|
||||
builder,
|
||||
active,
|
||||
edits: Vec::new(),
|
||||
@@ -2867,10 +2874,11 @@ impl CodegenAlternative {
|
||||
let telemetry = self.telemetry.clone();
|
||||
let language_name = {
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let mut ranges = snapshot.range_to_buffer_ranges(self.range.clone());
|
||||
ranges
|
||||
.first()
|
||||
.and_then(|(buffer, _, _)| buffer.read(cx).language())
|
||||
.next()
|
||||
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||
.map(|language| language.name())
|
||||
};
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
proto.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -5,7 +5,7 @@ use collections::HashMap;
|
||||
use gpui::{
|
||||
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
|
||||
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
|
||||
TextStyleRefinement, View, WeakView,
|
||||
TextStyleRefinement, UnderlineStyle, View, WeakView,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::Role;
|
||||
@@ -22,7 +22,7 @@ pub struct ActiveThread {
|
||||
workspace: WeakView<Workspace>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
thread: Model<Thread>,
|
||||
pub(crate) thread: Model<Thread>,
|
||||
messages: Vec<MessageId>,
|
||||
list_state: ListState,
|
||||
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
|
||||
@@ -140,6 +140,15 @@ impl ActiveThread {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.01)),
|
||||
..Default::default()
|
||||
},
|
||||
link: TextStyleRefinement {
|
||||
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||
underline: Some(UnderlineStyle {
|
||||
color: Some(colors.text_accent.opacity(0.5)),
|
||||
thickness: px(1.),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -274,12 +283,11 @@ impl ActiveThread {
|
||||
.when_some(context, |parent, context| {
|
||||
if !context.is_empty() {
|
||||
parent.child(
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_1()
|
||||
.px_1p5()
|
||||
.pb_1p5()
|
||||
.children(context.iter().map(|c| ContextPill::new(c.clone()))),
|
||||
h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
|
||||
context
|
||||
.iter()
|
||||
.map(|context| ContextPill::new(context.clone())),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
parent
|
||||
|
||||
@@ -19,7 +19,7 @@ use workspace::Workspace;
|
||||
use crate::active_thread::ActiveThread;
|
||||
use crate::assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use crate::message_editor::MessageEditor;
|
||||
use crate::thread::{ThreadError, ThreadId};
|
||||
use crate::thread::{Thread, ThreadError, ThreadId};
|
||||
use crate::thread_history::{PastThread, ThreadHistory};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{NewThread, OpenHistory, ToggleFocus};
|
||||
@@ -206,6 +206,10 @@ impl AssistantPanel {
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
pub(crate) fn active_thread(&self, cx: &AppContext) -> Model<Thread> {
|
||||
self.thread.read(cx).thread.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
|
||||
self.thread_store
|
||||
.update(cx, |this, cx| this.delete_thread(thread_id, cx));
|
||||
|
||||
@@ -257,17 +257,21 @@ impl CodegenAlternative {
|
||||
) -> Self {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
|
||||
let (old_buffer, _, _) = buffer
|
||||
.read(cx)
|
||||
.range_to_buffer_ranges(range.clone(), cx)
|
||||
.pop()
|
||||
// TODO: Could be more efficient by using a reverse iterator.
|
||||
let (old_excerpt, _) = snapshot
|
||||
.range_to_buffer_ranges(range.clone())
|
||||
.last()
|
||||
.unwrap();
|
||||
let old_buffer = cx.new_model(|cx| {
|
||||
let old_buffer = old_buffer.read(cx);
|
||||
let text = old_buffer.as_rope().clone();
|
||||
let line_ending = old_buffer.line_ending();
|
||||
let language = old_buffer.language().cloned();
|
||||
let language_registry = old_buffer.language_registry();
|
||||
let text = old_excerpt.buffer().as_rope().clone();
|
||||
let line_ending = old_excerpt.buffer().line_ending();
|
||||
let language = old_excerpt.buffer().language().cloned();
|
||||
let language_registry = buffer
|
||||
.read(cx)
|
||||
.buffer(old_excerpt.buffer_id())
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.language_registry();
|
||||
|
||||
let mut buffer = Buffer::local_normalized(text, line_ending, cx);
|
||||
buffer.set_language(language, cx);
|
||||
@@ -471,10 +475,11 @@ impl CodegenAlternative {
|
||||
let telemetry = self.telemetry.clone();
|
||||
let language_name = {
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let mut ranges = snapshot.range_to_buffer_ranges(self.range.clone());
|
||||
ranges
|
||||
.first()
|
||||
.and_then(|(buffer, _, _)| buffer.read(cx).language())
|
||||
.next()
|
||||
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||
.map(|language| language.name())
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use gpui::SharedString;
|
||||
use language_model::{LanguageModelRequestMessage, MessageContent};
|
||||
use project::ProjectEntryId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::post_inc;
|
||||
|
||||
use crate::thread::ThreadId;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct ContextId(pub(crate) usize);
|
||||
|
||||
@@ -21,12 +24,12 @@ pub struct Context {
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ContextKind {
|
||||
File,
|
||||
File(ProjectEntryId),
|
||||
Directory,
|
||||
FetchedUrl,
|
||||
Thread,
|
||||
Thread(ThreadId),
|
||||
}
|
||||
|
||||
pub fn attach_context_to_message(
|
||||
@@ -40,7 +43,7 @@ pub fn attach_context_to_message(
|
||||
|
||||
for context in context.into_iter() {
|
||||
match context.kind {
|
||||
ContextKind::File => {
|
||||
ContextKind::File(_) => {
|
||||
file_context.push_str(&context.text);
|
||||
file_context.push('\n');
|
||||
}
|
||||
@@ -54,7 +57,7 @@ pub fn attach_context_to_message(
|
||||
fetch_context.push_str(&context.text);
|
||||
fetch_context.push('\n');
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
ContextKind::Thread(_) => {
|
||||
thread_context.push_str(&context.name);
|
||||
thread_context.push('\n');
|
||||
thread_context.push_str(&context.text);
|
||||
|
||||
@@ -10,12 +10,10 @@ use gpui::{
|
||||
WeakModel, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use release_channel::ReleaseChannel;
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::directory_context_picker::DirectoryContextPicker;
|
||||
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
||||
use crate::context_picker::file_context_picker::FileContextPicker;
|
||||
@@ -54,29 +52,24 @@ impl ContextPicker {
|
||||
let mut entries = Vec::new();
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "File".into(),
|
||||
kind: ContextKind::File,
|
||||
kind: ContextPickerEntryKind::File,
|
||||
icon: IconName::File,
|
||||
});
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
// The directory context picker isn't fully implemented yet, so limit it
|
||||
// to development builds.
|
||||
if release_channel == ReleaseChannel::Dev {
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Folder".into(),
|
||||
kind: ContextKind::Directory,
|
||||
icon: IconName::Folder,
|
||||
});
|
||||
}
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Folder".into(),
|
||||
kind: ContextPickerEntryKind::Directory,
|
||||
icon: IconName::Folder,
|
||||
});
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Fetch".into(),
|
||||
kind: ContextKind::FetchedUrl,
|
||||
kind: ContextPickerEntryKind::FetchedUrl,
|
||||
icon: IconName::Globe,
|
||||
});
|
||||
|
||||
if thread_store.is_some() {
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Thread".into(),
|
||||
kind: ContextKind::Thread,
|
||||
kind: ContextPickerEntryKind::Thread,
|
||||
icon: IconName::MessageCircle,
|
||||
});
|
||||
}
|
||||
@@ -140,10 +133,18 @@ impl Render for ContextPicker {
|
||||
#[derive(Clone)]
|
||||
struct ContextPickerEntry {
|
||||
name: SharedString,
|
||||
kind: ContextKind,
|
||||
kind: ContextPickerEntryKind,
|
||||
icon: IconName,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ContextPickerEntryKind {
|
||||
File,
|
||||
Directory,
|
||||
FetchedUrl,
|
||||
Thread,
|
||||
}
|
||||
|
||||
pub(crate) struct ContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
@@ -183,7 +184,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
self.context_picker
|
||||
.update(cx, |this, cx| {
|
||||
match entry.kind {
|
||||
ContextKind::File => {
|
||||
ContextPickerEntryKind::File => {
|
||||
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
||||
FileContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
@@ -194,7 +195,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Directory => {
|
||||
ContextPickerEntryKind::Directory => {
|
||||
this.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
|
||||
DirectoryContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
@@ -205,7 +206,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
ContextPickerEntryKind::FetchedUrl => {
|
||||
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
||||
FetchContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
@@ -216,7 +217,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
ContextPickerEntryKind::Thread => {
|
||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||
this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
|
||||
ThreadContextPicker::new(
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
// TODO: Remove this when we finish the implementation.
|
||||
#![allow(unused)]
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, WorktreeId};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, Worktree, WorktreeId};
|
||||
use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::file_context_picker::codeblock_fence_for_path;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
@@ -193,14 +192,61 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let worktree = project.update(&mut cx, |project, cx| {
|
||||
project
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.ok_or_else(|| anyhow!("no worktree found for {worktree_id:?}"))
|
||||
})??;
|
||||
|
||||
let files = worktree.update(&mut cx, |worktree, _cx| {
|
||||
collect_files_in_path(worktree, &path)
|
||||
})?;
|
||||
|
||||
let open_buffer_tasks = project.update(&mut cx, |project, cx| {
|
||||
files
|
||||
.into_iter()
|
||||
.map(|file_path| {
|
||||
project.open_buffer(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: file_path.clone(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let open_all_buffers_tasks = cx.background_executor().spawn(async move {
|
||||
let mut buffers = Vec::with_capacity(open_buffer_tasks.len());
|
||||
|
||||
for open_buffer_task in open_buffer_tasks {
|
||||
let buffer = open_buffer_task.await?;
|
||||
|
||||
buffers.push(buffer);
|
||||
}
|
||||
|
||||
anyhow::Ok(buffers)
|
||||
});
|
||||
|
||||
let buffers = open_all_buffers_tasks.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let mut text = String::new();
|
||||
|
||||
// TODO: Add the files from the selected directory.
|
||||
for buffer in buffers {
|
||||
text.push_str(&codeblock_fence_for_path(Some(&path), None));
|
||||
text.push_str(&buffer.read(cx).text());
|
||||
if !text.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
text.push_str("```\n");
|
||||
}
|
||||
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
.update(cx, |context_store, _cx| {
|
||||
context_store.insert_context(
|
||||
ContextKind::Directory,
|
||||
path.to_string_lossy().to_string(),
|
||||
@@ -247,3 +293,17 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in worktree.child_entries(path) {
|
||||
if entry.is_dir() {
|
||||
files.extend(collect_files_in_path(worktree, &entry.path));
|
||||
} else if entry.is_file() {
|
||||
files.push(entry.path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::sync::Arc;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, WorktreeId};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||
use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
@@ -207,11 +207,20 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let Some(open_buffer_task) = project
|
||||
let Some((entry_id, open_buffer_task)) = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, path.clone()), cx)
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: path.clone(),
|
||||
};
|
||||
|
||||
let entry_id = project.entry_for_path(&project_path, cx)?.id;
|
||||
let task = project.open_buffer(project_path, cx);
|
||||
|
||||
Some((entry_id, task))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
return anyhow::Ok(());
|
||||
};
|
||||
@@ -232,7 +241,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
text.push_str("```\n");
|
||||
|
||||
context_store.insert_context(
|
||||
ContextKind::File,
|
||||
ContextKind::File(entry_id),
|
||||
path.to_string_lossy().to_string(),
|
||||
text,
|
||||
);
|
||||
@@ -307,7 +316,10 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<RangeInclusive<u32>>) -> String {
|
||||
pub(crate) fn codeblock_fence_for_path(
|
||||
path: Option<&Path>,
|
||||
row_range: Option<RangeInclusive<u32>>,
|
||||
) -> String {
|
||||
let mut text = String::new();
|
||||
write!(text, "```").unwrap();
|
||||
|
||||
|
||||
@@ -169,25 +169,11 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
|
||||
self.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
let text = thread.update(cx, |thread, _cx| {
|
||||
let mut text = String::new();
|
||||
|
||||
for message in thread.messages() {
|
||||
text.push_str(match message.role {
|
||||
language_model::Role::User => "User:",
|
||||
language_model::Role::Assistant => "Assistant:",
|
||||
language_model::Role::System => "System:",
|
||||
});
|
||||
text.push('\n');
|
||||
|
||||
text.push_str(&message.text);
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
text
|
||||
});
|
||||
|
||||
context_store.insert_context(ContextKind::Thread, entry.summary.clone(), text);
|
||||
context_store.insert_context(
|
||||
ContextKind::Thread(thread.read(cx).id().clone()),
|
||||
entry.summary.clone(),
|
||||
thread.read(cx).text(),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use gpui::SharedString;
|
||||
use project::ProjectEntryId;
|
||||
|
||||
use crate::context::{Context, ContextId, ContextKind};
|
||||
use crate::{
|
||||
context::{Context, ContextId, ContextKind},
|
||||
thread::ThreadId,
|
||||
};
|
||||
|
||||
pub struct ContextStore {
|
||||
context: Vec<Context>,
|
||||
@@ -44,4 +48,18 @@ impl ContextStore {
|
||||
pub fn remove_context(&mut self, id: &ContextId) {
|
||||
self.context.retain(|context| context.id != *id);
|
||||
}
|
||||
|
||||
pub fn contains_project_entry(&self, entry_id: ProjectEntryId) -> bool {
|
||||
self.context.iter().any(|probe| match probe.kind {
|
||||
ContextKind::File(probe_entry_id) => probe_entry_id == entry_id,
|
||||
ContextKind::Directory | ContextKind::FetchedUrl | ContextKind::Thread(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn contains_thread(&self, thread_id: &ThreadId) -> bool {
|
||||
self.context.iter().any(|probe| match probe.kind {
|
||||
ContextKind::Thread(ref probe_thread_id) => probe_thread_id == thread_id,
|
||||
ContextKind::File(_) | ContextKind::Directory | ContextKind::FetchedUrl => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{FocusHandle, Model, View, WeakModel, WeakView};
|
||||
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
use editor::Editor;
|
||||
use gpui::{AppContext, FocusHandle, Model, View, WeakModel, WeakView};
|
||||
use language::Buffer;
|
||||
use project::ProjectEntryId;
|
||||
use ui::{prelude::*, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::thread::{Thread, ThreadId};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::ui::ContextPill;
|
||||
use crate::ToggleContextPicker;
|
||||
use settings::Settings;
|
||||
use crate::{AssistantPanel, ToggleContextPicker};
|
||||
|
||||
pub struct ContextStrip {
|
||||
context_store: Model<ContextStore>,
|
||||
context_picker: View<ContextPicker>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
focus_handle: FocusHandle,
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
workspace: WeakView<Workspace>,
|
||||
}
|
||||
|
||||
impl ContextStrip {
|
||||
@@ -25,6 +31,7 @@ impl ContextStrip {
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
focus_handle: FocusHandle,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -40,16 +47,76 @@ impl ContextStrip {
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
focus_handle,
|
||||
suggest_context_kind,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
|
||||
fn suggested_context(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
|
||||
match self.suggest_context_kind {
|
||||
SuggestContextKind::File => self.suggested_file(cx),
|
||||
SuggestContextKind::Thread => self.suggested_thread(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn suggested_file(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let active_item = workspace.read(cx).active_item(cx)?;
|
||||
let entry_id = *active_item.project_entry_ids(cx).first()?;
|
||||
|
||||
if self.context_store.read(cx).contains_project_entry(entry_id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
||||
let active_buffer = editor.buffer().read(cx).as_singleton()?;
|
||||
|
||||
let file = active_buffer.read(cx).file()?;
|
||||
let title = file.path().to_string_lossy().into_owned().into();
|
||||
|
||||
Some(SuggestedContext::File {
|
||||
entry_id,
|
||||
title,
|
||||
buffer: active_buffer.downgrade(),
|
||||
})
|
||||
}
|
||||
|
||||
fn suggested_thread(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let active_thread = workspace
|
||||
.read(cx)
|
||||
.panel::<AssistantPanel>(cx)?
|
||||
.read(cx)
|
||||
.active_thread(cx);
|
||||
let weak_active_thread = active_thread.downgrade();
|
||||
|
||||
let active_thread = active_thread.read(cx);
|
||||
|
||||
if self
|
||||
.context_store
|
||||
.read(cx)
|
||||
.contains_thread(active_thread.id())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SuggestedContext::Thread {
|
||||
id: active_thread.id().clone(),
|
||||
title: active_thread.summary().unwrap_or("Active Thread".into()),
|
||||
thread: weak_active_thread,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContextStrip {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let context = self.context_store.read(cx).context().clone();
|
||||
let context_store = self.context_store.read(cx);
|
||||
let context = context_store.context().clone();
|
||||
let context_picker = self.context_picker.clone();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
let suggested_context = self.suggested_context(cx);
|
||||
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_1()
|
||||
@@ -60,13 +127,17 @@ impl Render for ContextStrip {
|
||||
IconButton::new("add-context", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ui::ButtonStyle::Filled)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Add Context",
|
||||
&ToggleContextPicker,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Add Context",
|
||||
&ToggleContextPicker,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
@@ -77,26 +148,22 @@ impl Render for ContextStrip {
|
||||
})
|
||||
.with_handle(self.context_picker_menu_handle.clone()),
|
||||
)
|
||||
.when(context.is_empty(), {
|
||||
.when(context.is_empty() && suggested_context.is_none(), {
|
||||
|parent| {
|
||||
parent.child(
|
||||
h_flex()
|
||||
.id("no-content-info")
|
||||
.ml_1p5()
|
||||
.gap_2()
|
||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||
.text_size(TextSize::Small.rems(cx))
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child("Add Context")
|
||||
.children(
|
||||
ui::KeyBinding::for_action_in(
|
||||
&ToggleContextPicker,
|
||||
&self.focus_handle,
|
||||
cx,
|
||||
)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
.child(
|
||||
Label::new("Add Context")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.opacity(0.5),
|
||||
.opacity(0.5)
|
||||
.children(
|
||||
KeyBinding::for_action_in(&ToggleContextPicker, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -112,6 +179,30 @@ impl Render for ContextStrip {
|
||||
}))
|
||||
})
|
||||
}))
|
||||
.when_some(suggested_context, |el, suggested| {
|
||||
el.child(
|
||||
Button::new("add-suggested-context", suggested.title().clone())
|
||||
.on_click({
|
||||
let context_store = self.context_store.clone();
|
||||
|
||||
cx.listener(move |_this, _event, cx| {
|
||||
context_store.update(cx, |context_store, cx| {
|
||||
suggested.accept(context_store, cx);
|
||||
});
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.label_size(LabelSize::Small)
|
||||
.style(ButtonStyle::Filled)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(!context.is_empty(), {
|
||||
move |parent| {
|
||||
parent.child(
|
||||
@@ -130,3 +221,63 @@ impl Render for ContextStrip {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SuggestContextKind {
|
||||
File,
|
||||
Thread,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SuggestedContext {
|
||||
File {
|
||||
entry_id: ProjectEntryId,
|
||||
title: SharedString,
|
||||
buffer: WeakModel<Buffer>,
|
||||
},
|
||||
Thread {
|
||||
id: ThreadId,
|
||||
title: SharedString,
|
||||
thread: WeakModel<Thread>,
|
||||
},
|
||||
}
|
||||
|
||||
impl SuggestedContext {
|
||||
pub fn title(&self) -> &SharedString {
|
||||
match self {
|
||||
Self::File { title, .. } => title,
|
||||
Self::Thread { title, .. } => title,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accept(&self, context_store: &mut ContextStore, cx: &mut AppContext) {
|
||||
match self {
|
||||
Self::File {
|
||||
entry_id,
|
||||
title,
|
||||
buffer,
|
||||
} => {
|
||||
let Some(buffer) = buffer.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let text = buffer.read(cx).text();
|
||||
|
||||
context_store.insert_context(
|
||||
ContextKind::File(*entry_id),
|
||||
title.clone(),
|
||||
text.clone(),
|
||||
);
|
||||
}
|
||||
Self::Thread { id, title, thread } => {
|
||||
let Some(thread) = thread.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
context_store.insert_context(
|
||||
ContextKind::Thread(id.clone()),
|
||||
title.clone(),
|
||||
thread.read(cx).text(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ impl InlineAssistant {
|
||||
};
|
||||
let enabled = AssistantSettings::get_global(cx).enabled;
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.asssistant_enabled(enabled, cx)
|
||||
terminal_panel.set_assistant_enabled(enabled, cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
@@ -294,19 +294,20 @@ impl InlineAssistant {
|
||||
let newest_selection = newest_selection.unwrap();
|
||||
|
||||
let mut codegen_ranges = Vec::new();
|
||||
for (excerpt_id, buffer, buffer_range) in
|
||||
snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
|
||||
for (excerpt, buffer_range) in
|
||||
snapshot.disjoint_ranges_to_buffer_ranges(selections.iter().map(|selection| {
|
||||
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
|
||||
}))
|
||||
{
|
||||
let buffer = excerpt.buffer();
|
||||
let start = Anchor {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
text_anchor: buffer.anchor_before(buffer_range.start),
|
||||
};
|
||||
let end = Anchor {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
text_anchor: buffer.anchor_after(buffer_range.end),
|
||||
};
|
||||
codegen_ranges.push(start..end);
|
||||
@@ -871,10 +872,11 @@ impl InlineAssistant {
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
let language_name = assist.editor.upgrade().and_then(|editor| {
|
||||
let multibuffer = editor.read(cx).buffer().read(cx);
|
||||
let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let mut ranges = snapshot.range_to_buffer_ranges(assist.range.clone());
|
||||
ranges
|
||||
.first()
|
||||
.and_then(|(buffer, _, _)| buffer.read(cx).language())
|
||||
.next()
|
||||
.and_then(|(excerpt, _)| excerpt.buffer().language())
|
||||
.map(|language| language.name())
|
||||
});
|
||||
report_assistant_event(
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::assistant_model_selector::AssistantModelSelector;
|
||||
use crate::buffer_codegen::BufferCodegen;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::context_strip::{ContextStrip, SuggestContextKind};
|
||||
use crate::terminal_codegen::TerminalCodegen;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
||||
@@ -793,6 +793,7 @@ impl PromptEditor<BufferCodegen> {
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
@@ -932,6 +933,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||
thread_store.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -20,7 +20,7 @@ use workspace::Workspace;
|
||||
use crate::assistant_model_selector::AssistantModelSelector;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::context_strip::{ContextStrip, SuggestContextKind};
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{Chat, ToggleContextPicker, ToggleModelSelector};
|
||||
@@ -87,6 +87,7 @@ impl MessageEditor {
|
||||
Some(thread_store.clone()),
|
||||
editor.focus_handle(cx),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::File,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -164,6 +164,27 @@ impl Thread {
|
||||
id
|
||||
}
|
||||
|
||||
/// Returns the representation of this [`Thread`] in a textual form.
|
||||
///
|
||||
/// This is the representation we use when attaching a thread as context to another thread.
|
||||
pub fn text(&self) -> String {
|
||||
let mut text = String::new();
|
||||
|
||||
for message in &self.messages {
|
||||
text.push_str(match message.role {
|
||||
language_model::Role::User => "User:",
|
||||
language_model::Role::Assistant => "Assistant:",
|
||||
language_model::Role::System => "System:",
|
||||
});
|
||||
text.push('\n');
|
||||
|
||||
text.push_str(&message.text);
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
text
|
||||
}
|
||||
|
||||
pub fn to_completion_request(
|
||||
&self,
|
||||
_request_kind: RequestKind,
|
||||
|
||||
@@ -238,5 +238,46 @@ impl ThreadStore {
|
||||
Async programming in Rust provides a powerful way to write concurrent code that's both safe and efficient. It's particularly useful for servers, network programming, and any application that deals with many concurrent operations.".unindent(), cx);
|
||||
thread
|
||||
}));
|
||||
|
||||
self.threads.push(cx.new_model(|cx| {
|
||||
let mut thread = Thread::new(self.tools.clone(), cx);
|
||||
thread.set_summary("Rust code with long lines", cx);
|
||||
thread.insert_user_message("Could you write me some Rust code with long lines?", Vec::new(), cx);
|
||||
thread.insert_message(Role::Assistant, r#"Here's some Rust code with some intentionally long lines:
|
||||
```rust
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
fn main() {
|
||||
let very_long_vector = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25];
|
||||
|
||||
let complicated_hashmap: HashMap<String, Vec<(i32, f64, String)>> = [("key1".to_string(), vec![(1, 1.1, "value1".to_string()), (2, 2.2, "value2".to_string())]), ("key2".to_string(), vec![(3, 3.3, "value3".to_string()), (4, 4.4, "value4".to_string())])].iter().cloned().collect();
|
||||
|
||||
let nested_structure = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
let long_closure = |x: i32, y: i32, z: i32| -> i32 { let result = x * y + z; println!("The result of the long closure calculation is: {}", result); result };
|
||||
|
||||
let thread_handles: Vec<_> = (0..10).map(|i| {
|
||||
let nested_structure_clone = Arc::clone(&nested_structure);
|
||||
thread::spawn(move || {
|
||||
let mut lock = nested_structure_clone.lock().unwrap();
|
||||
lock.entry(format!("thread_{}", i)).or_insert_with(|| HashSet::new()).insert(i * i);
|
||||
})
|
||||
}).collect();
|
||||
|
||||
for handle in thread_handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
println!("The final state of the nested structure is: {:?}", nested_structure.lock().unwrap());
|
||||
|
||||
let complex_expression = very_long_vector.iter().filter(|&&x| x % 2 == 0).map(|&x| x * x).fold(0, |acc, x| acc + x) + long_closure(5, 10, 15);
|
||||
|
||||
println!("The result of the complex expression is: {}", complex_expression);
|
||||
}
|
||||
```"#.unindent(), cx);
|
||||
thread
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ impl RenderOnce for ContextPill {
|
||||
px(4.)
|
||||
};
|
||||
let icon = match self.context.kind {
|
||||
ContextKind::File => IconName::File,
|
||||
ContextKind::File(_) => IconName::File,
|
||||
ContextKind::Directory => IconName::Folder,
|
||||
ContextKind::FetchedUrl => IconName::Globe,
|
||||
ContextKind::Thread => IconName::MessageCircle,
|
||||
ContextKind::Thread(_) => IconName::MessageCircle,
|
||||
};
|
||||
|
||||
h_flex()
|
||||
|
||||
@@ -2,4 +2,10 @@ fn main() {
|
||||
if std::env::var("ZED_UPDATE_EXPLANATION").is_ok() {
|
||||
println!(r#"cargo:rustc-cfg=feature="no-bundled-uninstall""#);
|
||||
}
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7");
|
||||
// Weakly link ScreenCaptureKit to ensure can be used on macOS 10.15+.
|
||||
println!("cargo:rustc-link-arg=-Wl,-weak_framework,ScreenCaptureKit");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
|
||||
Ok(existing_path) => PathWithPosition::from_path(existing_path),
|
||||
Err(_) => {
|
||||
let path = PathWithPosition::parse_str(argument_str);
|
||||
let curdir = env::current_dir().context("reteiving current directory")?;
|
||||
let curdir = env::current_dir().context("retrieving current directory")?;
|
||||
path.map_path(|path| match fs::canonicalize(&path) {
|
||||
Ok(path) => Ok(path),
|
||||
Err(e) => {
|
||||
|
||||
@@ -106,6 +106,22 @@ CREATE TABLE "worktree_repositories" (
|
||||
CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id");
|
||||
CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id");
|
||||
|
||||
CREATE TABLE "worktree_repository_statuses" (
|
||||
"project_id" INTEGER NOT NULL,
|
||||
"worktree_id" INT8 NOT NULL,
|
||||
"work_directory_id" INT8 NOT NULL,
|
||||
"repo_path" VARCHAR NOT NULL,
|
||||
"status" INT8 NOT NULL,
|
||||
"scan_id" INT8 NOT NULL,
|
||||
"is_deleted" BOOL NOT NULL,
|
||||
PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path),
|
||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX "index_wt_repos_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id");
|
||||
CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
|
||||
CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id_and_wd_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
|
||||
|
||||
CREATE TABLE "worktree_settings_files" (
|
||||
"project_id" INTEGER NOT NULL,
|
||||
"worktree_id" INTEGER NOT NULL,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context as _;
|
||||
|
||||
use util::ResultExt;
|
||||
|
||||
use super::*;
|
||||
@@ -274,8 +275,8 @@ impl Database {
|
||||
mtime_nanos: ActiveValue::set(mtime.nanos as i32),
|
||||
canonical_path: ActiveValue::set(entry.canonical_path.clone()),
|
||||
is_ignored: ActiveValue::set(entry.is_ignored),
|
||||
git_status: ActiveValue::set(None),
|
||||
is_external: ActiveValue::set(entry.is_external),
|
||||
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
|
||||
is_deleted: ActiveValue::set(false),
|
||||
scan_id: ActiveValue::set(update.scan_id as i64),
|
||||
is_fifo: ActiveValue::set(entry.is_fifo),
|
||||
@@ -295,7 +296,6 @@ impl Database {
|
||||
worktree_entry::Column::MtimeNanos,
|
||||
worktree_entry::Column::CanonicalPath,
|
||||
worktree_entry::Column::IsIgnored,
|
||||
worktree_entry::Column::GitStatus,
|
||||
worktree_entry::Column::ScanId,
|
||||
])
|
||||
.to_owned(),
|
||||
@@ -349,6 +349,79 @@ impl Database {
|
||||
)
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
let has_any_statuses = update
|
||||
.updated_repositories
|
||||
.iter()
|
||||
.any(|repository| !repository.updated_statuses.is_empty());
|
||||
|
||||
if has_any_statuses {
|
||||
worktree_repository_statuses::Entity::insert_many(
|
||||
update.updated_repositories.iter().flat_map(
|
||||
|repository: &proto::RepositoryEntry| {
|
||||
repository.updated_statuses.iter().map(|status_entry| {
|
||||
worktree_repository_statuses::ActiveModel {
|
||||
project_id: ActiveValue::set(project_id),
|
||||
worktree_id: ActiveValue::set(worktree_id),
|
||||
work_directory_id: ActiveValue::set(
|
||||
repository.work_directory_id as i64,
|
||||
),
|
||||
scan_id: ActiveValue::set(update.scan_id as i64),
|
||||
is_deleted: ActiveValue::set(false),
|
||||
repo_path: ActiveValue::set(status_entry.repo_path.clone()),
|
||||
status: ActiveValue::set(status_entry.status as i64),
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
.on_conflict(
|
||||
OnConflict::columns([
|
||||
worktree_repository_statuses::Column::ProjectId,
|
||||
worktree_repository_statuses::Column::WorktreeId,
|
||||
worktree_repository_statuses::Column::WorkDirectoryId,
|
||||
worktree_repository_statuses::Column::RepoPath,
|
||||
])
|
||||
.update_columns([
|
||||
worktree_repository_statuses::Column::ScanId,
|
||||
worktree_repository_statuses::Column::Status,
|
||||
])
|
||||
.to_owned(),
|
||||
)
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let has_any_removed_statuses = update
|
||||
.updated_repositories
|
||||
.iter()
|
||||
.any(|repository| !repository.removed_statuses.is_empty());
|
||||
|
||||
if has_any_removed_statuses {
|
||||
worktree_repository_statuses::Entity::update_many()
|
||||
.filter(
|
||||
worktree_repository_statuses::Column::ProjectId
|
||||
.eq(project_id)
|
||||
.and(
|
||||
worktree_repository_statuses::Column::WorktreeId
|
||||
.eq(worktree_id),
|
||||
)
|
||||
.and(
|
||||
worktree_repository_statuses::Column::RepoPath.is_in(
|
||||
update.updated_repositories.iter().flat_map(|repository| {
|
||||
repository.removed_statuses.iter()
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.set(worktree_repository_statuses::ActiveModel {
|
||||
is_deleted: ActiveValue::Set(true),
|
||||
scan_id: ActiveValue::Set(update.scan_id as i64),
|
||||
..Default::default()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if !update.removed_repositories.is_empty() {
|
||||
@@ -643,7 +716,6 @@ impl Database {
|
||||
canonical_path: db_entry.canonical_path,
|
||||
is_ignored: db_entry.is_ignored,
|
||||
is_external: db_entry.is_external,
|
||||
git_status: db_entry.git_status.map(|status| status as i32),
|
||||
// This is only used in the summarization backlog, so if it's None,
|
||||
// that just means we won't be able to detect when to resummarize
|
||||
// based on total number of backlogged bytes - instead, we'd go
|
||||
@@ -657,23 +729,49 @@ impl Database {
|
||||
|
||||
// Populate repository entries.
|
||||
{
|
||||
let mut db_repository_entries = worktree_repository::Entity::find()
|
||||
let db_repository_entries = worktree_repository::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(worktree_repository::Column::ProjectId.eq(project.id))
|
||||
.add(worktree_repository::Column::IsDeleted.eq(false)),
|
||||
)
|
||||
.stream(tx)
|
||||
.all(tx)
|
||||
.await?;
|
||||
while let Some(db_repository_entry) = db_repository_entries.next().await {
|
||||
let db_repository_entry = db_repository_entry?;
|
||||
for db_repository_entry in db_repository_entries {
|
||||
if let Some(worktree) = worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
|
||||
{
|
||||
let mut repository_statuses = worktree_repository_statuses::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(worktree_repository_statuses::Column::ProjectId.eq(project.id))
|
||||
.add(
|
||||
worktree_repository_statuses::Column::WorktreeId
|
||||
.eq(worktree.id),
|
||||
)
|
||||
.add(
|
||||
worktree_repository_statuses::Column::WorkDirectoryId
|
||||
.eq(db_repository_entry.work_directory_id),
|
||||
)
|
||||
.add(worktree_repository_statuses::Column::IsDeleted.eq(false)),
|
||||
)
|
||||
.stream(tx)
|
||||
.await?;
|
||||
let mut updated_statuses = Vec::new();
|
||||
while let Some(status_entry) = repository_statuses.next().await {
|
||||
let status_entry: worktree_repository_statuses::Model = status_entry?;
|
||||
updated_statuses.push(proto::StatusEntry {
|
||||
repo_path: status_entry.repo_path,
|
||||
status: status_entry.status as i32,
|
||||
});
|
||||
}
|
||||
|
||||
worktree.repository_entries.insert(
|
||||
db_repository_entry.work_directory_id as u64,
|
||||
proto::RepositoryEntry {
|
||||
work_directory_id: db_repository_entry.work_directory_id as u64,
|
||||
branch: db_repository_entry.branch,
|
||||
updated_statuses,
|
||||
removed_statuses: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -662,7 +662,6 @@ impl Database {
|
||||
canonical_path: db_entry.canonical_path,
|
||||
is_ignored: db_entry.is_ignored,
|
||||
is_external: db_entry.is_external,
|
||||
git_status: db_entry.git_status.map(|status| status as i32),
|
||||
// This is only used in the summarization backlog, so if it's None,
|
||||
// that just means we won't be able to detect when to resummarize
|
||||
// based on total number of backlogged bytes - instead, we'd go
|
||||
@@ -682,26 +681,69 @@ impl Database {
|
||||
worktree_repository::Column::IsDeleted.eq(false)
|
||||
};
|
||||
|
||||
let mut db_repositories = worktree_repository::Entity::find()
|
||||
let db_repositories = worktree_repository::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(worktree_repository::Column::ProjectId.eq(project.id))
|
||||
.add(worktree_repository::Column::WorktreeId.eq(worktree.id))
|
||||
.add(repository_entry_filter),
|
||||
)
|
||||
.stream(tx)
|
||||
.all(tx)
|
||||
.await?;
|
||||
|
||||
while let Some(db_repository) = db_repositories.next().await {
|
||||
let db_repository = db_repository?;
|
||||
for db_repository in db_repositories.into_iter() {
|
||||
if db_repository.is_deleted {
|
||||
worktree
|
||||
.removed_repositories
|
||||
.push(db_repository.work_directory_id as u64);
|
||||
} else {
|
||||
let status_entry_filter = if let Some(rejoined_worktree) = rejoined_worktree
|
||||
{
|
||||
worktree_repository_statuses::Column::ScanId
|
||||
.gt(rejoined_worktree.scan_id)
|
||||
} else {
|
||||
worktree_repository_statuses::Column::IsDeleted.eq(false)
|
||||
};
|
||||
|
||||
let mut db_statuses = worktree_repository_statuses::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(
|
||||
worktree_repository_statuses::Column::ProjectId
|
||||
.eq(project.id),
|
||||
)
|
||||
.add(
|
||||
worktree_repository_statuses::Column::WorktreeId
|
||||
.eq(worktree.id),
|
||||
)
|
||||
.add(
|
||||
worktree_repository_statuses::Column::WorkDirectoryId
|
||||
.eq(db_repository.work_directory_id),
|
||||
)
|
||||
.add(status_entry_filter),
|
||||
)
|
||||
.stream(tx)
|
||||
.await?;
|
||||
let mut removed_statuses = Vec::new();
|
||||
let mut updated_statuses = Vec::new();
|
||||
|
||||
while let Some(db_status) = db_statuses.next().await {
|
||||
let db_status: worktree_repository_statuses::Model = db_status?;
|
||||
if db_status.is_deleted {
|
||||
removed_statuses.push(db_status.repo_path);
|
||||
} else {
|
||||
updated_statuses.push(proto::StatusEntry {
|
||||
repo_path: db_status.repo_path,
|
||||
status: db_status.status as i32,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
worktree.updated_repositories.push(proto::RepositoryEntry {
|
||||
work_directory_id: db_repository.work_directory_id as u64,
|
||||
branch: db_repository.branch,
|
||||
updated_statuses,
|
||||
removed_statuses,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2925,8 +2925,6 @@ async fn test_git_status_sync(
|
||||
assert_eq!(snapshot.status_for_file(file), status);
|
||||
}
|
||||
|
||||
// Smoke test status reading
|
||||
|
||||
project_local.read_with(cx_a, |project, cx| {
|
||||
assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx);
|
||||
assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx);
|
||||
@@ -4067,7 +4065,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
DiagnosticEntry {
|
||||
range: Point::new(0, 4)..Point::new(0, 7),
|
||||
diagnostic: Diagnostic {
|
||||
group_id: 2,
|
||||
group_id: 3,
|
||||
message: "message 1".to_string(),
|
||||
severity: lsp::DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
@@ -4077,7 +4075,7 @@ async fn test_collaborating_with_diagnostics(
|
||||
DiagnosticEntry {
|
||||
range: Point::new(0, 10)..Point::new(0, 13),
|
||||
diagnostic: Diagnostic {
|
||||
group_id: 3,
|
||||
group_id: 4,
|
||||
severity: lsp::DiagnosticSeverity::WARNING,
|
||||
message: "message 2".to_string(),
|
||||
is_primary: true,
|
||||
@@ -6669,6 +6667,10 @@ async fn test_remote_git_branches(
|
||||
client_a
|
||||
.fs()
|
||||
.insert_branches(Path::new("/project/.git"), &branches);
|
||||
let branches_set = branches
|
||||
.into_iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
@@ -6690,10 +6692,10 @@ async fn test_remote_git_branches(
|
||||
|
||||
let branches_b = branches_b
|
||||
.into_iter()
|
||||
.map(|branch| branch.name)
|
||||
.collect::<Vec<_>>();
|
||||
.map(|branch| branch.name.to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(&branches_b, &branches);
|
||||
assert_eq!(branches_b, branches_set);
|
||||
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
|
||||
@@ -229,6 +229,10 @@ async fn test_ssh_collaboration_git_branches(
|
||||
.await;
|
||||
|
||||
let branches = ["main", "dev", "feature-1"];
|
||||
let branches_set = branches
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<HashSet<_>>();
|
||||
remote_fs.insert_branches(Path::new("/project/.git"), &branches);
|
||||
|
||||
// User A connects to the remote project via SSH.
|
||||
@@ -281,10 +285,10 @@ async fn test_ssh_collaboration_git_branches(
|
||||
|
||||
let branches_b = branches_b
|
||||
.into_iter()
|
||||
.map(|branch| branch.name)
|
||||
.collect::<Vec<_>>();
|
||||
.map(|branch| branch.name.to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(&branches_b, &branches);
|
||||
assert_eq!(&branches_b, &branches_set);
|
||||
|
||||
cx_b.update(|cx| {
|
||||
project_b.update(cx, |project, cx| {
|
||||
|
||||
@@ -1135,15 +1135,20 @@ impl Panel for ChatPanel {
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
|
||||
match ChatPanelSettings::get_global(cx).button {
|
||||
ChatPanelButton::Never => None,
|
||||
ChatPanelButton::Always => Some(ui::IconName::MessageBubbles),
|
||||
ChatPanelButton::WhenInCall => ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.room()
|
||||
.filter(|room| room.read(cx).contains_guests())
|
||||
.map(|_| ui::IconName::MessageBubbles),
|
||||
}
|
||||
let show_icon = match ChatPanelSettings::get_global(cx).button {
|
||||
ChatPanelButton::Never => false,
|
||||
ChatPanelButton::Always => true,
|
||||
ChatPanelButton::WhenInCall => {
|
||||
let is_in_call = ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.room()
|
||||
.map_or(false, |room| room.read(cx).contains_guests());
|
||||
|
||||
self.active || is_in_call
|
||||
}
|
||||
};
|
||||
|
||||
show_icon.then(|| ui::IconName::MessageBubbles)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
|
||||
@@ -16,4 +16,5 @@ doctest = false
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
rustc-hash = "1.1"
|
||||
indexmap.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
|
||||
@@ -4,12 +4,24 @@ pub type HashMap<K, V> = FxHashMap<K, V>;
|
||||
#[cfg(feature = "test-support")]
|
||||
pub type HashSet<T> = FxHashSet<T>;
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub type IndexMap<K, V> = indexmap::IndexMap<K, V, rustc_hash::FxBuildHasher>;
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub type IndexSet<T> = indexmap::IndexSet<T, rustc_hash::FxBuildHasher>;
|
||||
|
||||
#[cfg(not(feature = "test-support"))]
|
||||
pub type HashMap<K, V> = std::collections::HashMap<K, V>;
|
||||
|
||||
#[cfg(not(feature = "test-support"))]
|
||||
pub type HashSet<T> = std::collections::HashSet<T>;
|
||||
|
||||
#[cfg(not(feature = "test-support"))]
|
||||
pub type IndexMap<K, V> = indexmap::IndexMap<K, V>;
|
||||
|
||||
#[cfg(not(feature = "test-support"))]
|
||||
pub type IndexSet<T> = indexmap::IndexSet<T>;
|
||||
|
||||
pub use rustc_hash::FxHasher;
|
||||
pub use rustc_hash::{FxHashMap, FxHashSet};
|
||||
pub use std::collections::*;
|
||||
|
||||
@@ -82,7 +82,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -95,7 +95,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -106,7 +106,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -117,7 +117,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::INFORMATION,
|
||||
is_primary: false,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -128,7 +128,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -139,7 +139,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -241,7 +241,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
}],
|
||||
@@ -348,7 +348,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -359,7 +359,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
is_primary: true,
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -775,7 +775,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
assert!(view.focus_handle.is_focused(cx));
|
||||
});
|
||||
|
||||
let mut next_group_id = 0;
|
||||
let mut next_group_id = 1;
|
||||
let mut next_filename = 0;
|
||||
let mut language_server_ids = vec![LanguageServerId(0)];
|
||||
let mut updated_language_servers = HashSet::default();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use editor::Editor;
|
||||
use editor::{AnchorRangeExt, Editor};
|
||||
use gpui::{
|
||||
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
|
||||
ViewContext, WeakView,
|
||||
};
|
||||
use language::Diagnostic;
|
||||
use language::{Diagnostic, DiagnosticEntry};
|
||||
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
||||
use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
|
||||
|
||||
@@ -148,7 +148,11 @@ impl DiagnosticIndicator {
|
||||
(buffer, cursor_position)
|
||||
});
|
||||
let new_diagnostic = buffer
|
||||
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
|
||||
.diagnostics_in_range(cursor_position..cursor_position, false)
|
||||
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
|
||||
diagnostic,
|
||||
range: range.to_offset(&buffer),
|
||||
})
|
||||
.filter(|entry| !entry.range.is_empty())
|
||||
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
|
||||
.map(|entry| entry.diagnostic);
|
||||
|
||||
@@ -2748,7 +2748,7 @@ mod tests {
|
||||
.iter()
|
||||
.filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
|
||||
.count(),
|
||||
"Should have one folded block, prodicing a header of the second buffer"
|
||||
"Should have one folded block, producing a header of the second buffer"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.text(),
|
||||
@@ -2994,7 +2994,7 @@ mod tests {
|
||||
}
|
||||
})
|
||||
.count(),
|
||||
"Should have one folded block, prodicing a header of the second buffer"
|
||||
"Should have one folded block, producing a header of the second buffer"
|
||||
);
|
||||
assert_eq!(blocks_snapshot.text(), "\n");
|
||||
assert_eq!(
|
||||
|
||||
@@ -99,8 +99,8 @@ use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
|
||||
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
||||
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
|
||||
Point, Selection, SelectionGoal, TransactionId,
|
||||
CursorShape, Diagnostic, DiagnosticEntry, Documentation, IndentKind, IndentSize, Language,
|
||||
OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
|
||||
};
|
||||
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
||||
use linked_editing_ranges::refresh_linked_ranges;
|
||||
@@ -109,7 +109,7 @@ pub use proposed_changes_editor::{
|
||||
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
|
||||
};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use std::iter::Peekable;
|
||||
use std::iter::{self, Peekable};
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
|
||||
@@ -3549,13 +3549,11 @@ impl Editor {
|
||||
Bias::Left,
|
||||
);
|
||||
let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
|
||||
multi_buffer
|
||||
.range_to_buffer_ranges(multi_buffer_visible_range, cx)
|
||||
.into_iter()
|
||||
.filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
|
||||
.filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let buffer_file = project::File::from_dyn(buffer.file())?;
|
||||
multi_buffer_snapshot
|
||||
.disjoint_ranges_to_buffer_ranges(iter::once(multi_buffer_visible_range))
|
||||
.filter(|(_, excerpt_visible_range)| !excerpt_visible_range.is_empty())
|
||||
.filter_map(|(excerpt, excerpt_visible_range)| {
|
||||
let buffer_file = project::File::from_dyn(excerpt.buffer().file())?;
|
||||
let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
|
||||
let worktree_entry = buffer_worktree
|
||||
.read(cx)
|
||||
@@ -3564,17 +3562,17 @@ impl Editor {
|
||||
return None;
|
||||
}
|
||||
|
||||
let language = buffer.language()?;
|
||||
let language = excerpt.buffer().language()?;
|
||||
if let Some(restrict_to_languages) = restrict_to_languages {
|
||||
if !restrict_to_languages.contains(language) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some((
|
||||
excerpt_id,
|
||||
excerpt.id(),
|
||||
(
|
||||
buffer_handle,
|
||||
buffer.version().clone(),
|
||||
multi_buffer.buffer(excerpt.buffer_id()).unwrap(),
|
||||
excerpt.buffer().version().clone(),
|
||||
excerpt_visible_range,
|
||||
),
|
||||
))
|
||||
@@ -9149,11 +9147,12 @@ impl Editor {
|
||||
// If there is an active Diagnostic Popover jump to its diagnostic instead.
|
||||
if direction == Direction::Next {
|
||||
if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
|
||||
let (group_id, jump_to) = popover.activation_info();
|
||||
if self.activate_diagnostics(group_id, cx) {
|
||||
self.activate_diagnostics(popover.group_id(), cx);
|
||||
if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
|
||||
let primary_range_start = active_diagnostics.primary_range.start;
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
let mut new_selection = s.newest_anchor().clone();
|
||||
new_selection.collapse_to(jump_to, SelectionGoal::None);
|
||||
new_selection.collapse_to(primary_range_start, SelectionGoal::None);
|
||||
s.select_anchors(vec![new_selection.clone()]);
|
||||
});
|
||||
}
|
||||
@@ -9179,10 +9178,23 @@ impl Editor {
|
||||
let snapshot = self.snapshot(cx);
|
||||
loop {
|
||||
let diagnostics = if direction == Direction::Prev {
|
||||
buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
|
||||
buffer
|
||||
.diagnostics_in_range(0..search_start, true)
|
||||
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
|
||||
diagnostic,
|
||||
range: range.to_offset(&buffer),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
|
||||
buffer
|
||||
.diagnostics_in_range(search_start..buffer.len(), false)
|
||||
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
|
||||
diagnostic,
|
||||
range: range.to_offset(&buffer),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
.into_iter()
|
||||
.filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
|
||||
let group = diagnostics
|
||||
// relies on diagnostics_in_range to return diagnostics with the same starting range to
|
||||
@@ -9212,7 +9224,8 @@ impl Editor {
|
||||
});
|
||||
|
||||
if let Some((primary_range, group_id)) = group {
|
||||
if self.activate_diagnostics(group_id, cx) {
|
||||
self.activate_diagnostics(group_id, cx);
|
||||
if self.active_diagnostics.is_some() {
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select(vec![Selection {
|
||||
id: selection.id,
|
||||
@@ -10289,11 +10302,12 @@ impl Editor {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
|
||||
let is_valid = buffer
|
||||
.diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
|
||||
.diagnostics_in_range(active_diagnostics.primary_range.clone(), false)
|
||||
.any(|entry| {
|
||||
let range = entry.range.to_offset(&buffer);
|
||||
entry.diagnostic.is_primary
|
||||
&& !entry.range.is_empty()
|
||||
&& entry.range.start == primary_range_start
|
||||
&& !range.is_empty()
|
||||
&& range.start == primary_range_start
|
||||
&& entry.diagnostic.message == active_diagnostics.primary_message
|
||||
});
|
||||
|
||||
@@ -10313,7 +10327,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
|
||||
fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) {
|
||||
self.dismiss_diagnostics(cx);
|
||||
let snapshot = self.snapshot(cx);
|
||||
self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
|
||||
@@ -10323,16 +10337,18 @@ impl Editor {
|
||||
let mut primary_message = None;
|
||||
let mut group_end = Point::zero();
|
||||
let diagnostic_group = buffer
|
||||
.diagnostic_group::<MultiBufferPoint>(group_id)
|
||||
.diagnostic_group(group_id)
|
||||
.filter_map(|entry| {
|
||||
if snapshot.is_line_folded(MultiBufferRow(entry.range.start.row))
|
||||
&& (entry.range.start.row == entry.range.end.row
|
||||
|| snapshot.is_line_folded(MultiBufferRow(entry.range.end.row)))
|
||||
let start = entry.range.start.to_point(&buffer);
|
||||
let end = entry.range.end.to_point(&buffer);
|
||||
if snapshot.is_line_folded(MultiBufferRow(start.row))
|
||||
&& (start.row == end.row
|
||||
|| snapshot.is_line_folded(MultiBufferRow(end.row)))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if entry.range.end > group_end {
|
||||
group_end = entry.range.end;
|
||||
if end > group_end {
|
||||
group_end = end;
|
||||
}
|
||||
if entry.diagnostic.is_primary {
|
||||
primary_range = Some(entry.range.clone());
|
||||
@@ -10343,8 +10359,6 @@ impl Editor {
|
||||
.collect::<Vec<_>>();
|
||||
let primary_range = primary_range?;
|
||||
let primary_message = primary_message?;
|
||||
let primary_range =
|
||||
buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
|
||||
|
||||
let blocks = display_map
|
||||
.insert_blocks(
|
||||
@@ -10375,7 +10389,6 @@ impl Editor {
|
||||
is_valid: true,
|
||||
})
|
||||
});
|
||||
self.active_diagnostics.is_some()
|
||||
}
|
||||
|
||||
fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
|
||||
@@ -10484,13 +10497,13 @@ impl Editor {
|
||||
} else {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut toggled_buffers = HashSet::default();
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
let buffer_id = excerpt.buffer().remote_id();
|
||||
if toggled_buffers.insert(buffer_id) {
|
||||
if self.buffer_folded(buffer_id, cx) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
@@ -10570,13 +10583,13 @@ impl Editor {
|
||||
} else {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut folded_buffers = HashSet::default();
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
let buffer_id = excerpt.buffer().remote_id();
|
||||
if folded_buffers.insert(buffer_id) {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
}
|
||||
@@ -10736,13 +10749,13 @@ impl Editor {
|
||||
} else {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut unfolded_buffers = HashSet::default();
|
||||
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
|
||||
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
|
||||
self.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
) {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
let buffer_id = excerpt.buffer().remote_id();
|
||||
if unfolded_buffers.insert(buffer_id) {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
}
|
||||
@@ -11486,34 +11499,36 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn get_permalink_to_line(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<url::Url>> {
|
||||
let buffer_and_selection = maybe!({
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
let buffer_and_selection_rows = maybe!({
|
||||
let multi_buffer = self.buffer().read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let selection = self.selections.newest_anchor();
|
||||
let selection_range = selection.range();
|
||||
|
||||
let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
(buffer, selection_range.start.row..selection_range.end.row)
|
||||
let (buffer, selection_rows) = if let Some(buffer) = multi_buffer.as_singleton() {
|
||||
(
|
||||
buffer,
|
||||
selection_range.start.to_point(&multi_buffer_snapshot).row
|
||||
..selection_range.end.to_point(&multi_buffer_snapshot).row,
|
||||
)
|
||||
} else {
|
||||
let buffer_ranges = self
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.range_to_buffer_ranges(selection_range, cx);
|
||||
|
||||
let (buffer, range, _) = if selection.reversed {
|
||||
buffer_ranges.first()
|
||||
} else {
|
||||
buffer_ranges.last()
|
||||
}?;
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let selection = text::ToPoint::to_point(&range.start, &snapshot).row
|
||||
..text::ToPoint::to_point(&range.end, &snapshot).row;
|
||||
(buffer.clone(), selection)
|
||||
let selection_head = selection.head();
|
||||
let excerpt =
|
||||
multi_buffer_snapshot.excerpt_containing(selection_head..selection_head)?;
|
||||
let range =
|
||||
excerpt.map_range_to_buffer(selection_range.to_offset(&multi_buffer_snapshot));
|
||||
let snapshot = excerpt.buffer();
|
||||
(
|
||||
multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone(),
|
||||
text::ToPoint::to_point(&range.start, &snapshot).row
|
||||
..text::ToPoint::to_point(&range.end, &snapshot).row,
|
||||
)
|
||||
};
|
||||
|
||||
Some((buffer, selection))
|
||||
Some((buffer, selection_rows))
|
||||
});
|
||||
|
||||
let Some((buffer, selection)) = buffer_and_selection else {
|
||||
let Some((buffer, selection_rows)) = buffer_and_selection_rows else {
|
||||
return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
|
||||
};
|
||||
|
||||
@@ -11522,7 +11537,7 @@ impl Editor {
|
||||
};
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.get_permalink_to_line(&buffer, selection, cx)
|
||||
project.get_permalink_to_line(&buffer, selection_rows, cx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11765,7 +11780,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
/// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
|
||||
/// Rerturns a map of display rows that are highlighted and their corresponding highlight color.
|
||||
/// Returns a map of display rows that are highlighted and their corresponding highlight color.
|
||||
/// Allows to ignore certain kinds of highlights.
|
||||
pub fn highlighted_display_rows(
|
||||
&mut self,
|
||||
@@ -12399,17 +12414,18 @@ impl Editor {
|
||||
};
|
||||
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let buffer = self.buffer.read(cx);
|
||||
let multi_buffer = self.buffer.read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let mut new_selections_by_buffer = HashMap::default();
|
||||
for selection in selections {
|
||||
for (buffer, range, _) in
|
||||
buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
|
||||
for (excerpt, range) in
|
||||
multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
|
||||
{
|
||||
let mut range = range.to_point(buffer.read(cx));
|
||||
let mut range = range.to_point(excerpt.buffer());
|
||||
range.start.column = 0;
|
||||
range.end.column = buffer.read(cx).line_len(range.end.row);
|
||||
range.end.column = excerpt.buffer().line_len(range.end.row);
|
||||
new_selections_by_buffer
|
||||
.entry(buffer)
|
||||
.entry(multi_buffer.buffer(excerpt.buffer_id()).unwrap())
|
||||
.or_insert(Vec::new())
|
||||
.push(range)
|
||||
}
|
||||
@@ -12508,13 +12524,15 @@ impl Editor {
|
||||
}
|
||||
None => {
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let buffer = self.buffer.read(cx);
|
||||
let multi_buffer = self.buffer.read(cx);
|
||||
for selection in selections {
|
||||
for (mut buffer_handle, mut range, _) in
|
||||
buffer.range_to_buffer_ranges(selection.range(), cx)
|
||||
for (excerpt, mut range) in multi_buffer
|
||||
.snapshot(cx)
|
||||
.range_to_buffer_ranges(selection.range())
|
||||
{
|
||||
// When editing branch buffers, jump to the corresponding location
|
||||
// in their base buffer.
|
||||
let mut buffer_handle = multi_buffer.buffer(excerpt.buffer_id()).unwrap();
|
||||
let buffer = buffer_handle.read(cx);
|
||||
if let Some(base_buffer) = buffer.base_buffer() {
|
||||
range = buffer.range_to_version(range, &base_buffer.read(cx).version());
|
||||
@@ -12555,7 +12573,7 @@ impl Editor {
|
||||
.file()
|
||||
.is_none()
|
||||
.then(|| {
|
||||
// Handle file-less buffers separately: those are not really the project items, so won't have a paroject path or entity id,
|
||||
// Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
|
||||
// so `workspace.open_project_item` will never find them, always opening a new editor.
|
||||
// Instead, we try to activate the existing editor in the pane first.
|
||||
let (editor, pane_item_index) =
|
||||
|
||||
@@ -105,7 +105,7 @@ pub struct Scrollbar {
|
||||
pub git_diff: bool,
|
||||
pub selected_symbol: bool,
|
||||
pub search_results: bool,
|
||||
pub diagnostics: bool,
|
||||
pub diagnostics: ScrollbarDiagnostics,
|
||||
pub cursors: bool,
|
||||
pub axes: ScrollbarAxes,
|
||||
}
|
||||
@@ -150,6 +150,73 @@ pub struct ScrollbarAxes {
|
||||
pub vertical: bool,
|
||||
}
|
||||
|
||||
/// Which diagnostic indicators to show in the scrollbar.
|
||||
///
|
||||
/// Default: all
|
||||
#[derive(Copy, Clone, Debug, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ScrollbarDiagnostics {
|
||||
/// Show all diagnostic levels: hint, information, warnings, error.
|
||||
All,
|
||||
/// Show only the following diagnostic levels: information, warning, error.
|
||||
Information,
|
||||
/// Show only the following diagnostic levels: warning, error.
|
||||
Warning,
|
||||
/// Show only the following diagnostic level: error.
|
||||
Error,
|
||||
/// Do not show diagnostics.
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ScrollbarDiagnostics {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = ScrollbarDiagnostics;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
r#"a boolean or one of "all", "information", "warning", "error", "none""#
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
match b {
|
||||
false => Ok(ScrollbarDiagnostics::None),
|
||||
true => Ok(ScrollbarDiagnostics::All),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
match s {
|
||||
"all" => Ok(ScrollbarDiagnostics::All),
|
||||
"information" => Ok(ScrollbarDiagnostics::Information),
|
||||
"warning" => Ok(ScrollbarDiagnostics::Warning),
|
||||
"error" => Ok(ScrollbarDiagnostics::Error),
|
||||
"none" => Ok(ScrollbarDiagnostics::None),
|
||||
_ => Err(E::unknown_variant(
|
||||
s,
|
||||
&["all", "information", "warning", "error", "none"],
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// The key to use for adding multiple cursors
|
||||
///
|
||||
/// Default: alt
|
||||
@@ -348,10 +415,10 @@ pub struct ScrollbarContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub selected_symbol: Option<bool>,
|
||||
/// Whether to show diagnostic indicators in the scrollbar.
|
||||
/// Which diagnostic indicators to show in the scrollbar:
|
||||
///
|
||||
/// Default: true
|
||||
pub diagnostics: Option<bool>,
|
||||
/// Default: all
|
||||
pub diagnostics: Option<ScrollbarDiagnostics>,
|
||||
/// Whether to show cursor positions in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
},
|
||||
editor_settings::{
|
||||
CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ScrollBeyondLastLine,
|
||||
ShowScrollbar,
|
||||
ScrollbarDiagnostics, ShowScrollbar,
|
||||
},
|
||||
git::blame::{CommitDetails, GitBlame},
|
||||
hover_popover::{
|
||||
@@ -45,7 +45,7 @@ use language::{
|
||||
IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings,
|
||||
ShowWhitespaceSetting,
|
||||
},
|
||||
ChunkRendererContext,
|
||||
ChunkRendererContext, DiagnosticEntry,
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::{
|
||||
@@ -1235,7 +1235,7 @@ impl EditorElement {
|
||||
(is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
|
||||
||
|
||||
// Diagnostics
|
||||
(is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
|
||||
(is_singleton && scrollbar_settings.diagnostics != ScrollbarDiagnostics::None && snapshot.buffer_snapshot.has_diagnostics())
|
||||
||
|
||||
// Cursors out of sight
|
||||
non_visible_cursors
|
||||
@@ -4738,13 +4738,39 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
if scrollbar_settings.diagnostics {
|
||||
if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None {
|
||||
let diagnostics = snapshot
|
||||
.buffer_snapshot
|
||||
.diagnostics_in_range::<_, Point>(
|
||||
Point::zero()..max_point,
|
||||
false,
|
||||
)
|
||||
.diagnostics_in_range(Point::zero()..max_point, false)
|
||||
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
|
||||
diagnostic,
|
||||
range: range.to_point(&snapshot.buffer_snapshot),
|
||||
})
|
||||
// Don't show diagnostics the user doesn't care about
|
||||
.filter(|diagnostic| {
|
||||
match (
|
||||
scrollbar_settings.diagnostics,
|
||||
diagnostic.diagnostic.severity,
|
||||
) {
|
||||
(ScrollbarDiagnostics::All, _) => true,
|
||||
(
|
||||
ScrollbarDiagnostics::Error,
|
||||
DiagnosticSeverity::ERROR,
|
||||
) => true,
|
||||
(
|
||||
ScrollbarDiagnostics::Warning,
|
||||
DiagnosticSeverity::ERROR
|
||||
| DiagnosticSeverity::WARNING,
|
||||
) => true,
|
||||
(
|
||||
ScrollbarDiagnostics::Information,
|
||||
DiagnosticSeverity::ERROR
|
||||
| DiagnosticSeverity::WARNING
|
||||
| DiagnosticSeverity::INFORMATION,
|
||||
) => true,
|
||||
(_, _) => false,
|
||||
}
|
||||
})
|
||||
// We want to sort by severity, in order to paint the most severe diagnostics last.
|
||||
.sorted_by_key(|diagnostic| {
|
||||
std::cmp::Reverse(diagnostic.diagnostic.severity)
|
||||
|
||||
@@ -194,14 +194,24 @@ impl ProjectDiffEditor {
|
||||
let open_tasks = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let worktree = project.worktree_for_id(id, cx)?;
|
||||
let applicable_entries = worktree
|
||||
.read(cx)
|
||||
.entries(false, 0)
|
||||
.filter(|entry| !entry.is_external)
|
||||
.filter(|entry| entry.is_file())
|
||||
.filter_map(|entry| Some((entry.git_status?, entry)))
|
||||
.filter_map(|(git_status, entry)| {
|
||||
Some((git_status, entry.id, project.path_for_entry(entry.id, cx)?))
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let applicable_entries = snapshot
|
||||
.repositories()
|
||||
.flat_map(|entry| {
|
||||
entry.status().map(|git_entry| {
|
||||
(git_entry.status, entry.join(git_entry.repo_path))
|
||||
})
|
||||
})
|
||||
.filter_map(|(status, path)| {
|
||||
let id = snapshot.entry_for_path(&path)?.id;
|
||||
Some((
|
||||
status,
|
||||
id,
|
||||
ProjectPath {
|
||||
worktree_id: snapshot.id(),
|
||||
path: path.into(),
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Some(
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
hover_links::{InlayHighlight, RangeInEditor},
|
||||
scroll::ScrollAmount,
|
||||
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
|
||||
Hover, RangeToAnchorExt,
|
||||
Hover,
|
||||
};
|
||||
use gpui::{
|
||||
div, px, AnyElement, AsyncWindowContext, FontWeight, Hsla, InteractiveElement, IntoElement,
|
||||
@@ -263,29 +263,7 @@ fn show_hover(
|
||||
delay.await;
|
||||
}
|
||||
|
||||
// If there's a diagnostic, assign it on the hover state and notify
|
||||
let mut local_diagnostic = snapshot
|
||||
.buffer_snapshot
|
||||
.diagnostics_in_range::<_, usize>(anchor..anchor, false)
|
||||
// Find the entry with the most specific range
|
||||
.min_by_key(|entry| entry.range.end - entry.range.start)
|
||||
.map(|entry| DiagnosticEntry {
|
||||
diagnostic: entry.diagnostic,
|
||||
range: entry.range.to_anchors(&snapshot.buffer_snapshot),
|
||||
});
|
||||
|
||||
// Pull the primary diagnostic out so we can jump to it if the popover is clicked
|
||||
let primary_diagnostic = local_diagnostic.as_ref().and_then(|local_diagnostic| {
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.diagnostic_group::<usize>(local_diagnostic.diagnostic.group_id)
|
||||
.find(|diagnostic| diagnostic.diagnostic.is_primary)
|
||||
.map(|entry| DiagnosticEntry {
|
||||
diagnostic: entry.diagnostic,
|
||||
range: entry.range.to_anchors(&snapshot.buffer_snapshot),
|
||||
})
|
||||
});
|
||||
if let Some(invisible) = snapshot
|
||||
let local_diagnostic = if let Some(invisible) = snapshot
|
||||
.buffer_snapshot
|
||||
.chars_at(anchor)
|
||||
.next()
|
||||
@@ -294,7 +272,7 @@ fn show_hover(
|
||||
let after = snapshot.buffer_snapshot.anchor_after(
|
||||
anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(),
|
||||
);
|
||||
local_diagnostic = Some(DiagnosticEntry {
|
||||
Some(DiagnosticEntry {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: format!("Unicode character U+{:02X}", invisible as u32),
|
||||
@@ -311,7 +289,7 @@ fn show_hover(
|
||||
let before = snapshot.buffer_snapshot.anchor_before(
|
||||
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(),
|
||||
);
|
||||
local_diagnostic = Some(DiagnosticEntry {
|
||||
Some(DiagnosticEntry {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: format!("Unicode character U+{:02X}", invisible as u32),
|
||||
@@ -319,7 +297,16 @@ fn show_hover(
|
||||
},
|
||||
range: before..anchor,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.diagnostics_in_range(anchor..anchor, false)
|
||||
// Find the entry with the most specific range
|
||||
.min_by_key(|entry| {
|
||||
let range = entry.range.to_offset(&snapshot.buffer_snapshot);
|
||||
range.end - range.start
|
||||
})
|
||||
};
|
||||
|
||||
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
|
||||
let text = match local_diagnostic.diagnostic.source {
|
||||
@@ -388,7 +375,6 @@ fn show_hover(
|
||||
|
||||
Some(DiagnosticPopover {
|
||||
local_diagnostic,
|
||||
primary_diagnostic,
|
||||
parsed_content,
|
||||
border_color,
|
||||
background_color,
|
||||
@@ -783,7 +769,6 @@ impl InfoPopover {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiagnosticPopover {
|
||||
local_diagnostic: DiagnosticEntry<Anchor>,
|
||||
primary_diagnostic: Option<DiagnosticEntry<Anchor>>,
|
||||
parsed_content: Option<View<Markdown>>,
|
||||
border_color: Option<Hsla>,
|
||||
background_color: Option<Hsla>,
|
||||
@@ -837,13 +822,8 @@ impl DiagnosticPopover {
|
||||
diagnostic_div.into_any_element()
|
||||
}
|
||||
|
||||
pub fn activation_info(&self) -> (usize, Anchor) {
|
||||
let entry = self
|
||||
.primary_diagnostic
|
||||
.as_ref()
|
||||
.unwrap_or(&self.local_diagnostic);
|
||||
|
||||
(entry.diagnostic.group_id, entry.range.start)
|
||||
pub fn group_id(&self) -> usize {
|
||||
self.local_diagnostic.diagnostic.group_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -456,16 +456,16 @@ impl Editor {
|
||||
range: Range<Anchor>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<()> {
|
||||
let (buffer, range, _) = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.range_to_buffer_ranges(range, cx)
|
||||
.into_iter()
|
||||
.next()?;
|
||||
let multi_buffer = self.buffer.read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let (excerpt, range) = multi_buffer_snapshot.range_to_buffer_ranges(range).next()?;
|
||||
|
||||
buffer.update(cx, |branch_buffer, cx| {
|
||||
branch_buffer.merge_into_base(vec![range], cx);
|
||||
});
|
||||
multi_buffer
|
||||
.buffer(excerpt.buffer_id())
|
||||
.unwrap()
|
||||
.update(cx, |branch_buffer, cx| {
|
||||
branch_buffer.merge_into_base(vec![range], cx);
|
||||
});
|
||||
|
||||
if let Some(project) = self.project.clone() {
|
||||
self.save(true, project, cx).detach_and_log_err(cx);
|
||||
|
||||
@@ -36,6 +36,7 @@ pub struct InlayHintCache {
|
||||
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
|
||||
version: usize,
|
||||
pub(super) enabled: bool,
|
||||
enabled_in_settings: bool,
|
||||
update_tasks: HashMap<ExcerptId, TasksForRanges>,
|
||||
refresh_task: Option<Task<()>>,
|
||||
invalidate_debounce: Option<Duration>,
|
||||
@@ -268,6 +269,7 @@ impl InlayHintCache {
|
||||
Self {
|
||||
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
|
||||
enabled: inlay_hint_settings.enabled,
|
||||
enabled_in_settings: inlay_hint_settings.enabled,
|
||||
hints: HashMap::default(),
|
||||
update_tasks: HashMap::default(),
|
||||
refresh_task: None,
|
||||
@@ -288,10 +290,21 @@ impl InlayHintCache {
|
||||
visible_hints: Vec<Inlay>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> ControlFlow<Option<InlaySplice>> {
|
||||
let old_enabled = self.enabled;
|
||||
// If the setting for inlay hints has changed, update `enabled`. This condition avoids inlay
|
||||
// hint visibility changes when other settings change (such as theme).
|
||||
//
|
||||
// Another option might be to store whether the user has manually toggled inlay hint
|
||||
// visibility, and prefer this. This could lead to confusion as it means inlay hint
|
||||
// visibility would not change when updating the setting if they were ever toggled.
|
||||
if new_hint_settings.enabled != self.enabled_in_settings {
|
||||
self.enabled = new_hint_settings.enabled;
|
||||
};
|
||||
self.enabled_in_settings = new_hint_settings.enabled;
|
||||
self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms);
|
||||
self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms);
|
||||
let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
|
||||
match (self.enabled, new_hint_settings.enabled) {
|
||||
match (old_enabled, self.enabled) {
|
||||
(false, false) => {
|
||||
self.allowed_hint_kinds = new_allowed_hint_kinds;
|
||||
ControlFlow::Break(None)
|
||||
@@ -314,7 +327,6 @@ impl InlayHintCache {
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
self.enabled = new_hint_settings.enabled;
|
||||
self.allowed_hint_kinds = new_allowed_hint_kinds;
|
||||
if self.hints.is_empty() {
|
||||
ControlFlow::Break(None)
|
||||
@@ -327,7 +339,6 @@ impl InlayHintCache {
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
self.enabled = new_hint_settings.enabled;
|
||||
self.allowed_hint_kinds = new_allowed_hint_kinds;
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
@@ -615,9 +615,20 @@ impl Item for Editor {
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.and_then(|buffer| buffer.read(cx).project_path(cx))
|
||||
.and_then(|path| self.project.as_ref()?.read(cx).entry_for_path(&path, cx))
|
||||
.map(|entry| {
|
||||
entry_git_aware_label_color(entry.git_status, entry.is_ignored, params.selected)
|
||||
.and_then(|path| {
|
||||
let project = self.project.as_ref()?.read(cx);
|
||||
let entry = project.entry_for_path(&path, cx)?;
|
||||
let git_status = project
|
||||
.worktree_for_id(path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.status_for_file(path.path);
|
||||
|
||||
Some(entry_git_aware_label_color(
|
||||
git_status,
|
||||
entry.is_ignored,
|
||||
params.selected,
|
||||
))
|
||||
})
|
||||
.unwrap_or_else(|| entry_label_color(params.selected))
|
||||
} else {
|
||||
@@ -1457,10 +1468,11 @@ impl SearchableItem for Editor {
|
||||
search_within_ranges
|
||||
};
|
||||
|
||||
for (excerpt_id, search_buffer, search_range) in
|
||||
buffer.excerpts_in_ranges(search_within_ranges)
|
||||
for (excerpt, search_range) in
|
||||
buffer.disjoint_ranges_to_buffer_ranges(search_within_ranges)
|
||||
{
|
||||
if !search_range.is_empty() {
|
||||
let search_buffer = excerpt.buffer();
|
||||
ranges.extend(
|
||||
query
|
||||
.search(search_buffer, Some(search_range.clone()))
|
||||
@@ -1471,8 +1483,8 @@ impl SearchableItem for Editor {
|
||||
.anchor_after(search_range.start + match_range.start);
|
||||
let end = search_buffer
|
||||
.anchor_before(search_range.start + match_range.end);
|
||||
buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
|
||||
..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
|
||||
buffer.anchor_in_excerpt(excerpt.id(), start).unwrap()
|
||||
..buffer.anchor_in_excerpt(excerpt.id(), end).unwrap()
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -1559,10 +1571,10 @@ pub fn entry_git_aware_label_color(
|
||||
Color::Ignored
|
||||
} else {
|
||||
match git_status {
|
||||
Some(GitFileStatus::Added) => Color::Created,
|
||||
Some(GitFileStatus::Added) | Some(GitFileStatus::Untracked) => Color::Created,
|
||||
Some(GitFileStatus::Modified) => Color::Modified,
|
||||
Some(GitFileStatus::Conflict) => Color::Conflict,
|
||||
None => entry_label_color(selected),
|
||||
Some(GitFileStatus::Deleted) | None => entry_label_color(selected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +257,8 @@ impl EditorLspTestContext {
|
||||
Self::new(language, Default::default(), cx).await
|
||||
}
|
||||
|
||||
// Constructs lsp range using a marked string with '[', ']' range delimiters
|
||||
/// Constructs lsp range using a marked string with '[', ']' range delimiters
|
||||
#[track_caller]
|
||||
pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
|
||||
let ranges = self.ranges(marked_text);
|
||||
self.to_lsp_range(ranges[0].clone())
|
||||
|
||||
@@ -230,6 +230,7 @@ impl EditorTestContext {
|
||||
self.cx.background_executor.run_until_parked();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
|
||||
let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
|
||||
assert_eq!(self.buffer_text(), unmarked_text);
|
||||
|
||||
@@ -720,7 +720,16 @@ impl Fs for RealFs {
|
||||
}
|
||||
|
||||
// Check if path is a symlink and follow the target parent
|
||||
if let Some(target) = self.read_link(&path).await.ok() {
|
||||
if let Some(mut target) = self.read_link(&path).await.ok() {
|
||||
// Check if symlink target is relative path, if so make it absolute
|
||||
if target.is_relative() {
|
||||
if let Some(parent) = path.parent() {
|
||||
target = parent.join(target);
|
||||
if let Ok(canonical) = self.canonicalize(&target).await {
|
||||
target = canonical;
|
||||
}
|
||||
}
|
||||
}
|
||||
watcher.add(&target).ok();
|
||||
if let Some(parent) = target.parent() {
|
||||
watcher.add(parent).log_err();
|
||||
|
||||
@@ -14,7 +14,6 @@ pub struct Matcher<'a> {
|
||||
lowercase_query: &'a [char],
|
||||
query_char_bag: CharBag,
|
||||
smart_case: bool,
|
||||
max_results: usize,
|
||||
min_score: f64,
|
||||
match_positions: Vec<usize>,
|
||||
last_positions: Vec<usize>,
|
||||
@@ -22,11 +21,6 @@ pub struct Matcher<'a> {
|
||||
best_position_matrix: Vec<usize>,
|
||||
}
|
||||
|
||||
pub trait Match: Ord {
|
||||
fn score(&self) -> f64;
|
||||
fn set_positions(&mut self, positions: Vec<usize>);
|
||||
}
|
||||
|
||||
pub trait MatchCandidate {
|
||||
fn has_chars(&self, bag: CharBag) -> bool;
|
||||
fn to_string(&self) -> Cow<'_, str>;
|
||||
@@ -38,7 +32,6 @@ impl<'a> Matcher<'a> {
|
||||
lowercase_query: &'a [char],
|
||||
query_char_bag: CharBag,
|
||||
smart_case: bool,
|
||||
max_results: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
query,
|
||||
@@ -50,10 +43,11 @@ impl<'a> Matcher<'a> {
|
||||
score_matrix: Vec::new(),
|
||||
best_position_matrix: Vec::new(),
|
||||
smart_case,
|
||||
max_results,
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter and score fuzzy match candidates. Results are returned unsorted, in the same order as
|
||||
/// the input candidates.
|
||||
pub fn match_candidates<C: MatchCandidate, R, F>(
|
||||
&mut self,
|
||||
prefix: &[char],
|
||||
@@ -63,8 +57,7 @@ impl<'a> Matcher<'a> {
|
||||
cancel_flag: &AtomicBool,
|
||||
build_match: F,
|
||||
) where
|
||||
R: Match,
|
||||
F: Fn(&C, f64) -> R,
|
||||
F: Fn(&C, f64, &Vec<usize>) -> R,
|
||||
{
|
||||
let mut candidate_chars = Vec::new();
|
||||
let mut lowercase_candidate_chars = Vec::new();
|
||||
@@ -103,20 +96,7 @@ impl<'a> Matcher<'a> {
|
||||
);
|
||||
|
||||
if score > 0.0 {
|
||||
let mut mat = build_match(&candidate, score);
|
||||
if let Err(i) = results.binary_search_by(|m| mat.cmp(m)) {
|
||||
if results.len() < self.max_results {
|
||||
mat.set_positions(self.match_positions.clone());
|
||||
results.insert(i, mat);
|
||||
} else if i < results.len() {
|
||||
results.pop();
|
||||
mat.set_positions(self.match_positions.clone());
|
||||
results.insert(i, mat);
|
||||
}
|
||||
if results.len() == self.max_results {
|
||||
self.min_score = results.last().unwrap().score();
|
||||
}
|
||||
}
|
||||
results.push(build_match(&candidate, score, &self.match_positions));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,18 +305,18 @@ mod tests {
|
||||
#[test]
|
||||
fn test_get_last_positions() {
|
||||
let mut query: &[char] = &['d', 'c'];
|
||||
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
|
||||
let mut matcher = Matcher::new(query, query, query.into(), false);
|
||||
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
|
||||
assert!(!result);
|
||||
|
||||
query = &['c', 'd'];
|
||||
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
|
||||
let mut matcher = Matcher::new(query, query, query.into(), false);
|
||||
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
|
||||
assert!(result);
|
||||
assert_eq!(matcher.last_positions, vec![2, 4]);
|
||||
|
||||
query = &['z', '/', 'z', 'f'];
|
||||
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
|
||||
let mut matcher = Matcher::new(query, query, query.into(), false);
|
||||
let result = matcher.find_last_positions(&['z', 'e', 'd', '/'], &['z', 'e', 'd', '/', 'f']);
|
||||
assert!(result);
|
||||
assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
|
||||
@@ -451,7 +431,7 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case, 100);
|
||||
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case);
|
||||
|
||||
let cancel_flag = AtomicBool::new(false);
|
||||
let mut results = Vec::new();
|
||||
@@ -462,16 +442,17 @@ mod tests {
|
||||
path_entries.into_iter(),
|
||||
&mut results,
|
||||
&cancel_flag,
|
||||
|candidate, score| PathMatch {
|
||||
|candidate, score, positions| PathMatch {
|
||||
score,
|
||||
worktree_id: 0,
|
||||
positions: Vec::new(),
|
||||
positions: positions.clone(),
|
||||
path: Arc::from(candidate.path),
|
||||
path_prefix: "".into(),
|
||||
distance_to_relative_ancestor: usize::MAX,
|
||||
is_dir: false,
|
||||
},
|
||||
);
|
||||
results.sort_by(|a, b| b.cmp(a));
|
||||
|
||||
results
|
||||
.into_iter()
|
||||
|
||||
@@ -3,11 +3,14 @@ use std::{
|
||||
borrow::Cow,
|
||||
cmp::{self, Ordering},
|
||||
path::Path,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
sync::{
|
||||
atomic::{self, AtomicBool},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
matcher::{Match, MatchCandidate, Matcher},
|
||||
matcher::{MatchCandidate, Matcher},
|
||||
CharBag,
|
||||
};
|
||||
|
||||
@@ -42,16 +45,6 @@ pub trait PathMatchCandidateSet<'a>: Send + Sync {
|
||||
fn candidates(&'a self, start: usize) -> Self::Candidates;
|
||||
}
|
||||
|
||||
impl Match for PathMatch {
|
||||
fn score(&self) -> f64 {
|
||||
self.score
|
||||
}
|
||||
|
||||
fn set_positions(&mut self, positions: Vec<usize>) {
|
||||
self.positions = positions;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MatchCandidate for PathMatchCandidate<'a> {
|
||||
fn has_chars(&self, bag: CharBag) -> bool {
|
||||
self.char_bag.is_superset(bag)
|
||||
@@ -102,13 +95,7 @@ pub fn match_fixed_path_set(
|
||||
let query = query.chars().collect::<Vec<_>>();
|
||||
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
||||
|
||||
let mut matcher = Matcher::new(
|
||||
&query,
|
||||
&lowercase_query,
|
||||
query_char_bag,
|
||||
smart_case,
|
||||
max_results,
|
||||
);
|
||||
let mut matcher = Matcher::new(&query, &lowercase_query, query_char_bag, smart_case);
|
||||
|
||||
let mut results = Vec::new();
|
||||
matcher.match_candidates(
|
||||
@@ -117,16 +104,17 @@ pub fn match_fixed_path_set(
|
||||
candidates.into_iter(),
|
||||
&mut results,
|
||||
&AtomicBool::new(false),
|
||||
|candidate, score| PathMatch {
|
||||
|candidate, score, positions| PathMatch {
|
||||
score,
|
||||
worktree_id,
|
||||
positions: Vec::new(),
|
||||
positions: positions.clone(),
|
||||
is_dir: candidate.is_dir,
|
||||
path: Arc::from(candidate.path),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: usize::MAX,
|
||||
},
|
||||
);
|
||||
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
|
||||
results
|
||||
}
|
||||
|
||||
@@ -164,16 +152,15 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
|
||||
scope.spawn(async move {
|
||||
let segment_start = segment_idx * segment_size;
|
||||
let segment_end = segment_start + segment_size;
|
||||
let mut matcher = Matcher::new(
|
||||
query,
|
||||
lowercase_query,
|
||||
query_char_bag,
|
||||
smart_case,
|
||||
max_results,
|
||||
);
|
||||
let mut matcher =
|
||||
Matcher::new(query, lowercase_query, query_char_bag, smart_case);
|
||||
|
||||
let mut tree_start = 0;
|
||||
for candidate_set in candidate_sets {
|
||||
if cancel_flag.load(atomic::Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
||||
let tree_end = tree_start + candidate_set.len();
|
||||
|
||||
if tree_start < segment_end && segment_start < tree_end {
|
||||
@@ -193,10 +180,10 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
|
||||
candidates,
|
||||
results,
|
||||
cancel_flag,
|
||||
|candidate, score| PathMatch {
|
||||
|candidate, score, positions| PathMatch {
|
||||
score,
|
||||
worktree_id,
|
||||
positions: Vec::new(),
|
||||
positions: positions.clone(),
|
||||
path: Arc::from(candidate.path),
|
||||
is_dir: candidate.is_dir,
|
||||
path_prefix: candidate_set.prefix(),
|
||||
@@ -222,14 +209,12 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
|
||||
})
|
||||
.await;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for segment_result in segment_results {
|
||||
if results.is_empty() {
|
||||
results = segment_result;
|
||||
} else {
|
||||
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
|
||||
}
|
||||
if cancel_flag.load(atomic::Ordering::Relaxed) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut results = segment_results.concat();
|
||||
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
|
||||
results
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
matcher::{Match, MatchCandidate, Matcher},
|
||||
matcher::{MatchCandidate, Matcher},
|
||||
CharBag,
|
||||
};
|
||||
use gpui::BackgroundExecutor;
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
cmp::{self, Ordering},
|
||||
iter,
|
||||
ops::Range,
|
||||
sync::atomic::AtomicBool,
|
||||
sync::atomic::{self, AtomicBool},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -46,16 +46,6 @@ pub struct StringMatch {
|
||||
pub string: String,
|
||||
}
|
||||
|
||||
impl Match for StringMatch {
|
||||
fn score(&self) -> f64 {
|
||||
self.score
|
||||
}
|
||||
|
||||
fn set_positions(&mut self, positions: Vec<usize>) {
|
||||
self.positions = positions;
|
||||
}
|
||||
}
|
||||
|
||||
impl StringMatch {
|
||||
pub fn ranges(&self) -> impl '_ + Iterator<Item = Range<usize>> {
|
||||
let mut positions = self.positions.iter().peekable();
|
||||
@@ -167,13 +157,8 @@ pub async fn match_strings(
|
||||
scope.spawn(async move {
|
||||
let segment_start = cmp::min(segment_idx * segment_size, candidates.len());
|
||||
let segment_end = cmp::min(segment_start + segment_size, candidates.len());
|
||||
let mut matcher = Matcher::new(
|
||||
query,
|
||||
lowercase_query,
|
||||
query_char_bag,
|
||||
smart_case,
|
||||
max_results,
|
||||
);
|
||||
let mut matcher =
|
||||
Matcher::new(query, lowercase_query, query_char_bag, smart_case);
|
||||
|
||||
matcher.match_candidates(
|
||||
&[],
|
||||
@@ -181,10 +166,10 @@ pub async fn match_strings(
|
||||
candidates[segment_start..segment_end].iter(),
|
||||
results,
|
||||
cancel_flag,
|
||||
|candidate, score| StringMatch {
|
||||
|candidate, score, positions| StringMatch {
|
||||
candidate_id: candidate.id,
|
||||
score,
|
||||
positions: Vec::new(),
|
||||
positions: positions.clone(),
|
||||
string: candidate.string.to_string(),
|
||||
},
|
||||
);
|
||||
@@ -193,13 +178,11 @@ pub async fn match_strings(
|
||||
})
|
||||
.await;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for segment_result in segment_results {
|
||||
if results.is_empty() {
|
||||
results = segment_result;
|
||||
} else {
|
||||
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
|
||||
}
|
||||
if cancel_flag.load(atomic::Ordering::Relaxed) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut results = segment_results.concat();
|
||||
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
|
||||
results
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use std::sync::LazyLock;
|
||||
pub use crate::hosting_provider::*;
|
||||
pub use crate::remote::*;
|
||||
pub use git2 as libgit;
|
||||
pub use repository::WORK_DIRECTORY_REPO_PATH;
|
||||
|
||||
pub static DOT_GIT: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".git"));
|
||||
pub static COOKIES: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("cookies"));
|
||||
|
||||
@@ -7,6 +7,8 @@ use gpui::SharedString;
|
||||
use parking_lot::Mutex;
|
||||
use rope::Rope;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Borrow;
|
||||
use std::sync::LazyLock;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
path::{Component, Path, PathBuf},
|
||||
@@ -37,7 +39,8 @@ pub trait GitRepository: Send + Sync {
|
||||
/// Returns the SHA of the current HEAD.
|
||||
fn head_sha(&self) -> Option<String>;
|
||||
|
||||
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus>;
|
||||
/// Returns the list of git statuses, sorted by path
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>>;
|
||||
fn change_branch(&self, _: &str) -> Result<()>;
|
||||
@@ -132,7 +135,7 @@ impl GitRepository for RealGitRepository {
|
||||
Some(self.repository.lock().head().ok()?.target()?.to_string())
|
||||
}
|
||||
|
||||
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus> {
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
@@ -289,8 +292,9 @@ impl GitRepository for FakeGitRepository {
|
||||
state.dot_git_dir.clone()
|
||||
}
|
||||
|
||||
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus> {
|
||||
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
|
||||
let state = self.state.lock();
|
||||
|
||||
let mut entries = state
|
||||
.worktree_statuses
|
||||
.iter()
|
||||
@@ -306,6 +310,7 @@ impl GitRepository for FakeGitRepository {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
Ok(GitStatus {
|
||||
entries: entries.into(),
|
||||
})
|
||||
@@ -394,6 +399,8 @@ pub enum GitFileStatus {
|
||||
Added,
|
||||
Modified,
|
||||
Conflict,
|
||||
Deleted,
|
||||
Untracked,
|
||||
}
|
||||
|
||||
impl GitFileStatus {
|
||||
@@ -421,20 +428,34 @@ impl GitFileStatus {
|
||||
}
|
||||
}
|
||||
|
||||
pub static WORK_DIRECTORY_REPO_PATH: LazyLock<RepoPath> =
|
||||
LazyLock::new(|| RepoPath(Path::new("").into()));
|
||||
|
||||
#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)]
|
||||
pub struct RepoPath(pub PathBuf);
|
||||
pub struct RepoPath(pub Arc<Path>);
|
||||
|
||||
impl RepoPath {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
debug_assert!(path.is_relative(), "Repo paths must be relative");
|
||||
|
||||
RepoPath(path)
|
||||
RepoPath(path.into())
|
||||
}
|
||||
|
||||
pub fn from_str(path: &str) -> Self {
|
||||
let path = Path::new(path);
|
||||
debug_assert!(path.is_relative(), "Repo paths must be relative");
|
||||
|
||||
RepoPath(path.into())
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> String {
|
||||
self.0.to_string_lossy().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Path> for RepoPath {
|
||||
fn from(value: &Path) -> Self {
|
||||
RepoPath::new(value.to_path_buf())
|
||||
RepoPath::new(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,9 +465,15 @@ impl From<PathBuf> for RepoPath {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for RepoPath {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RepoPath {
|
||||
fn default() -> Self {
|
||||
RepoPath(PathBuf::new())
|
||||
RepoPath(Path::new("").into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,13 +484,19 @@ impl AsRef<Path> for RepoPath {
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RepoPath {
|
||||
type Target = PathBuf;
|
||||
type Target = Path;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<Path> for RepoPath {
|
||||
fn borrow(&self) -> &Path {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RepoPathDescendants<'a>(pub &'a Path);
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use crate::repository::{GitFileStatus, RepoPath};
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{path::Path, process::Stdio, sync::Arc};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GitStatus {
|
||||
@@ -15,7 +11,7 @@ impl GitStatus {
|
||||
pub(crate) fn new(
|
||||
git_binary: &Path,
|
||||
working_directory: &Path,
|
||||
path_prefixes: &[PathBuf],
|
||||
path_prefixes: &[RepoPath],
|
||||
) -> Result<Self> {
|
||||
let child = util::command::new_std_command(git_binary)
|
||||
.current_dir(working_directory)
|
||||
@@ -27,7 +23,7 @@ impl GitStatus {
|
||||
"-z",
|
||||
])
|
||||
.args(path_prefixes.iter().map(|path_prefix| {
|
||||
if *path_prefix == Path::new("") {
|
||||
if path_prefix.0.as_ref() == Path::new("") {
|
||||
Path::new(".")
|
||||
} else {
|
||||
path_prefix
|
||||
@@ -55,10 +51,12 @@ impl GitStatus {
|
||||
let (status, path) = entry.split_at(3);
|
||||
let status = status.trim();
|
||||
Some((
|
||||
RepoPath(PathBuf::from(path)),
|
||||
RepoPath(Path::new(path).into()),
|
||||
match status {
|
||||
"A" | "??" => GitFileStatus::Added,
|
||||
"A" => GitFileStatus::Added,
|
||||
"M" => GitFileStatus::Modified,
|
||||
"D" => GitFileStatus::Deleted,
|
||||
"??" => GitFileStatus::Untracked,
|
||||
_ => return None,
|
||||
},
|
||||
))
|
||||
@@ -75,7 +73,7 @@ impl GitStatus {
|
||||
|
||||
pub fn get(&self, path: &Path) -> Option<GitFileStatus> {
|
||||
self.entries
|
||||
.binary_search_by(|(repo_path, _)| repo_path.0.as_path().cmp(path))
|
||||
.binary_search_by(|(repo_path, _)| repo_path.0.as_ref().cmp(path))
|
||||
.ok()
|
||||
.map(|index| self.entries[index].1)
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ path = "src/git_ui.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
menu.workspace = true
|
||||
@@ -29,8 +31,7 @@ settings.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
git.workspace = true
|
||||
collections.workspace = true
|
||||
worktree.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use crate::{git_status_icon, settings::GitPanelSettings};
|
||||
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::{
|
||||
scroll::{Autoscroll, AutoscrollStrategy},
|
||||
Editor, MultiBuffer, DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
};
|
||||
use git::{diff::DiffHunk, repository::GitFileStatus};
|
||||
use git::{
|
||||
diff::DiffHunk,
|
||||
repository::{GitFileStatus, RepoPath},
|
||||
};
|
||||
use gpui::*;
|
||||
use gpui::{
|
||||
actions, prelude::*, uniform_list, Action, AppContext, AsyncWindowContext, ClickEvent,
|
||||
CursorStyle, EventEmitter, FocusHandle, FocusableView, KeyContext,
|
||||
@@ -14,7 +19,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Buffer, BufferRow, OffsetRangeExt};
|
||||
use menu::{SelectNext, SelectPrev};
|
||||
use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, WorktreeId};
|
||||
use project::{EntryKind, Fs, Project, ProjectEntryId, ProjectPath, WorktreeId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings as _;
|
||||
use std::{
|
||||
@@ -22,7 +27,7 @@ use std::{
|
||||
collections::HashSet,
|
||||
ffi::OsStr,
|
||||
ops::{Deref, Range},
|
||||
path::{Path, PathBuf},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
@@ -37,9 +42,7 @@ use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
ItemHandle, Workspace,
|
||||
};
|
||||
|
||||
use crate::{git_status_icon, settings::GitPanelSettings};
|
||||
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
|
||||
use worktree::StatusEntry;
|
||||
|
||||
actions!(git_panel, [ToggleFocus]);
|
||||
|
||||
@@ -69,7 +72,7 @@ pub struct GitStatusEntry {}
|
||||
struct EntryDetails {
|
||||
filename: String,
|
||||
display_name: String,
|
||||
path: Arc<Path>,
|
||||
path: RepoPath,
|
||||
kind: EntryKind,
|
||||
depth: usize,
|
||||
is_expanded: bool,
|
||||
@@ -101,7 +104,8 @@ pub struct GitPanel {
|
||||
scrollbar_state: ScrollbarState,
|
||||
selected_item: Option<usize>,
|
||||
show_scrollbar: bool,
|
||||
expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
|
||||
// TODO Reintroduce expanded directories, once we're deriving directories from paths
|
||||
// expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
|
||||
|
||||
// The entries that are currently shown in the panel, aka
|
||||
// not hidden by folding or such
|
||||
@@ -115,18 +119,20 @@ pub struct GitPanel {
|
||||
#[derive(Debug, Clone)]
|
||||
struct WorktreeEntries {
|
||||
worktree_id: WorktreeId,
|
||||
// TODO support multiple repositories per worktree
|
||||
work_directory: worktree::WorkDirectory,
|
||||
visible_entries: Vec<GitPanelEntry>,
|
||||
paths: Rc<OnceCell<HashSet<Arc<Path>>>>,
|
||||
paths: Rc<OnceCell<HashSet<RepoPath>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct GitPanelEntry {
|
||||
entry: Entry,
|
||||
entry: worktree::StatusEntry,
|
||||
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
|
||||
}
|
||||
|
||||
impl Deref for GitPanelEntry {
|
||||
type Target = Entry;
|
||||
type Target = worktree::StatusEntry;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.entry
|
||||
@@ -134,11 +140,11 @@ impl Deref for GitPanelEntry {
|
||||
}
|
||||
|
||||
impl WorktreeEntries {
|
||||
fn paths(&self) -> &HashSet<Arc<Path>> {
|
||||
fn paths(&self) -> &HashSet<RepoPath> {
|
||||
self.paths.get_or_init(|| {
|
||||
self.visible_entries
|
||||
.iter()
|
||||
.map(|e| (e.entry.path.clone()))
|
||||
.map(|e| (e.entry.repo_path.clone()))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
@@ -165,8 +171,11 @@ impl GitPanel {
|
||||
})
|
||||
.detach();
|
||||
cx.subscribe(&project, |this, _, event, cx| match event {
|
||||
project::Event::WorktreeRemoved(id) => {
|
||||
this.expanded_dir_ids.remove(id);
|
||||
project::Event::GitRepositoryUpdated => {
|
||||
this.update_visible_entries(None, None, cx);
|
||||
}
|
||||
project::Event::WorktreeRemoved(_id) => {
|
||||
// this.expanded_dir_ids.remove(id);
|
||||
this.update_visible_entries(None, None, cx);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -183,7 +192,7 @@ impl GitPanel {
|
||||
project::Event::Closed => {
|
||||
this.git_diff_editor_updates = Task::ready(());
|
||||
this.reveal_in_editor = Task::ready(());
|
||||
this.expanded_dir_ids.clear();
|
||||
// this.expanded_dir_ids.clear();
|
||||
this.visible_entries.clear();
|
||||
this.git_diff_editor = None;
|
||||
}
|
||||
@@ -200,8 +209,7 @@ impl GitPanel {
|
||||
pending_serialization: Task::ready(None),
|
||||
visible_entries: Vec::new(),
|
||||
current_modifiers: cx.modifiers(),
|
||||
expanded_dir_ids: Default::default(),
|
||||
|
||||
// expanded_dir_ids: Default::default(),
|
||||
width: Some(px(360.)),
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()).parent_view(cx.view()),
|
||||
scroll_handle,
|
||||
@@ -288,16 +296,16 @@ impl GitPanel {
|
||||
}
|
||||
|
||||
fn calculate_depth_and_difference(
|
||||
entry: &Entry,
|
||||
visible_worktree_entries: &HashSet<Arc<Path>>,
|
||||
entry: &StatusEntry,
|
||||
visible_worktree_entries: &HashSet<RepoPath>,
|
||||
) -> (usize, usize) {
|
||||
let (depth, difference) = entry
|
||||
.path
|
||||
.repo_path
|
||||
.ancestors()
|
||||
.skip(1) // Skip the entry itself
|
||||
.find_map(|ancestor| {
|
||||
if let Some(parent_entry) = visible_worktree_entries.get(ancestor) {
|
||||
let entry_path_components_count = entry.path.components().count();
|
||||
let entry_path_components_count = entry.repo_path.components().count();
|
||||
let parent_path_components_count = parent_entry.components().count();
|
||||
let difference = entry_path_components_count - parent_path_components_count;
|
||||
let depth = parent_entry
|
||||
@@ -432,13 +440,7 @@ impl GitPanel {
|
||||
fn entry_count(&self) -> usize {
|
||||
self.visible_entries
|
||||
.iter()
|
||||
.map(|worktree_entries| {
|
||||
worktree_entries
|
||||
.visible_entries
|
||||
.iter()
|
||||
.filter(|entry| entry.git_status.is_some())
|
||||
.count()
|
||||
})
|
||||
.map(|worktree_entries| worktree_entries.visible_entries.len())
|
||||
.sum()
|
||||
}
|
||||
|
||||
@@ -446,7 +448,7 @@ impl GitPanel {
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<Self>),
|
||||
mut callback: impl FnMut(usize, EntryDetails, &mut ViewContext<Self>),
|
||||
) {
|
||||
let mut ix = 0;
|
||||
for worktree_entries in &self.visible_entries {
|
||||
@@ -468,11 +470,11 @@ impl GitPanel {
|
||||
{
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let root_name = OsStr::new(snapshot.root_name());
|
||||
let expanded_entry_ids = self
|
||||
.expanded_dir_ids
|
||||
.get(&snapshot.id())
|
||||
.map(Vec::as_slice)
|
||||
.unwrap_or(&[]);
|
||||
// let expanded_entry_ids = self
|
||||
// .expanded_dir_ids
|
||||
// .get(&snapshot.id())
|
||||
// .map(Vec::as_slice)
|
||||
// .unwrap_or(&[]);
|
||||
|
||||
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
|
||||
let entries = worktree_entries.paths();
|
||||
@@ -483,22 +485,22 @@ impl GitPanel {
|
||||
.enumerate()
|
||||
{
|
||||
let index = index_start + i;
|
||||
let status = entry.git_status;
|
||||
let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
|
||||
let status = entry.status;
|
||||
let is_expanded = true; //expanded_entry_ids.binary_search(&entry.id).is_ok();
|
||||
|
||||
let (depth, difference) = Self::calculate_depth_and_difference(entry, entries);
|
||||
|
||||
let filename = match difference {
|
||||
diff if diff > 1 => entry
|
||||
.path
|
||||
.repo_path
|
||||
.iter()
|
||||
.skip(entry.path.components().count() - diff)
|
||||
.skip(entry.repo_path.components().count() - diff)
|
||||
.collect::<PathBuf>()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
_ => entry
|
||||
.path
|
||||
.repo_path
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| root_name.to_string_lossy().to_string()),
|
||||
@@ -506,16 +508,17 @@ impl GitPanel {
|
||||
|
||||
let details = EntryDetails {
|
||||
filename,
|
||||
display_name: entry.path.to_string_lossy().into_owned(),
|
||||
kind: entry.kind,
|
||||
display_name: entry.repo_path.to_string_lossy().into_owned(),
|
||||
// TODO get it from StatusEntry?
|
||||
kind: EntryKind::File,
|
||||
is_expanded,
|
||||
path: entry.path.clone(),
|
||||
status,
|
||||
path: entry.repo_path.clone(),
|
||||
status: Some(status),
|
||||
hunks: entry.hunks.clone(),
|
||||
depth,
|
||||
index,
|
||||
};
|
||||
callback(entry.id, details, cx);
|
||||
callback(ix, details, cx);
|
||||
}
|
||||
}
|
||||
ix = end_ix;
|
||||
@@ -527,7 +530,7 @@ impl GitPanel {
|
||||
fn update_visible_entries(
|
||||
&mut self,
|
||||
for_worktree: Option<WorktreeId>,
|
||||
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
|
||||
_new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let project = self.project.read(cx);
|
||||
@@ -549,24 +552,36 @@ impl GitPanel {
|
||||
None => false,
|
||||
});
|
||||
for worktree in project.visible_worktrees(cx) {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let worktree_id = snapshot.id();
|
||||
|
||||
if for_worktree.is_some() && for_worktree != Some(worktree_id) {
|
||||
continue;
|
||||
}
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
|
||||
let mut visible_worktree_entries = snapshot
|
||||
.entries(false, 0)
|
||||
.filter(|entry| !entry.is_external)
|
||||
.filter(|entry| entry.git_status.is_some())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
|
||||
project::sort_worktree_entries(&mut visible_worktree_entries);
|
||||
let mut visible_worktree_entries = Vec::new();
|
||||
// Only use the first repository for now
|
||||
let repositories = snapshot.repositories().take(1);
|
||||
let mut work_directory = None;
|
||||
for repository in repositories {
|
||||
visible_worktree_entries.extend(repository.status());
|
||||
work_directory = Some(worktree::WorkDirectory::clone(repository));
|
||||
}
|
||||
|
||||
// TODO use the GitTraversal
|
||||
// let mut visible_worktree_entries = snapshot
|
||||
// .entries(false, 0)
|
||||
// .filter(|entry| !entry.is_external)
|
||||
// .filter(|entry| entry.git_status.is_some())
|
||||
// .cloned()
|
||||
// .collect::<Vec<_>>();
|
||||
// snapshot.propagate_git_statuses(&mut visible_worktree_entries);
|
||||
// project::sort_worktree_entries(&mut visible_worktree_entries);
|
||||
|
||||
if !visible_worktree_entries.is_empty() {
|
||||
self.visible_entries.push(WorktreeEntries {
|
||||
worktree_id,
|
||||
work_directory: work_directory.unwrap(),
|
||||
visible_entries: visible_worktree_entries
|
||||
.into_iter()
|
||||
.map(|entry| GitPanelEntry {
|
||||
@@ -580,24 +595,25 @@ impl GitPanel {
|
||||
}
|
||||
self.visible_entries.extend(after_update);
|
||||
|
||||
if let Some((worktree_id, entry_id)) = new_selected_entry {
|
||||
self.selected_item = self.visible_entries.iter().enumerate().find_map(
|
||||
|(worktree_index, worktree_entries)| {
|
||||
if worktree_entries.worktree_id == worktree_id {
|
||||
worktree_entries
|
||||
.visible_entries
|
||||
.iter()
|
||||
.position(|entry| entry.id == entry_id)
|
||||
.map(|entry_index| {
|
||||
worktree_index * worktree_entries.visible_entries.len()
|
||||
+ entry_index
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
// TODO re-implement this
|
||||
// if let Some((worktree_id, entry_id)) = new_selected_entry {
|
||||
// self.selected_item = self.visible_entries.iter().enumerate().find_map(
|
||||
// |(worktree_index, worktree_entries)| {
|
||||
// if worktree_entries.worktree_id == worktree_id {
|
||||
// worktree_entries
|
||||
// .visible_entries
|
||||
// .iter()
|
||||
// .position(|entry| entry.id == entry_id)
|
||||
// .map(|entry_index| {
|
||||
// worktree_index * worktree_entries.visible_entries.len()
|
||||
// + entry_index
|
||||
// })
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
let project = self.project.downgrade();
|
||||
self.git_diff_editor_updates = cx.spawn(|git_panel, mut cx| async move {
|
||||
@@ -612,12 +628,14 @@ impl GitPanel {
|
||||
.visible_entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let git_status = entry.git_status()?;
|
||||
let git_status = entry.status;
|
||||
let entry_hunks = entry.hunks.clone();
|
||||
let (entry_path, unstaged_changes_task) =
|
||||
project.update(cx, |project, cx| {
|
||||
let entry_path =
|
||||
project.path_for_entry(entry.id, cx)?;
|
||||
let entry_path = ProjectPath {
|
||||
worktree_id: worktree_entries.worktree_id,
|
||||
path: worktree_entries.work_directory.unrelativize(&entry.repo_path)?,
|
||||
};
|
||||
let open_task =
|
||||
project.open_path(entry_path.clone(), cx);
|
||||
let unstaged_changes_task =
|
||||
@@ -682,8 +700,8 @@ impl GitPanel {
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
// TODO support conflicts display
|
||||
GitFileStatus::Conflict => Vec::new(),
|
||||
// TODO support these
|
||||
GitFileStatus::Conflict | GitFileStatus::Deleted | GitFileStatus::Untracked => Vec::new(),
|
||||
}
|
||||
}).clone()
|
||||
})?;
|
||||
@@ -992,18 +1010,17 @@ impl GitPanel {
|
||||
|
||||
fn render_entry(
|
||||
&self,
|
||||
id: ProjectEntryId,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
details: EntryDetails,
|
||||
cx: &ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let id = id.to_proto() as usize;
|
||||
let checkbox_id = ElementId::Name(format!("checkbox_{}", id).into());
|
||||
let checkbox_id = ElementId::Name(format!("checkbox_{}", ix).into());
|
||||
let is_staged = ToggleState::Selected;
|
||||
let handle = cx.view().downgrade();
|
||||
|
||||
h_flex()
|
||||
.id(id)
|
||||
.id(("git-panel-entry", ix))
|
||||
.h(px(28.))
|
||||
.w_full()
|
||||
.pl(px(12. + 12. * details.depth as f32))
|
||||
@@ -1019,7 +1036,7 @@ impl GitPanel {
|
||||
this.child(git_status_icon(status))
|
||||
})
|
||||
.child(
|
||||
ListItem::new(("label", id))
|
||||
ListItem::new(details.path.0.clone())
|
||||
.toggle_state(selected)
|
||||
.child(h_flex().gap_1p5().child(details.display_name.clone()))
|
||||
.on_click(move |e, cx| {
|
||||
|
||||
@@ -44,10 +44,13 @@ const REMOVED_COLOR: Hsla = Hsla {
|
||||
// TODO: Add updated status colors to theme
|
||||
pub fn git_status_icon(status: GitFileStatus) -> impl IntoElement {
|
||||
match status {
|
||||
GitFileStatus::Added => Icon::new(IconName::SquarePlus).color(Color::Custom(ADDED_COLOR)),
|
||||
GitFileStatus::Added | GitFileStatus::Untracked => {
|
||||
Icon::new(IconName::SquarePlus).color(Color::Custom(ADDED_COLOR))
|
||||
}
|
||||
GitFileStatus::Modified => {
|
||||
Icon::new(IconName::SquareDot).color(Color::Custom(MODIFIED_COLOR))
|
||||
}
|
||||
GitFileStatus::Conflict => Icon::new(IconName::Warning).color(Color::Custom(REMOVED_COLOR)),
|
||||
GitFileStatus::Deleted => Icon::new(IconName::Warning).color(Color::Custom(REMOVED_COLOR)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,7 @@ macro_rules! actions {
|
||||
#[doc = "The `"]
|
||||
#[doc = stringify!($name)]
|
||||
#[doc = "` action, see [`gpui::actions!`]"]
|
||||
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)]
|
||||
#[serde(crate = "gpui::private::serde")]
|
||||
#[derive(::std::clone::Clone,::std::cmp::PartialEq, ::std::default::Default)]
|
||||
pub struct $name;
|
||||
|
||||
gpui::__impl_action!($namespace, $name, $name,
|
||||
@@ -211,14 +210,7 @@ macro_rules! action_as {
|
||||
#[doc = "The `"]
|
||||
#[doc = stringify!($name)]
|
||||
#[doc = "` action, see [`gpui::actions!`]"]
|
||||
#[derive(
|
||||
::std::cmp::PartialEq,
|
||||
::std::clone::Clone,
|
||||
::std::default::Default,
|
||||
::std::fmt::Debug,
|
||||
gpui::private::serde_derive::Deserialize,
|
||||
)]
|
||||
#[serde(crate = "gpui::private::serde")]
|
||||
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default)]
|
||||
pub struct $name;
|
||||
|
||||
gpui::__impl_action!(
|
||||
|
||||
@@ -117,8 +117,9 @@ impl EntityMap {
|
||||
|
||||
pub fn read<T: 'static>(&self, model: &Model<T>) -> &T {
|
||||
self.assert_valid_context(model);
|
||||
self.entities[model.entity_id]
|
||||
.downcast_ref()
|
||||
self.entities
|
||||
.get(model.entity_id)
|
||||
.and_then(|entity| entity.downcast_ref())
|
||||
.unwrap_or_else(|| double_lease_panic::<T>("read"))
|
||||
}
|
||||
|
||||
|
||||
@@ -1580,7 +1580,7 @@ impl LinuxClient for X11Client {
|
||||
}
|
||||
}
|
||||
|
||||
// Adatpted from:
|
||||
// Adapted from:
|
||||
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
|
||||
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
|
||||
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
|
||||
|
||||
@@ -55,6 +55,7 @@ x11rb::atom_manager! {
|
||||
WM_PROTOCOLS,
|
||||
WM_DELETE_WINDOW,
|
||||
WM_CHANGE_STATE,
|
||||
_NET_WM_PID,
|
||||
_NET_WM_NAME,
|
||||
_NET_WM_STATE,
|
||||
_NET_WM_STATE_MAXIMIZED_VERT,
|
||||
@@ -436,6 +437,18 @@ impl X11WindowState {
|
||||
|
||||
// Collect errors during setup, so that window can be destroyed on failure.
|
||||
let setup_result = maybe!({
|
||||
let pid = std::process::id();
|
||||
check_reply(
|
||||
|| "X11 ChangeProperty for _NET_WM_PID failed.",
|
||||
xcb.change_property32(
|
||||
xproto::PropMode::REPLACE,
|
||||
x_window,
|
||||
atoms._NET_WM_PID,
|
||||
xproto::AtomEnum::CARDINAL,
|
||||
&[pid],
|
||||
),
|
||||
)?;
|
||||
|
||||
if let Some(size) = params.window_min_size {
|
||||
let mut size_hints = WmSizeHints::new();
|
||||
let min_size = (size.width.0 as i32, size.height.0 as i32);
|
||||
|
||||
@@ -322,7 +322,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
let mut chars_with_shift = chars_for_modified_key(native_event.keyCode(), SHIFT_MOD);
|
||||
let always_use_cmd_layout = always_use_command_layout();
|
||||
|
||||
// Handle Dvorak+QWERTY / Russian / Armeniam
|
||||
// Handle Dvorak+QWERTY / Russian / Armenian
|
||||
if command || always_use_cmd_layout {
|
||||
let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), CMD_MOD);
|
||||
let chars_with_both =
|
||||
|
||||
@@ -47,6 +47,7 @@ pub(crate) fn handle_msg(
|
||||
WM_MOUSEMOVE => handle_mouse_move_msg(handle, lparam, wparam, state_ptr),
|
||||
WM_MOUSELEAVE => handle_mouse_leave_msg(state_ptr),
|
||||
WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr),
|
||||
WM_NCMOUSELEAVE => handle_nc_mouse_leave_msg(state_ptr),
|
||||
WM_NCLBUTTONDOWN => {
|
||||
handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
|
||||
}
|
||||
@@ -314,6 +315,18 @@ fn handle_mouse_move_msg(
|
||||
Some(1)
|
||||
}
|
||||
|
||||
fn handle_nc_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
lock.hovered = false;
|
||||
if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
|
||||
drop(lock);
|
||||
callback(false);
|
||||
state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
|
||||
}
|
||||
|
||||
Some(0)
|
||||
}
|
||||
|
||||
fn handle_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
lock.hovered = false;
|
||||
@@ -971,6 +984,27 @@ fn handle_nc_mouse_move_msg(
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
if !lock.hovered {
|
||||
lock.hovered = true;
|
||||
unsafe {
|
||||
TrackMouseEvent(&mut TRACKMOUSEEVENT {
|
||||
cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
|
||||
dwFlags: TME_LEAVE | TME_NONCLIENT,
|
||||
hwndTrack: handle,
|
||||
dwHoverTime: HOVER_DEFAULT,
|
||||
})
|
||||
.log_err()
|
||||
};
|
||||
if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
|
||||
drop(lock);
|
||||
callback(true);
|
||||
state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
|
||||
}
|
||||
} else {
|
||||
drop(lock);
|
||||
}
|
||||
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let scale_factor = lock.scale_factor;
|
||||
|
||||
@@ -81,6 +81,12 @@ impl<'a> PartialEq<&'a str> for SharedString {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SharedString> for SharedString {
|
||||
fn from(value: &SharedString) -> Self {
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedString> for Arc<str> {
|
||||
fn from(val: SharedString) -> Self {
|
||||
match val.0 {
|
||||
|
||||
@@ -356,7 +356,7 @@ impl WindowTextSystem {
|
||||
});
|
||||
}
|
||||
|
||||
let layout = self.layout_line(text.as_ref(), font_size, runs)?;
|
||||
let layout = self.layout_line(&text, font_size, runs)?;
|
||||
|
||||
Ok(ShapedLine {
|
||||
layout,
|
||||
@@ -483,12 +483,16 @@ impl WindowTextSystem {
|
||||
/// Subsets of the line can be styled independently with the `runs` parameter.
|
||||
/// Generally, you should prefer to use `TextLayout::shape_line` instead, which
|
||||
/// can be painted directly.
|
||||
pub fn layout_line(
|
||||
pub fn layout_line<Text>(
|
||||
&self,
|
||||
text: &str,
|
||||
text: Text,
|
||||
font_size: Pixels,
|
||||
runs: &[TextRun],
|
||||
) -> Result<Arc<LineLayout>> {
|
||||
) -> Result<Arc<LineLayout>>
|
||||
where
|
||||
Text: AsRef<str>,
|
||||
SharedString: From<Text>,
|
||||
{
|
||||
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
|
||||
for run in runs.iter() {
|
||||
let font_id = self.resolve_font(&run.font);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{point, px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
|
||||
use crate::{point, px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString, Size};
|
||||
use collections::FxHashMap;
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use smallvec::SmallVec;
|
||||
@@ -420,15 +420,19 @@ impl LineLayoutCache {
|
||||
curr_frame.used_wrapped_lines.clear();
|
||||
}
|
||||
|
||||
pub fn layout_wrapped_line(
|
||||
pub fn layout_wrapped_line<Text>(
|
||||
&self,
|
||||
text: &str,
|
||||
text: Text,
|
||||
font_size: Pixels,
|
||||
runs: &[FontRun],
|
||||
wrap_width: Option<Pixels>,
|
||||
) -> Arc<WrappedLineLayout> {
|
||||
) -> Arc<WrappedLineLayout>
|
||||
where
|
||||
Text: AsRef<str>,
|
||||
SharedString: From<Text>,
|
||||
{
|
||||
let key = &CacheKeyRef {
|
||||
text,
|
||||
text: text.as_ref(),
|
||||
font_size,
|
||||
runs,
|
||||
wrap_width,
|
||||
@@ -449,8 +453,8 @@ impl LineLayoutCache {
|
||||
layout
|
||||
} else {
|
||||
drop(current_frame);
|
||||
|
||||
let unwrapped_layout = self.layout_line(text, font_size, runs);
|
||||
let text = SharedString::from(text);
|
||||
let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs);
|
||||
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
|
||||
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
|
||||
} else {
|
||||
@@ -462,7 +466,7 @@ impl LineLayoutCache {
|
||||
wrap_width,
|
||||
});
|
||||
let key = Arc::new(CacheKey {
|
||||
text: text.into(),
|
||||
text,
|
||||
font_size,
|
||||
runs: SmallVec::from(runs),
|
||||
wrap_width,
|
||||
@@ -478,9 +482,18 @@ impl LineLayoutCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> Arc<LineLayout> {
|
||||
pub fn layout_line<Text>(
|
||||
&self,
|
||||
text: Text,
|
||||
font_size: Pixels,
|
||||
runs: &[FontRun],
|
||||
) -> Arc<LineLayout>
|
||||
where
|
||||
Text: AsRef<str>,
|
||||
SharedString: From<Text>,
|
||||
{
|
||||
let key = &CacheKeyRef {
|
||||
text,
|
||||
text: text.as_ref(),
|
||||
font_size,
|
||||
runs,
|
||||
wrap_width: None,
|
||||
@@ -497,9 +510,13 @@ impl LineLayoutCache {
|
||||
current_frame.used_lines.push(key);
|
||||
layout
|
||||
} else {
|
||||
let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
|
||||
let text = SharedString::from(text);
|
||||
let layout = Arc::new(
|
||||
self.platform_text_system
|
||||
.layout_line(&text, font_size, runs),
|
||||
);
|
||||
let key = Arc::new(CacheKey {
|
||||
text: text.into(),
|
||||
text,
|
||||
font_size,
|
||||
runs: SmallVec::from(runs),
|
||||
wrap_width: None,
|
||||
@@ -524,7 +541,7 @@ trait AsCacheKeyRef {
|
||||
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
struct CacheKey {
|
||||
text: String,
|
||||
text: SharedString,
|
||||
font_size: Pixels,
|
||||
runs: SmallVec<[FontRun; 1]>,
|
||||
wrap_width: Option<Pixels>,
|
||||
|
||||
@@ -4880,6 +4880,8 @@ pub enum ElementId {
|
||||
FocusHandle(FocusId),
|
||||
/// A combination of a name and an integer.
|
||||
NamedInteger(SharedString, usize),
|
||||
/// A path
|
||||
Path(Arc<std::path::Path>),
|
||||
}
|
||||
|
||||
impl Display for ElementId {
|
||||
@@ -4891,6 +4893,7 @@ impl Display for ElementId {
|
||||
ElementId::FocusHandle(_) => write!(f, "FocusHandle")?,
|
||||
ElementId::NamedInteger(s, i) => write!(f, "{}-{}", s, i)?,
|
||||
ElementId::Uuid(uuid) => write!(f, "{}", uuid)?,
|
||||
ElementId::Path(path) => write!(f, "{}", path.display())?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -4927,6 +4930,12 @@ impl From<SharedString> for ElementId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<std::path::Path>> for ElementId {
|
||||
fn from(path: Arc<std::path::Path>) -> Self {
|
||||
ElementId::Path(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for ElementId {
|
||||
fn from(name: &'static str) -> Self {
|
||||
ElementId::Name(name.into())
|
||||
|
||||
@@ -96,12 +96,18 @@ impl Item for ImageView {
|
||||
|
||||
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
|
||||
let project_path = self.image_item.read(cx).project_path(cx);
|
||||
|
||||
let label_color = if ItemSettings::get_global(cx).git_status {
|
||||
let git_status = self
|
||||
.project
|
||||
.read(cx)
|
||||
.project_path_git_status(&project_path, cx);
|
||||
|
||||
self.project
|
||||
.read(cx)
|
||||
.entry_for_path(&project_path, cx)
|
||||
.map(|entry| {
|
||||
entry_git_aware_label_color(entry.git_status, entry.is_ignored, params.selected)
|
||||
entry_git_aware_label_color(git_status, entry.is_ignored, params.selected)
|
||||
})
|
||||
.unwrap_or_else(|| params.text_color())
|
||||
} else {
|
||||
|
||||
@@ -208,10 +208,10 @@ pub struct Diagnostic {
|
||||
/// The human-readable message associated with this diagnostic.
|
||||
pub message: String,
|
||||
/// An id that identifies the group to which this diagnostic belongs.
|
||||
/// 0 is used for diagnostics that do not come from a language server.
|
||||
///
|
||||
/// When a language server produces a diagnostic with
|
||||
/// one or more associated diagnostics, those diagnostics are all
|
||||
/// assigned a single group ID.
|
||||
/// When a language server produces a diagnostic with one or more associated diagnostics, those
|
||||
/// diagnostics are all assigned a single group ID.
|
||||
pub group_id: usize,
|
||||
/// Whether this diagnostic is the primary diagnostic for its group.
|
||||
///
|
||||
@@ -3943,14 +3943,14 @@ impl BufferSnapshot {
|
||||
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
|
||||
where
|
||||
T: 'a + Clone + ToOffset,
|
||||
O: 'a + FromAnchor + Ord,
|
||||
O: 'a + FromAnchor,
|
||||
{
|
||||
let mut iterators: Vec<_> = self
|
||||
.diagnostics
|
||||
.iter()
|
||||
.map(|(_, collection)| {
|
||||
collection
|
||||
.range::<T, O>(search_range.clone(), self, true, reversed)
|
||||
.range::<T, text::Anchor>(search_range.clone(), self, true, reversed)
|
||||
.peekable()
|
||||
})
|
||||
.collect();
|
||||
@@ -3964,7 +3964,7 @@ impl BufferSnapshot {
|
||||
let cmp = a
|
||||
.range
|
||||
.start
|
||||
.cmp(&b.range.start)
|
||||
.cmp(&b.range.start, self)
|
||||
// when range is equal, sort by diagnostic severity
|
||||
.then(a.diagnostic.severity.cmp(&b.diagnostic.severity))
|
||||
// and stabilize order with group_id
|
||||
@@ -3975,7 +3975,13 @@ impl BufferSnapshot {
|
||||
cmp
|
||||
}
|
||||
})?;
|
||||
iterators[next_ix].next()
|
||||
iterators[next_ix]
|
||||
.next()
|
||||
.map(|DiagnosticEntry { range, diagnostic }| DiagnosticEntry {
|
||||
diagnostic,
|
||||
range: FromAnchor::from_anchor(&range.start, self)
|
||||
..FromAnchor::from_anchor(&range.end, self),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4013,12 +4019,12 @@ impl BufferSnapshot {
|
||||
}
|
||||
|
||||
/// Returns an iterator over the diagnostics for the given group.
|
||||
pub fn diagnostic_group<'a, O>(
|
||||
&'a self,
|
||||
pub fn diagnostic_group<O>(
|
||||
&self,
|
||||
group_id: usize,
|
||||
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
|
||||
) -> impl Iterator<Item = DiagnosticEntry<O>> + '_
|
||||
where
|
||||
O: 'a + FromAnchor,
|
||||
O: FromAnchor + 'static,
|
||||
{
|
||||
self.diagnostics
|
||||
.iter()
|
||||
|
||||
@@ -52,7 +52,7 @@ pub struct Summary {
|
||||
}
|
||||
|
||||
impl DiagnosticEntry<PointUtf16> {
|
||||
/// Returns a raw LSP diagnostic ssed to provide diagnostic context to LSP
|
||||
/// Returns a raw LSP diagnostic used to provide diagnostic context to LSP
|
||||
/// codeAction request
|
||||
pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
|
||||
let code = self
|
||||
|
||||
@@ -1850,16 +1850,20 @@ pub fn point_from_lsp(point: lsp::Position) -> Unclipped<PointUtf16> {
|
||||
}
|
||||
|
||||
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
|
||||
lsp::Range {
|
||||
start: point_to_lsp(range.start),
|
||||
end: point_to_lsp(range.end),
|
||||
let mut start = point_to_lsp(range.start);
|
||||
let mut end = point_to_lsp(range.end);
|
||||
if start > end {
|
||||
log::error!("range_to_lsp called with inverted range {start:?}-{end:?}");
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
lsp::Range { start, end }
|
||||
}
|
||||
|
||||
pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> {
|
||||
let mut start = point_from_lsp(range.start);
|
||||
let mut end = point_from_lsp(range.end);
|
||||
if start > end {
|
||||
log::warn!("range_from_lsp called with inverted range {start:?}-{end:?}");
|
||||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
start..end
|
||||
|
||||
@@ -68,6 +68,7 @@ impl CloudModel {
|
||||
anthropic::Model::Claude3Opus
|
||||
| anthropic::Model::Claude3Sonnet
|
||||
| anthropic::Model::Claude3Haiku
|
||||
| anthropic::Model::Claude3_5Haiku
|
||||
| anthropic::Model::Custom { .. } => {
|
||||
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ pub struct AvailableModel {
|
||||
pub cache_configuration: Option<LanguageModelCacheConfiguration>,
|
||||
pub max_output_tokens: Option<u32>,
|
||||
pub default_temperature: Option<f32>,
|
||||
#[serde(default)]
|
||||
pub extra_beta_headers: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct AnthropicLanguageModelProvider {
|
||||
@@ -202,6 +204,7 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
|
||||
}),
|
||||
max_output_tokens: model.max_output_tokens,
|
||||
default_temperature: model.default_temperature,
|
||||
extra_beta_headers: model.extra_beta_headers.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,9 @@ pub struct AvailableModel {
|
||||
pub cache_configuration: Option<LanguageModelCacheConfiguration>,
|
||||
/// The default temperature to use for this model.
|
||||
pub default_temperature: Option<f32>,
|
||||
/// Any extra beta headers to provide when using the model.
|
||||
#[serde(default)]
|
||||
pub extra_beta_headers: Vec<String>,
|
||||
}
|
||||
|
||||
struct GlobalRefreshLlmTokenListener(Model<RefreshLlmTokenListener>);
|
||||
@@ -323,6 +326,7 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
|
||||
}),
|
||||
default_temperature: model.default_temperature,
|
||||
max_output_tokens: model.max_output_tokens,
|
||||
extra_beta_headers: model.extra_beta_headers.clone(),
|
||||
}),
|
||||
AvailableProvider::OpenAi => CloudModel::OpenAi(open_ai::Model::Custom {
|
||||
name: model.name.clone(),
|
||||
|
||||
@@ -97,6 +97,7 @@ impl AnthropicSettingsContent {
|
||||
cache_configuration,
|
||||
max_output_tokens,
|
||||
default_temperature,
|
||||
extra_beta_headers,
|
||||
} => Some(provider::anthropic::AvailableModel {
|
||||
name,
|
||||
display_name,
|
||||
@@ -111,6 +112,7 @@ impl AnthropicSettingsContent {
|
||||
),
|
||||
max_output_tokens,
|
||||
default_temperature,
|
||||
extra_beta_headers,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
|
||||
@@ -128,13 +128,20 @@ impl SyntaxTreeView {
|
||||
fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
|
||||
// Find which excerpt the cursor is in, and the position within that excerpted buffer.
|
||||
let editor_state = self.editor.as_mut()?;
|
||||
let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor_state
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
let (excerpt, buffer, range) = editor_state.editor.update(cx, |editor, cx| {
|
||||
let selection = editor.selections.last::<usize>(cx);
|
||||
let selection_range = editor.selections.last::<usize>(cx).range();
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.range_to_buffer_ranges(selection_range, cx)
|
||||
.pop()
|
||||
let selection_head = selection.head();
|
||||
let multi_buffer = editor.buffer().read(cx);
|
||||
let excerpt = snapshot
|
||||
.buffer_snapshot
|
||||
.excerpt_containing(selection_head..selection_head)?;
|
||||
let range = excerpt.map_range_to_buffer(selection_range);
|
||||
let buffer = multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone();
|
||||
Some((excerpt, buffer, range))
|
||||
})?;
|
||||
|
||||
// If the cursor has moved into a different excerpt, retrieve a new syntax layer
|
||||
@@ -143,16 +150,16 @@ impl SyntaxTreeView {
|
||||
.active_buffer
|
||||
.get_or_insert_with(|| BufferState {
|
||||
buffer: buffer.clone(),
|
||||
excerpt_id,
|
||||
excerpt_id: excerpt.id(),
|
||||
active_layer: None,
|
||||
});
|
||||
let mut prev_layer = None;
|
||||
if did_reparse {
|
||||
prev_layer = buffer_state.active_layer.take();
|
||||
}
|
||||
if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt_id {
|
||||
if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt.id() {
|
||||
buffer_state.buffer = buffer.clone();
|
||||
buffer_state.excerpt_id = excerpt_id;
|
||||
buffer_state.excerpt_id = excerpt.id();
|
||||
buffer_state.active_layer = None;
|
||||
}
|
||||
|
||||
|
||||
@@ -770,6 +770,12 @@ impl PyLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
const BINARY_DIR: &str = if cfg!(target_os = "windows") {
|
||||
"Scripts"
|
||||
} else {
|
||||
"bin"
|
||||
};
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for PyLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
@@ -811,7 +817,7 @@ impl LspAdapter for PyLspAdapter {
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
|
||||
let pip_path = venv.join("bin").join("pip3");
|
||||
let pip_path = venv.join(BINARY_DIR).join("pip3");
|
||||
ensure!(
|
||||
util::command::new_smol_command(pip_path.as_path())
|
||||
.arg("install")
|
||||
@@ -842,7 +848,7 @@ impl LspAdapter for PyLspAdapter {
|
||||
.success(),
|
||||
"pylsp-mypy installation failed"
|
||||
);
|
||||
let pylsp = venv.join("bin").join("pylsp");
|
||||
let pylsp = venv.join(BINARY_DIR).join("pylsp");
|
||||
Ok(LanguageServerBinary {
|
||||
path: pylsp,
|
||||
env: None,
|
||||
@@ -856,7 +862,7 @@ impl LspAdapter for PyLspAdapter {
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let venv = self.base_venv(delegate).await.ok()?;
|
||||
let pylsp = venv.join("bin").join("pylsp");
|
||||
let pylsp = venv.join(BINARY_DIR).join("pylsp");
|
||||
Some(LanguageServerBinary {
|
||||
path: pylsp,
|
||||
env: None,
|
||||
|
||||
@@ -6,8 +6,8 @@ use gpui::{
|
||||
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
|
||||
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
|
||||
Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
|
||||
TextStyle, TextStyleRefinement, View,
|
||||
Point, Render, Stateful, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout,
|
||||
TextRun, TextStyle, TextStyleRefinement, View,
|
||||
};
|
||||
use language::{Language, LanguageRegistry, Rope};
|
||||
use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
|
||||
@@ -785,8 +785,52 @@ impl IntoElement for MarkdownElement {
|
||||
}
|
||||
}
|
||||
|
||||
enum AnyDiv {
|
||||
Div(Div),
|
||||
Stateful(Stateful<Div>),
|
||||
}
|
||||
|
||||
impl AnyDiv {
|
||||
fn into_any_element(self) -> AnyElement {
|
||||
match self {
|
||||
Self::Div(div) => div.into_any_element(),
|
||||
Self::Stateful(div) => div.into_any_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Div> for AnyDiv {
|
||||
fn from(value: Div) -> Self {
|
||||
Self::Div(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Stateful<Div>> for AnyDiv {
|
||||
fn from(value: Stateful<Div>) -> Self {
|
||||
Self::Stateful(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for AnyDiv {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
match self {
|
||||
Self::Div(div) => div.style(),
|
||||
Self::Stateful(div) => div.style(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for AnyDiv {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
match self {
|
||||
Self::Div(div) => div.extend(elements),
|
||||
Self::Stateful(div) => div.extend(elements),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MarkdownElementBuilder {
|
||||
div_stack: Vec<Div>,
|
||||
div_stack: Vec<AnyDiv>,
|
||||
rendered_lines: Vec<RenderedLine>,
|
||||
pending_line: PendingLine,
|
||||
rendered_links: Vec<RenderedLink>,
|
||||
@@ -812,7 +856,7 @@ struct ListStackEntry {
|
||||
impl MarkdownElementBuilder {
|
||||
fn new(base_text_style: TextStyle, syntax_theme: Arc<SyntaxTheme>) -> Self {
|
||||
Self {
|
||||
div_stack: vec![div().debug_selector(|| "inner".into())],
|
||||
div_stack: vec![div().debug_selector(|| "inner".into()).into()],
|
||||
rendered_lines: Vec::new(),
|
||||
pending_line: PendingLine::default(),
|
||||
rendered_links: Vec::new(),
|
||||
@@ -841,11 +885,12 @@ impl MarkdownElementBuilder {
|
||||
self.text_style_stack.pop();
|
||||
}
|
||||
|
||||
fn push_div(&mut self, mut div: Div, range: &Range<usize>, markdown_end: usize) {
|
||||
fn push_div(&mut self, div: impl Into<AnyDiv>, range: &Range<usize>, markdown_end: usize) {
|
||||
let mut div = div.into();
|
||||
self.flush_text();
|
||||
|
||||
if range.start == 0 {
|
||||
//first element, remove top margin
|
||||
// Remove the top margin on the first element.
|
||||
div.style().refine(&StyleRefinement {
|
||||
margin: gpui::EdgesRefinement {
|
||||
top: Some(Length::Definite(px(0.).into())),
|
||||
@@ -856,6 +901,7 @@ impl MarkdownElementBuilder {
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
if range.end == markdown_end {
|
||||
div.style().refine(&StyleRefinement {
|
||||
margin: gpui::EdgesRefinement {
|
||||
@@ -867,12 +913,13 @@ impl MarkdownElementBuilder {
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
self.div_stack.push(div);
|
||||
}
|
||||
|
||||
fn pop_div(&mut self) {
|
||||
self.flush_text();
|
||||
let div = self.div_stack.pop().unwrap().into_any();
|
||||
let div = self.div_stack.pop().unwrap().into_any_element();
|
||||
self.div_stack.last_mut().unwrap().extend(iter::once(div));
|
||||
}
|
||||
|
||||
@@ -973,7 +1020,7 @@ impl MarkdownElementBuilder {
|
||||
debug_assert_eq!(self.div_stack.len(), 1);
|
||||
self.flush_text();
|
||||
RenderedMarkdown {
|
||||
element: self.div_stack.pop().unwrap().into_any(),
|
||||
element: self.div_stack.pop().unwrap().into_any_element(),
|
||||
text: RenderedText {
|
||||
lines: self.rendered_lines.into(),
|
||||
links: self.rendered_links.into(),
|
||||
|
||||
@@ -1667,42 +1667,6 @@ impl MultiBuffer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn range_to_buffer_ranges<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
cx: &AppContext,
|
||||
) -> Vec<(Model<Buffer>, Range<usize>, ExcerptId)> {
|
||||
let snapshot = self.read(cx);
|
||||
let start = range.start.to_offset(&snapshot);
|
||||
let end = range.end.to_offset(&snapshot);
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
|
||||
cursor.seek(&start, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
}
|
||||
|
||||
while let Some(excerpt) = cursor.item() {
|
||||
if *cursor.start() > end {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut end_before_newline = cursor.end(&());
|
||||
if excerpt.has_trailing_newline {
|
||||
end_before_newline -= 1;
|
||||
}
|
||||
let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||
let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start());
|
||||
let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start());
|
||||
let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone();
|
||||
result.push((buffer, start..end, excerpt.id));
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn remove_excerpts(
|
||||
&mut self,
|
||||
excerpt_ids: impl IntoIterator<Item = ExcerptId>,
|
||||
@@ -3907,35 +3871,43 @@ impl MultiBufferSnapshot {
|
||||
.any(|excerpt| excerpt.buffer.has_diagnostics())
|
||||
}
|
||||
|
||||
pub fn diagnostic_group<'a, O>(
|
||||
&'a self,
|
||||
pub fn diagnostic_group(
|
||||
&self,
|
||||
group_id: usize,
|
||||
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
|
||||
where
|
||||
O: text::FromAnchor + 'a,
|
||||
{
|
||||
self.as_singleton()
|
||||
.into_iter()
|
||||
.flat_map(move |(_, _, buffer)| buffer.diagnostic_group(group_id))
|
||||
) -> impl Iterator<Item = DiagnosticEntry<Anchor>> + '_ {
|
||||
self.all_excerpts().flat_map(move |excerpt| {
|
||||
excerpt.buffer().diagnostic_group(group_id).map(
|
||||
move |DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
|
||||
diagnostic,
|
||||
range: self.anchor_in_excerpt(excerpt.id(), range.start).unwrap()
|
||||
..self.anchor_in_excerpt(excerpt.id(), range.end).unwrap(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn diagnostics_in_range<'a, T, O>(
|
||||
pub fn diagnostics_in_range<'a, T>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
reversed: bool,
|
||||
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
|
||||
) -> impl Iterator<Item = DiagnosticEntry<Anchor>> + 'a
|
||||
where
|
||||
T: 'a + ToOffset,
|
||||
O: 'a + text::FromAnchor + Ord,
|
||||
{
|
||||
self.as_singleton()
|
||||
.into_iter()
|
||||
.flat_map(move |(_, _, buffer)| {
|
||||
buffer.diagnostics_in_range(
|
||||
range.start.to_offset(self)..range.end.to_offset(self),
|
||||
reversed,
|
||||
)
|
||||
})
|
||||
let mut ranges = self.range_to_buffer_ranges(range).collect::<Vec<_>>();
|
||||
if reversed {
|
||||
ranges.reverse();
|
||||
}
|
||||
ranges.into_iter().flat_map(move |(excerpt, range)| {
|
||||
let excerpt_id = excerpt.id();
|
||||
excerpt.buffer().diagnostics_in_range(range, reversed).map(
|
||||
move |DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
|
||||
diagnostic,
|
||||
range: self.anchor_in_excerpt(excerpt_id, range.start).unwrap()
|
||||
..self.anchor_in_excerpt(excerpt_id, range.end).unwrap(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn syntax_ancestor<T: ToOffset>(
|
||||
@@ -4185,44 +4157,60 @@ impl MultiBufferSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns excerpts overlapping the given ranges. If range spans multiple excerpts returns one range for each excerpt
|
||||
/// Returns excerpts overlapping the given range. If range spans multiple excerpts returns one
|
||||
/// range for each excerpt.
|
||||
///
|
||||
/// The ranges are specified in the coordinate space of the multibuffer, not the individual excerpted buffers.
|
||||
/// Each returned excerpt's range is in the coordinate space of its source buffer.
|
||||
pub fn excerpts_in_ranges(
|
||||
pub fn range_to_buffer_ranges<T: ToOffset>(
|
||||
&self,
|
||||
ranges: impl IntoIterator<Item = Range<Anchor>>,
|
||||
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, Range<usize>)> {
|
||||
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
|
||||
range: Range<T>,
|
||||
) -> impl Iterator<Item = (MultiBufferExcerpt<'_>, Range<usize>)> {
|
||||
self.disjoint_ranges_to_buffer_ranges(iter::once(range))
|
||||
}
|
||||
|
||||
/// Returns excerpts overlapping the given ranges, which must be disjoint and sorted by start position.
|
||||
/// If range spans multiple excerpts returns one range for each excerpt
|
||||
///
|
||||
/// The ranges are specified in the coordinate space of the multibuffer, not the individual excerpted buffers.
|
||||
/// Each returned excerpt's range is in the coordinate space of its source buffer.
|
||||
pub fn disjoint_ranges_to_buffer_ranges<T: ToOffset>(
|
||||
&self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
) -> impl Iterator<Item = (MultiBufferExcerpt<'_>, Range<usize>)> {
|
||||
let mut ranges = ranges.into_iter();
|
||||
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
|
||||
cursor.next(&());
|
||||
let mut current_range = ranges.next();
|
||||
iter::from_fn(move || {
|
||||
let range = current_range.clone()?;
|
||||
if range.start >= cursor.end(&()).0 {
|
||||
cursor.seek_forward(&range.start, Bias::Right, &());
|
||||
if range.start == self.len() {
|
||||
let range = current_range.as_ref()?;
|
||||
let start = range.start.to_offset(self);
|
||||
let end = range.end.to_offset(self);
|
||||
if start >= cursor.end(&()).0 {
|
||||
cursor.seek_forward(&start, Bias::Right, &());
|
||||
if start == self.len() {
|
||||
cursor.prev(&());
|
||||
}
|
||||
}
|
||||
|
||||
let excerpt = cursor.item()?;
|
||||
let range_start_in_excerpt = cmp::max(range.start, cursor.start().0);
|
||||
let range_start_in_excerpt = cmp::max(start, cursor.start().0);
|
||||
let range_end_in_excerpt = if excerpt.has_trailing_newline {
|
||||
cmp::min(range.end, cursor.end(&()).0 - 1)
|
||||
cmp::min(end, cursor.end(&()).0 - 1)
|
||||
} else {
|
||||
cmp::min(range.end, cursor.end(&()).0)
|
||||
cmp::min(end, cursor.end(&()).0)
|
||||
};
|
||||
let buffer_range = MultiBufferExcerpt::new(excerpt, *cursor.start())
|
||||
let multi_buffer_excerpt = MultiBufferExcerpt::new(excerpt, *cursor.start());
|
||||
let buffer_range = multi_buffer_excerpt
|
||||
.map_range_to_buffer(range_start_in_excerpt..range_end_in_excerpt);
|
||||
|
||||
if range.end > cursor.end(&()).0 {
|
||||
if end > cursor.end(&()).0 {
|
||||
cursor.next(&());
|
||||
} else {
|
||||
current_range = ranges.next();
|
||||
}
|
||||
|
||||
Some((excerpt.id, &excerpt.buffer, buffer_range))
|
||||
Some((multi_buffer_excerpt, buffer_range))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4664,6 +4652,10 @@ impl<'a> MultiBufferExcerpt<'a> {
|
||||
self.excerpt.id
|
||||
}
|
||||
|
||||
pub fn buffer_id(&self) -> BufferId {
|
||||
self.excerpt.buffer_id
|
||||
}
|
||||
|
||||
pub fn start_anchor(&self) -> Anchor {
|
||||
Anchor {
|
||||
buffer_id: Some(self.excerpt.buffer_id),
|
||||
|
||||
@@ -1234,14 +1234,15 @@ fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
|
||||
start_ix..end_ix
|
||||
);
|
||||
|
||||
let excerpted_buffer_ranges = multibuffer
|
||||
.read(cx)
|
||||
.range_to_buffer_ranges(start_ix..end_ix, cx);
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
let excerpted_buffer_ranges = snapshot
|
||||
.range_to_buffer_ranges(start_ix..end_ix)
|
||||
.collect::<Vec<_>>();
|
||||
let excerpted_buffers_text = excerpted_buffer_ranges
|
||||
.iter()
|
||||
.map(|(buffer, buffer_range, _)| {
|
||||
buffer
|
||||
.read(cx)
|
||||
.map(|(excerpt, buffer_range)| {
|
||||
excerpt
|
||||
.buffer()
|
||||
.text_for_range(buffer_range.clone())
|
||||
.collect::<String>()
|
||||
})
|
||||
@@ -1489,7 +1490,7 @@ fn test_excerpts_in_ranges_no_ranges(cx: &mut AppContext) {
|
||||
|
||||
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
|
||||
let mut excerpts = snapshot.excerpts_in_ranges(iter::from_fn(|| None));
|
||||
let mut excerpts = snapshot.disjoint_ranges_to_buffer_ranges::<usize>(iter::from_fn(|| None));
|
||||
|
||||
assert!(excerpts.next().is_none());
|
||||
}
|
||||
@@ -1588,12 +1589,12 @@ fn test_excerpts_in_ranges_range_inside_the_excerpt(cx: &mut AppContext) {
|
||||
)];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(vec![range.clone()].into_iter())
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -1653,12 +1654,12 @@ fn test_excerpts_in_ranges_range_crosses_excerpts_boundary(cx: &mut AppContext)
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -1729,12 +1730,12 @@ fn test_excerpts_in_ranges_range_encloses_excerpt(cx: &mut AppContext) {
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(vec![expected_range.clone()].into_iter())
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -1795,12 +1796,12 @@ fn test_excerpts_in_ranges_multiple_ranges(cx: &mut AppContext) {
|
||||
});
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(ranges)
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(ranges)
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
@@ -1861,12 +1862,12 @@ fn test_excerpts_in_ranges_range_ends_at_excerpt_end(cx: &mut AppContext) {
|
||||
];
|
||||
|
||||
let excerpts = snapshot
|
||||
.excerpts_in_ranges(ranges.into_iter())
|
||||
.map(|(excerpt_id, buffer, actual_range)| {
|
||||
.disjoint_ranges_to_buffer_ranges(ranges.into_iter())
|
||||
.map(|(excerpt, actual_range)| {
|
||||
(
|
||||
excerpt_id,
|
||||
buffer.remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
|
||||
excerpt.id(),
|
||||
excerpt.buffer().remote_id(),
|
||||
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
@@ -19,8 +19,8 @@ db.workspace = true
|
||||
editor.workspace = true
|
||||
file_icons.workspace = true
|
||||
fuzzy.workspace = true
|
||||
itertools.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
@@ -36,8 +36,8 @@ smol.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
worktree.workspace = true
|
||||
workspace.workspace = true
|
||||
worktree.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
search = { workspace = true, features = ["test-support"] }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -569,9 +569,9 @@ impl LocalBufferStore {
|
||||
buffer_change_sets
|
||||
.into_iter()
|
||||
.filter_map(|(change_set, buffer_snapshot, path)| {
|
||||
let (repo_entry, local_repo_entry) = snapshot.repo_for_path(&path)?;
|
||||
let relative_path = repo_entry.relativize(&snapshot, &path).ok()?;
|
||||
let base_text = local_repo_entry.repo().load_index_text(&relative_path);
|
||||
let local_repo = snapshot.local_repo_for_path(&path)?;
|
||||
let relative_path = local_repo.relativize(&path).ok()?;
|
||||
let base_text = local_repo.repo().load_index_text(&relative_path);
|
||||
Some((change_set, buffer_snapshot, base_text))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@@ -1161,16 +1161,16 @@ impl BufferStore {
|
||||
Worktree::Local(worktree) => {
|
||||
let worktree = worktree.snapshot();
|
||||
let blame_params = maybe!({
|
||||
let (repo_entry, local_repo_entry) = match worktree.repo_for_path(&file.path) {
|
||||
let local_repo = match worktree.local_repo_for_path(&file.path) {
|
||||
Some(repo_for_path) => repo_for_path,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let relative_path = repo_entry
|
||||
.relativize(&worktree, &file.path)
|
||||
let relative_path = local_repo
|
||||
.relativize(&file.path)
|
||||
.context("failed to relativize buffer path")?;
|
||||
|
||||
let repo = local_repo_entry.repo().clone();
|
||||
let repo = local_repo.repo().clone();
|
||||
|
||||
let content = match version {
|
||||
Some(version) => buffer.rope_for_version(&version).clone(),
|
||||
@@ -1247,7 +1247,7 @@ impl BufferStore {
|
||||
});
|
||||
};
|
||||
|
||||
let path = match repo_entry.relativize(worktree, file.path()) {
|
||||
let path = match repo_entry.relativize(file.path()) {
|
||||
Ok(RepoPath(path)) => path,
|
||||
Err(e) => return Task::ready(Err(e)),
|
||||
};
|
||||
|
||||
@@ -87,9 +87,8 @@ pub use language::Location;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
|
||||
pub use worktree::{
|
||||
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, RepositoryEntry,
|
||||
UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,
|
||||
FS_WATCH_LATENCY,
|
||||
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, UpdatedEntriesSet,
|
||||
UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings, FS_WATCH_LATENCY,
|
||||
};
|
||||
|
||||
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
@@ -2961,7 +2960,7 @@ impl LspStore {
|
||||
http_client,
|
||||
fs,
|
||||
yarn,
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
next_diagnostic_group_id: 1,
|
||||
diagnostics: Default::default(),
|
||||
_subscription: cx.on_app_quit(|this, cx| {
|
||||
this.as_local_mut().unwrap().shutdown_language_servers(cx)
|
||||
@@ -3109,6 +3108,7 @@ impl LspStore {
|
||||
WorktreeStoreEvent::WorktreeUpdateSent(worktree) => {
|
||||
worktree.update(cx, |worktree, _cx| self.send_diagnostic_summaries(worktree));
|
||||
}
|
||||
WorktreeStoreEvent::GitRepositoryUpdated => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5586,13 +5586,13 @@ impl LspStore {
|
||||
<R::LspRequest as lsp::request::Request>::Result: Send,
|
||||
<R::LspRequest as lsp::request::Request>::Params: Send,
|
||||
{
|
||||
let Some(local) = self.as_local() else {
|
||||
return Task::ready(Vec::new());
|
||||
};
|
||||
debug_assert!(self.upstream_client().is_none());
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let scope = position.and_then(|position| snapshot.language_scope_at(position));
|
||||
let server_ids = local
|
||||
let server_ids = self
|
||||
.as_local()
|
||||
.unwrap()
|
||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||
.filter(|(adapter, _)| {
|
||||
scope
|
||||
|
||||
@@ -39,7 +39,10 @@ use futures::{
|
||||
pub use image_store::{ImageItem, ImageStore};
|
||||
use image_store::{ImageItemEvent, ImageStoreEvent};
|
||||
|
||||
use git::{blame::Blame, repository::GitRepository};
|
||||
use git::{
|
||||
blame::Blame,
|
||||
repository::{GitFileStatus, GitRepository},
|
||||
};
|
||||
use gpui::{
|
||||
AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context as _, EventEmitter, Hsla,
|
||||
Model, ModelContext, SharedString, Task, WeakModel, WindowContext,
|
||||
@@ -95,9 +98,8 @@ pub use task_inventory::{
|
||||
BasicContextProvider, ContextProviderWithTasks, Inventory, TaskSourceKind,
|
||||
};
|
||||
pub use worktree::{
|
||||
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, RepositoryEntry,
|
||||
UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,
|
||||
FS_WATCH_LATENCY,
|
||||
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, UpdatedEntriesSet,
|
||||
UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings, FS_WATCH_LATENCY,
|
||||
};
|
||||
|
||||
pub use buffer_store::ProjectTransaction;
|
||||
@@ -242,6 +244,7 @@ pub enum Event {
|
||||
ActivateProjectPanel,
|
||||
WorktreeAdded(WorktreeId),
|
||||
WorktreeOrderChanged,
|
||||
GitRepositoryUpdated,
|
||||
WorktreeRemoved(WorktreeId),
|
||||
WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
|
||||
WorktreeUpdatedGitRepositories(WorktreeId),
|
||||
@@ -1433,6 +1436,15 @@ impl Project {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn project_path_git_status(
|
||||
&self,
|
||||
project_path: &ProjectPath,
|
||||
cx: &AppContext,
|
||||
) -> Option<GitFileStatus> {
|
||||
self.worktree_for_id(project_path.worktree_id, cx)
|
||||
.and_then(|worktree| worktree.read(cx).status_for_file(&project_path.path))
|
||||
}
|
||||
|
||||
pub fn visibility_for_paths(&self, paths: &[PathBuf], cx: &AppContext) -> Option<bool> {
|
||||
paths
|
||||
.iter()
|
||||
@@ -2295,6 +2307,7 @@ impl Project {
|
||||
}
|
||||
WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
|
||||
WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
|
||||
WorktreeStoreEvent::GitRepositoryUpdated => cx.emit(Event::GitRepositoryUpdated),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3516,17 +3529,6 @@ impl Project {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_repo(
|
||||
&self,
|
||||
project_path: &ProjectPath,
|
||||
cx: &AppContext,
|
||||
) -> Option<Arc<dyn GitRepository>> {
|
||||
self.worktree_for_id(project_path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.as_local()?
|
||||
.local_git_repo(&project_path.path)
|
||||
}
|
||||
|
||||
pub fn get_first_worktree_root_repo(&self, cx: &AppContext) -> Option<Arc<dyn GitRepository>> {
|
||||
let worktree = self.visible_worktrees(cx).next()?.read(cx).as_local()?;
|
||||
let root_entry = worktree.root_git_entry()?;
|
||||
@@ -4426,8 +4428,10 @@ impl Completion {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort_worktree_entries(entries: &mut [Entry]) {
|
||||
pub fn sort_worktree_entries(entries: &mut [impl AsRef<Entry>]) {
|
||||
entries.sort_by(|entry_a, entry_b| {
|
||||
let entry_a = entry_a.as_ref();
|
||||
let entry_b = entry_b.as_ref();
|
||||
compare_paths(
|
||||
(&entry_a.path, entry_a.is_file()),
|
||||
(&entry_b.path, entry_b.is_file()),
|
||||
|
||||
@@ -1312,7 +1312,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||
diagnostic: Diagnostic {
|
||||
severity: lsp::DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'A'".to_string(),
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -1828,7 +1828,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'BB'".to_string(),
|
||||
is_disk_based: true,
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1840,7 +1840,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'CCC'".to_string(),
|
||||
is_disk_based: true,
|
||||
group_id: 2,
|
||||
group_id: 3,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -1906,7 +1906,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
message: "unreachable statement".to_string(),
|
||||
is_disk_based: true,
|
||||
group_id: 4,
|
||||
group_id: 5,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -1918,7 +1918,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'A'".to_string(),
|
||||
is_disk_based: true,
|
||||
group_id: 3,
|
||||
group_id: 4,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1998,7 +1998,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
message: "undefined variable 'A'".to_string(),
|
||||
is_disk_based: true,
|
||||
group_id: 6,
|
||||
group_id: 7,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -2010,7 +2010,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "undefined variable 'BB'".to_string(),
|
||||
is_disk_based: true,
|
||||
group_id: 5,
|
||||
group_id: 6,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -3838,7 +3838,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
message: "error 1".to_string(),
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -3848,27 +3848,27 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 1 hint 1".to_string(),
|
||||
group_id: 2,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: Point::new(1, 13)..Point::new(1, 15),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 1".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: Point::new(1, 13)..Point::new(1, 15),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 1".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: Point::new(1, 13)..Point::new(1, 15),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 2".to_string(),
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -3878,43 +3878,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "error 2".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
|
||||
&[
|
||||
DiagnosticEntry {
|
||||
range: Point::new(1, 13)..Point::new(1, 15),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 1".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: Point::new(1, 13)..Point::new(1, 15),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 2".to_string(),
|
||||
group_id: 0,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: Point::new(2, 8)..Point::new(2, 17),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "error 2".to_string(),
|
||||
group_id: 0,
|
||||
group_id: 1,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -3924,13 +3888,49 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
assert_eq!(
|
||||
buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
|
||||
&[
|
||||
DiagnosticEntry {
|
||||
range: Point::new(1, 13)..Point::new(1, 15),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 1".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: Point::new(1, 13)..Point::new(1, 15),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 2 hint 2".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
DiagnosticEntry {
|
||||
range: Point::new(2, 8)..Point::new(2, 17),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::ERROR,
|
||||
message: "error 2".to_string(),
|
||||
group_id: 1,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
buffer.diagnostic_group::<Point>(2).collect::<Vec<_>>(),
|
||||
&[
|
||||
DiagnosticEntry {
|
||||
range: Point::new(1, 8)..Point::new(1, 9),
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::WARNING,
|
||||
message: "error 1".to_string(),
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
is_primary: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -3940,7 +3940,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
diagnostic: Diagnostic {
|
||||
severity: DiagnosticSeverity::HINT,
|
||||
message: "error 1 hint 1".to_string(),
|
||||
group_id: 1,
|
||||
group_id: 2,
|
||||
is_primary: false,
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ impl Inventory {
|
||||
/// Pulls its task sources relevant to the worktree and the language given and resolves them with the [`TaskContext`] given.
|
||||
/// Joins the new resolutions with the resolved tasks that were used (spawned) before,
|
||||
/// orders them so that the most recently used come first, all equally used ones are ordered so that the most specific tasks come first.
|
||||
/// Deduplicates the tasks by their labels and contenxt and splits the ordered list into two: used tasks and the rest, newly resolved tasks.
|
||||
/// Deduplicates the tasks by their labels and context and splits the ordered list into two: used tasks and the rest, newly resolved tasks.
|
||||
pub fn used_and_current_resolved_tasks(
|
||||
&self,
|
||||
worktree: Option<WorktreeId>,
|
||||
|
||||
@@ -62,6 +62,7 @@ pub enum WorktreeStoreEvent {
|
||||
WorktreeReleased(EntityId, WorktreeId),
|
||||
WorktreeOrderChanged,
|
||||
WorktreeUpdateSent(Model<Worktree>),
|
||||
GitRepositoryUpdated,
|
||||
}
|
||||
|
||||
impl EventEmitter<WorktreeStoreEvent> for WorktreeStore {}
|
||||
@@ -322,6 +323,7 @@ impl WorktreeStore {
|
||||
let worktree = Worktree::local(path.clone(), visible, fs, next_entry_id, &mut cx).await;
|
||||
|
||||
let worktree = worktree?;
|
||||
|
||||
this.update(&mut cx, |this, cx| this.add(&worktree, cx))?;
|
||||
|
||||
if visible {
|
||||
@@ -374,6 +376,17 @@ impl WorktreeStore {
|
||||
this.send_project_updates(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.subscribe(
|
||||
worktree,
|
||||
|_this, _, event: &worktree::Event, cx| match event {
|
||||
worktree::Event::UpdatedGitRepositories(_) => {
|
||||
cx.emit(WorktreeStoreEvent::GitRepositoryUpdated);
|
||||
}
|
||||
worktree::Event::DeletedEntry(_) | worktree::Event::UpdatedEntries(_) => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
|
||||
@@ -583,11 +596,11 @@ impl WorktreeStore {
|
||||
pub fn shared(
|
||||
&mut self,
|
||||
remote_id: u64,
|
||||
downsteam_client: AnyProtoClient,
|
||||
downstream_client: AnyProtoClient,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.retain_worktrees = true;
|
||||
self.downstream_client = Some((downsteam_client, remote_id));
|
||||
self.downstream_client = Some((downstream_client, remote_id));
|
||||
|
||||
// When shared, retain all worktrees
|
||||
for worktree_handle in self.worktrees.iter_mut() {
|
||||
|
||||
@@ -63,7 +63,7 @@ use workspace::{
|
||||
notifications::{DetachAndPromptErr, NotifyTaskExt},
|
||||
DraggedSelection, OpenInTerminal, PreviewTabsSettings, SelectedEntry, Workspace,
|
||||
};
|
||||
use worktree::CreatedEntry;
|
||||
use worktree::{CreatedEntry, GitEntry, GitEntryRef};
|
||||
|
||||
const PROJECT_PANEL_KEY: &str = "ProjectPanel";
|
||||
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
|
||||
@@ -76,7 +76,7 @@ pub struct ProjectPanel {
|
||||
// An update loop that keeps incrementing/decrementing scroll offset while there is a dragged entry that's
|
||||
// hovered over the start/end of a list.
|
||||
hover_scroll_task: Option<Task<()>>,
|
||||
visible_entries: Vec<(WorktreeId, Vec<Entry>, OnceCell<HashSet<Arc<Path>>>)>,
|
||||
visible_entries: Vec<(WorktreeId, Vec<GitEntry>, OnceCell<HashSet<Arc<Path>>>)>,
|
||||
/// Maps from leaf project entry ID to the currently selected ancestor.
|
||||
/// Relevant only for auto-fold dirs, where a single project panel entry may actually consist of several
|
||||
/// project entries (and all non-leaf nodes are guaranteed to be directories).
|
||||
@@ -311,7 +311,8 @@ impl ProjectPanel {
|
||||
this.update_visible_entries(None, cx);
|
||||
cx.notify();
|
||||
}
|
||||
project::Event::WorktreeUpdatedEntries(_, _)
|
||||
project::Event::GitRepositoryUpdated
|
||||
| project::Event::WorktreeUpdatedEntries(_, _)
|
||||
| project::Event::WorktreeAdded(_)
|
||||
| project::Event::WorktreeOrderChanged => {
|
||||
this.update_visible_entries(None, cx);
|
||||
@@ -1159,7 +1160,7 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) {
|
||||
fn rename_impl(&mut self, selection: Option<Range<usize>>, cx: &mut ViewContext<Self>) {
|
||||
if let Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
@@ -1183,13 +1184,16 @@ impl ProjectPanel {
|
||||
.map(|s| s.to_string_lossy())
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let file_stem = entry.path.file_stem().map(|s| s.to_string_lossy());
|
||||
let selection_end =
|
||||
file_stem.map_or(file_name.len(), |file_stem| file_stem.len());
|
||||
let selection = selection.unwrap_or_else(|| {
|
||||
let file_stem = entry.path.file_stem().map(|s| s.to_string_lossy());
|
||||
let selection_end =
|
||||
file_stem.map_or(file_name.len(), |file_stem| file_stem.len());
|
||||
0..selection_end
|
||||
});
|
||||
self.filename_editor.update(cx, |editor, cx| {
|
||||
editor.set_text(file_name, cx);
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([0..selection_end])
|
||||
s.select_ranges([selection])
|
||||
});
|
||||
editor.focus(cx);
|
||||
});
|
||||
@@ -1201,6 +1205,10 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) {
|
||||
self.rename_impl(None, cx);
|
||||
}
|
||||
|
||||
fn trash(&mut self, action: &Trash, cx: &mut ViewContext<Self>) {
|
||||
self.remove(true, action.skip_prompt, cx);
|
||||
}
|
||||
@@ -1359,9 +1367,10 @@ impl ProjectPanel {
|
||||
let parent_entry = worktree.entry_for_path(parent_path)?;
|
||||
|
||||
// Remove all siblings that are being deleted except the last marked entry
|
||||
let mut siblings: Vec<Entry> = worktree
|
||||
let mut siblings: Vec<_> = worktree
|
||||
.snapshot()
|
||||
.child_entries(parent_path)
|
||||
.with_git_statuses()
|
||||
.filter(|sibling| {
|
||||
sibling.id == latest_entry.id
|
||||
|| !marked_entries_in_worktree.contains(&&SelectedEntry {
|
||||
@@ -1369,7 +1378,7 @@ impl ProjectPanel {
|
||||
entry_id: sibling.id,
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.map(|entry| entry.to_owned())
|
||||
.collect();
|
||||
|
||||
project::sort_worktree_entries(&mut siblings);
|
||||
@@ -1762,7 +1771,7 @@ impl ProjectPanel {
|
||||
source: &SelectedEntry,
|
||||
(worktree, target_entry): (Model<Worktree>, &Entry),
|
||||
cx: &AppContext,
|
||||
) -> Option<PathBuf> {
|
||||
) -> Option<(PathBuf, Option<Range<usize>>)> {
|
||||
let mut new_path = target_entry.path.to_path_buf();
|
||||
// If we're pasting into a file, or a directory into itself, go up one level.
|
||||
if target_entry.is_file() || (target_entry.is_dir() && target_entry.id == source.entry_id) {
|
||||
@@ -1778,6 +1787,8 @@ impl ProjectPanel {
|
||||
new_path.push(&clipboard_entry_file_name);
|
||||
let extension = new_path.extension().map(|e| e.to_os_string());
|
||||
let file_name_without_extension = Path::new(&clipboard_entry_file_name).file_stem()?;
|
||||
let file_name_len = file_name_without_extension.to_string_lossy().len();
|
||||
let mut disambiguation_range = None;
|
||||
let mut ix = 0;
|
||||
{
|
||||
let worktree = worktree.read(cx);
|
||||
@@ -1785,9 +1796,17 @@ impl ProjectPanel {
|
||||
new_path.pop();
|
||||
|
||||
let mut new_file_name = file_name_without_extension.to_os_string();
|
||||
new_file_name.push(" copy");
|
||||
|
||||
let disambiguation = " copy";
|
||||
let mut disambiguation_len = disambiguation.len();
|
||||
|
||||
new_file_name.push(disambiguation);
|
||||
|
||||
if ix > 0 {
|
||||
new_file_name.push(format!(" {}", ix));
|
||||
let extra_disambiguation = format!(" {}", ix);
|
||||
disambiguation_len += extra_disambiguation.len();
|
||||
|
||||
new_file_name.push(extra_disambiguation);
|
||||
}
|
||||
if let Some(extension) = extension.as_ref() {
|
||||
new_file_name.push(".");
|
||||
@@ -1795,10 +1814,11 @@ impl ProjectPanel {
|
||||
}
|
||||
|
||||
new_path.push(new_file_name);
|
||||
disambiguation_range = Some(file_name_len..(file_name_len + disambiguation_len));
|
||||
ix += 1;
|
||||
}
|
||||
}
|
||||
Some(new_path)
|
||||
Some((new_path, disambiguation_range))
|
||||
}
|
||||
|
||||
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
@@ -1816,9 +1836,10 @@ impl ProjectPanel {
|
||||
}
|
||||
let mut paste_entry_tasks: IndexMap<(ProjectEntryId, bool), PasteTask> =
|
||||
IndexMap::default();
|
||||
let mut disambiguation_range = None;
|
||||
let clip_is_cut = clipboard_entries.is_cut();
|
||||
for clipboard_entry in clipboard_entries.items() {
|
||||
let new_path =
|
||||
let (new_path, new_disambiguation_range) =
|
||||
self.create_paste_path(clipboard_entry, self.selected_sub_entry(cx)?, cx)?;
|
||||
let clip_entry_id = clipboard_entry.entry_id;
|
||||
let is_same_worktree = clipboard_entry.worktree_id == worktree_id;
|
||||
@@ -1855,8 +1876,11 @@ impl ProjectPanel {
|
||||
};
|
||||
let needs_delete = !is_same_worktree && clip_is_cut;
|
||||
paste_entry_tasks.insert((clip_entry_id, needs_delete), task);
|
||||
disambiguation_range = new_disambiguation_range.or(disambiguation_range);
|
||||
}
|
||||
|
||||
let item_count = paste_entry_tasks.len();
|
||||
|
||||
cx.spawn(|project_panel, mut cx| async move {
|
||||
let mut last_succeed = None;
|
||||
let mut need_delete_ids = Vec::new();
|
||||
@@ -1877,17 +1901,6 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
// update selection
|
||||
if let Some(entry_id) = last_succeed {
|
||||
project_panel
|
||||
.update(&mut cx, |project_panel, _cx| {
|
||||
project_panel.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
// remove entry for cut in difference worktree
|
||||
for entry_id in need_delete_ids {
|
||||
project_panel
|
||||
@@ -1899,6 +1912,22 @@ impl ProjectPanel {
|
||||
})??
|
||||
.await?;
|
||||
}
|
||||
// update selection
|
||||
if let Some(entry_id) = last_succeed {
|
||||
project_panel
|
||||
.update(&mut cx, |project_panel, cx| {
|
||||
project_panel.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
});
|
||||
|
||||
// if only one entry was pasted and it was disambiguated, open the rename editor
|
||||
if item_count == 1 && disambiguation_range.is_some() {
|
||||
project_panel.rename_impl(disambiguation_range, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@@ -2307,7 +2336,7 @@ impl ProjectPanel {
|
||||
}
|
||||
|
||||
let mut visible_worktree_entries = Vec::new();
|
||||
let mut entry_iter = snapshot.entries(true, 0);
|
||||
let mut entry_iter = snapshot.entries(true, 0).with_git_statuses();
|
||||
let mut auto_folded_ancestors = vec![];
|
||||
while let Some(entry) = entry_iter.entry() {
|
||||
if auto_collapse_dirs && entry.kind.is_dir() {
|
||||
@@ -2349,7 +2378,7 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
auto_folded_ancestors.clear();
|
||||
visible_worktree_entries.push(entry.clone());
|
||||
visible_worktree_entries.push(entry.to_owned());
|
||||
let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id {
|
||||
entry.id == new_entry_id || {
|
||||
self.ancestors.get(&entry.id).map_or(false, |entries| {
|
||||
@@ -2363,25 +2392,27 @@ impl ProjectPanel {
|
||||
false
|
||||
};
|
||||
if precedes_new_entry {
|
||||
visible_worktree_entries.push(Entry {
|
||||
id: NEW_ENTRY_ID,
|
||||
kind: new_entry_kind,
|
||||
path: entry.path.join("\0").into(),
|
||||
inode: 0,
|
||||
mtime: entry.mtime,
|
||||
size: entry.size,
|
||||
is_ignored: entry.is_ignored,
|
||||
is_external: false,
|
||||
is_private: false,
|
||||
is_always_included: entry.is_always_included,
|
||||
visible_worktree_entries.push(GitEntry {
|
||||
entry: Entry {
|
||||
id: NEW_ENTRY_ID,
|
||||
kind: new_entry_kind,
|
||||
path: entry.path.join("\0").into(),
|
||||
inode: 0,
|
||||
mtime: entry.mtime,
|
||||
size: entry.size,
|
||||
is_ignored: entry.is_ignored,
|
||||
is_external: false,
|
||||
is_private: false,
|
||||
is_always_included: entry.is_always_included,
|
||||
canonical_path: entry.canonical_path.clone(),
|
||||
char_bag: entry.char_bag,
|
||||
is_fifo: entry.is_fifo,
|
||||
},
|
||||
git_status: entry.git_status,
|
||||
canonical_path: entry.canonical_path.clone(),
|
||||
char_bag: entry.char_bag,
|
||||
is_fifo: entry.is_fifo,
|
||||
});
|
||||
}
|
||||
let worktree_abs_path = worktree.read(cx).abs_path();
|
||||
let (depth, path) = if Some(entry) == worktree.read(cx).root_entry() {
|
||||
let (depth, path) = if Some(entry.entry) == worktree.read(cx).root_entry() {
|
||||
let Some(path_name) = worktree_abs_path
|
||||
.file_name()
|
||||
.with_context(|| {
|
||||
@@ -2458,8 +2489,8 @@ impl ProjectPanel {
|
||||
entry_iter.advance();
|
||||
}
|
||||
|
||||
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
|
||||
project::sort_worktree_entries(&mut visible_worktree_entries);
|
||||
|
||||
self.visible_entries
|
||||
.push((worktree_id, visible_worktree_entries, OnceCell::new()));
|
||||
}
|
||||
@@ -2606,23 +2637,55 @@ impl ProjectPanel {
|
||||
let _ = maybe!({
|
||||
let project = self.project.read(cx);
|
||||
let target_worktree = project.worktree_for_entry(target_entry_id, cx)?;
|
||||
let worktree_id = target_worktree.read(cx).id();
|
||||
let target_entry = target_worktree
|
||||
.read(cx)
|
||||
.entry_for_id(target_entry_id)?
|
||||
.clone();
|
||||
|
||||
let mut copy_tasks = Vec::new();
|
||||
let mut disambiguation_range = None;
|
||||
for selection in selections.items() {
|
||||
let new_path = self.create_paste_path(
|
||||
let (new_path, new_disambiguation_range) = self.create_paste_path(
|
||||
selection,
|
||||
(target_worktree.clone(), &target_entry),
|
||||
cx,
|
||||
)?;
|
||||
self.project
|
||||
.update(cx, |project, cx| {
|
||||
project.copy_entry(selection.entry_id, None, new_path, cx)
|
||||
})
|
||||
.detach_and_log_err(cx)
|
||||
|
||||
let task = self.project.update(cx, |project, cx| {
|
||||
project.copy_entry(selection.entry_id, None, new_path, cx)
|
||||
});
|
||||
copy_tasks.push(task);
|
||||
disambiguation_range = new_disambiguation_range.or(disambiguation_range);
|
||||
}
|
||||
|
||||
let item_count = copy_tasks.len();
|
||||
|
||||
cx.spawn(|project_panel, mut cx| async move {
|
||||
let mut last_succeed = None;
|
||||
for task in copy_tasks.into_iter() {
|
||||
if let Some(Some(entry)) = task.await.log_err() {
|
||||
last_succeed = Some(entry.id);
|
||||
}
|
||||
}
|
||||
// update selection
|
||||
if let Some(entry_id) = last_succeed {
|
||||
project_panel
|
||||
.update(&mut cx, |project_panel, cx| {
|
||||
project_panel.selection = Some(SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id,
|
||||
});
|
||||
|
||||
// if only one entry was dragged and it was disambiguated, open the rename editor
|
||||
if item_count == 1 && disambiguation_range.is_some() {
|
||||
project_panel.rename_impl(disambiguation_range, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
Some(())
|
||||
});
|
||||
} else {
|
||||
@@ -2655,13 +2718,13 @@ impl ProjectPanel {
|
||||
None
|
||||
}
|
||||
|
||||
fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, &Entry)> {
|
||||
fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, GitEntryRef)> {
|
||||
let mut offset = 0;
|
||||
for (worktree_id, visible_worktree_entries, _) in &self.visible_entries {
|
||||
if visible_worktree_entries.len() > offset + index {
|
||||
return visible_worktree_entries
|
||||
.get(index)
|
||||
.map(|entry| (*worktree_id, entry));
|
||||
.map(|entry| (*worktree_id, entry.to_ref()));
|
||||
}
|
||||
offset += visible_worktree_entries.len();
|
||||
}
|
||||
@@ -2694,7 +2757,7 @@ impl ProjectPanel {
|
||||
.collect()
|
||||
});
|
||||
for entry in visible_worktree_entries[entry_range].iter() {
|
||||
callback(entry, entries, cx);
|
||||
callback(&entry, entries, cx);
|
||||
}
|
||||
ix = end_ix;
|
||||
}
|
||||
@@ -2763,7 +2826,7 @@ impl ProjectPanel {
|
||||
};
|
||||
|
||||
let (depth, difference) =
|
||||
ProjectPanel::calculate_depth_and_difference(entry, entries);
|
||||
ProjectPanel::calculate_depth_and_difference(&entry, entries);
|
||||
|
||||
let filename = match difference {
|
||||
diff if diff > 1 => entry
|
||||
@@ -2892,9 +2955,9 @@ impl ProjectPanel {
|
||||
worktree_id: WorktreeId,
|
||||
reverse_search: bool,
|
||||
only_visible_entries: bool,
|
||||
predicate: impl Fn(&Entry, WorktreeId) -> bool,
|
||||
predicate: impl Fn(GitEntryRef, WorktreeId) -> bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Entry> {
|
||||
) -> Option<GitEntry> {
|
||||
if only_visible_entries {
|
||||
let entries = self
|
||||
.visible_entries
|
||||
@@ -2909,15 +2972,18 @@ impl ProjectPanel {
|
||||
.clone();
|
||||
|
||||
return utils::ReversibleIterable::new(entries.iter(), reverse_search)
|
||||
.find(|ele| predicate(ele, worktree_id))
|
||||
.find(|ele| predicate(ele.to_ref(), worktree_id))
|
||||
.cloned();
|
||||
}
|
||||
|
||||
let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
|
||||
worktree.update(cx, |tree, _| {
|
||||
utils::ReversibleIterable::new(tree.entries(true, 0usize), reverse_search)
|
||||
.find_single_ended(|ele| predicate(ele, worktree_id))
|
||||
.cloned()
|
||||
utils::ReversibleIterable::new(
|
||||
tree.entries(true, 0usize).with_git_statuses(),
|
||||
reverse_search,
|
||||
)
|
||||
.find_single_ended(|ele| predicate(*ele, worktree_id))
|
||||
.map(|ele| ele.to_owned())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2925,7 +2991,7 @@ impl ProjectPanel {
|
||||
&self,
|
||||
start: Option<&SelectedEntry>,
|
||||
reverse_search: bool,
|
||||
predicate: impl Fn(&Entry, WorktreeId) -> bool,
|
||||
predicate: impl Fn(GitEntryRef, WorktreeId) -> bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<SelectedEntry> {
|
||||
let mut worktree_ids: Vec<_> = self
|
||||
@@ -2947,7 +3013,9 @@ impl ProjectPanel {
|
||||
let root_entry = tree.root_entry()?;
|
||||
let tree_id = tree.id();
|
||||
|
||||
let mut first_iter = tree.traverse_from_path(true, true, true, entry.path.as_ref());
|
||||
let mut first_iter = tree
|
||||
.traverse_from_path(true, true, true, entry.path.as_ref())
|
||||
.with_git_statuses();
|
||||
|
||||
if reverse_search {
|
||||
first_iter.next();
|
||||
@@ -2955,25 +3023,25 @@ impl ProjectPanel {
|
||||
|
||||
let first = first_iter
|
||||
.enumerate()
|
||||
.take_until(|(count, ele)| *ele == root_entry && *count != 0usize)
|
||||
.map(|(_, ele)| ele)
|
||||
.find(|ele| predicate(ele, tree_id))
|
||||
.cloned();
|
||||
.take_until(|(count, entry)| entry.entry == root_entry && *count != 0usize)
|
||||
.map(|(_, entry)| entry)
|
||||
.find(|ele| predicate(*ele, tree_id))
|
||||
.map(|ele| ele.to_owned());
|
||||
|
||||
let second_iter = tree.entries(true, 0usize);
|
||||
let second_iter = tree.entries(true, 0usize).with_git_statuses();
|
||||
|
||||
let second = if reverse_search {
|
||||
second_iter
|
||||
.take_until(|ele| ele.id == start.entry_id)
|
||||
.filter(|ele| predicate(ele, tree_id))
|
||||
.filter(|ele| predicate(*ele, tree_id))
|
||||
.last()
|
||||
.cloned()
|
||||
.map(|ele| ele.to_owned())
|
||||
} else {
|
||||
second_iter
|
||||
.take_while(|ele| ele.id != start.entry_id)
|
||||
.filter(|ele| predicate(ele, tree_id))
|
||||
.filter(|ele| predicate(*ele, tree_id))
|
||||
.last()
|
||||
.cloned()
|
||||
.map(|ele| ele.to_owned())
|
||||
};
|
||||
|
||||
if reverse_search {
|
||||
@@ -3030,7 +3098,7 @@ impl ProjectPanel {
|
||||
&self,
|
||||
start: Option<&SelectedEntry>,
|
||||
reverse_search: bool,
|
||||
predicate: impl Fn(&Entry, WorktreeId) -> bool,
|
||||
predicate: impl Fn(GitEntryRef, WorktreeId) -> bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<SelectedEntry> {
|
||||
let mut worktree_ids: Vec<_> = self
|
||||
@@ -3072,8 +3140,8 @@ impl ProjectPanel {
|
||||
)
|
||||
};
|
||||
|
||||
let first_search = first_iter.find(|ele| predicate(ele, start.worktree_id));
|
||||
let second_search = second_iter.find(|ele| predicate(ele, start.worktree_id));
|
||||
let first_search = first_iter.find(|ele| predicate(ele.to_ref(), start.worktree_id));
|
||||
let second_search = second_iter.find(|ele| predicate(ele.to_ref(), start.worktree_id));
|
||||
|
||||
if first_search.is_some() {
|
||||
return first_search.map(|entry| SelectedEntry {
|
||||
@@ -5283,11 +5351,22 @@ mod tests {
|
||||
//
|
||||
"v root1",
|
||||
" one.txt",
|
||||
" one copy.txt <== selected",
|
||||
" [EDITOR: 'one copy.txt'] <== selected",
|
||||
" one.two.txt",
|
||||
]
|
||||
);
|
||||
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.filename_editor.update(cx, |editor, cx| {
|
||||
let file_name_selections = editor.selections.all::<usize>(cx);
|
||||
assert_eq!(file_name_selections.len(), 1, "File editing should have a single selection, but got: {file_name_selections:?}");
|
||||
let file_name_selection = &file_name_selections[0];
|
||||
assert_eq!(file_name_selection.start, "one".len(), "Should select the file name disambiguation after the original file name");
|
||||
assert_eq!(file_name_selection.end, "one copy".len(), "Should select the file name disambiguation until the extension");
|
||||
});
|
||||
assert!(panel.confirm_edit(cx).is_none());
|
||||
});
|
||||
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.paste(&Default::default(), cx);
|
||||
});
|
||||
@@ -5300,10 +5379,12 @@ mod tests {
|
||||
"v root1",
|
||||
" one.txt",
|
||||
" one copy.txt",
|
||||
" one copy 1.txt <== selected",
|
||||
" [EDITOR: 'one copy 1.txt'] <== selected",
|
||||
" one.two.txt",
|
||||
]
|
||||
);
|
||||
|
||||
panel.update(cx, |panel, cx| assert!(panel.confirm_edit(cx).is_none()));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -5495,11 +5576,14 @@ mod tests {
|
||||
" four.txt",
|
||||
" one.txt",
|
||||
" three.txt",
|
||||
" three copy.txt <== selected",
|
||||
" [EDITOR: 'three copy.txt'] <== selected",
|
||||
" two.txt",
|
||||
]
|
||||
);
|
||||
|
||||
panel.update(cx, |panel, cx| panel.cancel(&menu::Cancel {}, cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
select_path(&panel, "root1/a", cx);
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.copy(&Default::default(), cx);
|
||||
@@ -5603,6 +5687,48 @@ mod tests {
|
||||
select_path(&panel, "root", cx);
|
||||
panel.update(cx, |panel, cx| panel.paste(&Default::default(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..50, cx),
|
||||
&[
|
||||
//
|
||||
"v root",
|
||||
" > a",
|
||||
" > [EDITOR: 'a copy'] <== selected",
|
||||
" v b",
|
||||
" v a",
|
||||
" v inner_dir",
|
||||
" four.txt",
|
||||
" three.txt",
|
||||
" one.txt",
|
||||
" two.txt"
|
||||
]
|
||||
);
|
||||
|
||||
let confirm = panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.filename_editor
|
||||
.update(cx, |editor, cx| editor.set_text("c", cx));
|
||||
panel.confirm_edit(cx).unwrap()
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..50, cx),
|
||||
&[
|
||||
//
|
||||
"v root",
|
||||
" > a",
|
||||
" > [PROCESSING: 'c'] <== selected",
|
||||
" v b",
|
||||
" v a",
|
||||
" v inner_dir",
|
||||
" four.txt",
|
||||
" three.txt",
|
||||
" one.txt",
|
||||
" two.txt"
|
||||
]
|
||||
);
|
||||
|
||||
confirm.await.unwrap();
|
||||
|
||||
panel.update(cx, |panel, cx| panel.paste(&Default::default(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
assert_eq!(
|
||||
@@ -5611,18 +5737,18 @@ mod tests {
|
||||
//
|
||||
"v root",
|
||||
" > a",
|
||||
" v a copy",
|
||||
" > a <== selected",
|
||||
" > inner_dir",
|
||||
" one.txt",
|
||||
" two.txt",
|
||||
" v b",
|
||||
" v a",
|
||||
" v inner_dir",
|
||||
" four.txt",
|
||||
" three.txt",
|
||||
" one.txt",
|
||||
" two.txt"
|
||||
" two.txt",
|
||||
" v c",
|
||||
" > a <== selected",
|
||||
" > inner_dir",
|
||||
" one.txt",
|
||||
" two.txt",
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -5703,6 +5829,33 @@ mod tests {
|
||||
],
|
||||
"Should copy dir1 as well as c.txt into dir2"
|
||||
);
|
||||
|
||||
// Disambiguating multiple files should not open the rename editor.
|
||||
select_path(&panel, "test/dir2", cx);
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.paste(&Default::default(), cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..15, cx),
|
||||
&[
|
||||
"v test",
|
||||
" v dir1 <== marked",
|
||||
" a.txt",
|
||||
" b.txt",
|
||||
" v dir2",
|
||||
" v dir1",
|
||||
" a.txt",
|
||||
" b.txt",
|
||||
" > dir1 copy <== selected",
|
||||
" c.txt",
|
||||
" c copy.txt",
|
||||
" c.txt <== marked",
|
||||
" d.txt",
|
||||
],
|
||||
"Should copy dir1 as well as c.txt into dir2 and disambiguate them without opening the rename editor"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -1768,7 +1768,7 @@ message Entry {
|
||||
bool is_ignored = 7;
|
||||
bool is_external = 8;
|
||||
reserved 6;
|
||||
optional GitStatus git_status = 9;
|
||||
reserved 9;
|
||||
bool is_fifo = 10;
|
||||
optional uint64 size = 11;
|
||||
optional string canonical_path = 12;
|
||||
@@ -1777,6 +1777,8 @@ message Entry {
|
||||
message RepositoryEntry {
|
||||
uint64 work_directory_id = 1;
|
||||
optional string branch = 2;
|
||||
repeated StatusEntry updated_statuses = 3;
|
||||
repeated string removed_statuses = 4;
|
||||
}
|
||||
|
||||
message StatusEntry {
|
||||
@@ -1788,6 +1790,7 @@ enum GitStatus {
|
||||
Added = 0;
|
||||
Modified = 1;
|
||||
Conflict = 2;
|
||||
Deleted = 3;
|
||||
}
|
||||
|
||||
message BufferState {
|
||||
|
||||
@@ -20,6 +20,7 @@ use serde_json::json;
|
||||
use settings::{initial_server_settings_content, Settings, SettingsLocation, SettingsStore};
|
||||
use smol::stream::StreamExt;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -1150,6 +1151,10 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
|
||||
|
||||
let (project, headless_project) = init_test(&fs, cx, server_cx).await;
|
||||
let branches = ["main", "dev", "feature-1"];
|
||||
let branches_set = branches
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<HashSet<_>>();
|
||||
fs.insert_branches(Path::new("/code/project1/.git"), &branches);
|
||||
|
||||
let (worktree, _) = project
|
||||
@@ -1173,10 +1178,10 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
|
||||
|
||||
let remote_branches = remote_branches
|
||||
.into_iter()
|
||||
.map(|branch| branch.name)
|
||||
.collect::<Vec<_>>();
|
||||
.map(|branch| branch.name.to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(&remote_branches, &branches);
|
||||
assert_eq!(&remote_branches, &branches_set);
|
||||
|
||||
cx.update(|cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
|
||||
@@ -39,7 +39,7 @@ pub async fn launch_remote_kernel(
|
||||
let kernel_launch_request = KernelLaunchRequest {
|
||||
name: kernel_name.to_string(),
|
||||
// Note: since the path we have locally may not be the same as the one on the remote server,
|
||||
// we don't send it. We'll have to evaluate this decisiion along the way.
|
||||
// we don't send it. We'll have to evaluate this decision along the way.
|
||||
path: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::proto;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{map, Value};
|
||||
use strum::{EnumVariantNames, VariantNames as _};
|
||||
use strum::VariantNames;
|
||||
|
||||
const KIND: &str = "kind";
|
||||
const ENTITY_ID: &str = "entity_id";
|
||||
@@ -15,7 +15,7 @@ const ENTITY_ID: &str = "entity_id";
|
||||
/// Most notification types have a special field which is aliased to
|
||||
/// `entity_id`. This field is stored in its own database column, and can
|
||||
/// be used to query the notification.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumVariantNames, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, VariantNames, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum Notification {
|
||||
ContactRequest {
|
||||
|
||||
@@ -5,7 +5,7 @@ use collections::HashMap;
|
||||
// for those users.
|
||||
//
|
||||
// The way macOS solves this problem is to move shortcuts around so that they are all reachable,
|
||||
// even if the mnemoic changes. https://developer.apple.com/documentation/swiftui/keyboardshortcut/localization-swift.struct
|
||||
// even if the mnemonic changes. https://developer.apple.com/documentation/swiftui/keyboardshortcut/localization-swift.struct
|
||||
//
|
||||
// For example, cmd-> is the "switch window" shortcut because the > key is right above tab.
|
||||
// To ensure this doesn't cause problems for shortcuts defined for a QWERTY layout, apple moves
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user