Compare commits
355 Commits
v0.87.2-pr
...
v0.89.1-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c7973d274 | ||
|
|
0071a4598a | ||
|
|
d7e4544638 | ||
|
|
432d407539 | ||
|
|
6f4fd74f43 | ||
|
|
bf2016adf5 | ||
|
|
27ef0e2b52 | ||
|
|
45b42c512d | ||
|
|
705e36827c | ||
|
|
b875d4ed40 | ||
|
|
ed0b9acb0a | ||
|
|
ef80b539d1 | ||
|
|
62660f2766 | ||
|
|
193474a346 | ||
|
|
c48fed26bd | ||
|
|
3f6aa94a5f | ||
|
|
893615236d | ||
|
|
1f72f9e18b | ||
|
|
a10933c063 | ||
|
|
ac41564a8f | ||
|
|
c6672dbac2 | ||
|
|
76927b6d95 | ||
|
|
c9820fde61 | ||
|
|
584e5f7958 | ||
|
|
0122cd61c5 | ||
|
|
685e8d7007 | ||
|
|
9c707eff27 | ||
|
|
e80ab5f096 | ||
|
|
5e4a9abd09 | ||
|
|
5065804388 | ||
|
|
8f0fc918e9 | ||
|
|
9c9af5ed94 | ||
|
|
efcf78cbe9 | ||
|
|
e9ce85ebc0 | ||
|
|
501f9ab2c6 | ||
|
|
1a23fe91b4 | ||
|
|
84f98f13c4 | ||
|
|
31516b7863 | ||
|
|
51c7078f47 | ||
|
|
4568f80f2e | ||
|
|
14efc18eca | ||
|
|
05d1dd6b11 | ||
|
|
ac63043c27 | ||
|
|
0d62e76cd6 | ||
|
|
1fc9103b61 | ||
|
|
bd494037bb | ||
|
|
2269c19169 | ||
|
|
8a851ad7f8 | ||
|
|
063613bf4e | ||
|
|
88eb2b2163 | ||
|
|
6d3464fd1f | ||
|
|
edf8e276af | ||
|
|
d593377e45 | ||
|
|
cf75b090d3 | ||
|
|
986b02e217 | ||
|
|
fdad1adaf6 | ||
|
|
e4530471de | ||
|
|
1f42bfc1bd | ||
|
|
2db57b5139 | ||
|
|
02b95ef320 | ||
|
|
9cfe39f1aa | ||
|
|
5fda9e934d | ||
|
|
3a3c1c5a5b | ||
|
|
6628c4df28 | ||
|
|
59bfd40679 | ||
|
|
f890eefdef | ||
|
|
cf2bbfc85a | ||
|
|
508533ebb7 | ||
|
|
2fdc960704 | ||
|
|
b75c27da6f | ||
|
|
364631a155 | ||
|
|
128c19875d | ||
|
|
5501dd741c | ||
|
|
7900d2a20a | ||
|
|
5e39ba596e | ||
|
|
f40c498491 | ||
|
|
8d662edb6c | ||
|
|
cede296b04 | ||
|
|
54421b11f3 | ||
|
|
ae3bdd755e | ||
|
|
739d5ca373 | ||
|
|
24098c561d | ||
|
|
4185a178e7 | ||
|
|
f549ada54f | ||
|
|
aa8c0475b1 | ||
|
|
e2ff829f98 | ||
|
|
414b97adce | ||
|
|
dba7ec4a35 | ||
|
|
c2f3ac24a4 | ||
|
|
7e1ea8f274 | ||
|
|
aa58d0fd77 | ||
|
|
f812151840 | ||
|
|
eca6d2b597 | ||
|
|
58a56bdda2 | ||
|
|
d1f4b60fa1 | ||
|
|
b9dabb165e | ||
|
|
3327e8a6dd | ||
|
|
f6d7b3d2e8 | ||
|
|
793486b2e8 | ||
|
|
3f79b0c7cc | ||
|
|
cc3186cb4a | ||
|
|
0b7d095c0a | ||
|
|
37d35db3d7 | ||
|
|
26051de160 | ||
|
|
a1a66b8da4 | ||
|
|
747322a02d | ||
|
|
66b3be8687 | ||
|
|
d495c1b804 | ||
|
|
35f9996a4f | ||
|
|
ba79a8ba94 | ||
|
|
50e1e17d09 | ||
|
|
051c7566cc | ||
|
|
8abaf66602 | ||
|
|
6368ade1da | ||
|
|
8832248bb9 | ||
|
|
f67a22828b | ||
|
|
0cf1632d39 | ||
|
|
50cfe5eec3 | ||
|
|
7534c4e670 | ||
|
|
2a8e0824a6 | ||
|
|
8f49552121 | ||
|
|
e5fd953b4f | ||
|
|
1a353ad25d | ||
|
|
9575ffc1e3 | ||
|
|
168ff99927 | ||
|
|
e463e3b2e8 | ||
|
|
e6857866d4 | ||
|
|
637ed79603 | ||
|
|
65fc50745b | ||
|
|
d934da1905 | ||
|
|
ba7659fa77 | ||
|
|
2457c55fe7 | ||
|
|
aac83ceea7 | ||
|
|
7be41e19f7 | ||
|
|
ad866c5b21 | ||
|
|
774530dd05 | ||
|
|
073967c80b | ||
|
|
5362e7d346 | ||
|
|
394e87d17c | ||
|
|
dcc4dd7eb8 | ||
|
|
19b817e48a | ||
|
|
208ff2fba7 | ||
|
|
0de5a444d3 | ||
|
|
234dbc3ca9 | ||
|
|
c4d88bc529 | ||
|
|
30de64845f | ||
|
|
a5b5de5651 | ||
|
|
a37b1b6a45 | ||
|
|
b19a712799 | ||
|
|
da96802bf0 | ||
|
|
7be8dead07 | ||
|
|
7e6cccfa3d | ||
|
|
049b72e3ac | ||
|
|
54c04a6618 | ||
|
|
9307719a4b | ||
|
|
51d94f532b | ||
|
|
fc82489128 | ||
|
|
1b5be04f76 | ||
|
|
e4454f1e7f | ||
|
|
5c31c84c90 | ||
|
|
fb2f17e5c9 | ||
|
|
2d1c4a1971 | ||
|
|
dcf4791182 | ||
|
|
9bec74f1d4 | ||
|
|
9a47415cb5 | ||
|
|
5dfb0e3691 | ||
|
|
11eb9b17c9 | ||
|
|
4c9d24da7c | ||
|
|
96224fa7e8 | ||
|
|
0e332b5f94 | ||
|
|
7689cdf3f9 | ||
|
|
1d07cff765 | ||
|
|
dcb987ba9d | ||
|
|
cfdf9198da | ||
|
|
687ccd4c6f | ||
|
|
366f13bb5c | ||
|
|
49566e5677 | ||
|
|
9c32b774aa | ||
|
|
bafc1d922e | ||
|
|
e59c8e9d61 | ||
|
|
e129ed2d91 | ||
|
|
04355215b2 | ||
|
|
a69144911f | ||
|
|
85266131cf | ||
|
|
6f4b6eec5b | ||
|
|
6264b24788 | ||
|
|
8669dcdc81 | ||
|
|
f22067b236 | ||
|
|
33f6c56b14 | ||
|
|
6f39d49b18 | ||
|
|
048498e39b | ||
|
|
ef81813d56 | ||
|
|
a355b4c135 | ||
|
|
af73c1af06 | ||
|
|
3ca95678f1 | ||
|
|
9e46b17149 | ||
|
|
2200a22c07 | ||
|
|
75a0742c90 | ||
|
|
5b7e852903 | ||
|
|
deaf60005c | ||
|
|
b80cb73745 | ||
|
|
4aa2858b2b | ||
|
|
21ada545b0 | ||
|
|
44903bc193 | ||
|
|
185a624b99 | ||
|
|
10e947cb5f | ||
|
|
7190840081 | ||
|
|
146809eef0 | ||
|
|
2098ac2c77 | ||
|
|
cb1b64e51b | ||
|
|
f2a74017df | ||
|
|
c701901c7b | ||
|
|
d5cf595761 | ||
|
|
986eafd84e | ||
|
|
912fd23006 | ||
|
|
35a9b90528 | ||
|
|
bbb68c523c | ||
|
|
e32233c826 | ||
|
|
43e301eeef | ||
|
|
fb11c3e4bf | ||
|
|
1474429271 | ||
|
|
c795c9b844 | ||
|
|
623a177fe6 | ||
|
|
560160b100 | ||
|
|
2a41a32aac | ||
|
|
d7d29472c2 | ||
|
|
06a89d9104 | ||
|
|
7ae642b9b8 | ||
|
|
c193b0b8fc | ||
|
|
32c7157906 | ||
|
|
6359333749 | ||
|
|
065f71d671 | ||
|
|
7d1833b759 | ||
|
|
844b8d9e1e | ||
|
|
2c8fffc4f8 | ||
|
|
01a3e49946 | ||
|
|
9f157bdb67 | ||
|
|
729a93db6b | ||
|
|
6792788216 | ||
|
|
5a8fb18c20 | ||
|
|
5ff49bde31 | ||
|
|
d480555ec9 | ||
|
|
847d1e73a3 | ||
|
|
459cc9c959 | ||
|
|
583b15badc | ||
|
|
e49281699c | ||
|
|
924ec961ff | ||
|
|
3d6b728364 | ||
|
|
3984cc6d39 | ||
|
|
4bda5c4d69 | ||
|
|
34b0d6200f | ||
|
|
ffe35d171c | ||
|
|
58f704abcb | ||
|
|
87e206c451 | ||
|
|
99fcf23798 | ||
|
|
95a7d69bce | ||
|
|
2883d6f1ef | ||
|
|
2a11a89827 | ||
|
|
55b241a4f6 | ||
|
|
c71b59b248 | ||
|
|
923be1892d | ||
|
|
2ec994dfcd | ||
|
|
201d513c50 | ||
|
|
711d2c6fe7 | ||
|
|
bd29812391 | ||
|
|
5ab1ae1521 | ||
|
|
83f8981308 | ||
|
|
71ad7e7612 | ||
|
|
f2ad17dbc0 | ||
|
|
263afc75a8 | ||
|
|
5ab4eab369 | ||
|
|
89d8bb1425 | ||
|
|
5c437e2098 | ||
|
|
a9b107f155 | ||
|
|
d23fc6f135 | ||
|
|
a56793c214 | ||
|
|
823e15d85a | ||
|
|
667f476f7f | ||
|
|
42eca3048f | ||
|
|
258723566f | ||
|
|
2d5f03e148 | ||
|
|
1f0e79ee9d | ||
|
|
67a25126d4 | ||
|
|
5ef03992c5 | ||
|
|
f4e99ecde4 | ||
|
|
88bc55ddf5 | ||
|
|
6cf439e734 | ||
|
|
ae3394f4de | ||
|
|
1c9a7a7ad8 | ||
|
|
aa89632286 | ||
|
|
5c729c0e56 | ||
|
|
7d11329e80 | ||
|
|
9e5983305b | ||
|
|
89204e85c0 | ||
|
|
cb0c613da2 | ||
|
|
d2ba18eae9 | ||
|
|
4898417617 | ||
|
|
05fb051924 | ||
|
|
f097444546 | ||
|
|
747fbfadeb | ||
|
|
981129ef8e | ||
|
|
6403bb86e1 | ||
|
|
cbd4771f10 | ||
|
|
65e3713d4d | ||
|
|
39618ae32d | ||
|
|
f87ae6032e | ||
|
|
adf361b374 | ||
|
|
c03e470fe6 | ||
|
|
ba50b35de6 | ||
|
|
6c60853842 | ||
|
|
bd795d7607 | ||
|
|
214354b4da | ||
|
|
9ae10a5dd9 | ||
|
|
bc5b78198a | ||
|
|
cee7edabf9 | ||
|
|
68867fe2e1 | ||
|
|
aa6ea920e2 | ||
|
|
9b06be2aa2 | ||
|
|
926d7b356d | ||
|
|
b6b2c5d1d1 | ||
|
|
9a6a2d9d27 | ||
|
|
316f791a77 | ||
|
|
24e06334d0 | ||
|
|
5de9652a22 | ||
|
|
5549669316 | ||
|
|
cc21421ea8 | ||
|
|
0ccb4a50e6 | ||
|
|
e507eadb4b | ||
|
|
9b9d53fcf8 | ||
|
|
cdcb7c8084 | ||
|
|
f28eee88b6 | ||
|
|
8fa379bbc5 | ||
|
|
6e3ce6332a | ||
|
|
9173916ab8 | ||
|
|
f554877ff4 | ||
|
|
ba248244ec | ||
|
|
6a7feb4c4c | ||
|
|
0d78266ddb | ||
|
|
6d16d2224d | ||
|
|
6645323f1b | ||
|
|
8f12489937 | ||
|
|
45df09245b | ||
|
|
37d3ed5f5f | ||
|
|
641f5d1107 | ||
|
|
634b699281 | ||
|
|
ad7f32d7d2 | ||
|
|
26fe7a81f3 | ||
|
|
e6be35c9a5 | ||
|
|
02066afb0e | ||
|
|
506f978c41 | ||
|
|
916612caf1 | ||
|
|
6ddc610fa2 | ||
|
|
1ddbda5095 | ||
|
|
03f8c1206a | ||
|
|
1919a826f9 |
9
.github/pull_request_template.md
vendored
9
.github/pull_request_template.md
vendored
@@ -2,4 +2,11 @@
|
||||
|
||||
Release Notes:
|
||||
|
||||
* [[Added foo / Fixed bar / No notes]]
|
||||
- N/A
|
||||
|
||||
or
|
||||
|
||||
- (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
|
||||
|
||||
If the release notes are only intended for a specific release channel only, add `(<release_channel>-only)` to the end of the release note line.
|
||||
These will be removed by the person making the release.
|
||||
|
||||
54
.github/workflows/build_dmg.yml
vendored
54
.github/workflows/build_dmg.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: Build Zed.dmg
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "v[0-9]+.[0-9]+.x"
|
||||
pull_request:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -euxo pipefail {0}
|
||||
|
||||
concurrency:
|
||||
# Allow only one workflow per any non-`main` branch.
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
COPT: '-Werror'
|
||||
|
||||
jobs:
|
||||
build-dmg:
|
||||
if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-build-dmg')
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Install Rust
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup update stable
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Build dmg bundle
|
||||
run: ./script/bundle
|
||||
|
||||
- name: Upload the build artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
path: ./target/release/Zed.dmg
|
||||
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -42,6 +42,7 @@ jobs:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
needs: rustfmt
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
steps:
|
||||
@@ -85,7 +86,7 @@ jobs:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
|
||||
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
||||
needs: tests
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
@@ -147,11 +148,11 @@ jobs:
|
||||
- name: Create app bundle
|
||||
run: script/bundle
|
||||
|
||||
- name: Upload app bundle to workflow run if main branch
|
||||
- name: Upload app bundle to workflow run if main branch or specifi label
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
||||
with:
|
||||
name: Zed.dmg
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
path: target/release/Zed.dmg
|
||||
|
||||
- uses: softprops/action-gh-release@v1
|
||||
|
||||
1802
Cargo.lock
generated
1802
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/ai",
|
||||
"crates/assets",
|
||||
"crates/auto_update",
|
||||
"crates/breadcrumbs",
|
||||
@@ -77,8 +78,9 @@ async-trait = { version = "0.1" }
|
||||
ctor = { version = "0.1" }
|
||||
env_logger = { version = "0.9" }
|
||||
futures = { version = "0.3" }
|
||||
glob = { version = "0.3" }
|
||||
globset = { version = "0.4" }
|
||||
indoc = "1"
|
||||
isahc = "1.7.2"
|
||||
lazy_static = { version = "1.4.0" }
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
ordered-float = { version = "2.1.1" }
|
||||
@@ -86,6 +88,7 @@ parking_lot = { version = "0.11.1" }
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
rand = { version = "0.8.5" }
|
||||
regex = { version = "1.5" }
|
||||
schemars = { version = "0.8" }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||
@@ -94,6 +97,7 @@ smol = { version = "1.2" }
|
||||
tempdir = { version = "0.3.7" }
|
||||
thiserror = { version = "1.0.29" }
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
toml = { version = "0.5" }
|
||||
unindent = { version = "0.1.7" }
|
||||
|
||||
[patch.crates-io]
|
||||
|
||||
6
assets/contexts/system.zmd
Normal file
6
assets/contexts/system.zmd
Normal file
@@ -0,0 +1,6 @@
|
||||
User input begins on a line starting with /.
|
||||
Don't apologize ever.
|
||||
Never say "I apologize".
|
||||
Use simple language and don't flatter the users.
|
||||
Keep it short.
|
||||
Risk being rude.
|
||||
@@ -39,8 +39,8 @@
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"cmd-\\": "workspace::ToggleLeftSidebar",
|
||||
"cmd-k cmd-b": "workspace::ToggleLeftSidebar",
|
||||
"cmd-\\": "workspace::ToggleLeftDock",
|
||||
"cmd-k cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-t": "file_finder::Toggle",
|
||||
"cmd-shift-r": "project_symbols::Toggle"
|
||||
}
|
||||
@@ -62,9 +62,5 @@
|
||||
"ctrl-f": "project_panel::ExpandSelectedEntry",
|
||||
"ctrl-shift-c": "project_panel::CopyPath"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Dock",
|
||||
"bindings": {}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"cmd-shift-n": "workspace::NewWindow",
|
||||
"cmd-o": "workspace::Open",
|
||||
"alt-cmd-o": "projects::OpenRecent",
|
||||
"ctrl-`": "workspace::NewTerminal"
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"ctrl-`": "terminal_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -67,10 +68,12 @@
|
||||
"cmd-z": "editor::Undo",
|
||||
"cmd-shift-z": "editor::Redo",
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::MoveToStartOfParagraph",
|
||||
"pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::MovePageUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"ctrl-down": "editor::MoveToEndOfParagraph",
|
||||
"pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::MovePageDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
@@ -103,6 +106,8 @@
|
||||
"alt-shift-b": "editor::SelectToPreviousWordStart",
|
||||
"alt-shift-right": "editor::SelectToNextWordEnd",
|
||||
"alt-shift-f": "editor::SelectToNextWordEnd",
|
||||
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
||||
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
||||
"cmd-shift-up": "editor::SelectToBeginning",
|
||||
"cmd-shift-down": "editor::SelectToEnd",
|
||||
"cmd-a": "editor::SelectAll",
|
||||
@@ -183,6 +188,12 @@
|
||||
"alt-[": "copilot::PreviousSuggestion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && extension == zmd",
|
||||
"bindings": {
|
||||
"cmd-enter": "ai::Assist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == auto_height",
|
||||
"bindings": {
|
||||
@@ -219,7 +230,8 @@
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||
"alt-cmd-w": "search::ToggleWholeWord",
|
||||
"alt-cmd-r": "search::ToggleRegex"
|
||||
"alt-cmd-r": "search::ToggleRegex",
|
||||
"shift-escape": "workspace::ToggleZoom"
|
||||
}
|
||||
},
|
||||
// Bindings from VS Code
|
||||
@@ -361,7 +373,9 @@
|
||||
"workspace::ActivatePane",
|
||||
8
|
||||
],
|
||||
"cmd-b": "workspace::ToggleLeftSidebar",
|
||||
"cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-r": "workspace::ToggleRightDock",
|
||||
"cmd-j": "workspace::ToggleBottomDock",
|
||||
"cmd-shift-f": "workspace::NewSearch",
|
||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||
"cmd-k cmd-s": "zed::OpenKeymap",
|
||||
@@ -455,32 +469,6 @@
|
||||
"cmd-enter": "project_search::SearchInNew"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"shift-escape": "dock::FocusDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-shift-k cmd-shift-right": "dock::AnchorDockRight",
|
||||
"cmd-shift-k cmd-shift-down": "dock::AnchorDockBottom",
|
||||
"cmd-shift-k cmd-shift-up": "dock::ExpandDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"cmd-escape": "dock::AddTabToDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane && docked",
|
||||
"bindings": {
|
||||
"shift-escape": "dock::HideDock",
|
||||
"cmd-escape": "dock::RemoveTabFromDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
|
||||
@@ -68,15 +68,8 @@
|
||||
"cmd-shift-o": "file_finder::Toggle",
|
||||
"cmd-shift-a": "command_palette::Toggle",
|
||||
"cmd-alt-o": "project_symbols::Toggle",
|
||||
"cmd-1": "workspace::ToggleLeftSidebar",
|
||||
"cmd-6": "diagnostics::Deploy",
|
||||
"alt-f12": "dock::FocusDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Dock",
|
||||
"bindings": {
|
||||
"alt-f12": "dock::HideDock"
|
||||
"cmd-1": "workspace::ToggleLeftDock",
|
||||
"cmd-6": "diagnostics::Deploy"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -45,18 +45,11 @@
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"ctrl-`": "dock::FocusDock",
|
||||
"cmd-k cmd-b": "workspace::ToggleLeftSidebar",
|
||||
"cmd-k cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-t": "file_finder::Toggle",
|
||||
"shift-cmd-r": "project_symbols::Toggle",
|
||||
// Currently busted: https://github.com/zed-industries/feedback/issues/898
|
||||
"ctrl-0": "project_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Dock",
|
||||
"bindings": {
|
||||
"ctrl-`": "dock::HideDock"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"cmd-alt-ctrl-d": "workspace::ToggleLeftSidebar",
|
||||
"cmd-alt-ctrl-d": "workspace::ToggleLeftDock",
|
||||
"cmd-t": "file_finder::Toggle",
|
||||
"cmd-shift-t": "project_symbols::Toggle"
|
||||
}
|
||||
@@ -83,9 +83,5 @@
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {}
|
||||
},
|
||||
{
|
||||
"context": "Dock",
|
||||
"bindings": {}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
{
|
||||
// The name of the Zed theme to use for the UI
|
||||
"theme": "One Dark",
|
||||
// The name of a base set of key bindings to use.
|
||||
// This setting can take four values, each named after another
|
||||
// text editor:
|
||||
//
|
||||
// 1. "VSCode"
|
||||
// 2. "JetBrains"
|
||||
// 3. "SublimeText"
|
||||
// 4. "Atom"
|
||||
"base_keymap": "VSCode",
|
||||
// Features that can be globally enabled or disabled
|
||||
"features": {
|
||||
// Show Copilot icon in status bar
|
||||
@@ -30,6 +39,9 @@
|
||||
// Whether to pop the completions menu while typing in an editor without
|
||||
// explicitly requesting it.
|
||||
"show_completions_on_input": true,
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
// Controls whether copilot provides suggestion immediately
|
||||
// or waits for a `copilot::Toggle`
|
||||
"show_copilot_suggestions": true,
|
||||
@@ -43,19 +55,32 @@
|
||||
// 3. Draw all invisible symbols:
|
||||
// "all"
|
||||
"show_whitespaces": "selection",
|
||||
// Whether to show the scrollbar in the editor.
|
||||
// This setting can take four values:
|
||||
//
|
||||
// 1. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 2. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 3. Always show the scrollbar:
|
||||
// "always"
|
||||
// 4. Never show the scrollbar:
|
||||
// "never"
|
||||
"show_scrollbars": "auto",
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the editor.
|
||||
// This setting can take four values:
|
||||
//
|
||||
// 1. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 2. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 3. Always show the scrollbar:
|
||||
// "always"
|
||||
// 4. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": "auto",
|
||||
// Whether to show git diff indicators in the scrollbar.
|
||||
"git_diff": true
|
||||
},
|
||||
"project_panel": {
|
||||
// Whether to show the git status in the project panel.
|
||||
"git_status": true,
|
||||
// Where to dock project panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Default width of the project panel.
|
||||
"default_width": 240
|
||||
},
|
||||
// Whether the screen sharing icon is shown in the os status bar.
|
||||
"show_call_status_icon": true,
|
||||
// Whether to use language servers to provide code intelligence.
|
||||
@@ -72,16 +97,6 @@
|
||||
// 4. Save when idle for a certain amount of time:
|
||||
// "autosave": { "after_delay": {"milliseconds": 500} },
|
||||
"autosave": "off",
|
||||
// Where to place the dock by default. This setting can take three
|
||||
// values:
|
||||
//
|
||||
// 1. Position the dock attached to the bottom of the workspace
|
||||
// "default_dock_anchor": "bottom"
|
||||
// 2. Position the dock to the right of the workspace like a side panel
|
||||
// "default_dock_anchor": "right"
|
||||
// 3. Position the dock full screen over the entire workspace"
|
||||
// "default_dock_anchor": "expanded"
|
||||
"default_dock_anchor": "bottom",
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
"remove_trailing_whitespace_on_save": true,
|
||||
@@ -172,6 +187,12 @@
|
||||
// }
|
||||
// }
|
||||
"shell": "system",
|
||||
// Where to dock terminals panel. Can be 'left', 'right', 'bottom'.
|
||||
"dock": "bottom",
|
||||
// Default width when the terminal is docked to the left or right.
|
||||
"default_width": 640,
|
||||
// Default height when the terminal is docked to the bottom.
|
||||
"default_height": 320,
|
||||
// What working directory to use when launching the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Use the current file's project directory. Will Fallback to the
|
||||
|
||||
@@ -16,6 +16,11 @@ gpui = { path = "../gpui" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
util = { path = "../util" }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
futures.workspace = true
|
||||
smallvec.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -9,7 +9,6 @@ use gpui::{
|
||||
};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
||||
use project::{LanguageServerProgress, Project};
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp::Reverse, fmt::Write, sync::Arc};
|
||||
use util::ResultExt;
|
||||
@@ -325,12 +324,7 @@ impl View for ActivityIndicator {
|
||||
} = self.content_to_render(cx);
|
||||
|
||||
let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
|
||||
let theme = &cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.lsp_status;
|
||||
let theme = &theme::current(cx).workspace.status_bar.lsp_status;
|
||||
let style = if state.hovered() && on_click.is_some() {
|
||||
theme.hover.as_ref().unwrap_or(&theme.default)
|
||||
} else {
|
||||
|
||||
25
crates/ai/Cargo.toml
Normal file
25
crates/ai/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "ai"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/ai.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
assets = { path = "../assets"}
|
||||
collections = { path = "../collections"}
|
||||
editor = { path = "../editor" }
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
isahc.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
63
crates/ai/README.zmd
Normal file
63
crates/ai/README.zmd
Normal file
@@ -0,0 +1,63 @@
|
||||
> Hello, I'm Nathan Sobo.
|
||||
I'm currently your sole creator and the leader of Zed Industries.
|
||||
Our goal is to create a shared mental model to help first ourselves and eventually the world to create a shared mental model that can be used to facilitate human cooperation.
|
||||
|
||||
< Hello, Nathan! Welcome back to Zed. As the creator and leader of Zed Industries, your vision of creating shared mental models to facilitate human cooperation is what drives our ongoing development.
|
||||
|
||||
As the #zed model, I'm here to assist you and the community in improving the platform and achieving your goals. Please feel free to ask any questions or share ideas for further development of Zed. Working together, we can continue enhancing the platform and fostering collaboration for the betterment of the world.
|
||||
|
||||
> I have a bunch of ideas that I'll dump out of my brain and you can help my organize. Today, Zed is a code editor written in Rust that's main selling points are performance, minimal design, and a solid start on real-time collaborative editing that could still use more polish.
|
||||
|
||||
We've decided that our next milestone will be open-sourcing Zed on the Zed platform.
|
||||
|
||||
< Nathan, it's exciting to hear that you have more ideas to enhance Zed! Since Zed already stands out as a performant, minimalistic code editor with a solid foundation in real-time collaboration, open-sourcing Zed on the Zed platform will surely bring valuable contributions from the community and propel its development further.
|
||||
|
||||
Let's organize your ideas and create a plan for your next milestone. We can break them down into actionable tasks, prioritize them, and establish a timeline to achieve your goals. Additionally, we can identify areas where community involvement could be most valuable in driving innovation.
|
||||
|
||||
Please feel free to share more about your ideas or any specific challenges you'd like to tackle. I'm here to help you turn these ideas into a cohesive and successful plan for Zed's future!
|
||||
|
||||
@nathan> #zed, can you describe the conversation UI as you currently understand it.
|
||||
|
||||
< Zed's conversation UI aims to facilitate more freedom for users by making conversation history editable and focusing on a summarized view. Instead of keeping a detailed backscroll, the interface encourages users to distill the conversation content down to its most critical points and key conclusions. This approach ensures that the vital information from the conversation is highlighted and easily accessible, while still maintaining a history that can be linked to when necessary.
|
||||
|
||||
Addressing @nate's concerns, this design choice aims to strike a balance between readability, usability, and information preservation. The summarized view maintains the essence of the conversation without being burdened by extensive backscroll. Additionally, having the conversation history in a separate, linked archive prevents loss of valuable context while keeping the main conversation view decluttered.
|
||||
|
||||
@nathan> What details around the conversation view are worth exploring?
|
||||
|
||||
< @nathan, there are several aspects of the conversation view that are worth exploring and refining:
|
||||
|
||||
1. Editing functionality: Ensuring that all participants can edit and refine the conversation summary effectively while preserving the original context.
|
||||
2. Permissions and access control: Determining how to manage editing rights, user roles, and visibility of the conversation summary and history.
|
||||
3. Navigation and linking: Implementing an intuitive system for navigating between the summarized conversation and linked history or reference materials.
|
||||
4. Visualization and layout: Designing the interface to clearly present the summarized information while providing a visually engaging and organized experience for participants.
|
||||
5. Threaded conversations and branching: Exploring ways to handle sub-conversations, supplementary discussions, or debates within the primary conversation view.
|
||||
|
||||
Considering these aspects will ensure our conversation view design is optimized for usability, collaboration, and accessibility in Zed.
|
||||
|
||||
@nathan> @nate, which of these would you like to explore?
|
||||
|
||||
@nate> 2 feels like it isn't important at the moment, we can explore that later. Let's start with 4, which I think will lead us to discussion 3 and 5.
|
||||
|
||||
#zed share your thoughts on the points we need to consider to design a layout and visualization for a conversation view between you (#zed) and multuple peoople, or between multiple people and multiple bots (you and other bots).
|
||||
|
||||
@nathan> Agreed. I'm interested in threading I think more than anything. Or 4 yeah. I think we need to scope the threading conversation. Also, asking #zed to propose the solution... not sure it will be that effective but it's worth a try...
|
||||
|
||||
#zed describe a hypothetical conversation UI that meets the description.
|
||||
|
||||
< @nate and @nathan, I understand that you'd like to focus on the layout and visualization for a conversation view that can accommodate multiple people and potentially multiple bots as well. Here's a proposal for a conversation UI that meets the requirements:
|
||||
|
||||
1. Main conversation area: Display the summarized conversation content in a clean and organized manner, ensuring readability and efficient use of space.
|
||||
|
||||
2. Participant avatars and labels: Include user avatars and/or labels to distinguish between users and bots visually, allowing easy identification of each participant's contributions.
|
||||
|
||||
3. Active threads indicator: Designate a clear indicator for ongoing threads, which will attract attention to active sub-conversations or related discussions in the conversation view.
|
||||
|
||||
4. Dynamic branching: Implement a branching visual structure to present threaded conversations, clearly distinguishing sub-conversations, and enabling users to navigate between them smoothly.
|
||||
|
||||
5. Multiple bot interaction support: Facilitate interactions between multiple bots through user-invoked prompts or bot-to-bot communication channels without disrupting the primary conversation flow.
|
||||
|
||||
6. Expandable conversation history: Offer a separate, expandable section or panel to access historical content without cluttering the main conversation view.
|
||||
|
||||
7. Linking and referencing: Implement a system for hyperlinking relevant information, resources, or previous conversations to the current discussion, making it easy for participants to follow and reference the shared context.
|
||||
|
||||
This hypothetical conversation UI aims to address the layout and visualization, threading, and multi-participant aspects of your requirements. Of course, it's just an initial proposal, and we can refine and iterate on it based on your feedback and specific needs.
|
||||
3
crates/ai/features.zmd
Normal file
3
crates/ai/features.zmd
Normal file
@@ -0,0 +1,3 @@
|
||||
Push content to a deeper layer.
|
||||
A context can have multiple sublayers.
|
||||
You can enable or disable arbitrary sublayers at arbitrary nesting depths when viewing the document.
|
||||
313
crates/ai/src/ai.rs
Normal file
313
crates/ai/src/ai.rs
Normal file
@@ -0,0 +1,313 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use assets::Assets;
|
||||
use collections::HashMap;
|
||||
use editor::Editor;
|
||||
use futures::AsyncBufReadExt;
|
||||
use futures::{io::BufReader, AsyncReadExt, Stream, StreamExt};
|
||||
use gpui::executor::Background;
|
||||
use gpui::{actions, AppContext, Task, ViewContext};
|
||||
use isahc::prelude::*;
|
||||
use isahc::{http::StatusCode, Request};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::fs;
|
||||
use std::rc::Rc;
|
||||
use std::{io, sync::Arc};
|
||||
use util::channel::{ReleaseChannel, RELEASE_CHANNEL};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
actions!(ai, [Assist]);
|
||||
|
||||
// Data types for chat completion requests
|
||||
#[derive(Serialize)]
|
||||
struct OpenAIRequest {
|
||||
model: String,
|
||||
messages: Vec<RequestMessage>,
|
||||
stream: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
struct RequestMessage {
|
||||
role: Role,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
struct ResponseMessage {
|
||||
role: Option<Role>,
|
||||
content: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum Role {
|
||||
User,
|
||||
Assistant,
|
||||
System,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct OpenAIResponseStreamEvent {
|
||||
pub id: Option<String>,
|
||||
pub object: String,
|
||||
pub created: u32,
|
||||
pub model: String,
|
||||
pub choices: Vec<ChatChoiceDelta>,
|
||||
pub usage: Option<Usage>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Usage {
|
||||
pub prompt_tokens: u32,
|
||||
pub completion_tokens: u32,
|
||||
pub total_tokens: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ChatChoiceDelta {
|
||||
pub index: u32,
|
||||
pub delta: ResponseMessage,
|
||||
pub finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct OpenAIUsage {
|
||||
prompt_tokens: u64,
|
||||
completion_tokens: u64,
|
||||
total_tokens: u64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct OpenAIChoice {
|
||||
text: String,
|
||||
index: u32,
|
||||
logprobs: Option<serde_json::Value>,
|
||||
finish_reason: Option<String>,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
if *RELEASE_CHANNEL == ReleaseChannel::Stable {
|
||||
return;
|
||||
}
|
||||
|
||||
let assistant = Rc::new(Assistant::default());
|
||||
cx.add_action({
|
||||
let assistant = assistant.clone();
|
||||
move |editor: &mut Editor, _: &Assist, cx: &mut ViewContext<Editor>| {
|
||||
assistant.assist(editor, cx).log_err();
|
||||
}
|
||||
});
|
||||
cx.capture_action({
|
||||
let assistant = assistant.clone();
|
||||
move |_: &mut Editor, _: &editor::Cancel, cx: &mut ViewContext<Editor>| {
|
||||
if !assistant.cancel_last_assist(cx.view_id()) {
|
||||
cx.propagate_action();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
type CompletionId = usize;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Assistant(RefCell<AssistantState>);
|
||||
|
||||
#[derive(Default)]
|
||||
struct AssistantState {
|
||||
assist_stacks: HashMap<usize, Vec<(CompletionId, Task<Option<()>>)>>,
|
||||
next_completion_id: CompletionId,
|
||||
}
|
||||
|
||||
impl Assistant {
|
||||
fn assist(self: &Rc<Self>, editor: &mut Editor, cx: &mut ViewContext<Editor>) -> Result<()> {
|
||||
let api_key = std::env::var("OPENAI_API_KEY")?;
|
||||
|
||||
let selections = editor.selections.all(cx);
|
||||
let (user_message, insertion_site) = editor.buffer().update(cx, |buffer, cx| {
|
||||
// Insert markers around selected text as described in the system prompt above.
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let mut user_message = String::new();
|
||||
let mut user_message_suffix = String::new();
|
||||
let mut buffer_offset = 0;
|
||||
for selection in selections {
|
||||
if !selection.is_empty() {
|
||||
if user_message_suffix.is_empty() {
|
||||
user_message_suffix.push_str("\n\n");
|
||||
}
|
||||
user_message_suffix.push_str("[Selected excerpt from above]\n");
|
||||
user_message_suffix
|
||||
.extend(snapshot.text_for_range(selection.start..selection.end));
|
||||
user_message_suffix.push_str("\n\n");
|
||||
}
|
||||
|
||||
user_message.extend(snapshot.text_for_range(buffer_offset..selection.start));
|
||||
user_message.push_str("[SELECTION_START]");
|
||||
user_message.extend(snapshot.text_for_range(selection.start..selection.end));
|
||||
buffer_offset = selection.end;
|
||||
user_message.push_str("[SELECTION_END]");
|
||||
}
|
||||
if buffer_offset < snapshot.len() {
|
||||
user_message.extend(snapshot.text_for_range(buffer_offset..snapshot.len()));
|
||||
}
|
||||
user_message.push_str(&user_message_suffix);
|
||||
|
||||
// Ensure the document ends with 4 trailing newlines.
|
||||
let trailing_newline_count = snapshot
|
||||
.reversed_chars_at(snapshot.len())
|
||||
.take_while(|c| *c == '\n')
|
||||
.take(4);
|
||||
let buffer_suffix = "\n".repeat(4 - trailing_newline_count.count());
|
||||
buffer.edit([(snapshot.len()..snapshot.len(), buffer_suffix)], None, cx);
|
||||
|
||||
let snapshot = buffer.snapshot(cx); // Take a new snapshot after editing.
|
||||
let insertion_site = snapshot.anchor_after(snapshot.len() - 2);
|
||||
|
||||
(user_message, insertion_site)
|
||||
});
|
||||
|
||||
let this = self.clone();
|
||||
let buffer = editor.buffer().clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
let editor_id = cx.view_id();
|
||||
let assist_id = util::post_inc(&mut self.0.borrow_mut().next_completion_id);
|
||||
let assist_task = cx.spawn(|_, mut cx| {
|
||||
async move {
|
||||
// TODO: We should have a get_string method on assets. This is repateated elsewhere.
|
||||
let content = Assets::get("contexts/system.zmd").unwrap();
|
||||
let mut system_message = std::str::from_utf8(content.data.as_ref())
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
if let Ok(custom_system_message_path) =
|
||||
std::env::var("ZED_ASSISTANT_SYSTEM_PROMPT_PATH")
|
||||
{
|
||||
system_message.push_str(
|
||||
"\n\nAlso consider the following user-defined system prompt:\n\n",
|
||||
);
|
||||
// TODO: Replace this with our file system trait object.
|
||||
system_message.push_str(
|
||||
&cx.background()
|
||||
.spawn(async move { fs::read_to_string(custom_system_message_path) })
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
let stream = stream_completion(
|
||||
api_key,
|
||||
executor,
|
||||
OpenAIRequest {
|
||||
model: "gpt-4".to_string(),
|
||||
messages: vec![
|
||||
RequestMessage {
|
||||
role: Role::System,
|
||||
content: system_message.to_string(),
|
||||
},
|
||||
RequestMessage {
|
||||
role: Role::User,
|
||||
content: user_message,
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
},
|
||||
);
|
||||
|
||||
let mut messages = stream.await?;
|
||||
while let Some(message) = messages.next().await {
|
||||
let mut message = message?;
|
||||
if let Some(choice) = message.choices.pop() {
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
let text: Arc<str> = choice.delta.content?.into();
|
||||
buffer.edit([(insertion_site.clone()..insertion_site, text)], None, cx);
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.0
|
||||
.borrow_mut()
|
||||
.assist_stacks
|
||||
.get_mut(&editor_id)
|
||||
.unwrap()
|
||||
.retain(|(id, _)| *id != assist_id);
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
});
|
||||
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.assist_stacks
|
||||
.entry(cx.view_id())
|
||||
.or_default()
|
||||
.push((assist_id, assist_task));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cancel_last_assist(self: &Rc<Self>, editor_id: usize) -> bool {
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.assist_stacks
|
||||
.get_mut(&editor_id)
|
||||
.and_then(|assists| assists.pop())
|
||||
.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_completion(
|
||||
api_key: String,
|
||||
executor: Arc<Background>,
|
||||
mut request: OpenAIRequest,
|
||||
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
|
||||
request.stream = true;
|
||||
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
|
||||
|
||||
let json_data = serde_json::to_string(&request)?;
|
||||
let mut response = Request::post("https://api.openai.com/v1/chat/completions")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.body(json_data)?
|
||||
.send_async()
|
||||
.await?;
|
||||
|
||||
let status = response.status();
|
||||
if status == StatusCode::OK {
|
||||
executor
|
||||
.spawn(async move {
|
||||
let mut lines = BufReader::new(response.body_mut()).lines();
|
||||
|
||||
fn parse_line(
|
||||
line: Result<String, io::Error>,
|
||||
) -> Result<Option<OpenAIResponseStreamEvent>> {
|
||||
if let Some(data) = line?.strip_prefix("data: ") {
|
||||
let event = serde_json::from_str(&data)?;
|
||||
Ok(Some(event))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(line) = lines.next().await {
|
||||
if let Some(event) = parse_line(line).transpose() {
|
||||
tx.unbounded_send(event).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(rx)
|
||||
} else {
|
||||
let mut body = String::new();
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
|
||||
Err(anyhow!(
|
||||
"Failed to connect to OpenAI API: {} {}",
|
||||
response.status(),
|
||||
body,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace" }
|
||||
util = { path = "../util" }
|
||||
anyhow.workspace = true
|
||||
isahc = "1.7"
|
||||
isahc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod update_notification;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use client::{Client, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
|
||||
use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{
|
||||
actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
||||
@@ -10,7 +10,7 @@ use gpui::{
|
||||
use isahc::AsyncBody;
|
||||
use serde::Deserialize;
|
||||
use serde_derive::Serialize;
|
||||
use settings::Settings;
|
||||
use settings::{Setting, SettingsStore};
|
||||
use smol::{fs::File, io::AsyncReadExt, process::Command};
|
||||
use std::{ffi::OsString, sync::Arc, time::Duration};
|
||||
use update_notification::UpdateNotification;
|
||||
@@ -58,18 +58,37 @@ impl Entity for AutoUpdater {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
struct AutoUpdateSetting(bool);
|
||||
|
||||
impl Setting for AutoUpdateSetting {
|
||||
const KEY: Option<&'static str> = Some("auto_update");
|
||||
|
||||
type FileContent = Option<bool>;
|
||||
|
||||
fn load(
|
||||
default_value: &Option<bool>,
|
||||
user_values: &[&Option<bool>],
|
||||
_: &AppContext,
|
||||
) -> Result<Self> {
|
||||
Ok(Self(
|
||||
Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
|
||||
settings::register::<AutoUpdateSetting>(cx);
|
||||
|
||||
if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) {
|
||||
let auto_updater = cx.add_model(|cx| {
|
||||
let updater = AutoUpdater::new(version, http_client, server_url);
|
||||
|
||||
let mut update_subscription = cx
|
||||
.global::<Settings>()
|
||||
.auto_update
|
||||
let mut update_subscription = settings::get::<AutoUpdateSetting>(cx)
|
||||
.0
|
||||
.then(|| updater.start_polling(cx));
|
||||
|
||||
cx.observe_global::<Settings, _>(move |updater, cx| {
|
||||
if cx.global::<Settings>().auto_update {
|
||||
cx.observe_global::<SettingsStore, _>(move |updater, cx| {
|
||||
if settings::get::<AutoUpdateSetting>(cx).0 {
|
||||
if update_subscription.is_none() {
|
||||
update_subscription = Some(updater.start_polling(cx))
|
||||
}
|
||||
@@ -262,7 +281,7 @@ impl AutoUpdater {
|
||||
let release_channel = cx
|
||||
.has_global::<ReleaseChannel>()
|
||||
.then(|| cx.global::<ReleaseChannel>().display_name());
|
||||
let telemetry = cx.global::<Settings>().telemetry().metrics();
|
||||
let telemetry = settings::get::<TelemetrySettings>(cx).metrics;
|
||||
|
||||
(installation_id, release_channel, telemetry)
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@ use gpui::{
|
||||
Element, Entity, View, ViewContext,
|
||||
};
|
||||
use menu::Cancel;
|
||||
use settings::Settings;
|
||||
use util::channel::ReleaseChannel;
|
||||
use workspace::notifications::Notification;
|
||||
|
||||
@@ -27,7 +26,7 @@ impl View for UpdateNotification {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let theme = &theme.update_notification;
|
||||
|
||||
let app_name = cx.global::<ReleaseChannel>().display_name();
|
||||
|
||||
@@ -4,7 +4,6 @@ use gpui::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use search::ProjectSearchView;
|
||||
use settings::Settings;
|
||||
use workspace::{
|
||||
item::{ItemEvent, ItemHandle},
|
||||
ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
@@ -50,7 +49,7 @@ impl View for Breadcrumbs {
|
||||
};
|
||||
let not_editor = active_item.downcast::<editor::Editor>().is_none();
|
||||
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let style = &theme.workspace.breadcrumbs;
|
||||
|
||||
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
|
||||
|
||||
@@ -31,6 +31,7 @@ log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
schemars.workspace = true
|
||||
smol.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
|
||||
@@ -15,19 +15,17 @@ use futures::{
|
||||
TryStreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions,
|
||||
platform::AppVersion,
|
||||
serde_json::{self},
|
||||
AnyModelHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
|
||||
ModelHandle, Task, View, ViewContext, WeakViewHandle,
|
||||
actions, platform::AppVersion, serde_json, AnyModelHandle, AnyWeakModelHandle,
|
||||
AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Task, View, ViewContext,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::HashMap,
|
||||
@@ -72,25 +70,34 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
actions!(client, [SignIn, SignOut]);
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
pub fn init_settings(cx: &mut AppContext) {
|
||||
settings::register::<TelemetrySettings>(cx);
|
||||
}
|
||||
|
||||
pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
|
||||
init_settings(cx);
|
||||
|
||||
let client = Arc::downgrade(client);
|
||||
cx.add_global_action({
|
||||
let client = client.clone();
|
||||
move |_: &SignIn, cx| {
|
||||
let client = client.clone();
|
||||
cx.spawn(
|
||||
|cx| async move { client.authenticate_and_connect(true, &cx).log_err().await },
|
||||
)
|
||||
.detach();
|
||||
if let Some(client) = client.upgrade() {
|
||||
cx.spawn(
|
||||
|cx| async move { client.authenticate_and_connect(true, &cx).log_err().await },
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.add_global_action({
|
||||
let client = client.clone();
|
||||
move |_: &SignOut, cx| {
|
||||
let client = client.clone();
|
||||
cx.spawn(|cx| async move {
|
||||
client.disconnect(&cx);
|
||||
})
|
||||
.detach();
|
||||
if let Some(client) = client.upgrade() {
|
||||
cx.spawn(|cx| async move {
|
||||
client.disconnect(&cx);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -326,6 +333,42 @@ impl<T: Entity> Drop for PendingEntitySubscription<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TelemetrySettings {
|
||||
pub diagnostics: bool,
|
||||
pub metrics: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct TelemetrySettingsContent {
|
||||
pub diagnostics: Option<bool>,
|
||||
pub metrics: Option<bool>,
|
||||
}
|
||||
|
||||
impl settings::Setting for TelemetrySettings {
|
||||
const KEY: Option<&'static str> = Some("telemetry");
|
||||
|
||||
type FileContent = TelemetrySettingsContent;
|
||||
|
||||
fn load(
|
||||
default_value: &Self::FileContent,
|
||||
user_values: &[&Self::FileContent],
|
||||
_: &AppContext,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or(
|
||||
default_value
|
||||
.diagnostics
|
||||
.ok_or_else(Self::missing_default)?,
|
||||
),
|
||||
metrics: user_values
|
||||
.first()
|
||||
.and_then(|v| v.metrics)
|
||||
.unwrap_or(default_value.metrics.ok_or_else(Self::missing_default)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
@@ -447,9 +490,7 @@ impl Client {
|
||||
}));
|
||||
}
|
||||
Status::SignedOut | Status::UpgradeRequired => {
|
||||
let telemetry_settings = cx.read(|cx| cx.global::<Settings>().telemetry());
|
||||
self.telemetry
|
||||
.set_authenticated_user_info(None, false, telemetry_settings);
|
||||
cx.read(|cx| self.telemetry.set_authenticated_user_info(None, false, cx));
|
||||
state._reconnect_task.take();
|
||||
}
|
||||
_ => {}
|
||||
@@ -740,7 +781,7 @@ impl Client {
|
||||
self.telemetry().report_mixpanel_event(
|
||||
"read credentials from keychain",
|
||||
Default::default(),
|
||||
cx.global::<Settings>().telemetry(),
|
||||
*settings::get::<TelemetrySettings>(cx),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1033,7 +1074,8 @@ impl Client {
|
||||
let executor = cx.background();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let http = self.http.clone();
|
||||
let metrics_enabled = cx.read(|cx| cx.global::<Settings>().telemetry());
|
||||
|
||||
let telemetry_settings = cx.read(|cx| *settings::get::<TelemetrySettings>(cx));
|
||||
|
||||
executor.clone().spawn(async move {
|
||||
// Generate a pair of asymmetric encryption keys. The public key will be used by the
|
||||
@@ -1120,7 +1162,7 @@ impl Client {
|
||||
telemetry.report_mixpanel_event(
|
||||
"authenticate with browser",
|
||||
Default::default(),
|
||||
metrics_enabled,
|
||||
telemetry_settings,
|
||||
);
|
||||
|
||||
Ok(Credentials {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
||||
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{
|
||||
executor::Background,
|
||||
@@ -9,8 +9,8 @@ use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use settings::TelemetrySettings;
|
||||
use std::{
|
||||
env,
|
||||
io::Write,
|
||||
mem,
|
||||
path::PathBuf,
|
||||
@@ -34,8 +34,9 @@ struct TelemetryState {
|
||||
installation_id: Option<Arc<str>>, // Per app installation
|
||||
app_version: Option<Arc<str>>,
|
||||
release_channel: Option<&'static str>,
|
||||
os_version: Option<Arc<str>>,
|
||||
os_name: &'static str,
|
||||
os_version: Option<Arc<str>>,
|
||||
architecture: &'static str,
|
||||
mixpanel_events_queue: Vec<MixpanelEvent>,
|
||||
clickhouse_events_queue: Vec<ClickhouseEventWrapper>,
|
||||
next_mixpanel_event_id: usize,
|
||||
@@ -64,6 +65,7 @@ struct ClickhouseEventRequestBody {
|
||||
app_version: Option<Arc<str>>,
|
||||
os_name: &'static str,
|
||||
os_version: Option<Arc<str>>,
|
||||
architecture: &'static str,
|
||||
release_channel: Option<&'static str>,
|
||||
events: Vec<ClickhouseEventWrapper>,
|
||||
}
|
||||
@@ -154,12 +156,14 @@ impl Telemetry {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
||||
let this = Arc::new(Self {
|
||||
http_client: client,
|
||||
executor: cx.background().clone(),
|
||||
state: Mutex::new(TelemetryState {
|
||||
os_version: platform.os_version().ok().map(|v| v.to_string().into()),
|
||||
os_name: platform.os_name().into(),
|
||||
os_version: platform.os_version().ok().map(|v| v.to_string().into()),
|
||||
architecture: env::consts::ARCH,
|
||||
app_version: platform.app_version().ok().map(|v| v.to_string().into()),
|
||||
release_channel,
|
||||
installation_id: None,
|
||||
@@ -246,9 +250,9 @@ impl Telemetry {
|
||||
self: &Arc<Self>,
|
||||
metrics_id: Option<String>,
|
||||
is_staff: bool,
|
||||
telemetry_settings: TelemetrySettings,
|
||||
cx: &AppContext,
|
||||
) {
|
||||
if !telemetry_settings.metrics() {
|
||||
if !settings::get::<TelemetrySettings>(cx).metrics {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -290,7 +294,7 @@ impl Telemetry {
|
||||
event: ClickhouseEvent,
|
||||
telemetry_settings: TelemetrySettings,
|
||||
) {
|
||||
if !telemetry_settings.metrics() {
|
||||
if !telemetry_settings.metrics {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -326,7 +330,7 @@ impl Telemetry {
|
||||
properties: Value,
|
||||
telemetry_settings: TelemetrySettings,
|
||||
) {
|
||||
if !telemetry_settings.metrics() {
|
||||
if !telemetry_settings.metrics {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -452,6 +456,8 @@ impl Telemetry {
|
||||
app_version: state.app_version.clone(),
|
||||
os_name: state.os_name,
|
||||
os_version: state.os_version.clone(),
|
||||
architecture: state.architecture,
|
||||
|
||||
release_channel: state.release_channel,
|
||||
events,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,6 @@ use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
|
||||
use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
|
||||
use postage::{sink::Sink, watch};
|
||||
use rpc::proto::{RequestMessage, UsersResponse};
|
||||
use settings::Settings;
|
||||
use staff_mode::StaffMode;
|
||||
use std::sync::{Arc, Weak};
|
||||
use util::http::HttpClient;
|
||||
@@ -144,11 +143,13 @@ impl UserStore {
|
||||
let fetch_metrics_id =
|
||||
client.request(proto::GetPrivateUserInfo {}).log_err();
|
||||
let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
|
||||
client.telemetry.set_authenticated_user_info(
|
||||
info.as_ref().map(|info| info.metrics_id.clone()),
|
||||
info.as_ref().map(|info| info.staff).unwrap_or(false),
|
||||
cx.read(|cx| cx.global::<Settings>().telemetry()),
|
||||
);
|
||||
cx.read(|cx| {
|
||||
client.telemetry.set_authenticated_user_info(
|
||||
info.as_ref().map(|info| info.metrics_id.clone()),
|
||||
info.as_ref().map(|info| info.staff).unwrap_or(false),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.update_default_global(|staff_mode: &mut StaffMode, _| {
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.12.2"
|
||||
version = "0.12.4"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
@@ -51,7 +51,7 @@ tokio = { version = "1", features = ["full"] }
|
||||
tokio-tungstenite = "0.17"
|
||||
tonic = "0.6"
|
||||
tower = "0.4"
|
||||
toml = "0.5.8"
|
||||
toml.workspace = true
|
||||
tracing = "0.1.34"
|
||||
tracing-log = "0.1.3"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
|
||||
@@ -76,7 +76,7 @@ workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
indoc = "1.0.4"
|
||||
indoc.workspace = true
|
||||
util = { path = "../util" }
|
||||
lazy_static.workspace = true
|
||||
sea-orm = { git = "https://github.com/zed-industries/sea-orm", rev = "18f4c691085712ad014a51792af75a9044bacee6", features = ["sqlx-sqlite"] }
|
||||
|
||||
@@ -1514,6 +1514,7 @@ impl Database {
|
||||
let mut db_entries = worktree_entry::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(worktree_entry::Column::ProjectId.eq(project.id))
|
||||
.add(worktree_entry::Column::WorktreeId.eq(worktree.id))
|
||||
.add(entry_filter),
|
||||
)
|
||||
@@ -1553,6 +1554,7 @@ impl Database {
|
||||
let mut 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),
|
||||
)
|
||||
@@ -1590,6 +1592,10 @@ impl Database {
|
||||
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),
|
||||
|
||||
@@ -51,7 +51,7 @@ use std::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::sync::{watch, Semaphore};
|
||||
use tower::ServiceBuilder;
|
||||
@@ -223,6 +223,7 @@ impl Server {
|
||||
.add_request_handler(forward_project_request::<proto::RenameProjectEntry>)
|
||||
.add_request_handler(forward_project_request::<proto::CopyProjectEntry>)
|
||||
.add_request_handler(forward_project_request::<proto::DeleteProjectEntry>)
|
||||
.add_request_handler(forward_project_request::<proto::OnTypeFormatting>)
|
||||
.add_message_handler(create_buffer_for_peer)
|
||||
.add_request_handler(update_buffer)
|
||||
.add_message_handler(update_buffer_file)
|
||||
@@ -397,10 +398,16 @@ impl Server {
|
||||
"message received"
|
||||
);
|
||||
});
|
||||
let start_time = Instant::now();
|
||||
let future = (handler)(*envelope, session);
|
||||
async move {
|
||||
if let Err(error) = future.await {
|
||||
tracing::error!(%error, "error handling message");
|
||||
let result = future.await;
|
||||
let duration_ms = start_time.elapsed().as_micros() as f64 / 1000.0;
|
||||
match result {
|
||||
Err(error) => {
|
||||
tracing::error!(%error, ?duration_ms, "error handling message")
|
||||
}
|
||||
Ok(()) => tracing::info!(?duration_ms, "finished handling message"),
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
|
||||
@@ -19,7 +19,7 @@ use gpui::{
|
||||
use language::LanguageRegistry;
|
||||
use parking_lot::Mutex;
|
||||
use project::{Project, WorktreeId};
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
env,
|
||||
@@ -30,7 +30,6 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use theme::ThemeRegistry;
|
||||
use util::http::FakeHttpClient;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -102,7 +101,7 @@ impl TestServer {
|
||||
|
||||
async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
|
||||
cx.update(|cx| {
|
||||
cx.set_global(Settings::test(cx));
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
});
|
||||
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
@@ -191,16 +190,18 @@ impl TestServer {
|
||||
client: client.clone(),
|
||||
user_store: user_store.clone(),
|
||||
languages: Arc::new(LanguageRegistry::test()),
|
||||
themes: ThemeRegistry::new((), cx.font_cache()),
|
||||
fs: fs.clone(),
|
||||
build_window_options: |_, _, _| Default::default(),
|
||||
initialize_workspace: |_, _, _| unimplemented!(),
|
||||
dock_default_item_factory: |_, _| None,
|
||||
initialize_workspace: |_, _, _, _| unimplemented!(),
|
||||
background_actions: || &[],
|
||||
});
|
||||
|
||||
Project::init(&client);
|
||||
cx.update(|cx| {
|
||||
theme::init((), cx);
|
||||
Project::init(&client, cx);
|
||||
client::init(&client, cx);
|
||||
language::init(cx);
|
||||
editor::init_settings(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
call::init(client.clone(), user_store.clone(), cx);
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ use gpui::{
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, Formatter},
|
||||
tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
||||
LanguageConfig, OffsetRangeExt, Point, Rope,
|
||||
};
|
||||
@@ -26,7 +27,7 @@ use lsp::LanguageServerId;
|
||||
use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath};
|
||||
use rand::prelude::*;
|
||||
use serde_json::json;
|
||||
use settings::{Formatter, Settings};
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
env, future, mem,
|
||||
@@ -1438,7 +1439,6 @@ async fn test_host_disconnect(
|
||||
cx_b: &mut TestAppContext,
|
||||
cx_c: &mut TestAppContext,
|
||||
) {
|
||||
cx_b.update(editor::init);
|
||||
deterministic.forbid_parking();
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
@@ -1448,6 +1448,8 @@ async fn test_host_disconnect(
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
|
||||
.await;
|
||||
|
||||
cx_b.update(editor::init);
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
@@ -1545,7 +1547,6 @@ async fn test_project_reconnect(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
cx_b.update(editor::init);
|
||||
deterministic.forbid_parking();
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
@@ -1554,6 +1555,8 @@ async fn test_project_reconnect(
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
|
||||
cx_b.update(editor::init);
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
@@ -2434,7 +2437,7 @@ async fn test_git_diff_base_change(
|
||||
buffer_local_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "", "two\n")],
|
||||
@@ -2454,7 +2457,7 @@ async fn test_git_diff_base_change(
|
||||
buffer_remote_a.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "", "two\n")],
|
||||
@@ -2478,7 +2481,7 @@ async fn test_git_diff_base_change(
|
||||
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
|
||||
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(2..3, "", "three\n")],
|
||||
@@ -2489,7 +2492,7 @@ async fn test_git_diff_base_change(
|
||||
buffer_remote_a.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(2..3, "", "three\n")],
|
||||
@@ -2532,7 +2535,7 @@ async fn test_git_diff_base_change(
|
||||
buffer_local_b.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "", "two\n")],
|
||||
@@ -2552,7 +2555,7 @@ async fn test_git_diff_base_change(
|
||||
buffer_remote_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(1..2, "", "two\n")],
|
||||
@@ -2580,12 +2583,12 @@ async fn test_git_diff_base_change(
|
||||
"{:?}",
|
||||
buffer
|
||||
.snapshot()
|
||||
.git_diff_hunks_in_row_range(0..4, false)
|
||||
.git_diff_hunks_in_row_range(0..4)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(2..3, "", "three\n")],
|
||||
@@ -2596,7 +2599,7 @@ async fn test_git_diff_base_change(
|
||||
buffer_remote_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
|
||||
git::diff::assert_hunks(
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
|
||||
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[(2..3, "", "three\n")],
|
||||
@@ -2685,6 +2688,7 @@ async fn test_git_branch_name(
|
||||
});
|
||||
|
||||
let project_remote_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||
deterministic.run_until_parked();
|
||||
project_remote_c.read_with(cx_c, |project, cx| {
|
||||
assert_branch(Some("branch-2"), project, cx)
|
||||
});
|
||||
@@ -4367,10 +4371,12 @@ async fn test_formatting_buffer(
|
||||
// Ensure buffer can be formatted using an external command. Notice how the
|
||||
// host's configuration is honored as opposed to using the guest's settings.
|
||||
cx_a.update(|cx| {
|
||||
cx.update_global(|settings: &mut Settings, _| {
|
||||
settings.editor_defaults.formatter = Some(Formatter::External {
|
||||
command: "awk".to_string(),
|
||||
arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(Formatter::External {
|
||||
command: "awk".into(),
|
||||
arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5004,19 +5010,21 @@ async fn test_project_symbols(
|
||||
.unwrap();
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
|
||||
#[allow(deprecated)]
|
||||
Ok(Some(vec![lsp::SymbolInformation {
|
||||
name: "TWO".into(),
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
fake_language_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(|_, _| async move {
|
||||
Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
|
||||
#[allow(deprecated)]
|
||||
lsp::SymbolInformation {
|
||||
name: "TWO".into(),
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
},
|
||||
kind: lsp::SymbolKind::CONSTANT,
|
||||
tags: None,
|
||||
container_name: None,
|
||||
deprecated: None,
|
||||
},
|
||||
kind: lsp::SymbolKind::CONSTANT,
|
||||
tags: None,
|
||||
container_name: None,
|
||||
deprecated: None,
|
||||
}]))
|
||||
])))
|
||||
});
|
||||
|
||||
// Request the definition of a symbol as the guest.
|
||||
@@ -5137,7 +5145,6 @@ async fn test_collaborating_with_code_actions(
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
cx_b.update(editor::init);
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -5146,6 +5153,8 @@ async fn test_collaborating_with_code_actions(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
@@ -5350,7 +5359,6 @@ async fn test_collaborating_with_renames(
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
cx_b.update(editor::init);
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -5359,6 +5367,8 @@ async fn test_collaborating_with_renames(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
@@ -5540,8 +5550,6 @@ async fn test_language_server_statuses(
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
|
||||
cx_b.update(editor::init);
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -5550,6 +5558,8 @@ async fn test_language_server_statuses(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
@@ -6257,8 +6267,6 @@ async fn test_basic_following(
|
||||
cx_d: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
@@ -6276,6 +6284,9 @@ async fn test_basic_following(
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
@@ -6597,7 +6608,7 @@ async fn test_basic_following(
|
||||
// When client A navigates back and forth, client B does so as well.
|
||||
workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_back(workspace, None, cx)
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6608,7 +6619,7 @@ async fn test_basic_following(
|
||||
|
||||
workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_back(workspace, None, cx)
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6619,7 +6630,7 @@ async fn test_basic_following(
|
||||
|
||||
workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_forward(workspace, None, cx)
|
||||
workspace.go_forward(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6854,9 +6865,6 @@ async fn test_following_tab_order(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -6866,6 +6874,9 @@ async fn test_following_tab_order(
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
@@ -6976,9 +6987,6 @@ async fn test_peers_following_each_other(
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -6988,6 +6996,9 @@ async fn test_peers_following_each_other(
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// Client A shares a project.
|
||||
client_a
|
||||
.fs
|
||||
@@ -7147,8 +7158,6 @@ async fn test_auto_unfollowing(
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// 2 clients connect to a server.
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
@@ -7160,6 +7169,9 @@ async fn test_auto_unfollowing(
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
// Client A shares a project.
|
||||
client_a
|
||||
.fs
|
||||
@@ -7314,8 +7326,6 @@ async fn test_peers_simultaneously_following_each_other(
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
@@ -7325,6 +7335,9 @@ async fn test_peers_simultaneously_following_each_other(
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
client_a.fs.insert_tree("/a", json!({})).await;
|
||||
let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
|
||||
let workspace_a = client_a.build_workspace(&project_a, cx_a);
|
||||
@@ -7366,6 +7379,265 @@ async fn test_peers_simultaneously_following_each_other(
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_on_input_format_from_host_to_guest(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: ":".to_string(),
|
||||
more_trigger_character: Some(vec![">".to_string()]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry.add(Arc::new(language));
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { a }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a file in an editor as the host.
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let (window_a, _) = cx_a.add_window(|_| EmptyView);
|
||||
let editor_a = cx_a.add_view(window_a, |cx| {
|
||||
Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
|
||||
});
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
cx_b.foreground().run_until_parked();
|
||||
|
||||
// Receive an OnTypeFormatting request as the host's language server.
|
||||
// Return some formattings from the host's language server.
|
||||
fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
|
||||
|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
lsp::Position::new(0, 14),
|
||||
);
|
||||
|
||||
Ok(Some(vec![lsp::TextEdit {
|
||||
new_text: "~<".to_string(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
|
||||
}]))
|
||||
},
|
||||
);
|
||||
|
||||
// Open the buffer on the guest and see that the formattings worked
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
cx.focus(&editor_a);
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(">", cx);
|
||||
});
|
||||
|
||||
cx_b.foreground().run_until_parked();
|
||||
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a>~< }")
|
||||
});
|
||||
|
||||
// Undo should remove LSP edits first
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a>~< }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
});
|
||||
cx_b.foreground().run_until_parked();
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a> }")
|
||||
});
|
||||
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
});
|
||||
cx_b.foreground().run_until_parked();
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a }")
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_on_input_format_from_guest_to_host(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
let mut server = TestServer::start(&deterministic).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: ":".to_string(),
|
||||
more_trigger_character: Some(vec![">".to_string()]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry.add(Arc::new(language));
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { a }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a file in an editor as the guest.
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let (window_b, _) = cx_b.add_window(|_| EmptyView);
|
||||
let editor_b = cx_b.add_view(window_b, |cx| {
|
||||
Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
|
||||
});
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
cx_a.foreground().run_until_parked();
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(":", cx);
|
||||
cx.focus(&editor_b);
|
||||
});
|
||||
|
||||
// Receive an OnTypeFormatting request as the host's language server.
|
||||
// Return some formattings from the host's language server.
|
||||
cx_a.foreground().start_waiting();
|
||||
fake_language_server
|
||||
.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
lsp::Position::new(0, 14),
|
||||
);
|
||||
|
||||
Ok(Some(vec![lsp::TextEdit {
|
||||
new_text: "~:".to_string(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
|
||||
}]))
|
||||
})
|
||||
.next()
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.foreground().finish_waiting();
|
||||
|
||||
// Open the buffer on the host and see that the formattings worked
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.foreground().run_until_parked();
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a:~: }")
|
||||
});
|
||||
|
||||
// Undo should remove LSP edits first
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a:~: }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
});
|
||||
cx_a.foreground().run_until_parked();
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a: }")
|
||||
});
|
||||
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
});
|
||||
cx_a.foreground().run_until_parked();
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a }")
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct RoomParticipants {
|
||||
remote: Vec<String>,
|
||||
|
||||
@@ -21,7 +21,7 @@ use rand::{
|
||||
prelude::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
env,
|
||||
ops::Range,
|
||||
@@ -149,8 +149,9 @@ async fn test_random_collaboration(
|
||||
|
||||
for (client, mut cx) in clients {
|
||||
cx.update(|cx| {
|
||||
let store = cx.remove_global::<SettingsStore>();
|
||||
cx.clear_globals();
|
||||
cx.set_global(Settings::test(cx));
|
||||
cx.set_global(store);
|
||||
drop(client);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ use gpui::{
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::{AvatarStyle, Theme};
|
||||
use util::ResultExt;
|
||||
@@ -70,7 +69,7 @@ impl View for CollabTitlebarItem {
|
||||
};
|
||||
|
||||
let project = self.project.read(cx);
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let mut left_container = Flex::row();
|
||||
let mut right_container = Flex::row().align_children_center();
|
||||
|
||||
@@ -298,7 +297,7 @@ impl CollabTitlebarItem {
|
||||
}
|
||||
|
||||
pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let avatar_style = theme.workspace.titlebar.leader_avatar.clone();
|
||||
let item_style = theme.context_menu.item.disabled_style().clone();
|
||||
self.user_menu.update(cx, |user_menu, cx| {
|
||||
@@ -866,7 +865,7 @@ impl CollabTitlebarItem {
|
||||
) -> Option<AnyElement<Self>> {
|
||||
enum ConnectionStatusButton {}
|
||||
|
||||
let theme = &cx.global::<Settings>().theme.clone();
|
||||
let theme = &theme::current(cx).clone();
|
||||
match status {
|
||||
client::Status::ConnectionError
|
||||
| client::Status::ConnectionLost
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use client::{ContactRequestStatus, User, UserStore};
|
||||
use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use util::TryFutureExt;
|
||||
|
||||
@@ -98,7 +97,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
selected: bool,
|
||||
cx: &gpui::AppContext,
|
||||
) -> AnyElement<Picker<Self>> {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
let theme = &theme::current(cx);
|
||||
let user = &self.potential_contacts[ix];
|
||||
let request_status = self.user_store.read(cx).contact_request_status(user);
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ use gpui::{
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::Project;
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::{mem, sync::Arc};
|
||||
use theme::IconButton;
|
||||
use workspace::Workspace;
|
||||
@@ -192,7 +191,7 @@ impl ContactList {
|
||||
.detach();
|
||||
|
||||
let list_state = ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let is_selected = this.selection == Some(ix);
|
||||
let current_project_id = this.project.read(cx).remote_id();
|
||||
|
||||
@@ -1313,7 +1312,7 @@ impl View for ContactList {
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
enum AddContact {}
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
|
||||
Flex::column()
|
||||
.with_child(
|
||||
|
||||
@@ -9,7 +9,6 @@ use gpui::{
|
||||
};
|
||||
use picker::PickerEvent;
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use workspace::Workspace;
|
||||
|
||||
actions!(contacts_popover, [ToggleContactFinder]);
|
||||
@@ -108,7 +107,7 @@ impl View for ContactsPopover {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let child = match &self.child {
|
||||
Child::ContactList(child) => ChildView::new(child, cx),
|
||||
Child::ContactFinder(child) => ChildView::new(child, cx),
|
||||
|
||||
@@ -9,7 +9,6 @@ use gpui::{
|
||||
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
|
||||
AnyElement, AppContext, Entity, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
|
||||
@@ -26,7 +25,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
if let Some(incoming_call) = incoming_call {
|
||||
const PADDING: f32 = 16.;
|
||||
let window_size = cx.read(|cx| {
|
||||
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
|
||||
let theme = &theme::current(cx).incoming_call_notification;
|
||||
vec2f(theme.window_width, theme.window_height)
|
||||
});
|
||||
|
||||
@@ -42,6 +41,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
titlebar: None,
|
||||
center: false,
|
||||
focus: false,
|
||||
show: true,
|
||||
kind: WindowKind::PopUp,
|
||||
is_movable: false,
|
||||
screen: Some(screen),
|
||||
@@ -106,7 +106,7 @@ impl IncomingCallNotification {
|
||||
}
|
||||
|
||||
fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
|
||||
let theme = &theme::current(cx).incoming_call_notification;
|
||||
let default_project = proto::ParticipantProject::default();
|
||||
let initial_project = self
|
||||
.call
|
||||
@@ -170,10 +170,11 @@ impl IncomingCallNotification {
|
||||
enum Accept {}
|
||||
enum Decline {}
|
||||
|
||||
let theme = theme::current(cx);
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::<Accept, Self>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
|
||||
MouseEventHandler::<Accept, Self>::new(0, cx, |_, _| {
|
||||
let theme = &theme.incoming_call_notification;
|
||||
Label::new("Accept", theme.accept_button.text.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
@@ -186,8 +187,8 @@ impl IncomingCallNotification {
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Decline, Self>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
|
||||
MouseEventHandler::<Decline, Self>::new(0, cx, |_, _| {
|
||||
let theme = &theme.incoming_call_notification;
|
||||
Label::new("Decline", theme.decline_button.text.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
@@ -200,12 +201,7 @@ impl IncomingCallNotification {
|
||||
.flex(1., true),
|
||||
)
|
||||
.constrained()
|
||||
.with_width(
|
||||
cx.global::<Settings>()
|
||||
.theme
|
||||
.incoming_call_notification
|
||||
.button_width,
|
||||
)
|
||||
.with_width(theme.incoming_call_notification.button_width)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
@@ -220,12 +216,7 @@ impl View for IncomingCallNotification {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let background = cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
.incoming_call_notification
|
||||
.background;
|
||||
|
||||
let background = theme::current(cx).incoming_call_notification.background;
|
||||
Flex::row()
|
||||
.with_child(self.render_caller(cx))
|
||||
.with_child(self.render_buttons(cx))
|
||||
|
||||
@@ -4,7 +4,6 @@ use gpui::{
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AnyElement, Element, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
|
||||
enum Dismiss {}
|
||||
@@ -22,7 +21,7 @@ where
|
||||
F: 'static + Fn(&mut V, &mut ViewContext<V>),
|
||||
V: View,
|
||||
{
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let theme = &theme.contact_notification;
|
||||
|
||||
Flex::column()
|
||||
|
||||
@@ -7,7 +7,6 @@ use gpui::{
|
||||
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
|
||||
AppContext, Entity, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::sync::{Arc, Weak};
|
||||
use workspace::AppState;
|
||||
|
||||
@@ -22,7 +21,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
worktree_root_names,
|
||||
} => {
|
||||
const PADDING: f32 = 16.;
|
||||
let theme = &cx.global::<Settings>().theme.project_shared_notification;
|
||||
let theme = &theme::current(cx).project_shared_notification;
|
||||
let window_size = vec2f(theme.window_width, theme.window_height);
|
||||
|
||||
for screen in cx.platform().screens() {
|
||||
@@ -36,6 +35,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
titlebar: None,
|
||||
center: false,
|
||||
focus: false,
|
||||
show: true,
|
||||
kind: WindowKind::PopUp,
|
||||
is_movable: false,
|
||||
screen: Some(screen),
|
||||
@@ -109,7 +109,7 @@ impl ProjectSharedNotification {
|
||||
}
|
||||
|
||||
fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = &cx.global::<Settings>().theme.project_shared_notification;
|
||||
let theme = &theme::current(cx).project_shared_notification;
|
||||
Flex::row()
|
||||
.with_children(self.owner.avatar.clone().map(|avatar| {
|
||||
Image::from_data(avatar)
|
||||
@@ -167,10 +167,11 @@ impl ProjectSharedNotification {
|
||||
enum Open {}
|
||||
enum Dismiss {}
|
||||
|
||||
let theme = theme::current(cx);
|
||||
Flex::column()
|
||||
.with_child(
|
||||
MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.project_shared_notification;
|
||||
MouseEventHandler::<Open, Self>::new(0, cx, |_, _| {
|
||||
let theme = &theme.project_shared_notification;
|
||||
Label::new("Open", theme.open_button.text.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
@@ -181,8 +182,8 @@ impl ProjectSharedNotification {
|
||||
.flex(1., true),
|
||||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme.project_shared_notification;
|
||||
MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, _| {
|
||||
let theme = &theme.project_shared_notification;
|
||||
Label::new("Dismiss", theme.dismiss_button.text.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
@@ -195,12 +196,7 @@ impl ProjectSharedNotification {
|
||||
.flex(1., true),
|
||||
)
|
||||
.constrained()
|
||||
.with_width(
|
||||
cx.global::<Settings>()
|
||||
.theme
|
||||
.project_shared_notification
|
||||
.button_width,
|
||||
)
|
||||
.with_width(theme.project_shared_notification.button_width)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
@@ -215,11 +211,7 @@ impl View for ProjectSharedNotification {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
|
||||
let background = cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
.project_shared_notification
|
||||
.background;
|
||||
let background = theme::current(cx).project_shared_notification.background;
|
||||
Flex::row()
|
||||
.with_child(self.render_owner(cx))
|
||||
.with_child(self.render_buttons(cx))
|
||||
|
||||
@@ -6,7 +6,7 @@ use gpui::{
|
||||
platform::{Appearance, MouseButton},
|
||||
AnyElement, AppContext, Element, Entity, View, ViewContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::WorkspaceSettings;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let active_call = ActiveCall::global(cx);
|
||||
@@ -15,7 +15,9 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.observe(&active_call, move |call, cx| {
|
||||
if let Some(room) = call.read(cx).room() {
|
||||
if room.read(cx).is_screen_sharing() {
|
||||
if status_indicator.is_none() && cx.global::<Settings>().show_call_status_icon {
|
||||
if status_indicator.is_none()
|
||||
&& settings::get::<WorkspaceSettings>(cx).show_call_status_icon
|
||||
{
|
||||
status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator));
|
||||
}
|
||||
} else if let Some((window_id, _)) = status_indicator.take() {
|
||||
|
||||
@@ -23,6 +23,7 @@ workspace = { path = "../workspace" }
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
@@ -5,7 +5,6 @@ use gpui::{
|
||||
ViewContext,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use settings::Settings;
|
||||
use std::cmp;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
@@ -185,8 +184,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
) -> AnyElement<Picker<Self>> {
|
||||
let mat = &self.matches[ix];
|
||||
let command = &self.actions[mat.candidate_id];
|
||||
let settings = cx.global::<Settings>();
|
||||
let theme = &settings.theme;
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||
let key_style = &theme.command_palette.key.style_for(mouse_state, selected);
|
||||
let keystroke_spacing = theme.command_palette.keystroke_spacing;
|
||||
@@ -294,14 +292,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_command_palette(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||
deterministic.forbid_parking();
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
editor::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
init(cx);
|
||||
});
|
||||
let app_state = init_test(cx);
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
@@ -369,4 +360,16 @@ mod tests {
|
||||
assert!(palette.delegate().matches.is_empty())
|
||||
});
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
|
||||
cx.update(|cx| {
|
||||
let app_state = AppState::test(cx);
|
||||
theme::init((), cx);
|
||||
language::init(cx);
|
||||
editor::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
init(cx);
|
||||
app_state
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use gpui::{
|
||||
View, ViewContext,
|
||||
};
|
||||
use menu::*;
|
||||
use settings::Settings;
|
||||
use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
@@ -323,7 +322,7 @@ impl ContextMenu {
|
||||
}
|
||||
|
||||
fn render_menu_for_measurement(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
|
||||
let style = cx.global::<Settings>().theme.context_menu.clone();
|
||||
let style = theme::current(cx).context_menu.clone();
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
@@ -403,7 +402,7 @@ impl ContextMenu {
|
||||
enum Menu {}
|
||||
enum MenuItem {}
|
||||
|
||||
let style = cx.global::<Settings>().theme.context_menu.clone();
|
||||
let style = theme::current(cx).context_menu.clone();
|
||||
|
||||
MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
|
||||
Flex::column()
|
||||
|
||||
@@ -10,6 +10,7 @@ use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle,
|
||||
};
|
||||
use language::{
|
||||
language_settings::{all_language_settings, language_settings},
|
||||
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
|
||||
ToPointUtf16,
|
||||
};
|
||||
@@ -17,7 +18,7 @@ use log::{debug, error};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use node_runtime::NodeRuntime;
|
||||
use request::{LogMessage, StatusNotification};
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
@@ -302,56 +303,34 @@ impl Copilot {
|
||||
node_runtime: Arc<NodeRuntime>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
cx.observe_global::<Settings, _>({
|
||||
let http = http.clone();
|
||||
let node_runtime = node_runtime.clone();
|
||||
move |this, cx| {
|
||||
if cx.global::<Settings>().features.copilot {
|
||||
if matches!(this.server, CopilotServer::Disabled) {
|
||||
let start_task = cx
|
||||
.spawn({
|
||||
let http = http.clone();
|
||||
let node_runtime = node_runtime.clone();
|
||||
move |this, cx| {
|
||||
Self::start_language_server(http, node_runtime, this, cx)
|
||||
}
|
||||
})
|
||||
.shared();
|
||||
this.server = CopilotServer::Starting { task: start_task };
|
||||
cx.notify();
|
||||
}
|
||||
} else {
|
||||
this.server = CopilotServer::Disabled;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
let mut this = Self {
|
||||
http,
|
||||
node_runtime,
|
||||
server: CopilotServer::Disabled,
|
||||
buffers: Default::default(),
|
||||
};
|
||||
this.enable_or_disable_copilot(cx);
|
||||
cx.observe_global::<SettingsStore, _>(move |this, cx| this.enable_or_disable_copilot(cx))
|
||||
.detach();
|
||||
this
|
||||
}
|
||||
|
||||
if cx.global::<Settings>().features.copilot {
|
||||
let start_task = cx
|
||||
.spawn({
|
||||
let http = http.clone();
|
||||
let node_runtime = node_runtime.clone();
|
||||
move |this, cx| async {
|
||||
Self::start_language_server(http, node_runtime, this, cx).await
|
||||
}
|
||||
})
|
||||
.shared();
|
||||
|
||||
Self {
|
||||
http,
|
||||
node_runtime,
|
||||
server: CopilotServer::Starting { task: start_task },
|
||||
buffers: Default::default(),
|
||||
fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Copilot>) {
|
||||
let http = self.http.clone();
|
||||
let node_runtime = self.node_runtime.clone();
|
||||
if all_language_settings(cx).copilot_enabled(None, None) {
|
||||
if matches!(self.server, CopilotServer::Disabled) {
|
||||
let start_task = cx
|
||||
.spawn({
|
||||
move |this, cx| Self::start_language_server(http, node_runtime, this, cx)
|
||||
})
|
||||
.shared();
|
||||
self.server = CopilotServer::Starting { task: start_task };
|
||||
cx.notify();
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
http,
|
||||
node_runtime,
|
||||
server: CopilotServer::Disabled,
|
||||
buffers: Default::default(),
|
||||
}
|
||||
self.server = CopilotServer::Disabled;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,13 +784,13 @@ impl Copilot {
|
||||
let snapshot = registered_buffer.report_changes(buffer, cx);
|
||||
let buffer = buffer.read(cx);
|
||||
let uri = registered_buffer.uri.clone();
|
||||
let settings = cx.global::<Settings>();
|
||||
let position = position.to_point_utf16(buffer);
|
||||
let language = buffer.language_at(position);
|
||||
let language_name = language.map(|language| language.name());
|
||||
let language_name = language_name.as_deref();
|
||||
let tab_size = settings.tab_size(language_name);
|
||||
let hard_tabs = settings.hard_tabs(language_name);
|
||||
let settings = language_settings(
|
||||
buffer.language_at(position).map(|l| l.name()).as_deref(),
|
||||
cx,
|
||||
);
|
||||
let tab_size = settings.tab_size;
|
||||
let hard_tabs = settings.hard_tabs;
|
||||
let relative_path = buffer
|
||||
.file()
|
||||
.map(|file| file.path().to_path_buf())
|
||||
|
||||
@@ -6,7 +6,6 @@ use gpui::{
|
||||
AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use theme::ui::modal;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
@@ -68,12 +67,13 @@ fn create_copilot_auth_window(
|
||||
cx: &mut AppContext,
|
||||
status: &Status,
|
||||
) -> ViewHandle<CopilotCodeVerification> {
|
||||
let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
|
||||
let window_size = theme::current(cx).copilot.modal.dimensions();
|
||||
let window_options = WindowOptions {
|
||||
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
|
||||
titlebar: None,
|
||||
center: true,
|
||||
focus: true,
|
||||
show: true,
|
||||
kind: WindowKind::Normal,
|
||||
is_movable: true,
|
||||
screen: None,
|
||||
@@ -338,7 +338,7 @@ impl View for CopilotCodeVerification {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
enum ConnectModal {}
|
||||
|
||||
let style = cx.global::<Settings>().theme.clone();
|
||||
let style = theme::current(cx).clone();
|
||||
|
||||
modal::<ConnectModal, _, _, _, _>(
|
||||
"Connect Copilot to Zed",
|
||||
|
||||
@@ -12,8 +12,10 @@ doctest = false
|
||||
assets = { path = "../assets" }
|
||||
copilot = { path = "../copilot" }
|
||||
editor = { path = "../editor" }
|
||||
fs = { path = "../fs" }
|
||||
context_menu = { path = "../context_menu" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
@@ -21,3 +23,6 @@ workspace = { path = "../workspace" }
|
||||
anyhow.workspace = true
|
||||
smol.workspace = true
|
||||
futures.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -2,13 +2,15 @@ use anyhow::Result;
|
||||
use context_menu::{ContextMenu, ContextMenuItem};
|
||||
use copilot::{Copilot, SignOut, Status};
|
||||
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||
};
|
||||
use settings::{settings_file::SettingsFile, Settings};
|
||||
use language::language_settings::{self, all_language_settings, AllLanguageSettings};
|
||||
use settings::{update_settings_file, SettingsStore};
|
||||
use std::{path::Path, sync::Arc};
|
||||
use util::{paths, ResultExt};
|
||||
use workspace::{
|
||||
@@ -26,6 +28,7 @@ pub struct CopilotButton {
|
||||
editor_enabled: Option<bool>,
|
||||
language: Option<Arc<str>>,
|
||||
path: Option<Arc<Path>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
}
|
||||
|
||||
impl Entity for CopilotButton {
|
||||
@@ -38,13 +41,12 @@ impl View for CopilotButton {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
if !settings.features.copilot {
|
||||
let all_language_settings = &all_language_settings(cx);
|
||||
if !all_language_settings.copilot.feature_enabled {
|
||||
return Empty::new().into_any();
|
||||
}
|
||||
|
||||
let theme = settings.theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
let active = self.popup_menu.read(cx).visible();
|
||||
let Some(copilot) = Copilot::global(cx) else {
|
||||
return Empty::new().into_any();
|
||||
@@ -53,7 +55,7 @@ impl View for CopilotButton {
|
||||
|
||||
let enabled = self
|
||||
.editor_enabled
|
||||
.unwrap_or(settings.show_copilot_suggestions(None, None));
|
||||
.unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
|
||||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
@@ -64,8 +66,8 @@ impl View for CopilotButton {
|
||||
let style = theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.sidebar_buttons
|
||||
.item
|
||||
.panel_buttons
|
||||
.button
|
||||
.style_for(state, active);
|
||||
|
||||
Flex::row()
|
||||
@@ -143,7 +145,7 @@ impl View for CopilotButton {
|
||||
}
|
||||
|
||||
impl CopilotButton {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let button_view_id = cx.view_id();
|
||||
let menu = cx.add_view(|cx| {
|
||||
let mut menu = ContextMenu::new(button_view_id, cx);
|
||||
@@ -155,7 +157,7 @@ impl CopilotButton {
|
||||
|
||||
Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
|
||||
|
||||
cx.observe_global::<Settings, _>(move |_, cx| cx.notify())
|
||||
cx.observe_global::<SettingsStore, _>(move |_, cx| cx.notify())
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
@@ -164,17 +166,19 @@ impl CopilotButton {
|
||||
editor_enabled: None,
|
||||
language: None,
|
||||
path: None,
|
||||
fs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deploy_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let mut menu_options = Vec::with_capacity(2);
|
||||
let fs = self.fs.clone();
|
||||
|
||||
menu_options.push(ContextMenuItem::handler("Sign In", |cx| {
|
||||
initiate_sign_in(cx)
|
||||
}));
|
||||
menu_options.push(ContextMenuItem::handler("Disable Copilot", |cx| {
|
||||
hide_copilot(cx)
|
||||
menu_options.push(ContextMenuItem::handler("Disable Copilot", move |cx| {
|
||||
hide_copilot(fs.clone(), cx)
|
||||
}));
|
||||
|
||||
self.popup_menu.update(cx, |menu, cx| {
|
||||
@@ -188,22 +192,26 @@ impl CopilotButton {
|
||||
}
|
||||
|
||||
pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
let fs = self.fs.clone();
|
||||
let mut menu_options = Vec::with_capacity(8);
|
||||
|
||||
if let Some(language) = self.language.clone() {
|
||||
let language_enabled = settings.copilot_enabled_for_language(Some(language.as_ref()));
|
||||
let fs = fs.clone();
|
||||
let language_enabled =
|
||||
language_settings::language_settings(Some(language.as_ref()), cx)
|
||||
.show_copilot_suggestions;
|
||||
menu_options.push(ContextMenuItem::handler(
|
||||
format!(
|
||||
"{} Suggestions for {}",
|
||||
if language_enabled { "Hide" } else { "Show" },
|
||||
language
|
||||
),
|
||||
move |cx| toggle_copilot_for_language(language.clone(), cx),
|
||||
move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
|
||||
));
|
||||
}
|
||||
|
||||
let settings = settings::get::<AllLanguageSettings>(cx);
|
||||
|
||||
if let Some(path) = self.path.as_ref() {
|
||||
let path_enabled = settings.copilot_enabled_for_path(path);
|
||||
let path = path.clone();
|
||||
@@ -228,19 +236,19 @@ impl CopilotButton {
|
||||
));
|
||||
}
|
||||
|
||||
let globally_enabled = cx.global::<Settings>().features.copilot;
|
||||
let globally_enabled = settings.copilot_enabled(None, None);
|
||||
menu_options.push(ContextMenuItem::handler(
|
||||
if globally_enabled {
|
||||
"Hide Suggestions for All Files"
|
||||
} else {
|
||||
"Show Suggestions for All Files"
|
||||
},
|
||||
|cx| toggle_copilot_globally(cx),
|
||||
move |cx| toggle_copilot_globally(fs.clone(), cx),
|
||||
));
|
||||
|
||||
menu_options.push(ContextMenuItem::Separator);
|
||||
|
||||
let icon_style = settings.theme.copilot.out_link_icon.clone();
|
||||
let icon_style = theme::current(cx).copilot.out_link_icon.clone();
|
||||
menu_options.push(ContextMenuItem::action(
|
||||
move |state: &mut MouseState, style: &theme::ContextMenuItem| {
|
||||
Flex::row()
|
||||
@@ -266,22 +274,19 @@ impl CopilotButton {
|
||||
|
||||
pub fn update_enabled(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
|
||||
let editor = editor.read(cx);
|
||||
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let settings = cx.global::<Settings>();
|
||||
let suggestion_anchor = editor.selections.newest_anchor().start;
|
||||
|
||||
let language_name = snapshot
|
||||
.language_at(suggestion_anchor)
|
||||
.map(|language| language.name());
|
||||
let path = snapshot
|
||||
.file_at(suggestion_anchor)
|
||||
.map(|file| file.path().clone());
|
||||
let path = snapshot.file_at(suggestion_anchor).map(|file| file.path());
|
||||
|
||||
self.editor_enabled =
|
||||
Some(settings.show_copilot_suggestions(language_name.as_deref(), path.as_deref()));
|
||||
self.editor_enabled = Some(
|
||||
all_language_settings(cx)
|
||||
.copilot_enabled(language_name.as_deref(), path.map(|p| p.as_ref())),
|
||||
);
|
||||
self.language = language_name;
|
||||
self.path = path;
|
||||
self.path = path.cloned();
|
||||
|
||||
cx.notify()
|
||||
}
|
||||
@@ -310,7 +315,7 @@ async fn configure_disabled_globs(
|
||||
let settings_editor = workspace
|
||||
.update(&mut cx, |_, cx| {
|
||||
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
||||
Settings::initial_user_settings_content(&assets::Assets)
|
||||
settings::initial_user_settings_content(&assets::Assets)
|
||||
.as_ref()
|
||||
.into()
|
||||
})
|
||||
@@ -322,14 +327,16 @@ async fn configure_disabled_globs(
|
||||
settings_editor.downgrade().update(&mut cx, |item, cx| {
|
||||
let text = item.buffer().read(cx).snapshot(cx).text();
|
||||
|
||||
let edits = SettingsFile::update_unsaved(&text, cx, |file| {
|
||||
let settings = cx.global::<SettingsStore>();
|
||||
let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
|
||||
let copilot = file.copilot.get_or_insert_with(Default::default);
|
||||
let globs = copilot.disabled_globs.get_or_insert_with(|| {
|
||||
cx.global::<Settings>()
|
||||
settings
|
||||
.get::<AllLanguageSettings>(None)
|
||||
.copilot
|
||||
.disabled_globs
|
||||
.iter()
|
||||
.map(|glob| glob.as_str().to_string())
|
||||
.map(|glob| glob.glob().to_string())
|
||||
.collect()
|
||||
});
|
||||
|
||||
@@ -355,32 +362,26 @@ async fn configure_disabled_globs(
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn toggle_copilot_globally(cx: &mut AppContext) {
|
||||
let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None, None);
|
||||
SettingsFile::update(cx, move |file_contents| {
|
||||
file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
|
||||
fn toggle_copilot_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||
let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(None, None);
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
|
||||
file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
|
||||
});
|
||||
}
|
||||
|
||||
fn toggle_copilot_for_language(language: Arc<str>, cx: &mut AppContext) {
|
||||
let show_copilot_suggestions = cx
|
||||
.global::<Settings>()
|
||||
.show_copilot_suggestions(Some(&language), None);
|
||||
|
||||
SettingsFile::update(cx, move |file_contents| {
|
||||
file_contents.languages.insert(
|
||||
language,
|
||||
settings::EditorSettings {
|
||||
show_copilot_suggestions: Some((!show_copilot_suggestions).into()),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
fn toggle_copilot_for_language(language: Arc<str>, fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||
let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(Some(&language), None);
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
|
||||
file.languages
|
||||
.entry(language)
|
||||
.or_default()
|
||||
.show_copilot_suggestions = Some(!show_copilot_suggestions);
|
||||
});
|
||||
}
|
||||
|
||||
fn hide_copilot(cx: &mut AppContext) {
|
||||
SettingsFile::update(cx, move |file_contents| {
|
||||
file_contents.features.copilot = Some(false)
|
||||
fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
|
||||
file.features.get_or_insert(Default::default()).copilot = Some(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ sqlez = { path = "../sqlez" }
|
||||
sqlez_macros = { path = "../sqlez_macros" }
|
||||
util = { path = "../util" }
|
||||
anyhow.workspace = true
|
||||
indoc = "1.0.4"
|
||||
indoc.workspace = true
|
||||
async-trait.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -31,6 +31,7 @@ language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
theme = { path = "../theme", features = ["test-support"] }
|
||||
|
||||
serde_json.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
@@ -20,7 +20,6 @@ use language::{
|
||||
use lsp::LanguageServerId;
|
||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||
use serde_json::json;
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
@@ -30,10 +29,11 @@ use std::{
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::ThemeSettings;
|
||||
use util::TryFutureExt;
|
||||
use workspace::{
|
||||
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
|
||||
ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
|
||||
ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace,
|
||||
};
|
||||
|
||||
actions!(diagnostics, [Deploy]);
|
||||
@@ -89,12 +89,16 @@ impl View for ProjectDiagnosticsEditor {
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
if self.path_states.is_empty() {
|
||||
let theme = &cx.global::<Settings>().theme.project_diagnostics;
|
||||
Label::new("No problems in workspace", theme.empty_message.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(theme.container)
|
||||
.into_any()
|
||||
let theme = &theme::current(cx).project_diagnostics;
|
||||
PaneBackdrop::new(
|
||||
cx.view_id(),
|
||||
Label::new("No problems in workspace", theme.empty_message.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(theme.container)
|
||||
.into_any(),
|
||||
)
|
||||
.into_any()
|
||||
} else {
|
||||
ChildView::new(&self.editor, cx).into_any()
|
||||
}
|
||||
@@ -161,8 +165,13 @@ impl ProjectDiagnosticsEditor {
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor
|
||||
});
|
||||
cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()))
|
||||
.detach();
|
||||
cx.subscribe(&editor, |this, _, event, cx| {
|
||||
cx.emit(event.clone());
|
||||
if event == &editor::Event::Focused && this.path_states.is_empty() {
|
||||
cx.focus_self()
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let project = project_handle.read(cx);
|
||||
let paths_to_update = project
|
||||
@@ -537,7 +546,7 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
render_summary(
|
||||
&self.summary,
|
||||
&style.label.text,
|
||||
&cx.global::<Settings>().theme.project_diagnostics,
|
||||
&theme::current(cx).project_diagnostics,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -600,15 +609,6 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn git_diff_recalc(
|
||||
&mut self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.git_diff_recalc(project, cx))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
|
||||
Editor::to_item_events(event)
|
||||
}
|
||||
@@ -679,10 +679,10 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
||||
let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message);
|
||||
Arc::new(move |cx| {
|
||||
let settings = cx.global::<Settings>();
|
||||
let settings = settings::get::<ThemeSettings>(cx);
|
||||
let theme = &settings.theme.editor;
|
||||
let style = theme.diagnostic_header.clone();
|
||||
let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
|
||||
let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
|
||||
let icon_width = cx.em_width * style.icon_width_factor;
|
||||
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
|
||||
Svg::new("icons/circle_x_mark_12.svg")
|
||||
@@ -818,33 +818,35 @@ mod tests {
|
||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use unindent::Unindent as _;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/test",
|
||||
json!({
|
||||
"consts.rs": "
|
||||
const a: i32 = 'a';
|
||||
const b: i32 = c;
|
||||
"
|
||||
const a: i32 = 'a';
|
||||
const b: i32 = c;
|
||||
"
|
||||
.unindent(),
|
||||
|
||||
"main.rs": "
|
||||
fn main() {
|
||||
let x = vec![];
|
||||
let y = vec![];
|
||||
a(x);
|
||||
b(y);
|
||||
// comment 1
|
||||
// comment 2
|
||||
c(y);
|
||||
d(x);
|
||||
}
|
||||
"
|
||||
fn main() {
|
||||
let x = vec![];
|
||||
let y = vec![];
|
||||
a(x);
|
||||
b(y);
|
||||
// comment 1
|
||||
// comment 2
|
||||
c(y);
|
||||
d(x);
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
}),
|
||||
)
|
||||
@@ -1225,7 +1227,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/test",
|
||||
@@ -1489,6 +1492,17 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
language::init(cx);
|
||||
client::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
Project::init_settings(cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
|
||||
@@ -7,7 +7,6 @@ use gpui::{
|
||||
};
|
||||
use language::Diagnostic;
|
||||
use lsp::LanguageServerId;
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||
|
||||
use crate::ProjectDiagnosticsEditor;
|
||||
@@ -92,13 +91,12 @@ impl View for DiagnosticIndicator {
|
||||
enum Summary {}
|
||||
enum Message {}
|
||||
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
let in_progress = !self.in_progress_checks.is_empty();
|
||||
let mut element = Flex::row().with_child(
|
||||
MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
|
||||
let style = cx
|
||||
.global::<Settings>()
|
||||
.theme
|
||||
let theme = theme::current(cx);
|
||||
let style = theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.diagnostic_summary
|
||||
@@ -184,7 +182,7 @@ impl View for DiagnosticIndicator {
|
||||
.into_any(),
|
||||
);
|
||||
|
||||
let style = &cx.global::<Settings>().theme.workspace.status_bar;
|
||||
let style = &theme::current(cx).workspace.status_bar;
|
||||
let item_spacing = style.item_spacing;
|
||||
|
||||
if in_progress {
|
||||
|
||||
@@ -58,6 +58,7 @@ parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||
rand = { workspace = true, optional = true }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
smallvec.workspace = true
|
||||
@@ -80,7 +81,6 @@ workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
glob.workspace = true
|
||||
rand.workspace = true
|
||||
unindent.workspace = true
|
||||
tree-sitter = "0.20"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::EditorSettings;
|
||||
use gpui::{Entity, ModelContext};
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use smol::Timer;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct BlinkManager {
|
||||
blink_interval: Duration,
|
||||
@@ -15,8 +15,8 @@ pub struct BlinkManager {
|
||||
|
||||
impl BlinkManager {
|
||||
pub fn new(blink_interval: Duration, cx: &mut ModelContext<Self>) -> Self {
|
||||
cx.observe_global::<Settings, _>(move |this, cx| {
|
||||
// Make sure we blink the cursors if the setting is re-enabled
|
||||
// Make sure we blink the cursors if the setting is re-enabled
|
||||
cx.observe_global::<SettingsStore, _>(move |this, cx| {
|
||||
this.blink_cursors(this.blink_epoch, cx)
|
||||
})
|
||||
.detach();
|
||||
@@ -64,7 +64,7 @@ impl BlinkManager {
|
||||
}
|
||||
|
||||
fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
|
||||
if cx.global::<Settings>().cursor_blink {
|
||||
if settings::get::<EditorSettings>(cx).cursor_blink {
|
||||
if epoch == self.blink_epoch && self.enabled && !self.blinking_paused {
|
||||
self.visible = !self.visible;
|
||||
cx.notify();
|
||||
|
||||
@@ -13,8 +13,9 @@ use gpui::{
|
||||
fonts::{FontId, HighlightStyle},
|
||||
Entity, ModelContext, ModelHandle,
|
||||
};
|
||||
use language::{OffsetUtf16, Point, Subscription as BufferSubscription};
|
||||
use settings::Settings;
|
||||
use language::{
|
||||
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
|
||||
};
|
||||
use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
||||
pub use suggestion_map::Suggestion;
|
||||
use suggestion_map::SuggestionMap;
|
||||
@@ -276,8 +277,7 @@ impl DisplayMap {
|
||||
.as_singleton()
|
||||
.and_then(|buffer| buffer.read(cx).language())
|
||||
.map(|language| language.name());
|
||||
|
||||
cx.global::<Settings>().tab_size(language_name.as_deref())
|
||||
language_settings(language_name.as_deref(), cx).tab_size
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -844,8 +844,12 @@ pub mod tests {
|
||||
use super::*;
|
||||
use crate::{movement, test::marked_display_snapshot};
|
||||
use gpui::{color::Color, elements::*, test::observe, AppContext};
|
||||
use language::{Buffer, Language, LanguageConfig, SelectionGoal};
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
|
||||
Buffer, Language, LanguageConfig, SelectionGoal,
|
||||
};
|
||||
use rand::{prelude::*, Rng};
|
||||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{env, sync::Arc};
|
||||
use theme::SyntaxTheme;
|
||||
@@ -882,9 +886,7 @@ pub mod tests {
|
||||
log::info!("wrap width: {:?}", wrap_width);
|
||||
|
||||
cx.update(|cx| {
|
||||
let mut settings = Settings::test(cx);
|
||||
settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
|
||||
cx.set_global(settings)
|
||||
init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
|
||||
});
|
||||
|
||||
let buffer = cx.update(|cx| {
|
||||
@@ -939,9 +941,11 @@ pub mod tests {
|
||||
tab_size = *tab_sizes.choose(&mut rng).unwrap();
|
||||
log::info!("setting tab size to {:?}", tab_size);
|
||||
cx.update(|cx| {
|
||||
let mut settings = Settings::test(cx);
|
||||
settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
|
||||
cx.set_global(settings)
|
||||
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |s| {
|
||||
s.defaults.tab_size = NonZeroU32::new(tab_size);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
30..=44 => {
|
||||
@@ -1119,7 +1123,7 @@ pub mod tests {
|
||||
#[gpui::test(retries = 5)]
|
||||
fn test_soft_wraps(cx: &mut AppContext) {
|
||||
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
cx.foreground().forbid_parking();
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let font_cache = cx.font_cache();
|
||||
|
||||
@@ -1131,7 +1135,6 @@ pub mod tests {
|
||||
.unwrap();
|
||||
let font_size = 12.0;
|
||||
let wrap_width = Some(64.);
|
||||
cx.set_global(Settings::test(cx));
|
||||
|
||||
let text = "one two three four five\nsix seven eight";
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
@@ -1211,7 +1214,8 @@ pub mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_text_chunks(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let text = sample_text(6, 6, 'a');
|
||||
let buffer = MultiBuffer::build_simple(&text, cx);
|
||||
let family_id = cx
|
||||
@@ -1225,6 +1229,7 @@ pub mod tests {
|
||||
let font_size = 14.0;
|
||||
let map =
|
||||
cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
vec![
|
||||
@@ -1289,11 +1294,8 @@ pub mod tests {
|
||||
.unwrap(),
|
||||
);
|
||||
language.set_theme(&theme);
|
||||
cx.update(|cx| {
|
||||
let mut settings = Settings::test(cx);
|
||||
settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
|
||||
cx.set_global(settings);
|
||||
});
|
||||
|
||||
cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
|
||||
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
|
||||
buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
|
||||
@@ -1382,7 +1384,7 @@ pub mod tests {
|
||||
);
|
||||
language.set_theme(&theme);
|
||||
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
cx.update(|cx| init_test(cx, |_| {}));
|
||||
|
||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
|
||||
buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
|
||||
@@ -1429,9 +1431,8 @@ pub mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
|
||||
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
cx.update(|cx| init_test(cx, |_| {}));
|
||||
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
let theme = SyntaxTheme::new(vec![
|
||||
("operator".to_string(), Color::red().into()),
|
||||
("string".to_string(), Color::green().into()),
|
||||
@@ -1510,7 +1511,8 @@ pub mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_clip_point(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx, |_| {});
|
||||
|
||||
fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
|
||||
let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
|
||||
|
||||
@@ -1559,7 +1561,7 @@ pub mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx, |_| {});
|
||||
|
||||
fn assert(text: &str, cx: &mut gpui::AppContext) {
|
||||
let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
|
||||
@@ -1578,7 +1580,8 @@ pub mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let text = "✅\t\tα\nβ\t\n🏀β\t\tγ";
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
let font_cache = cx.font_cache();
|
||||
@@ -1639,7 +1642,8 @@ pub mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_max_point(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
|
||||
let font_cache = cx.font_cache();
|
||||
let family_id = font_cache
|
||||
@@ -1718,4 +1722,13 @@ pub mod tests {
|
||||
}
|
||||
chunks
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
|
||||
cx.foreground().forbid_parking();
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
language::init(cx);
|
||||
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,7 +993,7 @@ mod tests {
|
||||
use crate::multi_buffer::MultiBuffer;
|
||||
use gpui::{elements::Empty, Element};
|
||||
use rand::prelude::*;
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use std::env;
|
||||
use util::RandomCharIter;
|
||||
|
||||
@@ -1013,7 +1013,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_basic_blocks(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
let family_id = cx
|
||||
.font_cache()
|
||||
@@ -1189,7 +1189,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_blocks_on_wrapped_lines(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
let family_id = cx
|
||||
.font_cache()
|
||||
@@ -1239,7 +1239,7 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_blocks(cx: &mut gpui::AppContext, mut rng: StdRng) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
@@ -1647,6 +1647,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
}
|
||||
|
||||
impl TransformBlock {
|
||||
fn as_custom(&self) -> Option<&Block> {
|
||||
match self {
|
||||
|
||||
@@ -1204,7 +1204,7 @@ mod tests {
|
||||
use crate::{MultiBuffer, ToPoint};
|
||||
use collections::HashSet;
|
||||
use rand::prelude::*;
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use std::{cmp::Reverse, env, mem, sync::Arc};
|
||||
use sum_tree::TreeMap;
|
||||
use util::test::sample_text;
|
||||
@@ -1213,7 +1213,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_basic_folds(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
@@ -1286,7 +1286,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_adjacent_folds(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
@@ -1349,7 +1349,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
|
||||
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
@@ -1400,7 +1400,7 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
@@ -1676,6 +1676,10 @@ mod tests {
|
||||
assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
}
|
||||
|
||||
impl FoldMap {
|
||||
fn merged_fold_ranges(&self) -> Vec<Range<usize>> {
|
||||
let buffer = self.buffer.lock().clone();
|
||||
|
||||
@@ -578,7 +578,7 @@ mod tests {
|
||||
use crate::{display_map::fold_map::FoldMap, MultiBuffer};
|
||||
use gpui::AppContext;
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
env,
|
||||
ops::{Bound, RangeBounds},
|
||||
@@ -631,7 +631,8 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
@@ -834,6 +835,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut AppContext) {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
}
|
||||
|
||||
impl SuggestionMap {
|
||||
pub fn randomly_mutate(
|
||||
&self,
|
||||
|
||||
@@ -1043,16 +1043,16 @@ mod tests {
|
||||
};
|
||||
use gpui::test::observe;
|
||||
use rand::prelude::*;
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{cmp, env, num::NonZeroU32};
|
||||
use text::Rope;
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
init_test(cx);
|
||||
|
||||
cx.foreground().set_block_on_ticks(0..=50);
|
||||
cx.foreground().forbid_parking();
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
@@ -1287,6 +1287,14 @@ mod tests {
|
||||
wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
cx.update(|cx| {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn wrap_text(
|
||||
unwrapped_text: &str,
|
||||
wrap_width: Option<f32>,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod blink_manager;
|
||||
pub mod display_map;
|
||||
mod editor_settings;
|
||||
mod element;
|
||||
|
||||
mod git;
|
||||
@@ -19,15 +20,17 @@ mod editor_tests;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
|
||||
use ::git::diff::DiffHunk;
|
||||
use aho_corasick::AhoCorasick;
|
||||
use anyhow::{anyhow, Result};
|
||||
use blink_manager::BlinkManager;
|
||||
use client::ClickhouseEvent;
|
||||
use client::{ClickhouseEvent, TelemetrySettings};
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
||||
use copilot::Copilot;
|
||||
pub use display_map::DisplayPoint;
|
||||
use display_map::*;
|
||||
pub use editor_settings::EditorSettings;
|
||||
pub use element::*;
|
||||
use futures::FutureExt;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
@@ -51,6 +54,7 @@ pub use items::MAX_TAB_TITLE_LEN;
|
||||
use itertools::Itertools;
|
||||
pub use language::{char_kind, CharKind};
|
||||
use language::{
|
||||
language_settings::{self, all_language_settings},
|
||||
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
|
||||
Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt,
|
||||
OffsetUtf16, Point, Selection, SelectionGoal, TransactionId,
|
||||
@@ -70,7 +74,7 @@ use scroll::{
|
||||
};
|
||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use smallvec::SmallVec;
|
||||
use snippet::Snippet;
|
||||
use std::{
|
||||
@@ -85,7 +89,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
pub use sum_tree::Bias;
|
||||
use theme::{DiagnosticStyle, Theme};
|
||||
use theme::{DiagnosticStyle, Theme, ThemeSettings};
|
||||
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||
use workspace::{ItemNavHistory, ViewId, Workspace};
|
||||
|
||||
@@ -212,6 +216,8 @@ actions!(
|
||||
MoveToNextSubwordEnd,
|
||||
MoveToBeginningOfLine,
|
||||
MoveToEndOfLine,
|
||||
MoveToStartOfParagraph,
|
||||
MoveToEndOfParagraph,
|
||||
MoveToBeginning,
|
||||
MoveToEnd,
|
||||
SelectUp,
|
||||
@@ -222,6 +228,8 @@ actions!(
|
||||
SelectToPreviousSubwordStart,
|
||||
SelectToNextWordEnd,
|
||||
SelectToNextSubwordEnd,
|
||||
SelectToStartOfParagraph,
|
||||
SelectToEndOfParagraph,
|
||||
SelectToBeginning,
|
||||
SelectToEnd,
|
||||
SelectAll,
|
||||
@@ -286,7 +294,12 @@ pub enum Direction {
|
||||
Next,
|
||||
}
|
||||
|
||||
pub fn init_settings(cx: &mut AppContext) {
|
||||
settings::register::<EditorSettings>(cx);
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
init_settings(cx);
|
||||
cx.add_action(Editor::new_file);
|
||||
cx.add_action(Editor::cancel);
|
||||
cx.add_action(Editor::newline);
|
||||
@@ -328,6 +341,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(Editor::move_to_next_subword_end);
|
||||
cx.add_action(Editor::move_to_beginning_of_line);
|
||||
cx.add_action(Editor::move_to_end_of_line);
|
||||
cx.add_action(Editor::move_to_start_of_paragraph);
|
||||
cx.add_action(Editor::move_to_end_of_paragraph);
|
||||
cx.add_action(Editor::move_to_beginning);
|
||||
cx.add_action(Editor::move_to_end);
|
||||
cx.add_action(Editor::select_up);
|
||||
@@ -340,6 +355,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(Editor::select_to_next_subword_end);
|
||||
cx.add_action(Editor::select_to_beginning_of_line);
|
||||
cx.add_action(Editor::select_to_end_of_line);
|
||||
cx.add_action(Editor::select_to_start_of_paragraph);
|
||||
cx.add_action(Editor::select_to_end_of_paragraph);
|
||||
cx.add_action(Editor::select_to_beginning);
|
||||
cx.add_action(Editor::select_to_end);
|
||||
cx.add_action(Editor::select_all);
|
||||
@@ -436,7 +453,7 @@ pub enum EditorMode {
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SoftWrap {
|
||||
None,
|
||||
EditorWidth,
|
||||
@@ -471,7 +488,7 @@ pub struct Editor {
|
||||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||
ime_transaction: Option<TransactionId>,
|
||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||
soft_wrap_mode_override: Option<settings::SoftWrap>,
|
||||
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
|
||||
get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
|
||||
override_text_style: Option<Box<OverrideTextStyle>>,
|
||||
project: Option<ModelHandle<Project>>,
|
||||
@@ -516,15 +533,6 @@ pub struct EditorSnapshot {
|
||||
ongoing_scroll: OngoingScroll,
|
||||
}
|
||||
|
||||
impl EditorSnapshot {
|
||||
fn has_scrollbar_info(&self) -> bool {
|
||||
self.buffer_snapshot
|
||||
.git_diff_hunks_in_range(0..self.max_point().row(), false)
|
||||
.next()
|
||||
.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SelectionHistoryEntry {
|
||||
selections: Arc<[Selection<Anchor>]>,
|
||||
@@ -1238,8 +1246,8 @@ impl Editor {
|
||||
) -> Self {
|
||||
let editor_view_id = cx.view_id();
|
||||
let display_map = cx.add_model(|cx| {
|
||||
let settings = cx.global::<Settings>();
|
||||
let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx);
|
||||
let settings = settings::get::<ThemeSettings>(cx);
|
||||
let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx);
|
||||
DisplayMap::new(
|
||||
buffer.clone(),
|
||||
style.text.font_id,
|
||||
@@ -1256,7 +1264,7 @@ impl Editor {
|
||||
let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
|
||||
|
||||
let soft_wrap_mode_override =
|
||||
(mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None);
|
||||
(mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
|
||||
|
||||
let mut project_subscription = None;
|
||||
if mode == EditorMode::Full && buffer.read(cx).is_singleton() {
|
||||
@@ -1320,7 +1328,7 @@ impl Editor {
|
||||
cx.subscribe(&buffer, Self::on_buffer_event),
|
||||
cx.observe(&display_map, Self::on_display_map_changed),
|
||||
cx.observe(&blink_manager, |_, _, cx| cx.notify()),
|
||||
cx.observe_global::<Settings, _>(Self::settings_changed),
|
||||
cx.observe_global::<SettingsStore, _>(Self::settings_changed),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1419,7 +1427,7 @@ impl Editor {
|
||||
|
||||
fn style(&self, cx: &AppContext) -> EditorStyle {
|
||||
build_style(
|
||||
cx.global::<Settings>(),
|
||||
settings::get::<ThemeSettings>(cx),
|
||||
self.get_field_editor_theme.as_deref(),
|
||||
self.override_text_style.as_deref(),
|
||||
cx,
|
||||
@@ -2114,6 +2122,15 @@ impl Editor {
|
||||
let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
|
||||
|
||||
// When buffer contents is updated and caret is moved, try triggering on type formatting.
|
||||
if settings::get::<EditorSettings>(cx).use_on_type_format {
|
||||
if let Some(on_type_format_task) =
|
||||
this.trigger_on_type_formatting(text.to_string(), cx)
|
||||
{
|
||||
on_type_format_task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
if had_active_copilot_suggestion {
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
if !this.has_active_copilot_suggestion(cx) {
|
||||
@@ -2377,7 +2394,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||
if !cx.global::<Settings>().show_completions_on_input {
|
||||
if !settings::get::<EditorSettings>(cx).show_completions_on_input {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2492,6 +2509,52 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn trigger_on_type_formatting(
|
||||
&self,
|
||||
input: String,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if input.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let project = self.project.as_ref()?;
|
||||
let position = self.selections.newest_anchor().head();
|
||||
let (buffer, buffer_position) = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(position.clone(), cx)?;
|
||||
|
||||
// OnTypeFormatting retuns a list of edits, no need to pass them between Zed instances,
|
||||
// hence we do LSP request & edit on host side only — add formats to host's history.
|
||||
let push_to_lsp_host_history = true;
|
||||
// If this is not the host, append its history with new edits.
|
||||
let push_to_client_history = project.read(cx).is_remote();
|
||||
|
||||
let on_type_formatting = project.update(cx, |project, cx| {
|
||||
project.on_type_format(
|
||||
buffer.clone(),
|
||||
buffer_position,
|
||||
input,
|
||||
push_to_lsp_host_history,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
Some(cx.spawn(|editor, mut cx| async move {
|
||||
if let Some(transaction) = on_type_formatting.await? {
|
||||
if push_to_client_history {
|
||||
buffer.update(&mut cx, |buffer, _| {
|
||||
buffer.push_transaction(transaction, Instant::now());
|
||||
});
|
||||
}
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.refresh_document_highlights(cx);
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
|
||||
if self.pending_rename.is_some() {
|
||||
return;
|
||||
@@ -3144,17 +3207,12 @@ impl Editor {
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> bool {
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
let path = snapshot.file_at(location).map(|file| file.path());
|
||||
let path = snapshot.file_at(location).map(|file| file.path().as_ref());
|
||||
let language_name = snapshot
|
||||
.language_at(location)
|
||||
.map(|language| language.name());
|
||||
if !settings.show_copilot_suggestions(language_name.as_deref(), path.map(|p| p.as_ref())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
let settings = all_language_settings(cx);
|
||||
settings.copilot_enabled(language_name.as_deref(), path)
|
||||
}
|
||||
|
||||
fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
|
||||
@@ -3455,12 +3513,9 @@ impl Editor {
|
||||
{
|
||||
let indent_size =
|
||||
buffer.indent_size_for_line(line_buffer_range.start.row);
|
||||
let language_name = buffer
|
||||
.language_at(line_buffer_range.start)
|
||||
.map(|language| language.name());
|
||||
let indent_len = match indent_size.kind {
|
||||
IndentKind::Space => {
|
||||
cx.global::<Settings>().tab_size(language_name.as_deref())
|
||||
buffer.settings_at(line_buffer_range.start, cx).tab_size
|
||||
}
|
||||
IndentKind::Tab => NonZeroU32::new(1).unwrap(),
|
||||
};
|
||||
@@ -3572,12 +3627,11 @@ impl Editor {
|
||||
}
|
||||
|
||||
// Otherwise, insert a hard or soft tab.
|
||||
let settings = cx.global::<Settings>();
|
||||
let language_name = buffer.language_at(cursor, cx).map(|l| l.name());
|
||||
let tab_size = if settings.hard_tabs(language_name.as_deref()) {
|
||||
let settings = buffer.settings_at(cursor, cx);
|
||||
let tab_size = if settings.hard_tabs {
|
||||
IndentSize::tab()
|
||||
} else {
|
||||
let tab_size = settings.tab_size(language_name.as_deref()).get();
|
||||
let tab_size = settings.tab_size.get();
|
||||
let char_column = snapshot
|
||||
.text_for_range(Point::new(cursor.row, 0)..cursor)
|
||||
.flat_map(str::chars)
|
||||
@@ -3630,10 +3684,9 @@ impl Editor {
|
||||
delta_for_start_row: u32,
|
||||
cx: &AppContext,
|
||||
) -> u32 {
|
||||
let language_name = buffer.language_at(selection.start, cx).map(|l| l.name());
|
||||
let settings = cx.global::<Settings>();
|
||||
let tab_size = settings.tab_size(language_name.as_deref()).get();
|
||||
let indent_kind = if settings.hard_tabs(language_name.as_deref()) {
|
||||
let settings = buffer.settings_at(selection.start, cx);
|
||||
let tab_size = settings.tab_size.get();
|
||||
let indent_kind = if settings.hard_tabs {
|
||||
IndentKind::Tab
|
||||
} else {
|
||||
IndentKind::Space
|
||||
@@ -3702,11 +3755,8 @@ impl Editor {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
for selection in &selections {
|
||||
let language_name = buffer.language_at(selection.start, cx).map(|l| l.name());
|
||||
let tab_size = cx
|
||||
.global::<Settings>()
|
||||
.tab_size(language_name.as_deref())
|
||||
.get();
|
||||
let settings = buffer.settings_at(selection.start, cx);
|
||||
let tab_size = settings.tab_size.get();
|
||||
let mut rows = selection.spanned_rows(false, &display_map);
|
||||
|
||||
// Avoid re-outdenting a row that has already been outdented by a
|
||||
@@ -4766,6 +4816,80 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn move_to_start_of_paragraph(
|
||||
&mut self,
|
||||
_: &MoveToStartOfParagraph,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate_action();
|
||||
return;
|
||||
}
|
||||
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
selection.collapse_to(
|
||||
movement::start_of_paragraph(map, selection.head()),
|
||||
SelectionGoal::None,
|
||||
)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn move_to_end_of_paragraph(
|
||||
&mut self,
|
||||
_: &MoveToEndOfParagraph,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate_action();
|
||||
return;
|
||||
}
|
||||
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
selection.collapse_to(
|
||||
movement::end_of_paragraph(map, selection.head()),
|
||||
SelectionGoal::None,
|
||||
)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select_to_start_of_paragraph(
|
||||
&mut self,
|
||||
_: &SelectToStartOfParagraph,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate_action();
|
||||
return;
|
||||
}
|
||||
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_heads_with(|map, head, _| {
|
||||
(movement::start_of_paragraph(map, head), SelectionGoal::None)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select_to_end_of_paragraph(
|
||||
&mut self,
|
||||
_: &SelectToEndOfParagraph,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate_action();
|
||||
return;
|
||||
}
|
||||
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_heads_with(|map, head, _| {
|
||||
(movement::end_of_paragraph(map, head), SelectionGoal::None)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate_action();
|
||||
@@ -4807,12 +4931,12 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn push_to_nav_history(
|
||||
&self,
|
||||
&mut self,
|
||||
cursor_anchor: Anchor,
|
||||
new_position: Option<Point>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(nav_history) = &self.nav_history {
|
||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
||||
let buffer = self.buffer.read(cx).read(cx);
|
||||
let cursor_position = cursor_anchor.to_point(&buffer);
|
||||
let scroll_state = self.scroll_manager.anchor();
|
||||
@@ -5574,68 +5698,91 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_hunk_impl(Direction::Next, cx)
|
||||
}
|
||||
|
||||
fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_hunk_impl(Direction::Prev, cx)
|
||||
}
|
||||
|
||||
pub fn go_to_hunk_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self
|
||||
.display_map
|
||||
.update(cx, |display_map, cx| display_map.snapshot(cx));
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
|
||||
fn seek_in_direction(
|
||||
this: &mut Editor,
|
||||
snapshot: &DisplaySnapshot,
|
||||
initial_point: Point,
|
||||
is_wrapped: bool,
|
||||
direction: Direction,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
let hunks = if direction == Direction::Next {
|
||||
if !self.seek_in_direction(
|
||||
&snapshot,
|
||||
selection.head(),
|
||||
false,
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX),
|
||||
cx,
|
||||
) {
|
||||
let wrapped_point = Point::zero();
|
||||
self.seek_in_direction(
|
||||
&snapshot,
|
||||
wrapped_point,
|
||||
true,
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(initial_point.row..u32::MAX, false)
|
||||
} else {
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(0..initial_point.row, true)
|
||||
};
|
||||
|
||||
let display_point = initial_point.to_display_point(snapshot);
|
||||
let mut hunks = hunks
|
||||
.map(|hunk| diff_hunk_to_display(hunk, &snapshot))
|
||||
.skip_while(|hunk| {
|
||||
if is_wrapped {
|
||||
false
|
||||
} else {
|
||||
hunk.contains_display_row(display_point.row())
|
||||
}
|
||||
})
|
||||
.dedup();
|
||||
|
||||
if let Some(hunk) = hunks.next() {
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
let row = hunk.start_display_row();
|
||||
let point = DisplayPoint::new(row, 0);
|
||||
s.select_display_ranges([point..point]);
|
||||
});
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
.git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !seek_in_direction(self, &snapshot, selection.head(), false, direction, cx) {
|
||||
let wrapped_point = match direction {
|
||||
Direction::Next => Point::zero(),
|
||||
Direction::Prev => snapshot.buffer_snapshot.max_point(),
|
||||
};
|
||||
seek_in_direction(self, &snapshot, wrapped_point, true, direction, cx);
|
||||
fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self
|
||||
.display_map
|
||||
.update(cx, |display_map, cx| display_map.snapshot(cx));
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
|
||||
if !self.seek_in_direction(
|
||||
&snapshot,
|
||||
selection.head(),
|
||||
false,
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range_rev(0..selection.head().row),
|
||||
cx,
|
||||
) {
|
||||
let wrapped_point = snapshot.buffer_snapshot.max_point();
|
||||
self.seek_in_direction(
|
||||
&snapshot,
|
||||
wrapped_point,
|
||||
true,
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range_rev(0..wrapped_point.row),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn seek_in_direction(
|
||||
&mut self,
|
||||
snapshot: &DisplaySnapshot,
|
||||
initial_point: Point,
|
||||
is_wrapped: bool,
|
||||
hunks: impl Iterator<Item = DiffHunk<u32>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
let display_point = initial_point.to_display_point(snapshot);
|
||||
let mut hunks = hunks
|
||||
.map(|hunk| diff_hunk_to_display(hunk, &snapshot))
|
||||
.skip_while(|hunk| {
|
||||
if is_wrapped {
|
||||
false
|
||||
} else {
|
||||
hunk.contains_display_row(display_point.row())
|
||||
}
|
||||
})
|
||||
.dedup();
|
||||
|
||||
if let Some(hunk) = hunks.next() {
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
let row = hunk.start_display_row();
|
||||
let point = DisplayPoint::new(row, 0);
|
||||
s.select_display_ranges([point..point]);
|
||||
});
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6467,27 +6614,24 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
|
||||
let language_name = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.and_then(|singleton_buffer| singleton_buffer.read(cx).language())
|
||||
.map(|l| l.name());
|
||||
|
||||
let settings = cx.global::<Settings>();
|
||||
let settings = self.buffer.read(cx).settings_at(0, cx);
|
||||
let mode = self
|
||||
.soft_wrap_mode_override
|
||||
.unwrap_or_else(|| settings.soft_wrap(language_name.as_deref()));
|
||||
.unwrap_or_else(|| settings.soft_wrap);
|
||||
match mode {
|
||||
settings::SoftWrap::None => SoftWrap::None,
|
||||
settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
|
||||
settings::SoftWrap::PreferredLineLength => {
|
||||
SoftWrap::Column(settings.preferred_line_length(language_name.as_deref()))
|
||||
language_settings::SoftWrap::None => SoftWrap::None,
|
||||
language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
|
||||
language_settings::SoftWrap::PreferredLineLength => {
|
||||
SoftWrap::Column(settings.preferred_line_length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_soft_wrap_mode(&mut self, mode: settings::SoftWrap, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_soft_wrap_mode(
|
||||
&mut self,
|
||||
mode: language_settings::SoftWrap,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.soft_wrap_mode_override = Some(mode);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -6502,8 +6646,8 @@ impl Editor {
|
||||
self.soft_wrap_mode_override.take();
|
||||
} else {
|
||||
let soft_wrap = match self.soft_wrap_mode(cx) {
|
||||
SoftWrap::None => settings::SoftWrap::EditorWidth,
|
||||
SoftWrap::EditorWidth | SoftWrap::Column(_) => settings::SoftWrap::None,
|
||||
SoftWrap::None => language_settings::SoftWrap::EditorWidth,
|
||||
SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None,
|
||||
};
|
||||
self.soft_wrap_mode_override = Some(soft_wrap);
|
||||
}
|
||||
@@ -6578,8 +6722,8 @@ impl Editor {
|
||||
let buffer = &snapshot.buffer_snapshot;
|
||||
let start = buffer.anchor_before(0);
|
||||
let end = buffer.anchor_after(buffer.len());
|
||||
let theme = cx.global::<Settings>().theme.as_ref();
|
||||
self.background_highlights_in_range(start..end, &snapshot, theme)
|
||||
let theme = theme::current(cx);
|
||||
self.background_highlights_in_range(start..end, &snapshot, theme.as_ref())
|
||||
}
|
||||
|
||||
fn document_highlights_for_position<'a>(
|
||||
@@ -6729,6 +6873,7 @@ impl Editor {
|
||||
multi_buffer::Event::Saved => cx.emit(Event::Saved),
|
||||
multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged),
|
||||
multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged),
|
||||
multi_buffer::Event::DiffBaseChanged => cx.emit(Event::DiffBaseChanged),
|
||||
multi_buffer::Event::Closed => cx.emit(Event::Closed),
|
||||
multi_buffer::Event::DiagnosticsUpdated => {
|
||||
self.refresh_active_diagnostics(cx);
|
||||
@@ -6910,7 +7055,7 @@ impl Editor {
|
||||
.map(|a| a.to_string());
|
||||
|
||||
let telemetry = project.read(cx).client().telemetry().clone();
|
||||
let telemetry_settings = cx.global::<Settings>().telemetry();
|
||||
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
|
||||
|
||||
let event = ClickhouseEvent::Copilot {
|
||||
suggestion_id,
|
||||
@@ -6940,33 +7085,37 @@ impl Editor {
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|a| a.to_string()));
|
||||
|
||||
let settings = cx.global::<Settings>();
|
||||
let vim_mode = cx
|
||||
.global::<SettingsStore>()
|
||||
.untyped_user_settings()
|
||||
.get("vim_mode")
|
||||
== Some(&serde_json::Value::Bool(true));
|
||||
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
|
||||
let copilot_enabled = all_language_settings(cx).copilot_enabled(None, None);
|
||||
let copilot_enabled_for_language = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.settings_at(0, cx)
|
||||
.show_copilot_suggestions;
|
||||
|
||||
let telemetry = project.read(cx).client().telemetry().clone();
|
||||
telemetry.report_mixpanel_event(
|
||||
match name {
|
||||
"open" => "open editor",
|
||||
"save" => "save editor",
|
||||
_ => name,
|
||||
},
|
||||
json!({ "File Extension": file_extension, "Vim Mode": settings.vim_mode, "In Clickhouse": true }),
|
||||
settings.telemetry(),
|
||||
);
|
||||
match name {
|
||||
"open" => "open editor",
|
||||
"save" => "save editor",
|
||||
_ => name,
|
||||
},
|
||||
json!({ "File Extension": file_extension, "Vim Mode": vim_mode, "In Clickhouse": true }),
|
||||
telemetry_settings,
|
||||
);
|
||||
let event = ClickhouseEvent::Editor {
|
||||
file_extension,
|
||||
vim_mode: settings.vim_mode,
|
||||
vim_mode,
|
||||
operation: name,
|
||||
copilot_enabled: settings.features.copilot,
|
||||
copilot_enabled_for_language: settings.show_copilot_suggestions(
|
||||
self.language_at(0, cx)
|
||||
.map(|language| language.name())
|
||||
.as_deref(),
|
||||
self.file_at(0, cx)
|
||||
.map(|file| file.path().clone())
|
||||
.as_deref(),
|
||||
),
|
||||
copilot_enabled,
|
||||
copilot_enabled_for_language,
|
||||
};
|
||||
telemetry.report_clickhouse_event(event, settings.telemetry())
|
||||
telemetry.report_clickhouse_event(event, telemetry_settings)
|
||||
}
|
||||
|
||||
/// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
|
||||
@@ -6998,7 +7147,7 @@ impl Editor {
|
||||
let mut lines = Vec::new();
|
||||
let mut line: VecDeque<Chunk> = VecDeque::new();
|
||||
|
||||
let theme = &cx.global::<Settings>().theme.editor.syntax;
|
||||
let theme = &theme::current(cx).editor.syntax;
|
||||
|
||||
for chunk in chunks {
|
||||
let highlight = chunk.syntax_highlight_id.and_then(|id| id.name(theme));
|
||||
@@ -7108,10 +7257,12 @@ pub enum Event {
|
||||
BufferEdited,
|
||||
Edited,
|
||||
Reparsed,
|
||||
Focused,
|
||||
Blurred,
|
||||
DirtyChanged,
|
||||
Saved,
|
||||
TitleChanged,
|
||||
DiffBaseChanged,
|
||||
SelectionsChanged {
|
||||
local: bool,
|
||||
},
|
||||
@@ -7161,6 +7312,7 @@ impl View for Editor {
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
let focused_event = EditorFocused(cx.handle());
|
||||
cx.emit(Event::Focused);
|
||||
cx.emit_global(focused_event);
|
||||
}
|
||||
if let Some(rename) = self.pending_rename.as_ref() {
|
||||
@@ -7235,7 +7387,7 @@ impl View for Editor {
|
||||
false
|
||||
}
|
||||
|
||||
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||
fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
|
||||
Self::reset_to_default_keymap_context(keymap);
|
||||
let mode = match self.mode {
|
||||
EditorMode::SingleLine => "single_line",
|
||||
@@ -7251,10 +7403,18 @@ impl View for Editor {
|
||||
Some(ContextMenu::CodeActions(_)) => keymap.add_identifier("showing_code_actions"),
|
||||
None => {}
|
||||
}
|
||||
|
||||
for layer in self.keymap_context_layers.values() {
|
||||
keymap.extend(layer);
|
||||
}
|
||||
|
||||
if let Some(extension) = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str())
|
||||
{
|
||||
keymap.add_key("extension", extension.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
fn text_for_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<String> {
|
||||
@@ -7420,7 +7580,7 @@ impl View for Editor {
|
||||
}
|
||||
|
||||
fn build_style(
|
||||
settings: &Settings,
|
||||
settings: &ThemeSettings,
|
||||
get_field_editor_theme: Option<&GetFieldEditorTheme>,
|
||||
override_text_style: Option<&OverrideTextStyle>,
|
||||
cx: &AppContext,
|
||||
@@ -7450,7 +7610,7 @@ fn build_style(
|
||||
let font_id = font_cache
|
||||
.select_font(font_family_id, &font_properties)
|
||||
.unwrap();
|
||||
let font_size = settings.buffer_font_size;
|
||||
let font_size = settings.buffer_font_size(cx);
|
||||
EditorStyle {
|
||||
text: TextStyle {
|
||||
color: settings.theme.editor.text_color,
|
||||
@@ -7620,10 +7780,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
|
||||
}
|
||||
|
||||
Arc::new(move |cx: &mut BlockContext| {
|
||||
let settings = cx.global::<Settings>();
|
||||
let settings = settings::get::<ThemeSettings>(cx);
|
||||
let theme = &settings.theme.editor;
|
||||
let style = diagnostic_style(diagnostic.severity, is_valid, theme);
|
||||
let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
|
||||
let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
|
||||
Flex::column()
|
||||
.with_children(highlighted_lines.iter().map(|(line, highlights)| {
|
||||
Label::new(
|
||||
|
||||
56
crates/editor/src/editor_settings.rs
Normal file
56
crates/editor/src/editor_settings.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Setting;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditorSettings {
|
||||
pub cursor_blink: bool,
|
||||
pub hover_popover_enabled: bool,
|
||||
pub show_completions_on_input: bool,
|
||||
pub use_on_type_format: bool,
|
||||
pub scrollbar: Scrollbar,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct Scrollbar {
|
||||
pub show: ShowScrollbar,
|
||||
pub git_diff: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShowScrollbar {
|
||||
Auto,
|
||||
System,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct EditorSettingsContent {
|
||||
pub cursor_blink: Option<bool>,
|
||||
pub hover_popover_enabled: Option<bool>,
|
||||
pub show_completions_on_input: Option<bool>,
|
||||
pub use_on_type_format: Option<bool>,
|
||||
pub scrollbar: Option<ScrollbarContent>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct ScrollbarContent {
|
||||
pub show: Option<ShowScrollbar>,
|
||||
pub git_diff: Option<bool>,
|
||||
}
|
||||
|
||||
impl Setting for EditorSettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
type FileContent = EditorSettingsContent;
|
||||
|
||||
fn load(
|
||||
default_value: &Self::FileContent,
|
||||
user_values: &[&Self::FileContent],
|
||||
_: &gpui::AppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock},
|
||||
editor_settings::ShowScrollbar,
|
||||
git::{diff_hunk_to_display, DisplayDiffHunk},
|
||||
hover_popover::{
|
||||
hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
|
||||
@@ -13,7 +14,7 @@ use crate::{
|
||||
link_go_to_definition::{
|
||||
go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
|
||||
},
|
||||
mouse_context_menu, EditorStyle, GutterHover, UnfoldAt,
|
||||
mouse_context_menu, EditorSettings, EditorStyle, GutterHover, UnfoldAt,
|
||||
};
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
@@ -35,9 +36,14 @@ use gpui::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use json::json;
|
||||
use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Selection};
|
||||
use project::ProjectPath;
|
||||
use settings::{GitGutter, Settings, ShowWhitespaces};
|
||||
use language::{
|
||||
language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16,
|
||||
Selection,
|
||||
};
|
||||
use project::{
|
||||
project_settings::{GitGutterSetting, ProjectSettings},
|
||||
ProjectPath,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@@ -47,6 +53,7 @@ use std::{
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
};
|
||||
use text::Point;
|
||||
use workspace::item::Item;
|
||||
|
||||
enum FoldMarkers {}
|
||||
@@ -547,11 +554,8 @@ impl EditorElement {
|
||||
let scroll_top = scroll_position.y() * line_height;
|
||||
|
||||
let show_gutter = matches!(
|
||||
&cx.global::<Settings>()
|
||||
.git_overrides
|
||||
.git_gutter
|
||||
.unwrap_or_default(),
|
||||
GitGutter::TrackedFiles
|
||||
settings::get::<ProjectSettings>(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
);
|
||||
|
||||
if show_gutter {
|
||||
@@ -608,7 +612,7 @@ impl EditorElement {
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
|
||||
let diff_style = &theme::current(cx).editor.diff.clone();
|
||||
let line_height = layout.position_map.line_height;
|
||||
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
@@ -648,7 +652,7 @@ impl EditorElement {
|
||||
|
||||
//TODO: This rendering is entirely a horrible hack
|
||||
DiffHunkStatus::Removed => {
|
||||
let row = *display_row_range.start();
|
||||
let row = display_row_range.start;
|
||||
|
||||
let offset = line_height / 2.;
|
||||
let start_y = row as f32 * line_height - offset - scroll_top;
|
||||
@@ -670,11 +674,11 @@ impl EditorElement {
|
||||
}
|
||||
};
|
||||
|
||||
let start_row = *display_row_range.start();
|
||||
let end_row = *display_row_range.end();
|
||||
let start_row = display_row_range.start;
|
||||
let end_row = display_row_range.end;
|
||||
|
||||
let start_y = start_row as f32 * line_height - scroll_top;
|
||||
let end_y = end_row as f32 * line_height - scroll_top + line_height;
|
||||
let end_y = end_row as f32 * line_height - scroll_top;
|
||||
|
||||
let width = diff_style.width_em * line_height;
|
||||
let highlight_origin = bounds.origin() + vec2f(-width, start_y);
|
||||
@@ -708,6 +712,7 @@ impl EditorElement {
|
||||
let scroll_left = scroll_position.x() * max_glyph_width;
|
||||
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
|
||||
let line_end_overshoot = 0.15 * layout.position_map.line_height;
|
||||
let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
|
||||
|
||||
scene.push_layer(Some(bounds));
|
||||
|
||||
@@ -882,9 +887,10 @@ impl EditorElement {
|
||||
content_origin,
|
||||
scroll_left,
|
||||
visible_text_bounds,
|
||||
cx,
|
||||
whitespace_setting,
|
||||
&invisible_display_ranges,
|
||||
visible_bounds,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1046,47 +1052,53 @@ impl EditorElement {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let diff_style = cx.global::<Settings>().theme.editor.diff.clone();
|
||||
for hunk in layout
|
||||
.position_map
|
||||
.snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(0..(max_row.floor() as u32), false)
|
||||
{
|
||||
let start_y = y_for_row(hunk.buffer_range.start as f32);
|
||||
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||
y_for_row((hunk.buffer_range.end + 1) as f32)
|
||||
} else {
|
||||
y_for_row((hunk.buffer_range.end) as f32)
|
||||
};
|
||||
if layout.is_singleton && settings::get::<EditorSettings>(cx).scrollbar.git_diff {
|
||||
let diff_style = theme::current(cx).editor.scrollbar.git.clone();
|
||||
for hunk in layout
|
||||
.position_map
|
||||
.snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
|
||||
{
|
||||
let start_display = Point::new(hunk.buffer_range.start, 0)
|
||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
let end_display = Point::new(hunk.buffer_range.end, 0)
|
||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||
let start_y = y_for_row(start_display.row() as f32);
|
||||
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||
y_for_row((end_display.row() + 1) as f32)
|
||||
} else {
|
||||
y_for_row((end_display.row()) as f32)
|
||||
};
|
||||
|
||||
if end_y - start_y < 1. {
|
||||
end_y = start_y + 1.;
|
||||
if end_y - start_y < 1. {
|
||||
end_y = start_y + 1.;
|
||||
}
|
||||
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
|
||||
|
||||
let color = match hunk.status() {
|
||||
DiffHunkStatus::Added => diff_style.inserted,
|
||||
DiffHunkStatus::Modified => diff_style.modified,
|
||||
DiffHunkStatus::Removed => diff_style.deleted,
|
||||
};
|
||||
|
||||
let border = Border {
|
||||
width: 1.,
|
||||
color: style.thumb.border.color,
|
||||
overlay: false,
|
||||
top: false,
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: true,
|
||||
};
|
||||
|
||||
scene.push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(color),
|
||||
border,
|
||||
corner_radius: style.thumb.corner_radius,
|
||||
})
|
||||
}
|
||||
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
|
||||
|
||||
let color = match hunk.status() {
|
||||
DiffHunkStatus::Added => diff_style.inserted,
|
||||
DiffHunkStatus::Modified => diff_style.modified,
|
||||
DiffHunkStatus::Removed => diff_style.deleted,
|
||||
};
|
||||
|
||||
let border = Border {
|
||||
width: 1.,
|
||||
color: style.thumb.border.color,
|
||||
overlay: false,
|
||||
top: false,
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: true,
|
||||
};
|
||||
|
||||
scene.push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(color),
|
||||
border,
|
||||
corner_radius: style.thumb.corner_radius,
|
||||
})
|
||||
}
|
||||
|
||||
scene.push_quad(Quad {
|
||||
@@ -1264,7 +1276,7 @@ impl EditorElement {
|
||||
.row;
|
||||
|
||||
buffer_snapshot
|
||||
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row, false)
|
||||
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
|
||||
.map(|hunk| diff_hunk_to_display(hunk, snapshot))
|
||||
.dedup()
|
||||
.collect()
|
||||
@@ -1457,7 +1469,7 @@ impl EditorElement {
|
||||
editor: &mut Editor,
|
||||
cx: &mut LayoutContext<Editor>,
|
||||
) -> (f32, Vec<BlockLayout>) {
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
let scroll_x = snapshot.scroll_anchor.offset.x();
|
||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||
.blocks_in_range(rows.clone())
|
||||
@@ -1783,9 +1795,10 @@ impl LineWithInvisibles {
|
||||
content_origin: Vector2F,
|
||||
scroll_left: f32,
|
||||
visible_text_bounds: RectF,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
whitespace_setting: ShowWhitespaceSetting,
|
||||
selection_ranges: &[Range<DisplayPoint>],
|
||||
visible_bounds: RectF,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
let line_y = row as f32 * line_height - scroll_top;
|
||||
@@ -1799,7 +1812,6 @@ impl LineWithInvisibles {
|
||||
);
|
||||
|
||||
self.draw_invisibles(
|
||||
cx,
|
||||
&selection_ranges,
|
||||
layout,
|
||||
content_origin,
|
||||
@@ -1809,12 +1821,13 @@ impl LineWithInvisibles {
|
||||
scene,
|
||||
visible_bounds,
|
||||
line_height,
|
||||
whitespace_setting,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_invisibles(
|
||||
&self,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
selection_ranges: &[Range<DisplayPoint>],
|
||||
layout: &LayoutState,
|
||||
content_origin: Vector2F,
|
||||
@@ -1824,17 +1837,13 @@ impl LineWithInvisibles {
|
||||
scene: &mut SceneBuilder,
|
||||
visible_bounds: RectF,
|
||||
line_height: f32,
|
||||
whitespace_setting: ShowWhitespaceSetting,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let settings = cx.global::<Settings>();
|
||||
let allowed_invisibles_regions = match settings
|
||||
.editor_overrides
|
||||
.show_whitespaces
|
||||
.or(settings.editor_defaults.show_whitespaces)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
ShowWhitespaces::None => return,
|
||||
ShowWhitespaces::Selection => Some(selection_ranges),
|
||||
ShowWhitespaces::All => None,
|
||||
let allowed_invisibles_regions = match whitespace_setting {
|
||||
ShowWhitespaceSetting::None => return,
|
||||
ShowWhitespaceSetting::Selection => Some(selection_ranges),
|
||||
ShowWhitespaceSetting::All => None,
|
||||
};
|
||||
|
||||
for invisible in &self.invisibles {
|
||||
@@ -1979,11 +1988,11 @@ impl Element<Editor> for EditorElement {
|
||||
let is_singleton = editor.is_singleton(cx);
|
||||
|
||||
let highlighted_rows = editor.highlighted_rows();
|
||||
let theme = cx.global::<Settings>().theme.as_ref();
|
||||
let theme = theme::current(cx);
|
||||
let highlighted_ranges = editor.background_highlights_in_range(
|
||||
start_anchor..end_anchor,
|
||||
&snapshot.display_snapshot,
|
||||
theme,
|
||||
theme.as_ref(),
|
||||
);
|
||||
|
||||
fold_ranges.extend(
|
||||
@@ -2058,13 +2067,17 @@ impl Element<Editor> for EditorElement {
|
||||
));
|
||||
}
|
||||
|
||||
let show_scrollbars = match cx.global::<Settings>().show_scrollbars {
|
||||
settings::ShowScrollbars::Auto => {
|
||||
snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible()
|
||||
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
|
||||
let show_scrollbars = match scrollbar_settings.show {
|
||||
ShowScrollbar::Auto => {
|
||||
// Git
|
||||
(is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
|
||||
// Scrollmanager
|
||||
|| editor.scroll_manager.scrollbars_visible()
|
||||
}
|
||||
settings::ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(),
|
||||
settings::ShowScrollbars::Always => true,
|
||||
settings::ShowScrollbars::Never => false,
|
||||
ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
|
||||
ShowScrollbar::Always => true,
|
||||
ShowScrollbar::Never => false,
|
||||
};
|
||||
|
||||
let include_root = editor
|
||||
@@ -2283,6 +2296,7 @@ impl Element<Editor> for EditorElement {
|
||||
text_size,
|
||||
scrollbar_row_range,
|
||||
show_scrollbars,
|
||||
is_singleton,
|
||||
max_row,
|
||||
gutter_margin,
|
||||
active_rows,
|
||||
@@ -2438,6 +2452,7 @@ pub struct LayoutState {
|
||||
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
|
||||
scrollbar_row_range: Range<f32>,
|
||||
show_scrollbars: bool,
|
||||
is_singleton: bool,
|
||||
max_row: u32,
|
||||
context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
|
||||
code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
|
||||
@@ -2826,17 +2841,19 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{BlockDisposition, BlockProperties},
|
||||
editor_tests::{init_test, update_test_settings},
|
||||
Editor, MultiBuffer,
|
||||
};
|
||||
use gpui::TestAppContext;
|
||||
use language::language_settings;
|
||||
use log::info;
|
||||
use settings::Settings;
|
||||
use std::{num::NonZeroU32, sync::Arc};
|
||||
use util::test::sample_text;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_layout_line_numbers(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
@@ -2854,7 +2871,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
@@ -2914,26 +2932,27 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
|
||||
let tab_size = 4;
|
||||
const TAB_SIZE: u32 = 4;
|
||||
|
||||
let input_text = "\t \t|\t| a b";
|
||||
let expected_invisibles = vec![
|
||||
Invisible::Tab {
|
||||
line_start_offset: 0,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: tab_size as usize,
|
||||
line_offset: TAB_SIZE as usize,
|
||||
},
|
||||
Invisible::Tab {
|
||||
line_start_offset: tab_size as usize + 1,
|
||||
line_start_offset: TAB_SIZE as usize + 1,
|
||||
},
|
||||
Invisible::Tab {
|
||||
line_start_offset: tab_size as usize * 2 + 1,
|
||||
line_start_offset: TAB_SIZE as usize * 2 + 1,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: tab_size as usize * 3 + 1,
|
||||
line_offset: TAB_SIZE as usize * 3 + 1,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: tab_size as usize * 3 + 3,
|
||||
line_offset: TAB_SIZE as usize * 3 + 3,
|
||||
},
|
||||
];
|
||||
assert_eq!(
|
||||
@@ -2945,12 +2964,11 @@ mod tests {
|
||||
"Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
|
||||
);
|
||||
|
||||
cx.update(|cx| {
|
||||
let mut test_settings = Settings::test(cx);
|
||||
test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
|
||||
test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap());
|
||||
cx.set_global(test_settings);
|
||||
init_test(cx, |s| {
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
|
||||
});
|
||||
|
||||
let actual_invisibles =
|
||||
collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0);
|
||||
|
||||
@@ -2959,11 +2977,9 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let mut test_settings = Settings::test(cx);
|
||||
test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
|
||||
test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(4).unwrap());
|
||||
cx.set_global(test_settings);
|
||||
init_test(cx, |s| {
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.tab_size = NonZeroU32::new(4);
|
||||
});
|
||||
|
||||
for editor_mode_without_invisibles in [
|
||||
@@ -3014,19 +3030,18 @@ mod tests {
|
||||
);
|
||||
info!("Expected invisibles: {expected_invisibles:?}");
|
||||
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// Put the same string with repeating whitespace pattern into editors of various size,
|
||||
// take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
|
||||
let resize_step = 10.0;
|
||||
let mut editor_width = 200.0;
|
||||
while editor_width <= 1000.0 {
|
||||
cx.update(|cx| {
|
||||
let mut test_settings = Settings::test(cx);
|
||||
test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap());
|
||||
test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
|
||||
test_settings.editor_defaults.preferred_line_length = Some(editor_width as u32);
|
||||
test_settings.editor_defaults.soft_wrap =
|
||||
Some(settings::SoftWrap::PreferredLineLength);
|
||||
cx.set_global(test_settings);
|
||||
update_test_settings(cx, |s| {
|
||||
s.defaults.tab_size = NonZeroU32::new(tab_size);
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.preferred_line_length = Some(editor_width as u32);
|
||||
s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
|
||||
});
|
||||
|
||||
let actual_invisibles =
|
||||
@@ -3074,7 +3089,7 @@ mod tests {
|
||||
|
||||
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
let (_, layout_state) = editor.update(cx, |editor, cx| {
|
||||
editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_wrap_width(Some(editor_width), cx);
|
||||
|
||||
let mut new_parents = Default::default();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::ops::RangeInclusive;
|
||||
use std::ops::Range;
|
||||
|
||||
use git::diff::{DiffHunk, DiffHunkStatus};
|
||||
use language::Point;
|
||||
@@ -15,7 +15,7 @@ pub enum DisplayDiffHunk {
|
||||
},
|
||||
|
||||
Unfolded {
|
||||
display_row_range: RangeInclusive<u32>,
|
||||
display_row_range: Range<u32>,
|
||||
status: DiffHunkStatus,
|
||||
},
|
||||
}
|
||||
@@ -26,7 +26,7 @@ impl DisplayDiffHunk {
|
||||
&DisplayDiffHunk::Folded { display_row } => display_row,
|
||||
DisplayDiffHunk::Unfolded {
|
||||
display_row_range, ..
|
||||
} => *display_row_range.start(),
|
||||
} => display_row_range.start,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl DisplayDiffHunk {
|
||||
|
||||
DisplayDiffHunk::Unfolded {
|
||||
display_row_range, ..
|
||||
} => display_row_range.clone(),
|
||||
} => display_row_range.start..=display_row_range.end - 1,
|
||||
};
|
||||
|
||||
range.contains(&display_row)
|
||||
@@ -77,16 +77,12 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
|
||||
} else {
|
||||
let start = hunk_start_point.to_display_point(snapshot).row();
|
||||
|
||||
let hunk_end_row_inclusive = hunk
|
||||
.buffer_range
|
||||
.end
|
||||
.saturating_sub(1)
|
||||
.max(hunk.buffer_range.start);
|
||||
let hunk_end_row_inclusive = hunk.buffer_range.end.max(hunk.buffer_range.start);
|
||||
let hunk_end_point = Point::new(hunk_end_row_inclusive, 0);
|
||||
let end = hunk_end_point.to_display_point(snapshot).row();
|
||||
|
||||
DisplayDiffHunk::Unfolded {
|
||||
display_row_range: start..=end,
|
||||
display_row_range: start..end,
|
||||
status: hunk.status(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,14 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::editor_lsp_test_context::EditorLspTestContext;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot,
|
||||
EditorStyle, RangeToAnchorExt,
|
||||
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings,
|
||||
EditorSnapshot, EditorStyle, RangeToAnchorExt,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
@@ -12,7 +12,6 @@ use gpui::{
|
||||
};
|
||||
use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry};
|
||||
use project::{HoverBlock, HoverBlockKind, Project};
|
||||
use settings::Settings;
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use util::TryFutureExt;
|
||||
|
||||
@@ -38,7 +37,7 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
|
||||
/// The internal hover action dispatches between `show_hover` or `hide_hover`
|
||||
/// depending on whether a point to hover over is provided.
|
||||
pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) {
|
||||
if cx.global::<Settings>().hover_popover_enabled {
|
||||
if settings::get::<EditorSettings>(cx).hover_popover_enabled {
|
||||
if let Some(point) = point {
|
||||
show_hover(editor, point, false, cx);
|
||||
} else {
|
||||
@@ -654,7 +653,7 @@ impl DiagnosticPopover {
|
||||
_ => style.hover_popover.container,
|
||||
};
|
||||
|
||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
|
||||
MouseEventHandler::<DiagnosticPopover, _>::new(0, cx, |_, _| {
|
||||
text.with_soft_wrap(true)
|
||||
@@ -694,7 +693,7 @@ impl DiagnosticPopover {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::editor_lsp_test_context::EditorLspTestContext;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
use gpui::fonts::Weight;
|
||||
use indoc::indoc;
|
||||
use language::{Diagnostic, DiagnosticSet};
|
||||
@@ -706,6 +705,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
@@ -773,6 +774,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
@@ -816,6 +819,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
@@ -882,7 +887,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_render_blocks(cx: &mut gpui::TestAppContext) {
|
||||
Settings::test_async(cx);
|
||||
init_test(cx, |_| {});
|
||||
|
||||
cx.add_window(|cx| {
|
||||
let editor = Editor::single_line(None, cx);
|
||||
let style = editor.style(cx);
|
||||
|
||||
@@ -16,7 +16,6 @@ use language::{
|
||||
};
|
||||
use project::{FormatTrigger, Item as _, Project, ProjectPath};
|
||||
use rpc::proto::{self, update_view};
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@@ -721,17 +720,6 @@ impl Item for Editor {
|
||||
})
|
||||
}
|
||||
|
||||
fn git_diff_recalc(
|
||||
&mut self,
|
||||
_project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.buffer().update(cx, |multibuffer, cx| {
|
||||
multibuffer.git_diff_recalc(cx);
|
||||
});
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
|
||||
let mut result = SmallVec::new();
|
||||
match event {
|
||||
@@ -1116,7 +1104,7 @@ impl View for CursorPosition {
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
if let Some(position) = self.position {
|
||||
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
|
||||
let theme = &theme::current(cx).workspace.status_bar;
|
||||
let mut text = format!(
|
||||
"{}{FILE_ROW_COLUMN_DELIMITER}{}",
|
||||
position.row + 1,
|
||||
@@ -1232,27 +1220,27 @@ mod tests {
|
||||
}
|
||||
|
||||
fn as_local(&self) -> Option<&dyn language::LocalFile> {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn mtime(&self) -> SystemTime {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn to_proto(&self) -> rpc::proto::File {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase};
|
||||
use gpui::{Task, ViewContext};
|
||||
use language::{Bias, ToOffset};
|
||||
use project::LocationLink;
|
||||
use settings::Settings;
|
||||
use std::ops::Range;
|
||||
use util::TryFutureExt;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -211,7 +209,7 @@ pub fn show_link_definition(
|
||||
});
|
||||
|
||||
// Highlight symbol using theme link definition highlight style
|
||||
let style = cx.global::<Settings>().theme.editor.link_definition;
|
||||
let style = theme::current(cx).editor.link_definition;
|
||||
this.highlight_text::<LinkGoToDefinitionState>(
|
||||
vec![highlight_range],
|
||||
style,
|
||||
@@ -297,6 +295,8 @@ fn go_to_fetched_definition_of_kind(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
platform::{self, Modifiers, ModifiersChangedEvent},
|
||||
@@ -305,12 +305,10 @@ mod tests {
|
||||
use indoc::indoc;
|
||||
use lsp::request::{GotoDefinition, GotoTypeDefinition};
|
||||
|
||||
use crate::test::editor_lsp_test_context::EditorLspTestContext;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
@@ -417,6 +415,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
|
||||
@@ -57,13 +57,14 @@ pub fn deploy_context_menu(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::test::editor_lsp_test_context::EditorLspTestContext;
|
||||
|
||||
use super::*;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
use indoc::indoc;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
|
||||
@@ -193,6 +193,44 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
|
||||
let point = display_point.to_point(map);
|
||||
if point.row == 0 {
|
||||
return map.max_point();
|
||||
}
|
||||
|
||||
let mut found_non_blank_line = false;
|
||||
for row in (0..point.row + 1).rev() {
|
||||
let blank = map.buffer_snapshot.is_line_blank(row);
|
||||
if found_non_blank_line && blank {
|
||||
return Point::new(row, 0).to_display_point(map);
|
||||
}
|
||||
|
||||
found_non_blank_line |= !blank;
|
||||
}
|
||||
|
||||
DisplayPoint::zero()
|
||||
}
|
||||
|
||||
pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
|
||||
let point = display_point.to_point(map);
|
||||
if point.row == map.max_buffer_row() {
|
||||
return DisplayPoint::zero();
|
||||
}
|
||||
|
||||
let mut found_non_blank_line = false;
|
||||
for row in point.row..map.max_buffer_row() + 1 {
|
||||
let blank = map.buffer_snapshot.is_line_blank(row);
|
||||
if found_non_blank_line && blank {
|
||||
return Point::new(row, 0).to_display_point(map);
|
||||
}
|
||||
|
||||
found_non_blank_line |= !blank;
|
||||
}
|
||||
|
||||
map.max_point()
|
||||
}
|
||||
|
||||
/// Scans for a boundary preceding the given start point `from` until a boundary is found, indicated by the
|
||||
/// given predicate returning true. The predicate is called with the character to the left and right
|
||||
/// of the candidate boundary location, and will be called with `\n` characters indicating the start
|
||||
@@ -369,11 +407,12 @@ pub fn split_display_range_by_lines(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer};
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_previous_word_start(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
@@ -400,7 +439,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_previous_subword_start(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
@@ -434,7 +474,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
fn assert(
|
||||
marked_text: &str,
|
||||
cx: &mut gpui::AppContext,
|
||||
@@ -466,7 +507,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_next_word_end(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
@@ -490,7 +532,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_next_subword_end(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
@@ -523,7 +566,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_find_boundary(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
fn assert(
|
||||
marked_text: &str,
|
||||
cx: &mut gpui::AppContext,
|
||||
@@ -555,7 +599,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_surrounding_word(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
@@ -576,7 +621,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_move_up_and_down_with_excerpts(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
init_test(cx);
|
||||
|
||||
let family_id = cx
|
||||
.font_cache()
|
||||
.load_family(&["Helvetica"], &Default::default())
|
||||
@@ -691,4 +737,11 @@ mod tests {
|
||||
(DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::AppContext) {
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
theme::init((), cx);
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ use git::diff::DiffHunk;
|
||||
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
|
||||
pub use language::Completion;
|
||||
use language::{
|
||||
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
||||
char_kind,
|
||||
language_settings::{language_settings, LanguageSettings},
|
||||
AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
||||
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
|
||||
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
|
||||
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
|
||||
@@ -64,6 +66,7 @@ pub enum Event {
|
||||
},
|
||||
Edited,
|
||||
Reloaded,
|
||||
DiffBaseChanged,
|
||||
LanguageChanged,
|
||||
Reparsed,
|
||||
Saved,
|
||||
@@ -341,17 +344,6 @@ impl MultiBuffer {
|
||||
self.read(cx).symbols_containing(offset, theme)
|
||||
}
|
||||
|
||||
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let buffers = self.buffers.borrow();
|
||||
for buffer_state in buffers.values() {
|
||||
if buffer_state.buffer.read(cx).needs_git_diff_recalc() {
|
||||
buffer_state
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.git_diff_recalc(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn edit<I, S, T>(
|
||||
&mut self,
|
||||
edits: I,
|
||||
@@ -1138,6 +1130,10 @@ impl MultiBuffer {
|
||||
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;
|
||||
@@ -1306,6 +1302,7 @@ impl MultiBuffer {
|
||||
language::Event::Saved => Event::Saved,
|
||||
language::Event::FileHandleChanged => Event::FileHandleChanged,
|
||||
language::Event::Reloaded => Event::Reloaded,
|
||||
language::Event::DiffBaseChanged => Event::DiffBaseChanged,
|
||||
language::Event::LanguageChanged => Event::LanguageChanged,
|
||||
language::Event::Reparsed => Event::Reparsed,
|
||||
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
|
||||
@@ -1375,6 +1372,15 @@ impl MultiBuffer {
|
||||
.and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
|
||||
}
|
||||
|
||||
pub fn settings_at<'a, T: ToOffset>(
|
||||
&self,
|
||||
point: T,
|
||||
cx: &'a AppContext,
|
||||
) -> &'a LanguageSettings {
|
||||
let language = self.language_at(point, cx);
|
||||
language_settings(language.map(|l| l.name()).as_deref(), cx)
|
||||
}
|
||||
|
||||
pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle<Buffer>)) {
|
||||
self.buffers
|
||||
.borrow()
|
||||
@@ -1535,6 +1541,13 @@ impl MultiBuffer {
|
||||
cx.add_model(|cx| Self::singleton(buffer, cx))
|
||||
}
|
||||
|
||||
pub fn build_from_buffer(
|
||||
buffer: ModelHandle<Buffer>,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> ModelHandle<Self> {
|
||||
cx.add_model(|cx| Self::singleton(buffer, cx))
|
||||
}
|
||||
|
||||
pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> ModelHandle<Self> {
|
||||
cx.add_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0);
|
||||
@@ -2767,6 +2780,16 @@ impl MultiBufferSnapshot {
|
||||
.and_then(|(buffer, offset)| buffer.language_at(offset))
|
||||
}
|
||||
|
||||
pub fn settings_at<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
point: T,
|
||||
cx: &'a AppContext,
|
||||
) -> &'a LanguageSettings {
|
||||
self.point_to_buffer_offset(point)
|
||||
.map(|(buffer, offset)| buffer.settings_at(offset, cx))
|
||||
.unwrap_or_else(|| language_settings(None, cx))
|
||||
}
|
||||
|
||||
pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option<LanguageScope> {
|
||||
self.point_to_buffer_offset(point)
|
||||
.and_then(|(buffer, offset)| buffer.language_scope_at(offset))
|
||||
@@ -2820,20 +2843,24 @@ impl MultiBufferSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn git_diff_hunks_in_range<'a>(
|
||||
pub fn has_git_diffs(&self) -> bool {
|
||||
for excerpt in self.excerpts.iter() {
|
||||
if !excerpt.buffer.git_diff.is_empty() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn git_diff_hunks_in_range_rev<'a>(
|
||||
&'a self,
|
||||
row_range: Range<u32>,
|
||||
reversed: bool,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
|
||||
if reversed {
|
||||
cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
}
|
||||
} else {
|
||||
cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &());
|
||||
cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &());
|
||||
if cursor.item().is_none() {
|
||||
cursor.prev(&());
|
||||
}
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
@@ -2863,7 +2890,7 @@ impl MultiBufferSnapshot {
|
||||
|
||||
let buffer_hunks = excerpt
|
||||
.buffer
|
||||
.git_diff_hunks_intersecting_range(buffer_start..buffer_end, reversed)
|
||||
.git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end)
|
||||
.filter_map(move |hunk| {
|
||||
let start = multibuffer_start.row
|
||||
+ hunk
|
||||
@@ -2883,12 +2910,70 @@ impl MultiBufferSnapshot {
|
||||
})
|
||||
});
|
||||
|
||||
if reversed {
|
||||
cursor.prev(&());
|
||||
} else {
|
||||
cursor.next(&());
|
||||
cursor.prev(&());
|
||||
|
||||
Some(buffer_hunks)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn git_diff_hunks_in_range<'a>(
|
||||
&'a self,
|
||||
row_range: Range<u32>,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let mut cursor = self.excerpts.cursor::<Point>();
|
||||
|
||||
cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &());
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
let excerpt = cursor.item()?;
|
||||
let multibuffer_start = *cursor.start();
|
||||
let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
|
||||
if multibuffer_start.row >= row_range.end {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buffer_start = excerpt.range.context.start;
|
||||
let mut buffer_end = excerpt.range.context.end;
|
||||
let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
|
||||
let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
|
||||
|
||||
if row_range.start > multibuffer_start.row {
|
||||
let buffer_start_point =
|
||||
excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0);
|
||||
buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
|
||||
}
|
||||
|
||||
if row_range.end < multibuffer_end.row {
|
||||
let buffer_end_point =
|
||||
excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0);
|
||||
buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
|
||||
}
|
||||
|
||||
let buffer_hunks = excerpt
|
||||
.buffer
|
||||
.git_diff_hunks_intersecting_range(buffer_start..buffer_end)
|
||||
.filter_map(move |hunk| {
|
||||
let start = multibuffer_start.row
|
||||
+ hunk
|
||||
.buffer_range
|
||||
.start
|
||||
.saturating_sub(excerpt_start_point.row);
|
||||
let end = multibuffer_start.row
|
||||
+ hunk
|
||||
.buffer_range
|
||||
.end
|
||||
.min(excerpt_end_point.row + 1)
|
||||
.saturating_sub(excerpt_start_point.row);
|
||||
|
||||
Some(DiffHunk {
|
||||
buffer_range: start..end,
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
})
|
||||
});
|
||||
|
||||
cursor.next(&());
|
||||
|
||||
Some(buffer_hunks)
|
||||
})
|
||||
.flatten()
|
||||
@@ -3783,15 +3868,17 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::editor_tests::init_test;
|
||||
|
||||
use super::*;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, TestAppContext};
|
||||
use language::{Buffer, Rope};
|
||||
use project::{FakeFs, Project};
|
||||
use rand::prelude::*;
|
||||
use settings::Settings;
|
||||
use settings::SettingsStore;
|
||||
use std::{env, rc::Rc};
|
||||
use unindent::Unindent;
|
||||
|
||||
use util::test::sample_text;
|
||||
|
||||
#[gpui::test]
|
||||
@@ -4478,73 +4565,85 @@ mod tests {
|
||||
#[gpui::test]
|
||||
async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
|
||||
use git::diff::DiffHunkStatus;
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
||||
// buffer has two modified hunks with two rows each
|
||||
let buffer_1 = cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(
|
||||
0,
|
||||
"
|
||||
1.zero
|
||||
1.ONE
|
||||
1.TWO
|
||||
1.three
|
||||
1.FOUR
|
||||
1.FIVE
|
||||
1.six
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_buffer(
|
||||
"
|
||||
1.zero
|
||||
1.ONE
|
||||
1.TWO
|
||||
1.three
|
||||
1.FOUR
|
||||
1.FIVE
|
||||
1.six
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
buffer_1.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(
|
||||
Some(
|
||||
"
|
||||
1.zero
|
||||
1.one
|
||||
1.two
|
||||
1.three
|
||||
1.four
|
||||
1.five
|
||||
1.six
|
||||
"
|
||||
1.zero
|
||||
1.one
|
||||
1.two
|
||||
1.three
|
||||
1.four
|
||||
1.five
|
||||
1.six
|
||||
"
|
||||
.unindent(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
buffer
|
||||
});
|
||||
|
||||
// buffer has a deletion hunk and an insertion hunk
|
||||
let buffer_2 = cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(
|
||||
0,
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.five
|
||||
2.six
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_buffer(
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.five
|
||||
2.six
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
buffer_2.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(
|
||||
Some(
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.one-and-a-half
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.six
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.one-and-a-half
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.six
|
||||
"
|
||||
.unindent(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
buffer
|
||||
});
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
@@ -4627,7 +4726,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.git_diff_hunks_in_range(0..12, false)
|
||||
.git_diff_hunks_in_range(0..12)
|
||||
.map(|hunk| (hunk.status(), hunk.buffer_range))
|
||||
.collect::<Vec<_>>(),
|
||||
&expected,
|
||||
@@ -4635,7 +4734,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.git_diff_hunks_in_range(0..12, true)
|
||||
.git_diff_hunks_in_range_rev(0..12)
|
||||
.map(|hunk| (hunk.status(), hunk.buffer_range))
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
@@ -4990,16 +5089,19 @@ mod tests {
|
||||
.read(cx)
|
||||
.range_to_buffer_ranges(start_ix..end_ix, cx);
|
||||
let excerpted_buffers_text = excerpted_buffer_ranges
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|(buffer, buffer_range)| {
|
||||
buffer
|
||||
.read(cx)
|
||||
.text_for_range(buffer_range)
|
||||
.text_for_range(buffer_range.clone())
|
||||
.collect::<String>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
assert_eq!(excerpted_buffers_text, text_for_range);
|
||||
if !expected_excerpts.is_empty() {
|
||||
assert!(!excerpted_buffer_ranges.is_empty());
|
||||
}
|
||||
|
||||
let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
|
||||
assert_eq!(
|
||||
@@ -5066,7 +5168,8 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
fn test_history(cx: &mut AppContext) {
|
||||
cx.set_global(Settings::test(cx));
|
||||
cx.set_global(SettingsStore::test(cx));
|
||||
|
||||
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "1234", cx));
|
||||
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "5678", cx));
|
||||
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
|
||||
@@ -34,13 +34,17 @@ impl<'a> EditorLspTestContext<'a> {
|
||||
) -> EditorLspTestContext<'a> {
|
||||
use json::json;
|
||||
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
theme::init((), cx);
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
pane::init(cx);
|
||||
Project::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
});
|
||||
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
let file_name = format!(
|
||||
"file.{}",
|
||||
language
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
use crate::{
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
||||
};
|
||||
use futures::Future;
|
||||
use gpui::{
|
||||
keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{Buffer, BufferSnapshot};
|
||||
use project::{FakeFs, Project};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
};
|
||||
|
||||
use futures::Future;
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::{
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
||||
};
|
||||
use gpui::{
|
||||
keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
|
||||
};
|
||||
use language::{Buffer, BufferSnapshot};
|
||||
use settings::Settings;
|
||||
use util::{
|
||||
assert_set_eq,
|
||||
test::{generate_marked_text, marked_text_ranges},
|
||||
@@ -28,17 +26,17 @@ pub struct EditorTestContext<'a> {
|
||||
}
|
||||
|
||||
impl<'a> EditorTestContext<'a> {
|
||||
pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
|
||||
pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.create_buffer("", None, cx))
|
||||
.unwrap();
|
||||
let (window_id, editor) = cx.update(|cx| {
|
||||
cx.set_global(Settings::test(cx));
|
||||
crate::init(cx);
|
||||
|
||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||
cx.add_window(Default::default(), |cx| {
|
||||
cx.focus_self();
|
||||
build_editor(MultiBuffer::build_simple("", cx), cx)
|
||||
});
|
||||
|
||||
(window_id, editor)
|
||||
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
|
||||
})
|
||||
});
|
||||
|
||||
Self {
|
||||
@@ -212,6 +210,7 @@ impl<'a> EditorTestContext<'a> {
|
||||
self.assert_selections(expected_selections, marked_text.to_string())
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
|
||||
let expected_ranges = self.ranges(marked_text);
|
||||
let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
|
||||
@@ -228,6 +227,7 @@ impl<'a> EditorTestContext<'a> {
|
||||
assert_set_eq!(actual_ranges, expected_ranges);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
|
||||
let expected_ranges = self.ranges(marked_text);
|
||||
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||
@@ -241,12 +241,14 @@ impl<'a> EditorTestContext<'a> {
|
||||
assert_set_eq!(actual_ranges, expected_ranges);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
|
||||
let expected_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &expected_selections, true);
|
||||
self.assert_selections(expected_selections, expected_marked_text)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_selections(
|
||||
&mut self,
|
||||
expected_selections: Vec<Range<usize>>,
|
||||
|
||||
@@ -16,6 +16,7 @@ editor = { path = "../editor" }
|
||||
language = { path = "../language" }
|
||||
gpui = { path = "../gpui" }
|
||||
project = { path = "../project" }
|
||||
regex.workspace = true
|
||||
search = { path = "../search" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
@@ -27,7 +28,7 @@ futures.workspace = true
|
||||
anyhow.workspace = true
|
||||
smallvec.workspace = true
|
||||
human_bytes = "0.4.1"
|
||||
isahc = "1.7"
|
||||
isahc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
postage.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -35,3 +36,6 @@ serde_derive.workspace = true
|
||||
sysinfo = "0.27.1"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
urlencoding = "2.1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -3,7 +3,6 @@ use gpui::{
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Entity, View, ViewContext, WeakViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||
|
||||
use crate::feedback_editor::{FeedbackEditor, GiveFeedback};
|
||||
@@ -33,15 +32,15 @@ impl View for DeployFeedbackButton {
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let active = self.active;
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
|
||||
let style = &theme
|
||||
.workspace
|
||||
.status_bar
|
||||
.sidebar_buttons
|
||||
.item
|
||||
.panel_buttons
|
||||
.button
|
||||
.style_for(state, active);
|
||||
|
||||
Svg::new("icons/feedback_16.svg")
|
||||
|
||||
@@ -14,6 +14,7 @@ use isahc::Request;
|
||||
use language::Buffer;
|
||||
use postage::prelude::Stream;
|
||||
use project::Project;
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
@@ -46,6 +47,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
#[derive(Serialize)]
|
||||
struct FeedbackRequestBody<'a> {
|
||||
feedback_text: &'a str,
|
||||
email: Option<String>,
|
||||
metrics_id: Option<Arc<str>>,
|
||||
installation_id: Option<Arc<str>>,
|
||||
system_specs: SystemSpecs,
|
||||
@@ -157,8 +159,18 @@ impl FeedbackEditor {
|
||||
let is_staff = telemetry.is_staff();
|
||||
let http_client = zed_client.http_client();
|
||||
|
||||
let re = Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap();
|
||||
|
||||
let emails: Vec<&str> = re
|
||||
.captures_iter(feedback_text)
|
||||
.map(|capture| capture.get(0).unwrap().as_str())
|
||||
.collect();
|
||||
|
||||
let email = emails.first().map(|e| e.to_string());
|
||||
|
||||
let request = FeedbackRequestBody {
|
||||
feedback_text: &feedback_text,
|
||||
email,
|
||||
metrics_id,
|
||||
installation_id,
|
||||
system_specs,
|
||||
|
||||
@@ -3,7 +3,6 @@ use gpui::{
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AnyElement, Element, Entity, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||
|
||||
use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo};
|
||||
@@ -30,12 +29,12 @@ impl View for FeedbackInfoText {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Text::new(
|
||||
"We read whatever you submit here. For issues and discussions, visit the ",
|
||||
"Share your feedback. Include your email for replies. For issues and discussions, visit the ",
|
||||
theme.feedback.info_text_default.text.clone(),
|
||||
)
|
||||
.with_soft_wrap(false)
|
||||
@@ -61,7 +60,7 @@ impl View for FeedbackInfoText {
|
||||
}),
|
||||
)
|
||||
.with_child(
|
||||
Text::new(" on GitHub.", theme.feedback.info_text_default.text.clone())
|
||||
Text::new(".", theme.feedback.info_text_default.text.clone())
|
||||
.with_soft_wrap(false)
|
||||
.aligned(),
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ use gpui::{
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use settings::Settings;
|
||||
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
@@ -46,7 +45,7 @@ impl View for SubmitFeedbackButton {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = theme::current(cx).clone();
|
||||
enum SubmitFeedbackButton {}
|
||||
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
|
||||
let style = theme.feedback.submit_button.style_for(state, false);
|
||||
|
||||
@@ -23,8 +23,12 @@ workspace = { path = "../workspace" }
|
||||
postage.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
theme = { path = "../theme", features = ["test-support"] }
|
||||
|
||||
serde_json.workspace = true
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
use std::ops::Range;
|
||||
use std::{iter, ops::Range};
|
||||
use sum_tree::SumTree;
|
||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point};
|
||||
|
||||
@@ -71,22 +71,66 @@ impl BufferDiff {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tree.is_empty()
|
||||
}
|
||||
|
||||
pub fn hunks_in_row_range<'a>(
|
||||
&'a self,
|
||||
range: Range<u32>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
reversed: bool,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let start = buffer.anchor_before(Point::new(range.start, 0));
|
||||
let end = buffer.anchor_after(Point::new(range.end, 0));
|
||||
self.hunks_intersecting_range(start..end, buffer, reversed)
|
||||
|
||||
self.hunks_intersecting_range(start..end, buffer)
|
||||
}
|
||||
|
||||
pub fn hunks_intersecting_range<'a>(
|
||||
&'a self,
|
||||
range: Range<Anchor>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
reversed: bool,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
|
||||
!before_start && !after_end
|
||||
});
|
||||
|
||||
let anchor_iter = std::iter::from_fn(move || {
|
||||
cursor.next(buffer);
|
||||
cursor.item()
|
||||
})
|
||||
.flat_map(move |hunk| {
|
||||
[
|
||||
(&hunk.buffer_range.start, hunk.diff_base_byte_range.start),
|
||||
(&hunk.buffer_range.end, hunk.diff_base_byte_range.end),
|
||||
]
|
||||
.into_iter()
|
||||
});
|
||||
|
||||
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
|
||||
iter::from_fn(move || {
|
||||
let (start_point, start_base) = summaries.next()?;
|
||||
let (end_point, end_base) = summaries.next()?;
|
||||
|
||||
let end_row = if end_point.column > 0 {
|
||||
end_point.row + 1
|
||||
} else {
|
||||
end_point.row
|
||||
};
|
||||
|
||||
Some(DiffHunk {
|
||||
buffer_range: start_point.row..end_row,
|
||||
diff_base_byte_range: start_base..end_base,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hunks_intersecting_range_rev<'a>(
|
||||
&'a self,
|
||||
range: Range<Anchor>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
|
||||
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
|
||||
@@ -95,14 +139,9 @@ impl BufferDiff {
|
||||
});
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
if reversed {
|
||||
cursor.prev(buffer);
|
||||
} else {
|
||||
cursor.next(buffer);
|
||||
}
|
||||
cursor.prev(buffer);
|
||||
|
||||
let hunk = cursor.item()?;
|
||||
|
||||
let range = hunk.buffer_range.to_point(buffer);
|
||||
let end_row = if range.end.column > 0 {
|
||||
range.end.row + 1
|
||||
@@ -122,13 +161,6 @@ impl BufferDiff {
|
||||
self.tree = SumTree::new();
|
||||
}
|
||||
|
||||
pub fn needs_update(&self, buffer: &text::BufferSnapshot) -> bool {
|
||||
match &self.last_buffer_version {
|
||||
Some(last) => buffer.version().changed_since(last),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update(&mut self, diff_base: &str, buffer: &text::BufferSnapshot) {
|
||||
let mut tree = SumTree::new();
|
||||
|
||||
@@ -151,7 +183,7 @@ impl BufferDiff {
|
||||
fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
|
||||
let start = text.anchor_before(Point::new(0, 0));
|
||||
let end = text.anchor_after(Point::new(u32::MAX, u32::MAX));
|
||||
self.hunks_intersecting_range(start..end, text, false)
|
||||
self.hunks_intersecting_range(start..end, text)
|
||||
}
|
||||
|
||||
fn diff<'a>(head: &'a str, current: &'a str) -> Option<GitPatch<'a>> {
|
||||
@@ -279,6 +311,8 @@ pub fn assert_hunks<Iter>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use text::Buffer;
|
||||
use unindent::Unindent as _;
|
||||
@@ -365,7 +399,7 @@ mod tests {
|
||||
assert_eq!(diff.hunks(&buffer).count(), 8);
|
||||
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(7..12, &buffer, false),
|
||||
diff.hunks_in_row_range(7..12, &buffer),
|
||||
&buffer,
|
||||
&diff_base,
|
||||
&[
|
||||
|
||||
@@ -16,4 +16,8 @@ settings = { path = "../settings" }
|
||||
text = { path = "../text" }
|
||||
workspace = { path = "../workspace" }
|
||||
postage.workspace = true
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -6,7 +6,6 @@ use gpui::{
|
||||
View, ViewContext, ViewHandle,
|
||||
};
|
||||
use menu::{Cancel, Confirm};
|
||||
use settings::Settings;
|
||||
use text::{Bias, Point};
|
||||
use util::paths::FILE_ROW_COLUMN_DELIMITER;
|
||||
use workspace::{Modal, Workspace};
|
||||
@@ -151,7 +150,7 @@ impl View for GoToLine {
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = &cx.global::<Settings>().theme.picker;
|
||||
let theme = &theme::current(cx).picker;
|
||||
|
||||
let label = format!(
|
||||
"{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines",
|
||||
|
||||
@@ -72,7 +72,7 @@ cocoa = "0.24"
|
||||
core-foundation = { version = "0.9.3", features = ["with-uuid"] }
|
||||
core-graphics = "0.22.3"
|
||||
core-text = "19.2"
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" }
|
||||
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
|
||||
foreign-types = "0.3"
|
||||
log.workspace = true
|
||||
metal = "0.21.0"
|
||||
|
||||
@@ -1174,7 +1174,7 @@ impl AppContext {
|
||||
this.notify_global(type_id);
|
||||
result
|
||||
} else {
|
||||
panic!("No global added for {}", std::any::type_name::<T>());
|
||||
panic!("no global added for {}", std::any::type_name::<T>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1182,6 +1182,15 @@ impl AppContext {
|
||||
self.globals.clear();
|
||||
}
|
||||
|
||||
pub fn remove_global<T: 'static>(&mut self) -> T {
|
||||
*self
|
||||
.globals
|
||||
.remove(&TypeId::of::<T>())
|
||||
.unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<T>()))
|
||||
.downcast()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
|
||||
where
|
||||
T: Entity,
|
||||
@@ -1451,27 +1460,13 @@ impl AppContext {
|
||||
self.views_metadata.remove(&(window_id, view_id));
|
||||
let mut view = self.views.remove(&(window_id, view_id)).unwrap();
|
||||
view.release(self);
|
||||
let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| {
|
||||
if let Some(window) = self.windows.get_mut(&window_id) {
|
||||
window.parents.remove(&view_id);
|
||||
window
|
||||
.invalidation
|
||||
.get_or_insert_with(Default::default)
|
||||
.removed
|
||||
.push(view_id);
|
||||
if window.focused_view_id == Some(view_id) {
|
||||
Some(window.root_view().id())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(view_id) = change_focus_to {
|
||||
self.pending_effects
|
||||
.push_back(Effect::Focus(FocusEffect::View {
|
||||
window_id,
|
||||
view_id: Some(view_id),
|
||||
is_forced: false,
|
||||
}));
|
||||
}
|
||||
|
||||
self.pending_effects
|
||||
@@ -1708,8 +1703,69 @@ impl AppContext {
|
||||
if let Some(invalidation) = invalidation {
|
||||
let appearance = cx.window.platform_window.appearance();
|
||||
cx.invalidate(invalidation, appearance);
|
||||
if cx.layout(refreshing).log_err().is_some() {
|
||||
if let Some(old_parents) = cx.layout(refreshing).log_err() {
|
||||
updated_windows.insert(window_id);
|
||||
|
||||
if let Some(focused_view_id) = cx.focused_view_id() {
|
||||
let old_ancestors = std::iter::successors(
|
||||
Some(focused_view_id),
|
||||
|&view_id| old_parents.get(&view_id).copied(),
|
||||
)
|
||||
.collect::<HashSet<_>>();
|
||||
let new_ancestors =
|
||||
cx.ancestors(focused_view_id).collect::<HashSet<_>>();
|
||||
|
||||
// Notify the old ancestors of the focused view when they don't contain it anymore.
|
||||
for old_ancestor in old_ancestors.iter().copied() {
|
||||
if !new_ancestors.contains(&old_ancestor) {
|
||||
if let Some(mut view) =
|
||||
cx.views.remove(&(window_id, old_ancestor))
|
||||
{
|
||||
view.focus_out(
|
||||
focused_view_id,
|
||||
cx,
|
||||
old_ancestor,
|
||||
);
|
||||
cx.views
|
||||
.insert((window_id, old_ancestor), view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the new ancestors of the focused view if they contain it now.
|
||||
for new_ancestor in new_ancestors.iter().copied() {
|
||||
if !old_ancestors.contains(&new_ancestor) {
|
||||
if let Some(mut view) =
|
||||
cx.views.remove(&(window_id, new_ancestor))
|
||||
{
|
||||
view.focus_in(
|
||||
focused_view_id,
|
||||
cx,
|
||||
new_ancestor,
|
||||
);
|
||||
cx.views
|
||||
.insert((window_id, new_ancestor), view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When the previously-focused view has been dropped and
|
||||
// there isn't any pending focus, focus the root view.
|
||||
let root_view_id = cx.window.root_view().id();
|
||||
if focused_view_id != root_view_id
|
||||
&& !cx.views.contains_key(&(window_id, focused_view_id))
|
||||
&& !focus_effects.contains_key(&window_id)
|
||||
{
|
||||
focus_effects.insert(
|
||||
window_id,
|
||||
FocusEffect::View {
|
||||
window_id,
|
||||
view_id: Some(root_view_id),
|
||||
is_forced: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1886,9 +1942,27 @@ impl AppContext {
|
||||
fn handle_focus_effect(&mut self, effect: FocusEffect) {
|
||||
let window_id = effect.window_id();
|
||||
self.update_window(window_id, |cx| {
|
||||
// Ensure the newly-focused view still exists, otherwise focus
|
||||
// the root view instead.
|
||||
let focused_id = match effect {
|
||||
FocusEffect::View { view_id, .. } => view_id,
|
||||
FocusEffect::ViewParent { view_id, .. } => cx.ancestors(view_id).skip(1).next(),
|
||||
FocusEffect::View { view_id, .. } => {
|
||||
if let Some(view_id) = view_id {
|
||||
if cx.views.contains_key(&(window_id, view_id)) {
|
||||
Some(view_id)
|
||||
} else {
|
||||
Some(cx.root_view().id())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
FocusEffect::ViewParent { view_id, .. } => Some(
|
||||
cx.window
|
||||
.parents
|
||||
.get(&view_id)
|
||||
.copied()
|
||||
.unwrap_or(cx.root_view().id()),
|
||||
),
|
||||
};
|
||||
|
||||
let focus_changed = cx.window.focused_view_id != focused_id;
|
||||
@@ -3316,15 +3390,14 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
||||
self.keystroke_matcher
|
||||
.bindings_for_action_type(action.as_any().type_id())
|
||||
.find_map(|b| {
|
||||
handler_depth
|
||||
.map(|highest_handler| {
|
||||
if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
|
||||
Some(b.keystrokes().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
let highest_handler = handler_depth?;
|
||||
if action.eq(b.action())
|
||||
&& (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..]))
|
||||
{
|
||||
Some(b.keystrokes().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3793,6 +3866,12 @@ impl<T> PartialEq for ViewHandle<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq<AnyViewHandle> for ViewHandle<T> {
|
||||
fn eq(&self, other: &AnyViewHandle) -> bool {
|
||||
self.window_id == other.window_id && self.view_id == other.view_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq<WeakViewHandle<T>> for ViewHandle<T> {
|
||||
fn eq(&self, other: &WeakViewHandle<T>) -> bool {
|
||||
self.window_id == other.window_id && self.view_id == other.view_id
|
||||
@@ -3943,6 +4022,12 @@ impl Clone for AnyViewHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyViewHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.window_id == other.window_id && self.view_id == other.view_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq<ViewHandle<T>> for AnyViewHandle {
|
||||
fn eq(&self, other: &ViewHandle<T>) -> bool {
|
||||
self.window_id == other.window_id && self.view_id == other.view_id
|
||||
@@ -4189,7 +4274,7 @@ impl<T> Hash for WeakViewHandle<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct AnyWeakViewHandle {
|
||||
window_id: usize,
|
||||
view_id: usize,
|
||||
@@ -6004,6 +6089,53 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
fn test_keystrokes_for_action_with_data(cx: &mut TestAppContext) {
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
struct ActionWithArg {
|
||||
#[serde(default)]
|
||||
arg: bool,
|
||||
}
|
||||
|
||||
struct View;
|
||||
impl super::Entity for View {
|
||||
type Event = ();
|
||||
}
|
||||
impl super::View for View {
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
Empty::new().into_any()
|
||||
}
|
||||
fn ui_name() -> &'static str {
|
||||
"View"
|
||||
}
|
||||
}
|
||||
|
||||
impl_actions!(test, [ActionWithArg]);
|
||||
|
||||
let (window_id, view) = cx.add_window(|_| View);
|
||||
cx.update(|cx| {
|
||||
cx.add_global_action(|_: &ActionWithArg, _| {});
|
||||
cx.add_bindings(vec![
|
||||
Binding::new("a", ActionWithArg { arg: false }, None),
|
||||
Binding::new("shift-a", ActionWithArg { arg: true }, None),
|
||||
]);
|
||||
});
|
||||
|
||||
let actions = cx.available_actions(window_id, view.id());
|
||||
assert_eq!(
|
||||
actions[0].1.as_any().downcast_ref::<ActionWithArg>(),
|
||||
Some(&ActionWithArg { arg: false })
|
||||
);
|
||||
assert_eq!(
|
||||
actions[0]
|
||||
.2
|
||||
.iter()
|
||||
.map(|b| b.keystrokes()[0].clone())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Keystroke::parse("a").unwrap()],
|
||||
);
|
||||
}
|
||||
|
||||
#[crate::test(self)]
|
||||
async fn test_model_condition(cx: &mut TestAppContext) {
|
||||
struct Counter(usize);
|
||||
|
||||
@@ -270,7 +270,7 @@ impl TestAppContext {
|
||||
.borrow_mut()
|
||||
.pop_front()
|
||||
.expect("prompt was not called");
|
||||
let _ = done_tx.try_send(answer);
|
||||
done_tx.try_send(answer).ok();
|
||||
}
|
||||
|
||||
pub fn has_pending_prompt(&self, window_id: usize) -> bool {
|
||||
@@ -434,7 +434,9 @@ impl<T: Entity> ModelHandle<T> {
|
||||
Duration::from_secs(1)
|
||||
};
|
||||
|
||||
let executor = cx.background().clone();
|
||||
async move {
|
||||
executor.start_waiting();
|
||||
let notification = crate::util::timeout(duration, rx.next())
|
||||
.await
|
||||
.expect("next notification timed out");
|
||||
|
||||
@@ -29,6 +29,7 @@ use sqlez::{
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
mem,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
};
|
||||
use util::ResultExt;
|
||||
@@ -393,17 +394,18 @@ impl<'a> WindowContext<'a> {
|
||||
.iter()
|
||||
.filter_map(move |(name, (type_id, deserialize))| {
|
||||
if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
|
||||
Some((
|
||||
*name,
|
||||
deserialize("{}").ok()?,
|
||||
self.keystroke_matcher
|
||||
.bindings_for_action_type(*type_id)
|
||||
.filter(|b| {
|
||||
(0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
))
|
||||
let action = deserialize("{}").ok()?;
|
||||
let bindings = self
|
||||
.keystroke_matcher
|
||||
.bindings_for_action_type(*type_id)
|
||||
.filter(|b| {
|
||||
action.eq(b.action())
|
||||
&& (0..=action_depth)
|
||||
.any(|depth| b.match_context(&contexts[depth..]))
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
Some((*name, action, bindings))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -890,7 +892,7 @@ impl<'a> WindowContext<'a> {
|
||||
Ok(element)
|
||||
}
|
||||
|
||||
pub(crate) fn layout(&mut self, refreshing: bool) -> Result<()> {
|
||||
pub(crate) fn layout(&mut self, refreshing: bool) -> Result<HashMap<usize, usize>> {
|
||||
let window_size = self.window.platform_window.content_size();
|
||||
let root_view_id = self.window.root_view().id();
|
||||
let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
|
||||
@@ -923,11 +925,11 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
self.window.parents = new_parents;
|
||||
let old_parents = mem::replace(&mut self.window.parents, new_parents);
|
||||
self.window
|
||||
.rendered_views
|
||||
.insert(root_view_id, rendered_root);
|
||||
Ok(())
|
||||
Ok(old_parents)
|
||||
}
|
||||
|
||||
pub(crate) fn paint(&mut self) -> Result<Scene> {
|
||||
|
||||
@@ -187,25 +187,23 @@ pub trait Element<V: View>: 'static {
|
||||
Tooltip::new::<Tag, V>(id, text, action, style, self.into_any(), cx)
|
||||
}
|
||||
|
||||
fn with_resize_handle<Tag: 'static>(
|
||||
fn resizable(
|
||||
self,
|
||||
element_id: usize,
|
||||
side: Side,
|
||||
handle_size: f32,
|
||||
initial_size: f32,
|
||||
cx: &mut ViewContext<V>,
|
||||
side: HandleSide,
|
||||
size: f32,
|
||||
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
|
||||
) -> Resizable<V>
|
||||
where
|
||||
Self: 'static + Sized,
|
||||
{
|
||||
Resizable::new::<Tag, V>(
|
||||
self.into_any(),
|
||||
element_id,
|
||||
side,
|
||||
handle_size,
|
||||
initial_size,
|
||||
cx,
|
||||
)
|
||||
Resizable::new(self.into_any(), side, size, on_resize)
|
||||
}
|
||||
|
||||
fn mouse<Tag>(self, region_id: usize) -> MouseEventHandler<Tag, V>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
MouseEventHandler::for_child(self.into_any(), region_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -990,7 +990,7 @@ mod tests {
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
) {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
@@ -1003,7 +1003,7 @@ mod tests {
|
||||
_: &V,
|
||||
_: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {
|
||||
|
||||
@@ -32,10 +32,25 @@ pub struct MouseEventHandler<Tag: 'static, V: View> {
|
||||
/// Element which provides a render_child callback with a MouseState and paints a mouse
|
||||
/// region under (or above) it for easy mouse event handling.
|
||||
impl<Tag, V: View> MouseEventHandler<Tag, V> {
|
||||
pub fn new<D, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
|
||||
pub fn for_child(child: impl Element<V>, region_id: usize) -> Self {
|
||||
Self {
|
||||
child: child.into_any(),
|
||||
region_id,
|
||||
cursor_style: None,
|
||||
handlers: Default::default(),
|
||||
notify_on_hover: false,
|
||||
notify_on_click: false,
|
||||
hoverable: false,
|
||||
above: false,
|
||||
padding: Default::default(),
|
||||
_tag: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new<E, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
|
||||
where
|
||||
D: Element<V>,
|
||||
F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
|
||||
E: Element<V>,
|
||||
F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
|
||||
{
|
||||
let mut mouse_state = cx.mouse_state::<Tag>(region_id);
|
||||
let child = render_child(&mut mouse_state, cx).into_any();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
use serde_json::json;
|
||||
@@ -7,25 +7,23 @@ use crate::{
|
||||
geometry::rect::RectF,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
scene::MouseDrag,
|
||||
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View,
|
||||
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
|
||||
use super::{ConstrainedBox, Hook};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Side {
|
||||
pub enum HandleSide {
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Side {
|
||||
impl HandleSide {
|
||||
fn axis(&self) -> Axis {
|
||||
match self {
|
||||
Side::Left | Side::Right => Axis::Horizontal,
|
||||
Side::Top | Side::Bottom => Axis::Vertical,
|
||||
HandleSide::Left | HandleSide::Right => Axis::Horizontal,
|
||||
HandleSide::Top | HandleSide::Bottom => Axis::Vertical,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +31,8 @@ impl Side {
|
||||
/// then top-to-bottom
|
||||
fn before_content(self) -> bool {
|
||||
match self {
|
||||
Side::Left | Side::Top => true,
|
||||
Side::Right | Side::Bottom => false,
|
||||
HandleSide::Left | HandleSide::Top => true,
|
||||
HandleSide::Right | HandleSide::Bottom => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +53,14 @@ impl Side {
|
||||
|
||||
fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
|
||||
match self {
|
||||
Side::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
|
||||
Side::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
|
||||
Side::Bottom => {
|
||||
HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
|
||||
HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
|
||||
HandleSide::Bottom => {
|
||||
let mut origin = bounds.lower_left();
|
||||
origin.set_y(origin.y() - handle_size);
|
||||
RectF::new(origin, vec2f(bounds.width(), handle_size))
|
||||
}
|
||||
Side::Right => {
|
||||
HandleSide::Right => {
|
||||
let mut origin = bounds.upper_right();
|
||||
origin.set_x(origin.x() - handle_size);
|
||||
RectF::new(origin, vec2f(handle_size, bounds.height()))
|
||||
@@ -71,69 +69,44 @@ impl Side {
|
||||
}
|
||||
}
|
||||
|
||||
struct ResizeHandleState {
|
||||
actual_dimension: Cell<f32>,
|
||||
custom_dimension: Cell<f32>,
|
||||
pub struct Resizable<V: View> {
|
||||
child: AnyElement<V>,
|
||||
handle_side: HandleSide,
|
||||
handle_size: f32,
|
||||
on_resize: Rc<RefCell<dyn FnMut(&mut V, f32, &mut ViewContext<V>)>>,
|
||||
}
|
||||
|
||||
pub struct Resizable<V: View> {
|
||||
side: Side,
|
||||
handle_size: f32,
|
||||
child: AnyElement<V>,
|
||||
state: Rc<ResizeHandleState>,
|
||||
_state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
|
||||
}
|
||||
const DEFAULT_HANDLE_SIZE: f32 = 4.0;
|
||||
|
||||
impl<V: View> Resizable<V> {
|
||||
pub fn new<Tag: 'static, T: View>(
|
||||
pub fn new(
|
||||
child: AnyElement<V>,
|
||||
element_id: usize,
|
||||
side: Side,
|
||||
handle_size: f32,
|
||||
initial_size: f32,
|
||||
cx: &mut ViewContext<V>,
|
||||
handle_side: HandleSide,
|
||||
size: f32,
|
||||
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
|
||||
) -> Self {
|
||||
let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>(
|
||||
element_id,
|
||||
Rc::new(ResizeHandleState {
|
||||
actual_dimension: Cell::new(initial_size),
|
||||
custom_dimension: Cell::new(initial_size),
|
||||
}),
|
||||
);
|
||||
|
||||
let state = state_handle.read(cx).clone();
|
||||
|
||||
let child = Hook::new({
|
||||
let constrained = ConstrainedBox::new(child);
|
||||
match side.axis() {
|
||||
Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()),
|
||||
Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()),
|
||||
}
|
||||
})
|
||||
.on_after_layout({
|
||||
let state = state.clone();
|
||||
move |size, _| {
|
||||
state.actual_dimension.set(side.relevant_component(size));
|
||||
}
|
||||
})
|
||||
let child = match handle_side.axis() {
|
||||
Axis::Horizontal => child.constrained().with_max_width(size),
|
||||
Axis::Vertical => child.constrained().with_max_height(size),
|
||||
}
|
||||
.into_any();
|
||||
|
||||
Self {
|
||||
side,
|
||||
child,
|
||||
handle_size,
|
||||
state,
|
||||
_state_handle: state_handle,
|
||||
handle_side,
|
||||
handle_size: DEFAULT_HANDLE_SIZE,
|
||||
on_resize: Rc::new(RefCell::new(on_resize)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_size(&self) -> f32 {
|
||||
self.state.actual_dimension.get()
|
||||
pub fn with_handle_size(mut self, handle_size: f32) -> Self {
|
||||
self.handle_size = handle_size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> Element<V> for Resizable<V> {
|
||||
type LayoutState = ();
|
||||
type LayoutState = SizeConstraint;
|
||||
type PaintState = ();
|
||||
|
||||
fn layout(
|
||||
@@ -142,7 +115,7 @@ impl<V: View> Element<V> for Resizable<V> {
|
||||
view: &mut V,
|
||||
cx: &mut LayoutContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
(self.child.layout(constraint, view, cx), ())
|
||||
(self.child.layout(constraint, view, cx), constraint)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
@@ -150,34 +123,44 @@ impl<V: View> Element<V> for Resizable<V> {
|
||||
scene: &mut SceneBuilder,
|
||||
bounds: pathfinder_geometry::rect::RectF,
|
||||
visible_bounds: pathfinder_geometry::rect::RectF,
|
||||
_child_size: &mut Self::LayoutState,
|
||||
constraint: &mut SizeConstraint,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
scene.push_stacking_context(None, None);
|
||||
|
||||
let handle_region = self.side.of_rect(bounds, self.handle_size);
|
||||
let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
|
||||
|
||||
enum ResizeHandle {}
|
||||
scene.push_mouse_region(
|
||||
MouseRegion::new::<ResizeHandle>(cx.view_id(), self.side as usize, handle_region)
|
||||
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
||||
.on_drag(MouseButton::Left, {
|
||||
let state = self.state.clone();
|
||||
let side = self.side;
|
||||
move |e, _: &mut V, cx| {
|
||||
let prev_width = state.actual_dimension.get();
|
||||
state
|
||||
.custom_dimension
|
||||
.set(0f32.max(prev_width + side.compute_delta(e)).round());
|
||||
cx.notify();
|
||||
MouseRegion::new::<ResizeHandle>(
|
||||
cx.view_id(),
|
||||
self.handle_side as usize,
|
||||
handle_region,
|
||||
)
|
||||
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
||||
.on_drag(MouseButton::Left, {
|
||||
let bounds = bounds.clone();
|
||||
let side = self.handle_side;
|
||||
let prev_size = side.relevant_component(bounds.size());
|
||||
let min_size = side.relevant_component(constraint.min);
|
||||
let max_size = side.relevant_component(constraint.max);
|
||||
let on_resize = self.on_resize.clone();
|
||||
move |event, view: &mut V, cx| {
|
||||
let new_size = min_size
|
||||
.max(prev_size + side.compute_delta(event))
|
||||
.min(max_size)
|
||||
.round();
|
||||
if new_size != prev_size {
|
||||
on_resize.borrow_mut()(view, new_size, cx);
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
scene.push_cursor_region(crate::CursorRegion {
|
||||
bounds: handle_region,
|
||||
style: match self.side.axis() {
|
||||
style: match self.handle_side.axis() {
|
||||
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
||||
Axis::Vertical => CursorStyle::ResizeUpDown,
|
||||
},
|
||||
|
||||
@@ -477,6 +477,14 @@ impl Deterministic {
|
||||
state.rng = StdRng::seed_from_u64(state.seed);
|
||||
}
|
||||
|
||||
pub fn allow_parking(&self) {
|
||||
use rand::prelude::*;
|
||||
|
||||
let mut state = self.state.lock();
|
||||
state.forbid_parking = false;
|
||||
state.rng = StdRng::seed_from_u64(state.seed);
|
||||
}
|
||||
|
||||
pub async fn simulate_random_delay(&self) {
|
||||
use rand::prelude::*;
|
||||
use smol::future::yield_now;
|
||||
@@ -698,6 +706,14 @@ impl Foreground {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn allow_parking(&self) {
|
||||
match self {
|
||||
Self::Deterministic { executor, .. } => executor.allow_parking(),
|
||||
_ => panic!("this method can only be called on a deterministic executor"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn advance_clock(&self, duration: Duration) {
|
||||
match self {
|
||||
@@ -860,6 +876,14 @@ impl Background {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn start_waiting(&self) {
|
||||
match self {
|
||||
Self::Deterministic { executor, .. } => executor.start_waiting(),
|
||||
_ => panic!("this method can only be called on a deterministic executor"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Background {
|
||||
@@ -944,7 +968,7 @@ impl<T: 'static, E: 'static + Display> Task<Result<T, E>> {
|
||||
pub fn detach_and_log_err(self, cx: &mut AppContext) {
|
||||
cx.spawn(|_| async move {
|
||||
if let Err(err) = self.await {
|
||||
log::error!("{}", err);
|
||||
log::error!("{:#}", err);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -25,8 +25,9 @@ struct Family {
|
||||
pub struct FontCache(RwLock<FontCacheState>);
|
||||
|
||||
pub struct FontCacheState {
|
||||
fonts: Arc<dyn platform::FontSystem>,
|
||||
font_system: Arc<dyn platform::FontSystem>,
|
||||
families: Vec<Family>,
|
||||
default_family: Option<FamilyId>,
|
||||
font_selections: HashMap<FamilyId, HashMap<Properties, FontId>>,
|
||||
metrics: HashMap<FontId, Metrics>,
|
||||
wrapper_pool: HashMap<(FontId, OrderedFloat<f32>), Vec<LineWrapper>>,
|
||||
@@ -42,8 +43,9 @@ unsafe impl Send for FontCache {}
|
||||
impl FontCache {
|
||||
pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
|
||||
Self(RwLock::new(FontCacheState {
|
||||
fonts,
|
||||
font_system: fonts,
|
||||
families: Default::default(),
|
||||
default_family: None,
|
||||
font_selections: Default::default(),
|
||||
metrics: Default::default(),
|
||||
wrapper_pool: Default::default(),
|
||||
@@ -73,14 +75,14 @@ impl FontCache {
|
||||
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
|
||||
if let Ok(font_ids) = state.fonts.load_family(name, features) {
|
||||
if let Ok(font_ids) = state.font_system.load_family(name, features) {
|
||||
if font_ids.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let family_id = FamilyId(state.families.len());
|
||||
for font_id in &font_ids {
|
||||
if state.fonts.glyph_for_char(*font_id, 'm').is_none() {
|
||||
if state.font_system.glyph_for_char(*font_id, 'm').is_none() {
|
||||
return Err(anyhow!("font must contain a glyph for the 'm' character"));
|
||||
}
|
||||
}
|
||||
@@ -99,6 +101,31 @@ impl FontCache {
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns an arbitrary font family that is available on the system.
|
||||
pub fn known_existing_family(&self) -> FamilyId {
|
||||
if let Some(family_id) = self.0.read().default_family {
|
||||
return family_id;
|
||||
}
|
||||
|
||||
let default_family = self
|
||||
.load_family(
|
||||
&["Courier", "Helvetica", "Arial", "Verdana"],
|
||||
&Default::default(),
|
||||
)
|
||||
.unwrap_or_else(|_| {
|
||||
let all_family_names = self.0.read().font_system.all_families();
|
||||
let all_family_names: Vec<_> = all_family_names
|
||||
.iter()
|
||||
.map(|string| string.as_str())
|
||||
.collect();
|
||||
self.load_family(&all_family_names, &Default::default())
|
||||
.expect("could not load any default font family")
|
||||
});
|
||||
|
||||
self.0.write().default_family = Some(default_family);
|
||||
default_family
|
||||
}
|
||||
|
||||
pub fn default_font(&self, family_id: FamilyId) -> FontId {
|
||||
self.select_font(family_id, &Properties::default()).unwrap()
|
||||
}
|
||||
@@ -115,7 +142,7 @@ impl FontCache {
|
||||
let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
|
||||
let family = &inner.families[family_id.0];
|
||||
let font_id = inner
|
||||
.fonts
|
||||
.font_system
|
||||
.select_font(&family.font_ids, properties)
|
||||
.unwrap_or(family.font_ids[0]);
|
||||
|
||||
@@ -137,7 +164,7 @@ impl FontCache {
|
||||
if let Some(metrics) = state.metrics.get(&font_id) {
|
||||
f(metrics)
|
||||
} else {
|
||||
let metrics = state.fonts.font_metrics(font_id);
|
||||
let metrics = state.font_system.font_metrics(font_id);
|
||||
let metric = f(&metrics);
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
state.metrics.insert(font_id, metrics);
|
||||
@@ -157,8 +184,11 @@ impl FontCache {
|
||||
let bounds;
|
||||
{
|
||||
let state = self.0.read();
|
||||
glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap();
|
||||
bounds = state.fonts.typographic_bounds(font_id, glyph_id).unwrap();
|
||||
glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
|
||||
bounds = state
|
||||
.font_system
|
||||
.typographic_bounds(font_id, glyph_id)
|
||||
.unwrap();
|
||||
}
|
||||
bounds.width() * self.em_scale(font_id, font_size)
|
||||
}
|
||||
@@ -168,8 +198,8 @@ impl FontCache {
|
||||
let advance;
|
||||
{
|
||||
let state = self.0.read();
|
||||
glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap();
|
||||
advance = state.fonts.advance(font_id, glyph_id).unwrap();
|
||||
glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
|
||||
advance = state.font_system.advance(font_id, glyph_id).unwrap();
|
||||
}
|
||||
advance.x() * self.em_scale(font_id, font_size)
|
||||
}
|
||||
@@ -214,7 +244,7 @@ impl FontCache {
|
||||
.or_default();
|
||||
let wrapper = wrappers
|
||||
.pop()
|
||||
.unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.fonts.clone()));
|
||||
.unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone()));
|
||||
LineWrapperHandle {
|
||||
wrapper: Some(wrapper),
|
||||
font_cache: self.clone(),
|
||||
|
||||
@@ -295,13 +295,14 @@ impl Default for TextStyle {
|
||||
.as_ref()
|
||||
.expect("TextStyle::default can only be called within a call to with_font_cache");
|
||||
|
||||
let font_family_name = Arc::from("Courier");
|
||||
let font_family_id = font_cache
|
||||
.load_family(&[&font_family_name], &Default::default())
|
||||
.unwrap();
|
||||
let font_family_id = font_cache.known_existing_family();
|
||||
let font_id = font_cache
|
||||
.select_font(font_family_id, &Default::default())
|
||||
.unwrap();
|
||||
.expect("did not have any font in system-provided family");
|
||||
let font_family_name = font_cache
|
||||
.family_name(font_family_id)
|
||||
.expect("we loaded this family from the font cache, so this should work");
|
||||
|
||||
Self {
|
||||
color: Default::default(),
|
||||
font_family_name,
|
||||
|
||||
@@ -173,6 +173,7 @@ pub struct WindowOptions<'a> {
|
||||
pub titlebar: Option<TitlebarOptions<'a>>,
|
||||
pub center: bool,
|
||||
pub focus: bool,
|
||||
pub show: bool,
|
||||
pub kind: WindowKind,
|
||||
pub is_movable: bool,
|
||||
pub screen: Option<Rc<dyn Screen>>,
|
||||
@@ -222,21 +223,21 @@ impl Bind for WindowBounds {
|
||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
||||
let (region, next_index) = match self {
|
||||
WindowBounds::Fullscreen => {
|
||||
let next_index = statement.bind("Fullscreen", start_index)?;
|
||||
let next_index = statement.bind(&"Fullscreen", start_index)?;
|
||||
(None, next_index)
|
||||
}
|
||||
WindowBounds::Maximized => {
|
||||
let next_index = statement.bind("Maximized", start_index)?;
|
||||
let next_index = statement.bind(&"Maximized", start_index)?;
|
||||
(None, next_index)
|
||||
}
|
||||
WindowBounds::Fixed(region) => {
|
||||
let next_index = statement.bind("Fixed", start_index)?;
|
||||
let next_index = statement.bind(&"Fixed", start_index)?;
|
||||
(Some(*region), next_index)
|
||||
}
|
||||
};
|
||||
|
||||
statement.bind(
|
||||
region.map(|region| {
|
||||
®ion.map(|region| {
|
||||
(
|
||||
region.min_x(),
|
||||
region.min_y(),
|
||||
@@ -342,6 +343,7 @@ pub enum RasterizationOptions {
|
||||
|
||||
pub trait FontSystem: Send + Sync {
|
||||
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
|
||||
fn all_families(&self) -> Vec<String>;
|
||||
fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
|
||||
fn select_font(
|
||||
&self,
|
||||
@@ -376,6 +378,7 @@ impl<'a> Default for WindowOptions<'a> {
|
||||
}),
|
||||
center: false,
|
||||
focus: true,
|
||||
show: true,
|
||||
kind: WindowKind::Normal,
|
||||
is_movable: true,
|
||||
screen: None,
|
||||
|
||||
@@ -66,6 +66,14 @@ impl platform::FontSystem for FontSystem {
|
||||
self.0.write().add_fonts(fonts)
|
||||
}
|
||||
|
||||
fn all_families(&self) -> Vec<String> {
|
||||
self.0
|
||||
.read()
|
||||
.system_source
|
||||
.all_families()
|
||||
.expect("core text should never return an error")
|
||||
}
|
||||
|
||||
fn load_family(&self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
|
||||
self.0.write().load_family(name, features)
|
||||
}
|
||||
|
||||
@@ -614,7 +614,7 @@ impl Window {
|
||||
}
|
||||
if options.focus {
|
||||
native_window.makeKeyAndOrderFront_(nil);
|
||||
} else {
|
||||
} else if options.show {
|
||||
native_window.orderFront_(nil);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,15 @@ editor = { path = "../editor" }
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
settings = { path = "../settings" }
|
||||
|
||||
anyhow.workspace = true
|
||||
chrono = "0.4"
|
||||
dirs = "4.0"
|
||||
serde.workspace = true
|
||||
schemars.workspace = true
|
||||
log.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
shellexpand = "2.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use chrono::{Datelike, Local, NaiveTime, Timelike};
|
||||
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
||||
use gpui::{actions, AppContext};
|
||||
use settings::{HourFormat, Settings};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
path::{Path, PathBuf},
|
||||
@@ -11,13 +13,48 @@ use workspace::AppState;
|
||||
|
||||
actions!(journal, [NewJournalEntry]);
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct JournalSettings {
|
||||
pub path: Option<String>,
|
||||
pub hour_format: Option<HourFormat>,
|
||||
}
|
||||
|
||||
impl Default for JournalSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
path: Some("~".into()),
|
||||
hour_format: Some(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum HourFormat {
|
||||
#[default]
|
||||
Hour12,
|
||||
Hour24,
|
||||
}
|
||||
|
||||
impl settings::Setting for JournalSettings {
|
||||
const KEY: Option<&'static str> = Some("journal");
|
||||
|
||||
type FileContent = Self;
|
||||
|
||||
fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
settings::register::<JournalSettings>(cx);
|
||||
|
||||
cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx));
|
||||
}
|
||||
|
||||
pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
let settings = cx.global::<Settings>();
|
||||
let journal_dir = match journal_dir(&settings) {
|
||||
let settings = settings::get::<JournalSettings>(cx);
|
||||
let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) {
|
||||
Some(journal_dir) => journal_dir,
|
||||
None => {
|
||||
log::error!("Can't determine journal directory");
|
||||
@@ -31,8 +68,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
.join(format!("{:02}", now.month()));
|
||||
let entry_path = month_dir.join(format!("{:02}.md", now.day()));
|
||||
let now = now.time();
|
||||
let hour_format = &settings.journal_overrides.hour_format;
|
||||
let entry_heading = heading_entry(now, &hour_format);
|
||||
let entry_heading = heading_entry(now, &settings.hour_format);
|
||||
|
||||
let create_entry = cx.background().spawn(async move {
|
||||
std::fs::create_dir_all(month_dir)?;
|
||||
@@ -76,14 +112,8 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn journal_dir(settings: &Settings) -> Option<PathBuf> {
|
||||
let journal_dir = settings
|
||||
.journal_overrides
|
||||
.path
|
||||
.as_ref()
|
||||
.unwrap_or(settings.journal_defaults.path.as_ref()?);
|
||||
|
||||
let expanded_journal_dir = shellexpand::full(&journal_dir) //TODO handle this better
|
||||
fn journal_dir(path: &str) -> Option<PathBuf> {
|
||||
let expanded_journal_dir = shellexpand::full(path) //TODO handle this better
|
||||
.ok()
|
||||
.map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal"));
|
||||
|
||||
|
||||
@@ -36,16 +36,19 @@ sum_tree = { path = "../sum_tree" }
|
||||
text = { path = "../text" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
|
||||
anyhow.workspace = true
|
||||
async-broadcast = "0.4"
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
globset.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -67,7 +70,7 @@ settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
indoc = "1.0.4"
|
||||
indoc.workspace = true
|
||||
rand.workspace = true
|
||||
tree-sitter-embedded-template = "*"
|
||||
tree-sitter-html = "*"
|
||||
|
||||
@@ -5,6 +5,7 @@ pub use crate::{
|
||||
};
|
||||
use crate::{
|
||||
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
|
||||
language_settings::{language_settings, LanguageSettings},
|
||||
outline::OutlineItem,
|
||||
syntax_map::{
|
||||
SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint,
|
||||
@@ -18,7 +19,6 @@ use futures::FutureExt as _;
|
||||
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task};
|
||||
use lsp::LanguageServerId;
|
||||
use parking_lot::Mutex;
|
||||
use settings::Settings;
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use smallvec::SmallVec;
|
||||
use smol::future::yield_now;
|
||||
@@ -50,16 +50,10 @@ pub use {tree_sitter_rust, tree_sitter_typescript};
|
||||
|
||||
pub use lsp::DiagnosticSeverity;
|
||||
|
||||
struct GitDiffStatus {
|
||||
diff: git::diff::BufferDiff,
|
||||
update_in_progress: bool,
|
||||
update_requested: bool,
|
||||
}
|
||||
|
||||
pub struct Buffer {
|
||||
text: TextBuffer,
|
||||
diff_base: Option<String>,
|
||||
git_diff_status: GitDiffStatus,
|
||||
git_diff: git::diff::BufferDiff,
|
||||
file: Option<Arc<dyn File>>,
|
||||
saved_version: clock::Global,
|
||||
saved_version_fingerprint: RopeFingerprint,
|
||||
@@ -195,6 +189,7 @@ pub enum Event {
|
||||
Saved,
|
||||
FileHandleChanged,
|
||||
Reloaded,
|
||||
DiffBaseChanged,
|
||||
LanguageChanged,
|
||||
Reparsed,
|
||||
DiagnosticsUpdated,
|
||||
@@ -466,11 +461,7 @@ impl Buffer {
|
||||
was_dirty_before_starting_transaction: None,
|
||||
text: buffer,
|
||||
diff_base,
|
||||
git_diff_status: GitDiffStatus {
|
||||
diff: git::diff::BufferDiff::new(),
|
||||
update_in_progress: false,
|
||||
update_requested: false,
|
||||
},
|
||||
git_diff: git::diff::BufferDiff::new(),
|
||||
file,
|
||||
syntax_map: Mutex::new(SyntaxMap::new()),
|
||||
parsing_in_background: false,
|
||||
@@ -501,7 +492,7 @@ impl Buffer {
|
||||
BufferSnapshot {
|
||||
text,
|
||||
syntax,
|
||||
git_diff: self.git_diff_status.diff.clone(),
|
||||
git_diff: self.git_diff.clone(),
|
||||
file: self.file.clone(),
|
||||
remote_selections: self.remote_selections.clone(),
|
||||
diagnostics: self.diagnostics.clone(),
|
||||
@@ -620,7 +611,6 @@ impl Buffer {
|
||||
cx,
|
||||
);
|
||||
}
|
||||
self.git_diff_recalc(cx);
|
||||
cx.emit(Event::Reloaded);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -676,50 +666,29 @@ impl Buffer {
|
||||
pub fn set_diff_base(&mut self, diff_base: Option<String>, cx: &mut ModelContext<Self>) {
|
||||
self.diff_base = diff_base;
|
||||
self.git_diff_recalc(cx);
|
||||
cx.emit(Event::DiffBaseChanged);
|
||||
}
|
||||
|
||||
pub fn needs_git_diff_recalc(&self) -> bool {
|
||||
self.git_diff_status.diff.needs_update(self)
|
||||
}
|
||||
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
|
||||
let diff_base = self.diff_base.clone()?; // TODO: Make this an Arc
|
||||
let snapshot = self.snapshot();
|
||||
|
||||
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if self.git_diff_status.update_in_progress {
|
||||
self.git_diff_status.update_requested = true;
|
||||
return;
|
||||
}
|
||||
let mut diff = self.git_diff.clone();
|
||||
let diff = cx.background().spawn(async move {
|
||||
diff.update(&diff_base, &snapshot).await;
|
||||
diff
|
||||
});
|
||||
|
||||
if let Some(diff_base) = &self.diff_base {
|
||||
let snapshot = self.snapshot();
|
||||
let diff_base = diff_base.clone();
|
||||
|
||||
let mut diff = self.git_diff_status.diff.clone();
|
||||
let diff = cx.background().spawn(async move {
|
||||
diff.update(&diff_base, &snapshot).await;
|
||||
diff
|
||||
});
|
||||
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let buffer_diff = diff.await;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.git_diff_status.diff = buffer_diff;
|
||||
this.git_diff_update_count += 1;
|
||||
cx.notify();
|
||||
|
||||
this.git_diff_status.update_in_progress = false;
|
||||
if this.git_diff_status.update_requested {
|
||||
this.git_diff_recalc(cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
} else {
|
||||
let snapshot = self.snapshot();
|
||||
self.git_diff_status.diff.clear(&snapshot);
|
||||
self.git_diff_update_count += 1;
|
||||
cx.notify();
|
||||
}
|
||||
let handle = cx.weak_handle();
|
||||
Some(cx.spawn_weak(|_, mut cx| async move {
|
||||
let buffer_diff = diff.await;
|
||||
if let Some(this) = handle.upgrade(&mut cx) {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.git_diff = buffer_diff;
|
||||
this.git_diff_update_count += 1;
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn close(&mut self, cx: &mut ModelContext<Self>) {
|
||||
@@ -1644,10 +1613,17 @@ impl Buffer {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if lamport_timestamp > self.diagnostics_timestamp {
|
||||
match self.diagnostics.binary_search_by_key(&server_id, |e| e.0) {
|
||||
Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)),
|
||||
Ok(ix) => self.diagnostics[ix].1 = diagnostics,
|
||||
};
|
||||
let ix = self.diagnostics.binary_search_by_key(&server_id, |e| e.0);
|
||||
if diagnostics.len() == 0 {
|
||||
if let Ok(ix) = ix {
|
||||
self.diagnostics.remove(ix);
|
||||
}
|
||||
} else {
|
||||
match ix {
|
||||
Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)),
|
||||
Ok(ix) => self.diagnostics[ix].1 = diagnostics,
|
||||
};
|
||||
}
|
||||
self.diagnostics_timestamp = lamport_timestamp;
|
||||
self.diagnostics_update_count += 1;
|
||||
self.text.lamport_clock.observe(lamport_timestamp);
|
||||
@@ -1827,11 +1803,11 @@ impl BufferSnapshot {
|
||||
|
||||
pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
|
||||
let language_name = self.language_at(position).map(|language| language.name());
|
||||
let settings = cx.global::<Settings>();
|
||||
if settings.hard_tabs(language_name.as_deref()) {
|
||||
let settings = language_settings(language_name.as_deref(), cx);
|
||||
if settings.hard_tabs {
|
||||
IndentSize::tab()
|
||||
} else {
|
||||
IndentSize::spaces(settings.tab_size(language_name.as_deref()).get())
|
||||
IndentSize::spaces(settings.tab_size.get())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2146,6 +2122,15 @@ impl BufferSnapshot {
|
||||
.or(self.language.as_ref())
|
||||
}
|
||||
|
||||
pub fn settings_at<'a, D: ToOffset>(
|
||||
&self,
|
||||
position: D,
|
||||
cx: &'a AppContext,
|
||||
) -> &'a LanguageSettings {
|
||||
let language = self.language_at(position);
|
||||
language_settings(language.map(|l| l.name()).as_deref(), cx)
|
||||
}
|
||||
|
||||
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
|
||||
let offset = position.to_offset(self);
|
||||
|
||||
@@ -2500,18 +2485,22 @@ impl BufferSnapshot {
|
||||
pub fn git_diff_hunks_in_row_range<'a>(
|
||||
&'a self,
|
||||
range: Range<u32>,
|
||||
reversed: bool,
|
||||
) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
|
||||
self.git_diff.hunks_in_row_range(range, self, reversed)
|
||||
self.git_diff.hunks_in_row_range(range, self)
|
||||
}
|
||||
|
||||
pub fn git_diff_hunks_intersecting_range<'a>(
|
||||
&'a self,
|
||||
range: Range<Anchor>,
|
||||
reversed: bool,
|
||||
) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
|
||||
self.git_diff
|
||||
.hunks_intersecting_range(range, self, reversed)
|
||||
self.git_diff.hunks_intersecting_range(range, self)
|
||||
}
|
||||
|
||||
pub fn git_diff_hunks_intersecting_range_rev<'a>(
|
||||
&'a self,
|
||||
range: Range<Anchor>,
|
||||
) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
|
||||
self.git_diff.hunks_intersecting_range_rev(range, self)
|
||||
}
|
||||
|
||||
pub fn diagnostics_in_range<'a, T, O>(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user