Compare commits

...

37 Commits

Author SHA1 Message Date
Nate Butler
58a7a277df wip 2024-10-21 10:20:26 -04:00
Nate Butler
5e9f084f12 Start on quick commit UI PoC 2024-10-21 09:33:15 -04:00
Michael Sloan
b355a6f449 Fix markdown preview handling of empty list items (#19449)
Before this change, `parse_block` was consuming events that it doesn't
handle. This was fine in its use in `parse_document`, but in its use in
`parse_list` this broke when there is an empty list item, causing it to
consume list end tags / list item starts / etc.

Release Notes:

- Fixed markdown preview rendering of empty list items.
2024-10-21 15:13:26 +02:00
reslear
6341ad2f7a docs: Correct link to Vue extension (#19508)
Closes -

Release Notes:

- N/A
2024-10-21 08:12:10 -04:00
Kirill Bulatov
d3cb08bf35 Support .editorconfig (#19455)
Closes https://github.com/zed-industries/zed/issues/8534
Supersedes https://github.com/zed-industries/zed/pull/16349

Potential concerns:
* we do not follow up to the `/` when looking for `.editorconfig`, only
up to the worktree root.
Seems fine for most of the cases, and the rest should be solved
generically later, as the same issue exists for settings.json
* `fn language` in `AllLanguageSettings` is very hot, called very
frequently during rendering. We accumulate and parse all `.editorconfig`
file contents beforehand, but have to go over globs and match these
against the path given + merge the properties still.
This does not seem to be very bad, but needs more testing and
potentially some extra caching.


Release Notes:

- Added .editorconfig support

---------

Co-authored-by: Ulysse Buonomo <buonomo.ulysse@gmail.com>
2024-10-21 13:05:30 +03:00
renovate[bot]
d95a4f8671 Update swatinem/rust-cache digest to 82a92a6 (#19318)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [swatinem/rust-cache](https://redirect.github.com/swatinem/rust-cache)
| action | digest | `23bce25` -> `82a92a6` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-21 11:10:45 +03:00
Alvaro Gaona
44dc693d30 docs: Update C# and C++ configuration pages (#19500)
- Update C# configuration page.
- Fix typo in C++ configuration page.

Release Notes:

- N/A
2024-10-21 11:07:12 +03:00
Conrad Irwin
92c29be74c SSH Remoting: Fix reconnects (#19485)
Before this change messages could be lost on reconnect, now they will
not be.

Release Notes:

- SSH Remoting: make reconnects smoother

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-10-19 23:14:19 -06:00
Kirill Bulatov
1ae30f5813 Show project panel symlink icons for remote clients (#19464) 2024-10-19 19:44:47 +03:00
Antonio Scandurra
e8207288e5 Refold updated patch only if the patch was already folded (#19462)
Release Notes:

- N/A
2024-10-19 17:10:50 +02:00
Nathan Sobo
781fff220c Fix merging of an update of a symbol with an insert_before operation before the same symbol (#19450)
When we insert before some text and then update that same text, we need
to preserve and concatenate the new text associated with both
operations.

Release Notes:

- N/A
2024-10-19 08:36:21 -06:00
Max Brunsfeld
d209eab058 Combine excerpt footer and header into a single block (#19441)
This simplifies rendering of excerpt headers and footers, and removes
the need to store a `BlockDisposition` on these boundary blocks. It's a
step toward implementing "replace blocks", which we want to use in the
assistant panel.

We've also cleaned up the way heights are specified for headers and
footers and fixed some visual asymmetries between the "expand upward"
and "expand downward" buttons.

Release Notes:

- N/A

---------

Co-authored-by: Richard <richard@zed.dev>
2024-10-18 17:58:07 -07:00
Vitaly Slobodin
3e0c5c10b7 lsp: Handle unregistration "textDocument/rename" from a server (#19427)
Hi. While working on https://github.com/zed-industries/zed/pull/19230 I
noticed that some servers send a request to unregistered the
`textDocument/rename` capability. I thought it would be good to handle
that message in Zed:

```plaintext
[2024-10-18T21:25:07+02:00 WARN  project::lsp_store] unhandled capability unregistration: Unregistration { id: "biome_rename", method: "textDocument/rename" }
```

So this pull request implements that. Thanks.

Release Notes:

- N/A
2024-10-19 00:52:17 +02:00
Mikayla Maki
8a912726d7 Fix flakey SSH connection (#19439)
Fixes a bug due to the `select!` macro tossing futures that had
partially read messages, causing us to desync our message reading with
the input stream.

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: conrad <conrad@zed.dev>
2024-10-18 15:41:43 -07:00
Marshall Bowers
30e081b3f7 elixir: Bump to v0.1.1 (#19437)
This PR bumps the Elixir extension to v0.1.1.

Changes:

- https://github.com/zed-industries/zed/pull/19135

Release Notes:

- N/A
2024-10-18 18:31:08 -04:00
Conrad Irwin
a5492b3ea6 Revert "SSH reconnect reliability (#19398)" (#19440)
This reverts commit 98ecb43b2d.

Tests fail on main?!

Closes #ISSUE

Release Notes:

- N/A
2024-10-18 15:08:56 -07:00
Marshall Bowers
47380001cc remote: Fix formatting (#19438)
This PR fixes some formatting issues from #19398 that slipped past CI,
somehow.

Release Notes:

- N/A
2024-10-18 17:31:41 -04:00
Conrad Irwin
98ecb43b2d SSH reconnect reliability (#19398)
Release Notes:

- SSH Remoting: Fix message reliability across restarts

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-10-18 15:28:08 -06:00
Valentine Briese
be474a6d6f docs: Update info on JDTLS install for Java (#19436) 2024-10-18 17:12:34 -04:00
Marshall Bowers
b44bed0115 collab: Unconditionally execute billing checks (#19432)
This PR removes the conditional checks around the billing-related
enforcement for LLM completions.

These were just in place to prevent executing any billing code before we
had rolled it out. Now that it is rolled out, we don't need this
conditional execution anymore.

Release Notes:

- N/A
2024-10-18 15:55:28 -04:00
Joseph T. Lyons
11a82e3347 Do not run CI on changes to community action config files (#19430)
Release Notes:

- N/A
2024-10-18 15:36:20 -04:00
Joseph T. Lyons
be81e29b0f Prefer users to open new issues if closed stale issue is valid (#19428)
I no longer want to have to keep my ears and eyes open for GitHub
notifications that relate to someone requesting a closed stale issue be
reopened. As a community maintainer, I can get hundreds or even
thousands of notifications a week, and a lot of those are about activity
on closed issues. If everyone following an issue did not react fast
enough (7 days) to keep an issue flagged as `stale` open, let's instruct
them to open new issues, so we are forced to see it during next triage.

Release Notes:

- N/A
2024-10-18 15:04:26 -04:00
Valentine Briese
6a463be1ae docs: Direct Java extension users to JDTLS initialization options (#19401)
Continuation of #19390
2024-10-18 15:02:42 -04:00
Vladimir Varankin
64a6e9cafb docs: Outline Jsonnet language (#19410)
This PR adds a basic documentation about the Jsonnet language support.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-18 14:20:15 -04:00
Marshall Bowers
fa738ee5e1 vue: Extract to zed-extensions/vue repository (#19426)
This PR extracts the Vue extension to the
[zed-extensions/vue](https://github.com/zed-extensions/vue) repository.

Release Notes:

- N/A
2024-10-18 14:08:32 -04:00
Marshall Bowers
15449cdf30 svelte: Extract to zed-extensions/svelte repository (#19425)
This PR extracts the Svelte extension to the
[zed-extensions/svelte](https://github.com/zed-extensions/svelte)
repository.

Release Notes:

- N/A
2024-10-18 13:36:07 -04:00
Shish
2db9090a2f remote: Polish for connection progress & error dialogs (#19379)
Before/after:

![err1-before](https://github.com/user-attachments/assets/43d959b3-c9d9-45dd-938e-42d34ec1cfc5)

![err1-after](https://github.com/user-attachments/assets/311d53e0-752c-4eb8-9816-64b1970c228d)

Before/after (I feel like text-wrapping would be more useful than
text-ellipsis here, but I don't see any wrap function):

![err2-before](https://github.com/user-attachments/assets/1626cda9-bf06-43fe-9b7d-3ec64f4db08a)

![err2-after](https://github.com/user-attachments/assets/749a6950-1409-4e75-808e-a1a96dbfc87e)

Before/after:

![prog-before](https://github.com/user-attachments/assets/f5f5a171-db42-4797-bab0-ad71c750bb20)

![prog-after](https://github.com/user-attachments/assets/b52a7694-36f6-4f7a-8a90-ceb223f12ec1)

Release Notes:

- N/A
2024-10-18 11:09:52 -06:00
Thomas
34b8655bf6 Improve increment/decrement with leading zeros in vim mode (#18362)
- Closes: 18360

Release Notes:

- Added support for incrementing and decrementing numbers with leading
zeros
2024-10-18 11:03:34 -06:00
张小白
5b745a82e1 reqwest_client: Fix socks proxy settings (#19123)
Closes #19362

This pull request includes several updates to the `reqwest_client` crate
and its dependencies. The most important changes involve adding support
for SOCKS proxies, improving error handling for proxy URIs, and adding
tests for proxy functionality.

### Dependency Updates:
*
[`Cargo.toml`](diffhunk://#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542L394-R401):
Added support for SOCKS proxies in the `reqwest` dependency by including
the `socks` feature.

### Code Improvements:
*
[`crates/reqwest_client/src/reqwest_client.rs`](diffhunk://#diff-8e036b034e987390be2f57373864b75d6983f0cf84e85c43793eb431d13538f3L47-R52):
Improved error handling when parsing proxy URIs by logging errors
instead of directly panicking.

### Testing Enhancements:
*
[`crates/reqwest_client/src/reqwest_client.rs`](diffhunk://#diff-8e036b034e987390be2f57373864b75d6983f0cf84e85c43793eb431d13538f3R274-R317):
Added tests to verify the handling of various proxy URIs, including
valid and invalid cases.

Release Notes:

- N/A
2024-10-18 09:57:00 -07:00
renovate[bot]
c59a75db1d Update actions/upload-artifact digest to b4b15b8 (#19310)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/upload-artifact](https://redirect.github.com/actions/upload-artifact)
| action | digest | `604373d` -> `b4b15b8` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 12:41:36 -04:00
David Baldwin
b3c93130ec elixir: Support describe, test, setup, setup_all in outlines (#19135)
Closes #9894 

Release Notes:

- N/A

### Before


![2024-10-12T204848@2x](https://github.com/user-attachments/assets/84b7f123-8845-4e6d-b1b1-444e54ea6599)

### After


![2024-10-12T204749@2x](https://github.com/user-attachments/assets/67fdcead-bad3-4967-9ac4-0b85f1da7bca)
2024-10-18 12:39:24 -04:00
Marshall Bowers
73a6c542f3 vue: Bump to v0.1.1 (#19421)
This PR bumps the Vue extension to v0.1.1.

Changes:

- https://github.com/zed-industries/zed/pull/19419

Release Notes:

- N/A
2024-10-18 11:56:16 -04:00
Marshall Bowers
2cd6c19873 svelte: Bump to v0.2.1 (#19420)
This PR bumps the Svelte extension to v0.2.1.

Changes:

- https://github.com/zed-industries/zed/pull/19418

Release Notes:

- N/A
2024-10-18 11:37:51 -04:00
Marshall Bowers
6f24c1da79 vue: Support lang attribute for style tag injections (#19419)
This PR adds support for injecting languages into `<style>` tags using
`<style lang="...">` in Vue.

Extracted from https://github.com/zed-industries/zed/pull/18052.

Release Notes:

- N/A

Co-authored-by: Albert Marashi <albert@lumina.earth>
2024-10-18 11:36:30 -04:00
Marshall Bowers
5508832ba6 svelte: Adjust block keyword highlighting (#19418)
This PR adjusts the highlights for `{#each ...}` and `{#if ...}`
expression blocks to use keyword highlighting.

Extracted from https://github.com/zed-industries/zed/pull/18052.

Release Notes:

- N/A

Co-authored-by: Albert Marashi <albert@lumina.earth>
2024-10-18 11:25:03 -04:00
Marshall Bowers
35f2f2aac4 Treat .postcss files as CSS (#19416)
This PR makes it so `.postcss` files are recognized as CSS.

The `tree-sitter-css` grammar has basic support for PostCSS:
https://github.com/tree-sitter/tree-sitter-css/issues/17#issuecomment-1830349808.

Closes #18051.

Release Notes:

- `.postcss` files are now recognized as CSS.
2024-10-18 11:11:29 -04:00
Peter Tripp
9e27b6694a keymap: Add cmd-o 'Go to symbol' for JetBrains MacOS key bindings (#19415) 2024-10-18 10:12:39 -04:00
106 changed files with 2554 additions and 2109 deletions

View File

@@ -14,6 +14,7 @@ on:
- "**"
paths-ignore:
- "docs/**"
- ".github/workflows/community_*"
concurrency:
# Allow only one workflow per any non-`main` branch.
@@ -137,7 +138,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -169,7 +170,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -192,7 +193,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"
@@ -266,20 +267,20 @@ jobs:
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (universal) to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
@@ -330,7 +331,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
@@ -377,7 +378,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz

View File

@@ -17,7 +17,7 @@ jobs:
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 7 days. Feel free to open a new issue if you're seeing this message after the issue has been closed.
Thanks for your help!
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, feel free to ping a Zed team member to reopen this issue or open a new one."
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
# We will increase `days-before-stale` to 365 on or after Jan 24th,
# 2024. This date marks one year since migrating issues from
# 'community' to 'zed' repository. The migration added activity to all

View File

@@ -21,7 +21,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"

30
Cargo.lock generated
View File

@@ -3649,6 +3649,12 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ec4rs"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf65d056c7da9c971c2847ce250fd1f0f9659d5718845c3ec0ad95f5668352c"
[[package]]
name = "ecdsa"
version = "0.14.8"
@@ -6210,6 +6216,7 @@ dependencies = [
"clock",
"collections",
"ctor",
"ec4rs",
"env_logger",
"futures 0.3.30",
"fuzzy",
@@ -9119,6 +9126,7 @@ name = "remote"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"collections",
"fs",
"futures 0.3.30",
@@ -9303,6 +9311,7 @@ dependencies = [
"system-configuration 0.6.1",
"tokio",
"tokio-rustls 0.26.0",
"tokio-socks",
"tokio-util",
"tower-service",
"url",
@@ -10300,6 +10309,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"ec4rs",
"fs",
"futures 0.3.30",
"gpui",
@@ -12001,6 +12011,7 @@ dependencies = [
"futures-io",
"futures-util",
"thiserror",
"tokio",
]
[[package]]
@@ -12593,9 +12604,11 @@ version = "0.1.0"
dependencies = [
"editor",
"gpui",
"menu",
"settings",
"theme",
"ui",
"workspace",
]
[[package]]
@@ -14740,7 +14753,7 @@ dependencies = [
[[package]]
name = "zed_elixir"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -14864,13 +14877,6 @@ dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_svelte"
version = "0.2.0"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_terraform"
version = "0.1.1"
@@ -14899,14 +14905,6 @@ dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_vue"
version = "0.1.0"
dependencies = [
"serde",
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_zig"
version = "0.3.1"

View File

@@ -158,12 +158,10 @@ members = [
"extensions/ruff",
"extensions/slash-commands-example",
"extensions/snippets",
"extensions/svelte",
"extensions/terraform",
"extensions/test-extension",
"extensions/toml",
"extensions/uiua",
"extensions/vue",
"extensions/zig",
#
@@ -349,6 +347,7 @@ ctor = "0.2.6"
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
exec = "0.3.1"
@@ -391,7 +390,14 @@ pulldown-cmark = { version = "0.12.0", default-features = false }
rand = "0.8.5"
regex = "1.5"
repair_json = "0.1.0"
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = ["charset", "http2", "macos-system-configuration", "rustls-tls-native-roots", "stream"]}
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
"rustls-tls-native-roots",
"socks",
"stream",
] }
rsa = "0.9.6"
runtimelib = { version = "0.15", default-features = false, features = [
"async-dispatcher-runtime",

View File

@@ -128,6 +128,7 @@
"php": "php",
"plist": "template",
"png": "image",
"postcss": "css",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",

View File

@@ -34,7 +34,7 @@
"cmd-]": "pane::GoForward",
"alt-f7": "editor::FindAllReferences",
"cmd-alt-f7": "editor::FindAllReferences",
"cmd-b": "editor::GoToDefinition",
"cmd-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
"cmd-alt-b": "editor::GoToDefinitionSplit",
"cmd-shift-b": "editor::GoToTypeDefinition",
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
@@ -64,7 +64,8 @@
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle",
"cmd-alt-o": "project_symbols::Toggle", // JetBrains: Go to Symbol
"cmd-o": "project_symbols::Toggle", // JetBrains: Go to Class
"cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy"
}

View File

@@ -2219,6 +2219,7 @@ impl ContextEditor {
merge_adjacent: false,
};
let should_refold;
if let Some(state) = self.patches.get_mut(&range) {
replaced_blocks.insert(state.footer_block_id, render_block);
if let Some(editor_state) = &state.editor {
@@ -2233,6 +2234,9 @@ impl ContextEditor {
});
}
}
should_refold =
snapshot.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot));
} else {
let block_ids = editor.insert_blocks(
[BlockProperties {
@@ -2266,10 +2270,14 @@ impl ContextEditor {
update_task: None,
},
);
should_refold = true;
}
editor.unfold_ranges([patch_start..patch_end], true, false, cx);
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
if should_refold {
editor.unfold_ranges([patch_start..patch_end], true, false, cx);
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
}
}
editor.remove_creases(removed_crease_ids, cx);

View File

@@ -146,12 +146,28 @@ impl ResolvedEdit {
return false;
}
if let Some(description) = &mut self.description {
if let Some(other_description) = &other.description {
let other_offset_range = other_range.to_offset(buffer);
let offset_range = range.to_offset(buffer);
// If the other range is empty at the start of this edit's range, combine the new text
if other_offset_range.is_empty() && other_offset_range.start == offset_range.start {
self.new_text = format!("{}\n{}", other.new_text, self.new_text);
self.range.start = other_range.start;
if let Some((description, other_description)) =
self.description.as_mut().zip(other.description.as_ref())
{
*description = format!("{}\n{}", other_description, description)
}
} else {
if let Some((description, other_description)) =
self.description.as_mut().zip(other.description.as_ref())
{
description.push('\n');
description.push_str(other_description);
}
}
true
}
}
@@ -699,6 +715,73 @@ mod tests {
.unindent(),
cx,
);
// Ensure InsertBefore merges correctly with Update of the same text
assert_edits(
"
fn foo() {
}
"
.unindent(),
vec![
AssistantEditKind::InsertBefore {
old_text: "
fn foo() {"
.unindent(),
new_text: "
fn bar() {
qux();
}"
.unindent(),
description: "implement bar".into(),
},
AssistantEditKind::Update {
old_text: "
fn foo() {
}"
.unindent(),
new_text: "
fn foo() {
bar();
}"
.unindent(),
description: "call bar in foo".into(),
},
AssistantEditKind::InsertAfter {
old_text: "
fn foo() {
}
"
.unindent(),
new_text: "
fn qux() {
// todo
}
"
.unindent(),
description: "implement qux".into(),
},
],
"
fn bar() {
qux();
}
fn foo() {
bar();
}
fn qux() {
// todo
}
"
.unindent(),
cx,
);
}
#[track_caller]

View File

@@ -78,10 +78,10 @@ CREATE TABLE "worktree_entries" (
"id" INTEGER NOT NULL,
"is_dir" BOOL NOT NULL,
"path" VARCHAR NOT NULL,
"canonical_path" TEXT,
"inode" INTEGER NOT NULL,
"mtime_seconds" INTEGER NOT NULL,
"mtime_nanos" INTEGER NOT NULL,
"is_symlink" BOOL NOT NULL,
"is_external" BOOL NOT NULL,
"is_ignored" BOOL NOT NULL,
"is_deleted" BOOL NOT NULL,

View File

@@ -0,0 +1,2 @@
ALTER TABLE worktree_entries ADD COLUMN canonical_path text;
ALTER TABLE worktree_entries ALTER COLUMN is_symlink SET DEFAULT false;

View File

@@ -317,7 +317,7 @@ impl Database {
inode: ActiveValue::set(entry.inode as i64),
mtime_seconds: ActiveValue::set(mtime.seconds as i64),
mtime_nanos: ActiveValue::set(mtime.nanos as i32),
is_symlink: ActiveValue::set(entry.is_symlink),
canonical_path: ActiveValue::set(entry.canonical_path.clone()),
is_ignored: ActiveValue::set(entry.is_ignored),
is_external: ActiveValue::set(entry.is_external),
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
@@ -338,7 +338,7 @@ impl Database {
worktree_entry::Column::Inode,
worktree_entry::Column::MtimeSeconds,
worktree_entry::Column::MtimeNanos,
worktree_entry::Column::IsSymlink,
worktree_entry::Column::CanonicalPath,
worktree_entry::Column::IsIgnored,
worktree_entry::Column::GitStatus,
worktree_entry::Column::ScanId,
@@ -735,7 +735,7 @@ impl Database {
seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32,
}),
is_symlink: db_entry.is_symlink,
canonical_path: db_entry.canonical_path,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),

View File

@@ -659,7 +659,7 @@ impl Database {
seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32,
}),
is_symlink: db_entry.is_symlink,
canonical_path: db_entry.canonical_path,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),

View File

@@ -16,12 +16,12 @@ pub struct Model {
pub mtime_seconds: i64,
pub mtime_nanos: i32,
pub git_status: Option<i64>,
pub is_symlink: bool,
pub is_ignored: bool,
pub is_external: bool,
pub is_deleted: bool,
pub scan_id: i64,
pub is_fifo: bool,
pub canonical_path: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@@ -198,10 +198,6 @@ impl Config {
}
}
pub fn is_llm_billing_enabled(&self) -> bool {
self.stripe_api_key.is_some()
}
#[cfg(test)]
pub fn test() -> Self {
Self {

View File

@@ -460,29 +460,27 @@ async fn check_usage_limit(
)
.await?;
if state.config.is_llm_billing_enabled() {
if usage.spending_this_month >= FREE_TIER_MONTHLY_SPENDING_LIMIT {
if !claims.has_llm_subscription {
return Err(Error::http(
StatusCode::PAYMENT_REQUIRED,
"Maximum spending limit reached for this month.".to_string(),
));
}
if usage.spending_this_month >= FREE_TIER_MONTHLY_SPENDING_LIMIT {
if !claims.has_llm_subscription {
return Err(Error::http(
StatusCode::PAYMENT_REQUIRED,
"Maximum spending limit reached for this month.".to_string(),
));
}
if (usage.spending_this_month - FREE_TIER_MONTHLY_SPENDING_LIMIT)
>= Cents(claims.max_monthly_spend_in_cents)
{
return Err(Error::Http(
StatusCode::FORBIDDEN,
"Maximum spending limit reached for this month.".to_string(),
[(
HeaderName::from_static(MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME),
HeaderValue::from_static("true"),
)]
.into_iter()
.collect(),
));
}
if (usage.spending_this_month - FREE_TIER_MONTHLY_SPENDING_LIMIT)
>= Cents(claims.max_monthly_spend_in_cents)
{
return Err(Error::Http(
StatusCode::FORBIDDEN,
"Maximum spending limit reached for this month.".to_string(),
[(
HeaderName::from_static(MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME),
HeaderValue::from_static("true"),
)]
.into_iter()
.collect(),
));
}
}
@@ -627,7 +625,6 @@ where
impl<S> Drop for TokenCountingStream<S> {
fn drop(&mut self) {
let state = self.state.clone();
let is_llm_billing_enabled = state.config.is_llm_billing_enabled();
let claims = self.claims.clone();
let provider = self.provider;
let model = std::mem::take(&mut self.model);
@@ -641,14 +638,7 @@ impl<S> Drop for TokenCountingStream<S> {
provider,
&model,
tokens,
// We're passing `false` here if LLM billing is not enabled
// so that we don't write any records to the
// `billing_events` table until we're ready to bill users.
if is_llm_billing_enabled {
claims.has_llm_subscription
} else {
false
},
claims.has_llm_subscription,
Cents(claims.max_monthly_spend_in_cents),
Utc::now(),
)

View File

@@ -2237,7 +2237,7 @@ fn join_project_internal(
worktree_id: worktree.id,
path: settings_file.path,
content: Some(settings_file.content),
kind: Some(proto::update_user_settings::Kind::Settings.into()),
kind: Some(settings_file.kind.to_proto() as i32),
},
)?;
}

View File

@@ -12,6 +12,7 @@ use editor::{
test::editor_test_context::{AssertionContextManager, EditorTestContext},
Editor,
};
use fs::Fs;
use futures::StreamExt;
use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
use indoc::indoc;
@@ -30,7 +31,7 @@ use serde_json::json;
use settings::SettingsStore;
use std::{
ops::Range,
path::Path,
path::{Path, PathBuf},
sync::{
atomic::{self, AtomicBool, AtomicUsize},
Arc,
@@ -60,7 +61,7 @@ async fn test_host_disconnect(
.fs()
.insert_tree(
"/a",
serde_json::json!({
json!({
"a.txt": "a-contents",
"b.txt": "b-contents",
}),
@@ -2152,6 +2153,295 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
});
}
#[gpui::test(iterations = 30)]
async fn test_collaborating_with_editorconfig(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
let mut server = TestServer::start(cx_a.executor()).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);
cx_b.update(editor::init);
// Set up a fake language server.
client_a.language_registry().add(rust_lang());
client_a
.fs()
.insert_tree(
"/a",
json!({
"src": {
"main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
"other_mod": {
"other.rs": "pub fn foo() -> usize {\n 4\n}",
".editorconfig": "",
},
},
".editorconfig": "[*]\ntab_width = 2\n",
}),
)
.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 main_buffer_a = project_a
.update(cx_a, |p, cx| {
p.open_buffer((worktree_id, "src/main.rs"), cx)
})
.await
.unwrap();
let other_buffer_a = project_a
.update(cx_a, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
})
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let main_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(main_buffer_a, Some(project_a.clone()), cx));
let other_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(other_buffer_a, Some(project_a), cx));
let mut main_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
editor: main_editor_a,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
editor: other_editor_a,
assertion_cx: AssertionContextManager::new(),
};
// Join the project as client B.
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let main_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/main.rs"), cx)
})
.await
.unwrap();
let other_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
})
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let main_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(main_buffer_b, Some(project_b.clone()), cx));
let other_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(other_buffer_b, Some(project_b.clone()), cx));
let mut main_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
editor: main_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
editor: other_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let initial_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
let initial_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
let first_tabbed_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
first_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
first_tabbed_main,
false,
);
let first_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
first_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
first_tabbed_other,
false,
);
client_a
.fs()
.atomic_write(
PathBuf::from("/a/src/.editorconfig"),
"[*]\ntab_width = 3\n".to_owned(),
)
.await
.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
let second_tabbed_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
false,
);
let second_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
second_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
second_tabbed_other,
false,
);
let editorconfig_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
})
.await
.unwrap();
editorconfig_buffer_b.update(cx_b, |buffer, cx| {
buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
});
project_b
.update(cx_b, |project, cx| {
project.save_buffer(editorconfig_buffer_b.clone(), cx)
})
.await
.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
false,
);
let third_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
third_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
third_tabbed_other,
false,
);
}
#[track_caller]
fn tab_undo_assert(
cx_a: &mut EditorTestContext,
cx_b: &mut EditorTestContext,
expected_initial: &str,
expected_tabbed: &str,
a_tabs: bool,
) {
cx_a.assert_editor_state(expected_initial);
cx_b.assert_editor_state(expected_initial);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
});
}
cx_a.run_until_parked();
cx_b.run_until_parked();
cx_a.assert_editor_state(expected_tabbed);
cx_b.assert_editor_state(expected_tabbed);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
});
}
cx_a.run_until_parked();
cx_b.run_until_parked();
cx_a.assert_editor_state(expected_initial);
cx_b.assert_editor_state(expected_initial);
}
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
let mut labels = Vec::new();
for hint in editor.inlay_hint_cache().hints() {

View File

@@ -34,7 +34,7 @@ use project::{
};
use rand::prelude::*;
use serde_json::json;
use settings::{LocalSettingsKind, SettingsStore};
use settings::SettingsStore;
use std::{
cell::{Cell, RefCell},
env, future, mem,
@@ -3328,16 +3328,8 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(
Path::new("").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":2}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
]
)
});
@@ -3355,16 +3347,8 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(
Path::new("").into(),
LocalSettingsKind::Settings,
r#"{}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
(Path::new("").into(), r#"{}"#.to_string()),
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
]
)
});
@@ -3392,16 +3376,8 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
(
Path::new("b").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":4}"#.to_string()
),
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
(Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
]
)
});
@@ -3431,11 +3407,7 @@ async fn test_local_settings(
store
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"hard_tabs":true}"#.to_string()
),]
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
)
});
}

View File

@@ -3,7 +3,7 @@ use call::ActiveCall;
use fs::{FakeFs, Fs as _};
use gpui::{Context as _, TestAppContext};
use http_client::BlockedHttpClient;
use language::{language_settings::all_language_settings, LanguageRegistry};
use language::{language_settings::language_settings, LanguageRegistry};
use node_runtime::NodeRuntime;
use project::ProjectPath;
use remote::SshRemoteClient;
@@ -26,7 +26,7 @@ async fn test_sharing_an_ssh_remote_project(
.await;
// Set up project on remote FS
let (client_ssh, server_ssh) = SshRemoteClient::fake(cx_a, server_cx);
let (port, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
let remote_fs = FakeFs::new(server_cx.executor());
remote_fs
.insert_tree(
@@ -67,6 +67,7 @@ async fn test_sharing_an_ssh_remote_project(
)
});
let client_ssh = SshRemoteClient::fake_client(port, cx_a).await;
let (project_a, worktree_id) = client_a
.build_ssh_project("/code/project1", client_ssh, cx_a)
.await;
@@ -134,9 +135,7 @@ async fn test_sharing_an_ssh_remote_project(
cx_b.read(|cx| {
let file = buffer_b.read(cx).file();
assert_eq!(
all_language_settings(file, cx)
.language(Some(&("Rust".into())))
.language_servers,
language_settings(Some("Rust".into()), file, cx).language_servers,
["override-rust-analyzer".to_string()]
)
});

View File

@@ -864,7 +864,11 @@ impl Copilot {
let buffer = buffer.read(cx);
let uri = registered_buffer.uri.clone();
let position = position.to_point_utf16(buffer);
let settings = language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx);
let settings = language_settings(
buffer.language_at(position).map(|l| l.name()),
buffer.file(),
cx,
);
let tab_size = settings.tab_size;
let hard_tabs = settings.hard_tabs;
let relative_path = buffer

View File

@@ -77,7 +77,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
let file = buffer.file();
let language = buffer.language_at(cursor_position);
let settings = all_language_settings(file, cx);
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()))
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx)
}
fn refresh(
@@ -209,7 +209,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
) {
let settings = AllLanguageSettings::get_global(cx);
let copilot_enabled = settings.inline_completions_enabled(None, None);
let copilot_enabled = settings.inline_completions_enabled(None, None, cx);
if !copilot_enabled {
return;

View File

@@ -962,7 +962,6 @@ fn random_diagnostic(
const FILE_HEADER: &str = "file header";
const EXCERPT_HEADER: &str = "excerpt header";
const EXCERPT_FOOTER: &str = "excerpt footer";
fn editor_blocks(
editor: &View<Editor>,
@@ -998,7 +997,7 @@ fn editor_blocks(
.ok()?
}
Block::ExcerptHeader {
Block::ExcerptBoundary {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
@@ -1007,7 +1006,6 @@ fn editor_blocks(
EXCERPT_HEADER.into()
}
}
Block::ExcerptFooter { .. } => EXCERPT_FOOTER.into(),
};
Some((row, name))

View File

@@ -423,11 +423,12 @@ impl DisplayMap {
}
fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
let language = buffer
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).language());
language_settings(language, None, cx).tab_size
.and_then(|buffer| buffer.language())
.map(|l| l.name());
let file = buffer.and_then(|buffer| buffer.file());
language_settings(language, file, cx).tab_size
}
#[cfg(test)]

View File

@@ -5,8 +5,8 @@ use super::{
use crate::{EditorStyle, GutterDimensions};
use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, EntityId, Pixels, WindowContext};
use language::{BufferSnapshot, Chunk, Patch, Point};
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _};
use language::{Chunk, Patch, Point};
use multi_buffer::{Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, ToPoint as _};
use parking_lot::Mutex;
use std::{
cell::RefCell,
@@ -128,26 +128,17 @@ pub struct BlockContext<'a, 'b> {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockId {
Custom(CustomBlockId),
ExcerptHeader(ExcerptId),
ExcerptFooter(ExcerptId),
}
impl From<BlockId> for EntityId {
fn from(value: BlockId) -> Self {
match value {
BlockId::Custom(CustomBlockId(id)) => EntityId::from(id as u64),
BlockId::ExcerptHeader(id) => id.into(),
BlockId::ExcerptFooter(id) => id.into(),
}
}
ExcerptBoundary(Option<ExcerptId>),
}
impl From<BlockId> for ElementId {
fn from(value: BlockId) -> Self {
match value {
BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
BlockId::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
BlockId::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
BlockId::ExcerptBoundary(next_excerpt) => match next_excerpt {
Some(id) => ("ExcerptBoundary", EntityId::from(id)).into(),
None => "LastExcerptBoundary".into(),
},
}
}
}
@@ -156,8 +147,7 @@ impl std::fmt::Display for BlockId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Custom(id) => write!(f, "Block({id:?})"),
Self::ExcerptHeader(id) => write!(f, "ExcerptHeader({id:?})"),
Self::ExcerptFooter(id) => write!(f, "ExcerptFooter({id:?})"),
Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
}
}
}
@@ -177,8 +167,7 @@ struct Transform {
pub(crate) enum BlockType {
Custom(CustomBlockId),
Header,
Footer,
ExcerptBoundary,
}
pub(crate) trait BlockLike {
@@ -191,27 +180,20 @@ pub(crate) trait BlockLike {
#[derive(Clone)]
pub enum Block {
Custom(Arc<CustomBlock>),
ExcerptHeader {
id: ExcerptId,
buffer: BufferSnapshot,
range: ExcerptRange<text::Anchor>,
ExcerptBoundary {
prev_excerpt: Option<ExcerptInfo>,
next_excerpt: Option<ExcerptInfo>,
height: u32,
starts_new_buffer: bool,
show_excerpt_controls: bool,
},
ExcerptFooter {
id: ExcerptId,
disposition: BlockDisposition,
height: u32,
},
}
impl BlockLike for Block {
fn block_type(&self) -> BlockType {
match self {
Block::Custom(block) => BlockType::Custom(block.id),
Block::ExcerptHeader { .. } => BlockType::Header,
Block::ExcerptFooter { .. } => BlockType::Footer,
Block::ExcerptBoundary { .. } => BlockType::ExcerptBoundary,
}
}
@@ -222,8 +204,7 @@ impl BlockLike for Block {
fn priority(&self) -> usize {
match self {
Block::Custom(block) => block.priority,
Block::ExcerptHeader { .. } => usize::MAX,
Block::ExcerptFooter { .. } => 0,
Block::ExcerptBoundary { .. } => usize::MAX,
}
}
}
@@ -232,32 +213,36 @@ impl Block {
pub fn id(&self) -> BlockId {
match self {
Block::Custom(block) => BlockId::Custom(block.id),
Block::ExcerptHeader { id, .. } => BlockId::ExcerptHeader(*id),
Block::ExcerptFooter { id, .. } => BlockId::ExcerptFooter(*id),
Block::ExcerptBoundary { next_excerpt, .. } => {
BlockId::ExcerptBoundary(next_excerpt.as_ref().map(|info| info.id))
}
}
}
fn disposition(&self) -> BlockDisposition {
match self {
Block::Custom(block) => block.disposition,
Block::ExcerptHeader { .. } => BlockDisposition::Above,
Block::ExcerptFooter { disposition, .. } => *disposition,
Block::ExcerptBoundary { next_excerpt, .. } => {
if next_excerpt.is_some() {
BlockDisposition::Above
} else {
BlockDisposition::Below
}
}
}
}
pub fn height(&self) -> u32 {
match self {
Block::Custom(block) => block.height,
Block::ExcerptHeader { height, .. } => *height,
Block::ExcerptFooter { height, .. } => *height,
Block::ExcerptBoundary { height, .. } => *height,
}
}
pub fn style(&self) -> BlockStyle {
match self {
Block::Custom(block) => block.style,
Block::ExcerptHeader { .. } => BlockStyle::Sticky,
Block::ExcerptFooter { .. } => BlockStyle::Sticky,
Block::ExcerptBoundary { .. } => BlockStyle::Sticky,
}
}
}
@@ -266,24 +251,17 @@ impl Debug for Block {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
Self::ExcerptHeader {
buffer,
Self::ExcerptBoundary {
starts_new_buffer,
id,
next_excerpt,
prev_excerpt,
..
} => f
.debug_struct("ExcerptHeader")
.field("id", &id)
.field("path", &buffer.file().map(|f| f.path()))
.debug_struct("ExcerptBoundary")
.field("prev_excerpt", &prev_excerpt)
.field("next_excerpt", &next_excerpt)
.field("starts_new_buffer", &starts_new_buffer)
.finish(),
Block::ExcerptFooter {
id, disposition, ..
} => f
.debug_struct("ExcerptFooter")
.field("id", &id)
.field("disposition", &disposition)
.finish(),
}
}
}
@@ -595,66 +573,62 @@ impl BlockMap {
{
buffer
.excerpt_boundaries_in_range(range)
.flat_map(move |excerpt_boundary| {
let mut wrap_row = wrap_snapshot
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
.row();
[
show_excerpt_controls
.then(|| {
let disposition;
if excerpt_boundary.next.is_some() {
disposition = BlockDisposition::Above;
} else {
wrap_row = wrap_snapshot
.make_wrap_point(
Point::new(
excerpt_boundary.row.0,
buffer.line_len(excerpt_boundary.row),
),
Bias::Left,
)
.row();
disposition = BlockDisposition::Below;
}
excerpt_boundary.prev.as_ref().map(|prev| {
(
wrap_row,
Block::ExcerptFooter {
id: prev.id,
height: excerpt_footer_height,
disposition,
},
)
})
})
.flatten(),
excerpt_boundary.next.map(|next| {
let starts_new_buffer = excerpt_boundary
.prev
.map_or(true, |prev| prev.buffer_id != next.buffer_id);
(
wrap_row,
Block::ExcerptHeader {
id: next.id,
buffer: next.buffer,
range: next.range,
height: if starts_new_buffer {
buffer_header_height
} else {
excerpt_header_height
},
starts_new_buffer,
show_excerpt_controls,
},
.filter_map(move |excerpt_boundary| {
let wrap_row;
if excerpt_boundary.next.is_some() {
wrap_row = wrap_snapshot
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
.row();
} else {
wrap_row = wrap_snapshot
.make_wrap_point(
Point::new(
excerpt_boundary.row.0,
buffer.line_len(excerpt_boundary.row),
),
Bias::Left,
)
}),
]
.row();
}
let starts_new_buffer = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
(_, None) => false,
(None, Some(_)) => true,
(Some(prev), Some(next)) => prev.buffer_id != next.buffer_id,
};
let mut height = 0;
if excerpt_boundary.prev.is_some() {
if show_excerpt_controls {
height += excerpt_footer_height;
}
}
if excerpt_boundary.next.is_some() {
if starts_new_buffer {
height += buffer_header_height;
if show_excerpt_controls {
height += excerpt_header_height;
}
} else {
height += excerpt_header_height;
}
}
if height == 0 {
return None;
}
Some((
wrap_row,
Block::ExcerptBoundary {
prev_excerpt: excerpt_boundary.prev,
next_excerpt: excerpt_boundary.next,
height,
starts_new_buffer,
show_excerpt_controls,
},
))
})
.flatten()
}
pub(crate) fn sort_blocks<B: BlockLike>(blocks: &mut [(u32, B)]) {
@@ -665,12 +639,9 @@ impl BlockMap {
.disposition()
.cmp(&block_b.disposition())
.then_with(|| match ((block_a.block_type()), (block_b.block_type())) {
(BlockType::Footer, BlockType::Footer) => Ordering::Equal,
(BlockType::Footer, _) => Ordering::Less,
(_, BlockType::Footer) => Ordering::Greater,
(BlockType::Header, BlockType::Header) => Ordering::Equal,
(BlockType::Header, _) => Ordering::Less,
(_, BlockType::Header) => Ordering::Greater,
(BlockType::ExcerptBoundary, BlockType::ExcerptBoundary) => Ordering::Equal,
(BlockType::ExcerptBoundary, _) => Ordering::Less,
(_, BlockType::ExcerptBoundary) => Ordering::Greater,
(BlockType::Custom(a_id), BlockType::Custom(b_id)) => block_b
.priority()
.cmp(&block_a.priority())
@@ -1045,33 +1016,19 @@ impl BlockSnapshot {
let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
Some(Block::Custom(custom_block.clone()))
}
BlockId::ExcerptHeader(excerpt_id) => {
let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
let wrap_point = self
.wrap_snapshot
.make_wrap_point(excerpt_range.start, Bias::Left);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
while let Some(transform) = cursor.item() {
if let Some(block) = transform.block.as_ref() {
if block.id() == block_id {
return Some(block.clone());
}
} else if cursor.start().0 > WrapRow(wrap_point.row()) {
break;
}
cursor.next(&());
BlockId::ExcerptBoundary(next_excerpt_id) => {
let wrap_point;
if let Some(next_excerpt_id) = next_excerpt_id {
let excerpt_range = buffer.range_for_excerpt::<Point>(next_excerpt_id)?;
wrap_point = self
.wrap_snapshot
.make_wrap_point(excerpt_range.start, Bias::Left);
} else {
wrap_point = self
.wrap_snapshot
.make_wrap_point(buffer.max_point(), Bias::Left);
}
None
}
BlockId::ExcerptFooter(excerpt_id) => {
let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
let wrap_point = self
.wrap_snapshot
.make_wrap_point(excerpt_range.end, Bias::Left);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
while let Some(transform) = cursor.item() {
@@ -1468,7 +1425,7 @@ mod tests {
};
use gpui::{div, font, px, AppContext, Context as _, Element};
use language::{Buffer, Capability};
use multi_buffer::MultiBuffer;
use multi_buffer::{ExcerptRange, MultiBuffer};
use rand::prelude::*;
use settings::SettingsStore;
use std::env;
@@ -1724,22 +1681,20 @@ mod tests {
// Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
assert_eq!(
snapshot.text(),
"\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n"
"\n\nBuff\ner 1\n\n\n\nBuff\ner 2\n\n\n\nBuff\ner 3\n"
);
let blocks: Vec<_> = snapshot
.blocks_in_range(0..u32::MAX)
.map(|(row, block)| (row, block.id()))
.map(|(row, block)| (row..row + block.height(), block.id()))
.collect();
assert_eq!(
blocks,
vec![
(0, BlockId::ExcerptHeader(excerpt_ids[0])),
(3, BlockId::ExcerptFooter(excerpt_ids[0])),
(4, BlockId::ExcerptHeader(excerpt_ids[1])),
(7, BlockId::ExcerptFooter(excerpt_ids[1])),
(8, BlockId::ExcerptHeader(excerpt_ids[2])),
(11, BlockId::ExcerptFooter(excerpt_ids[2]))
(0..2, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header
(4..7, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // footer, path, header
(9..12, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // footer, path, header
(14..15, BlockId::ExcerptBoundary(None)), // footer
]
);
}
@@ -2283,13 +2238,10 @@ mod tests {
#[derive(Debug, Eq, PartialEq)]
enum ExpectedBlock {
ExcerptHeader {
ExcerptBoundary {
height: u32,
starts_new_buffer: bool,
},
ExcerptFooter {
height: u32,
disposition: BlockDisposition,
is_last: bool,
},
Custom {
disposition: BlockDisposition,
@@ -2303,8 +2255,7 @@ mod tests {
fn block_type(&self) -> BlockType {
match self {
ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id),
ExpectedBlock::ExcerptHeader { .. } => BlockType::Header,
ExpectedBlock::ExcerptFooter { .. } => BlockType::Footer,
ExpectedBlock::ExcerptBoundary { .. } => BlockType::ExcerptBoundary,
}
}
@@ -2315,8 +2266,7 @@ mod tests {
fn priority(&self) -> usize {
match self {
ExpectedBlock::Custom { priority, .. } => *priority,
ExpectedBlock::ExcerptHeader { .. } => usize::MAX,
ExpectedBlock::ExcerptFooter { .. } => 0,
ExpectedBlock::ExcerptBoundary { .. } => usize::MAX,
}
}
}
@@ -2324,17 +2274,21 @@ mod tests {
impl ExpectedBlock {
fn height(&self) -> u32 {
match self {
ExpectedBlock::ExcerptHeader { height, .. } => *height,
ExpectedBlock::ExcerptBoundary { height, .. } => *height,
ExpectedBlock::Custom { height, .. } => *height,
ExpectedBlock::ExcerptFooter { height, .. } => *height,
}
}
fn disposition(&self) -> BlockDisposition {
match self {
ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
ExpectedBlock::ExcerptBoundary { is_last, .. } => {
if *is_last {
BlockDisposition::Below
} else {
BlockDisposition::Above
}
}
ExpectedBlock::Custom { disposition, .. } => *disposition,
ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition,
}
}
}
@@ -2348,21 +2302,15 @@ mod tests {
height: block.height,
priority: block.priority,
},
Block::ExcerptHeader {
Block::ExcerptBoundary {
height,
starts_new_buffer,
next_excerpt,
..
} => ExpectedBlock::ExcerptHeader {
} => ExpectedBlock::ExcerptBoundary {
height,
starts_new_buffer,
},
Block::ExcerptFooter {
height,
disposition,
..
} => ExpectedBlock::ExcerptFooter {
height,
disposition,
is_last: next_excerpt.is_none(),
},
}
}
@@ -2380,8 +2328,7 @@ mod tests {
fn as_custom(&self) -> Option<&CustomBlock> {
match self {
Block::Custom(block) => Some(block),
Block::ExcerptHeader { .. } => None,
Block::ExcerptFooter { .. } => None,
Block::ExcerptBoundary { .. } => None,
}
}
}

View File

@@ -73,12 +73,12 @@ use git::blame::GitBlame;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler,
VisualContext, WeakFocusHandle, WeakView, WindowContext,
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent,
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UTF16Selection,
UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext,
WeakFocusHandle, WeakView, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -90,7 +90,7 @@ pub use inline_completion_provider::*;
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
use language::{
language_settings::{self, all_language_settings, InlayHintSettings},
language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
Point, Selection, SelectionGoal, TransactionId,
@@ -171,7 +171,7 @@ use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSetti
use crate::hover_links::find_url;
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
pub const FILE_HEADER_HEIGHT: u32 = 1;
pub const FILE_HEADER_HEIGHT: u32 = 2;
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u32 = 1;
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
@@ -428,8 +428,7 @@ impl Default for EditorStyle {
}
pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
let show_background = all_language_settings(None, cx)
.language(None)
let show_background = language_settings::language_settings(None, None, cx)
.inlay_hints
.show_background;
@@ -640,7 +639,6 @@ pub struct Editor {
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
tasks_update_task: Option<Task<()>>,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
file_header_size: u32,
breadcrumb_header: Option<String>,
focused_block: Option<FocusedBlock>,
next_scroll_position: NextScrollCursorCenterTopBottom,
@@ -1846,7 +1844,6 @@ impl Editor {
}),
merge_adjacent: true,
};
let file_header_size = if show_excerpt_controls { 3 } else { 2 };
let display_map = cx.new_model(|cx| {
DisplayMap::new(
buffer.clone(),
@@ -1854,7 +1851,7 @@ impl Editor {
font_size,
None,
show_excerpt_controls,
file_header_size,
FILE_HEADER_HEIGHT,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
fold_placeholder,
@@ -2038,7 +2035,6 @@ impl Editor {
.restore_unsaved_buffers,
blame: None,
blame_subscription: None,
file_header_size,
tasks: Default::default(),
_subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed),
@@ -4251,7 +4247,10 @@ impl Editor {
.text_anchor_for_position(position, cx)?;
let settings = language_settings::language_settings(
buffer.read(cx).language_at(buffer_position).as_ref(),
buffer
.read(cx)
.language_at(buffer_position)
.map(|l| l.name()),
buffer.read(cx).file(),
cx,
);
@@ -6285,11 +6284,9 @@ impl Editor {
let project_path = buffer.read(cx).project_path(cx)?;
let project = self.project.as_ref()?.read(cx);
let entry = project.entry_for_path(&project_path, cx)?;
let abs_path = project.absolute_path(&project_path, cx)?;
let parent = if entry.is_symlink {
abs_path.canonicalize().ok()?
} else {
abs_path
let parent = match &entry.canonical_path {
Some(canonical_path) => canonical_path.to_path_buf(),
None => project.absolute_path(&project_path, cx)?,
}
.parent()?
.to_path_buf();
@@ -12808,7 +12805,7 @@ impl Editor {
}
pub fn file_header_size(&self) -> u32 {
self.file_header_size
FILE_HEADER_HEIGHT
}
pub fn revert(
@@ -13379,11 +13376,8 @@ fn inlay_hint_settings(
cx: &mut ViewContext<'_, Editor>,
) -> InlayHintSettings {
let file = snapshot.file_at(location);
let language = snapshot.language_at(location);
let settings = all_language_settings(file, cx);
settings
.language(language.map(|l| l.name()).as_ref())
.inlay_hints
let language = snapshot.language_at(location).map(|l| l.name());
language_settings(language, file, cx).inlay_hints
}
fn consume_contiguous_rows(
@@ -14120,7 +14114,7 @@ pub fn diagnostic_block_renderer(
let multi_line_diagnostic = diagnostic.message.contains('\n');
let buttons = |diagnostic: &Diagnostic, block_id: BlockId| {
let buttons = |diagnostic: &Diagnostic| {
if multi_line_diagnostic {
v_flex()
} else {
@@ -14128,7 +14122,7 @@ pub fn diagnostic_block_renderer(
}
.when(allow_closing, |div| {
div.children(diagnostic.is_primary.then(|| {
IconButton::new(("close-block", EntityId::from(block_id)), IconName::XCircle)
IconButton::new("close-block", IconName::XCircle)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.style(ButtonStyle::Transparent)
@@ -14138,7 +14132,7 @@ pub fn diagnostic_block_renderer(
}))
})
.child(
IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy)
IconButton::new("copy-block", IconName::Copy)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.style(ButtonStyle::Transparent)
@@ -14153,7 +14147,7 @@ pub fn diagnostic_block_renderer(
)
};
let icon_size = buttons(&diagnostic, cx.block_id)
let icon_size = buttons(&diagnostic)
.into_any_element()
.layout_as_root(AvailableSpace::min_size(), cx);
@@ -14170,7 +14164,7 @@ pub fn diagnostic_block_renderer(
.w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
.flex_shrink(),
)
.child(buttons(&diagnostic, cx.block_id))
.child(buttons(&diagnostic))
.child(div().flex().flex_shrink_0().child(
StyledText::new(text_without_backticks.clone()).with_highlights(
&text_style,

View File

@@ -21,7 +21,8 @@ use crate::{
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown,
PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
CURSORS_VISIBLE_FOR, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap};
@@ -31,7 +32,7 @@ use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
EntityId, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
@@ -46,7 +47,7 @@ use language::{
ChunkRendererContext,
};
use lsp::DiagnosticSeverity;
use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
use multi_buffer::{Anchor, ExcerptId, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow};
use project::{
project_settings::{GitGutterSetting, ProjectSettings},
ProjectPath,
@@ -1632,7 +1633,7 @@ impl EditorElement {
let mut block_offset = 0;
let mut found_excerpt_header = false;
for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) {
if matches!(block, Block::ExcerptHeader { .. }) {
if matches!(block, Block::ExcerptBoundary { .. }) {
found_excerpt_header = true;
break;
}
@@ -1649,7 +1650,7 @@ impl EditorElement {
let mut block_height = 0;
let mut found_excerpt_header = false;
for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) {
if matches!(block, Block::ExcerptHeader { .. }) {
if matches!(block, Block::ExcerptBoundary { .. }) {
found_excerpt_header = true;
}
block_height += block.height();
@@ -2100,23 +2101,14 @@ impl EditorElement {
.into_any_element()
}
Block::ExcerptHeader {
buffer,
range,
Block::ExcerptBoundary {
prev_excerpt,
next_excerpt,
show_excerpt_controls,
starts_new_buffer,
height,
id,
show_excerpt_controls,
..
} => {
let include_root = self
.editor
.read(cx)
.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default();
#[derive(Clone)]
struct JumpData {
position: Point,
@@ -2125,233 +2117,227 @@ impl EditorElement {
line_offset_from_top: u32,
}
let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
let jump_path = ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
};
let jump_anchor = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
let excerpt_start = range.context.start;
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
0
} else {
let excerpt_start_row =
language::ToPoint::to_point(&jump_anchor, buffer).row;
jump_position.row - excerpt_start_row
};
let line_offset_from_top =
block_row_start.0 + *height + offset_from_excerpt_start
- snapshot
.scroll_anchor
.scroll_position(&snapshot.display_snapshot)
.y as u32;
JumpData {
position: jump_position,
anchor: jump_anchor,
path: jump_path,
line_offset_from_top,
}
});
let icon_offset = gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin);
let element = if *starts_new_buffer {
let path = buffer.resolve_file_path(cx, include_root);
let mut filename = None;
let mut parent_path = None;
// Can't use .and_then() because `.file_name()` and `.parent()` return references :(
if let Some(path) = path {
filename = path.file_name().map(|f| f.to_string_lossy().to_string());
parent_path = path
.parent()
.map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
}
let header_padding = px(6.0);
let header_padding = px(6.0);
let mut result = v_flex().id(block_id).w_full();
v_flex()
.id(("path excerpt header", EntityId::from(block_id)))
.w_full()
.px(header_padding)
.pt(header_padding)
.child(
if let Some(prev_excerpt) = prev_excerpt {
if *show_excerpt_controls {
result = result.child(
h_flex()
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
.id("path header block")
.h(2. * cx.line_height())
.px(gpui::px(12.))
.rounded_md()
.shadow_md()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_subheader_background)
.justify_between()
.hover(|style| style.bg(cx.theme().colors().element_hover))
.child(
h_flex().gap_3().child(
h_flex()
.gap_2()
.child(
filename
.map(SharedString::from)
.unwrap_or_else(|| "untitled".into()),
)
.when_some(parent_path, |then, path| {
then.child(
div()
.child(path)
.text_color(cx.theme().colors().text_muted),
)
}),
),
)
.when_some(jump_data.clone(), |el, jump_data| {
el.child(Icon::new(IconName::ArrowUpRight))
.cursor_pointer()
.tooltip(|cx| {
Tooltip::for_action("Jump to File", &OpenExcerpts, cx)
})
.on_mouse_down(MouseButton::Left, |_, cx| {
cx.stop_propagation()
})
.on_click(cx.listener_for(&self.editor, {
move |editor, _, cx| {
editor.jump(
jump_data.path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
}),
)
.children(show_excerpt_controls.then(|| {
h_flex()
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.333)))
.h(1. * cx.line_height())
.pt_1()
.justify_end()
.w(icon_offset)
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
.flex_none()
.w(icon_offset - header_padding)
.child(
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(IconName::ArrowUpFromLine.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group("")
.hover(|style| {
style.text_color(
cx.theme()
.colors()
.editor_active_line_number,
)
}),
)
.on_click(cx.listener_for(&self.editor, {
let id = *id;
move |editor, _, cx| {
editor.expand_excerpt(
id,
multi_buffer::ExpandExcerptDirection::Up,
cx,
);
}
}))
.tooltip({
move |cx| {
Tooltip::for_action(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
cx,
)
}
}),
)
}))
} else {
v_flex()
.id(("excerpt header", EntityId::from(block_id)))
.w_full()
.h(snapshot.excerpt_header_height() as f32 * cx.line_height())
.child(
.justify_end()
.child(self.render_expand_excerpt_button(
prev_excerpt.id,
ExpandExcerptDirection::Down,
IconName::ArrowDownFromLine,
cx,
)),
);
}
}
if let Some(next_excerpt) = next_excerpt {
let buffer = &next_excerpt.buffer;
let range = &next_excerpt.range;
let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
let jump_path = ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
};
let jump_anchor = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
let excerpt_start = range.context.start;
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
0
} else {
let excerpt_start_row =
language::ToPoint::to_point(&jump_anchor, buffer).row;
jump_position.row - excerpt_start_row
};
let line_offset_from_top =
block_row_start.0 + *height + offset_from_excerpt_start
- snapshot
.scroll_anchor
.scroll_position(&snapshot.display_snapshot)
.y as u32;
JumpData {
position: jump_position,
anchor: jump_anchor,
path: jump_path,
line_offset_from_top,
}
});
if *starts_new_buffer {
let include_root = self
.editor
.read(cx)
.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default();
let path = buffer.resolve_file_path(cx, include_root);
let filename = path
.as_ref()
.and_then(|path| Some(path.file_name()?.to_string_lossy().to_string()));
let parent_path = path.as_ref().and_then(|path| {
Some(path.parent()?.to_string_lossy().to_string() + "/")
});
result = result.child(
div()
.flex()
.v_flex()
.px(header_padding)
.pt(header_padding)
.w_full()
.h(FILE_HEADER_HEIGHT as f32 * cx.line_height())
.child(
h_flex()
.id("path header block")
.size_full()
.flex_basis(Length::Definite(DefiniteLength::Fraction(
0.667,
)))
.px(gpui::px(12.))
.rounded_md()
.shadow_md()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_subheader_background)
.justify_between()
.hover(|style| style.bg(cx.theme().colors().element_hover))
.child(
h_flex().gap_3().child(
h_flex()
.gap_2()
.child(
filename
.map(SharedString::from)
.unwrap_or_else(|| "untitled".into()),
)
.when_some(parent_path, |then, path| {
then.child(div().child(path).text_color(
cx.theme().colors().text_muted,
))
}),
),
)
.when_some(jump_data, |el, jump_data| {
el.child(Icon::new(IconName::ArrowUpRight))
.cursor_pointer()
.tooltip(|cx| {
Tooltip::for_action(
"Jump to File",
&OpenExcerpts,
cx,
)
})
.on_mouse_down(MouseButton::Left, |_, cx| {
cx.stop_propagation()
})
.on_click(cx.listener_for(&self.editor, {
move |editor, _, cx| {
editor.jump(
jump_data.path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
}),
),
);
if *show_excerpt_controls {
result = result.child(
h_flex()
.w(icon_offset)
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
.flex_none()
.justify_end()
.child(self.render_expand_excerpt_button(
next_excerpt.id,
ExpandExcerptDirection::Up,
IconName::ArrowUpFromLine,
cx,
)),
);
}
} else {
result = result.child(
h_flex()
.id("excerpt header block")
.group("excerpt-jump-action")
.justify_start()
.id("jump to collapsed context")
.w(relative(1.0))
.h_full()
.w_full()
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
.relative()
.child(
div()
.h_px()
.top(px(0.))
.absolute()
.w_full()
.h_px()
.bg(cx.theme().colors().border_variant)
.group_hover("excerpt-jump-action", |style| {
style.bg(cx.theme().colors().border)
}),
),
)
.child(
h_flex()
.justify_end()
.flex_none()
.w(icon_offset)
.h_full()
)
.cursor_pointer()
.when_some(jump_data.clone(), |this, jump_data| {
this.on_click(cx.listener_for(&self.editor, {
let path = jump_data.path.clone();
move |editor, _, cx| {
cx.stop_propagation();
editor.jump(
path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
.tooltip(move |cx| {
Tooltip::for_action(
format!(
"Jump to {}:L{}",
jump_data.path.path.display(),
jump_data.position.row + 1
),
&OpenExcerpts,
cx,
)
})
})
.child(
show_excerpt_controls
.then(|| {
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(IconName::ArrowUpFromLine.path())
.size(IconSize::XSmall.rems())
.text_color(
cx.theme().colors().editor_line_number,
)
.group("")
.hover(|style| {
style.text_color(
cx.theme()
.colors()
.editor_active_line_number,
)
}),
)
.on_click(cx.listener_for(&self.editor, {
let id = *id;
move |editor, _, cx| {
editor.expand_excerpt(
id,
multi_buffer::ExpandExcerptDirection::Up,
cx,
);
}
}))
.tooltip({
move |cx| {
Tooltip::for_action(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
cx,
)
}
})
})
.unwrap_or_else(|| {
h_flex()
.w(icon_offset)
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32
* cx.line_height())
.flex_none()
.justify_end()
.child(if *show_excerpt_controls {
self.render_expand_excerpt_button(
next_excerpt.id,
ExpandExcerptDirection::Up,
IconName::ArrowUpFromLine,
cx,
)
} else {
ButtonLike::new("jump-icon")
.style(ButtonStyle::Transparent)
.child(
@@ -2361,7 +2347,6 @@ impl EditorElement {
.text_color(
cx.theme().colors().border_variant,
)
.group("excerpt-jump-action")
.group_hover(
"excerpt-jump-action",
|style| {
@@ -2371,118 +2356,13 @@ impl EditorElement {
},
),
)
.when_some(jump_data.clone(), |this, jump_data| {
this.on_click(cx.listener_for(&self.editor, {
let path = jump_data.path.clone();
move |editor, _, cx| {
cx.stop_propagation();
editor.jump(
path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
.tooltip(move |cx| {
Tooltip::for_action(
format!(
"Jump to {}:L{}",
jump_data.path.path.display(),
jump_data.position.row + 1
),
&OpenExcerpts,
cx,
)
})
})
}),
),
)
.group("excerpt-jump-action")
.cursor_pointer()
.when_some(jump_data.clone(), |this, jump_data| {
this.on_click(cx.listener_for(&self.editor, {
let path = jump_data.path.clone();
move |editor, _, cx| {
cx.stop_propagation();
);
}
}
editor.jump(
path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
.tooltip(move |cx| {
Tooltip::for_action(
format!(
"Jump to {}:L{}",
jump_data.path.path.display(),
jump_data.position.row + 1
),
&OpenExcerpts,
cx,
)
})
})
};
element.into_any()
}
Block::ExcerptFooter { id, .. } => {
let element = v_flex()
.id(("excerpt footer", EntityId::from(block_id)))
.w_full()
.h(snapshot.excerpt_footer_height() as f32 * cx.line_height())
.child(
h_flex()
.justify_end()
.flex_none()
.w(gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin))
.h_full()
.child(
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(IconName::ArrowDownFromLine.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group("")
.hover(|style| {
style.text_color(
cx.theme().colors().editor_active_line_number,
)
}),
)
.on_click(cx.listener_for(&self.editor, {
let id = *id;
move |editor, _, cx| {
editor.expand_excerpt(
id,
multi_buffer::ExpandExcerptDirection::Down,
cx,
);
}
}))
.tooltip({
move |cx| {
Tooltip::for_action(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
cx,
)
}
}),
),
);
element.into_any()
result.into_any()
}
};
@@ -2509,6 +2389,33 @@ impl EditorElement {
(element, final_size)
}
fn render_expand_excerpt_button(
&self,
excerpt_id: ExcerptId,
direction: ExpandExcerptDirection,
icon: IconName,
cx: &mut WindowContext,
) -> ButtonLike {
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(icon.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group("")
.hover(|style| style.text_color(cx.theme().colors().editor_active_line_number)),
)
.on_click(cx.listener_for(&self.editor, {
move |editor, _, cx| {
editor.expand_excerpt(excerpt_id, direction, cx);
}
}))
.tooltip({
move |cx| Tooltip::for_action("Expand Excerpt", &ExpandExcerpts { lines: 0 }, cx)
})
}
#[allow(clippy::too_many_arguments)]
fn render_blocks(
&self,
@@ -3367,7 +3274,7 @@ impl EditorElement {
let end_row_in_current_excerpt = snapshot
.blocks_in_range(start_row..end_row)
.find_map(|(start_row, block)| {
if matches!(block, Block::ExcerptHeader { .. }) {
if matches!(block, Block::ExcerptBoundary { .. }) {
Some(start_row)
} else {
None

View File

@@ -39,9 +39,13 @@ impl Editor {
) -> Option<Vec<MultiBufferIndentGuide>> {
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
language_settings(buffer.read(cx).language(), buffer.read(cx).file(), cx)
.indent_guides
.enabled
language_settings(
buffer.read(cx).language().map(|l| l.name()),
buffer.read(cx).file(),
cx,
)
.indent_guides
.enabled
} else {
true
}

View File

@@ -952,7 +952,7 @@ mod tests {
px(14.0),
None,
true,
2,
0,
2,
0,
FoldPlaceholder::test(),

View File

@@ -356,8 +356,11 @@ impl ExtensionImports for WasmState {
cx.update(|cx| match category.as_str() {
"language" => {
let key = key.map(|k| LanguageName::new(&k));
let settings =
AllLanguageSettings::get(location, cx).language(key.as_ref());
let settings = AllLanguageSettings::get(location, cx).language(
location,
key.as_ref(),
cx,
);
Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size,
})?)

View File

@@ -402,8 +402,11 @@ impl ExtensionImports for WasmState {
cx.update(|cx| match category.as_str() {
"language" => {
let key = key.map(|k| LanguageName::new(&k));
let settings =
AllLanguageSettings::get(location, cx).language(key.as_ref());
let settings = AllLanguageSettings::get(location, cx).language(
location,
key.as_ref(),
cx,
);
Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size,
})?)

View File

@@ -62,7 +62,7 @@ impl Render for InlineCompletionButton {
let status = copilot.read(cx).status();
let enabled = self.editor_enabled.unwrap_or_else(|| {
all_language_settings.inline_completions_enabled(None, None)
all_language_settings.inline_completions_enabled(None, None, cx)
});
let icon = match status {
@@ -248,8 +248,9 @@ impl InlineCompletionButton {
if let Some(language) = self.language.clone() {
let fs = fs.clone();
let language_enabled = language_settings::language_settings(Some(&language), None, cx)
.show_inline_completions;
let language_enabled =
language_settings::language_settings(Some(language.name()), None, cx)
.show_inline_completions;
menu = menu.entry(
format!(
@@ -292,7 +293,7 @@ impl InlineCompletionButton {
);
}
let globally_enabled = settings.inline_completions_enabled(None, None);
let globally_enabled = settings.inline_completions_enabled(None, None, cx);
menu.entry(
if globally_enabled {
"Hide Inline Completions for All Files"
@@ -340,6 +341,7 @@ impl InlineCompletionButton {
&& all_language_settings(file, cx).inline_completions_enabled(
language,
file.map(|file| file.path().as_ref()),
cx,
),
)
};
@@ -442,7 +444,7 @@ async fn configure_disabled_globs(
fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let show_inline_completions =
all_language_settings(None, cx).inline_completions_enabled(None, None);
all_language_settings(None, cx).inline_completions_enabled(None, None, cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.defaults.show_inline_completions = Some(!show_inline_completions)
});
@@ -466,7 +468,7 @@ fn toggle_inline_completions_for_language(
cx: &mut AppContext,
) {
let show_inline_completions =
all_language_settings(None, cx).inline_completions_enabled(Some(&language), None);
all_language_settings(None, cx).inline_completions_enabled(Some(&language), None, cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.languages
.entry(language.name())

View File

@@ -30,6 +30,7 @@ async-trait.workspace = true
async-watch.workspace = true
clock.workspace = true
collections.workspace = true
ec4rs.workspace = true
futures.workspace = true
fuzzy.workspace = true
git.workspace = true

View File

@@ -37,6 +37,7 @@ use smallvec::SmallVec;
use smol::future::yield_now;
use std::{
any::Any,
borrow::Cow,
cell::Cell,
cmp::{self, Ordering, Reverse},
collections::BTreeMap,
@@ -2490,7 +2491,11 @@ impl BufferSnapshot {
/// Returns [`IndentSize`] for a given position that respects user settings
/// and language preferences.
pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
let settings = language_settings(self.language_at(position), self.file(), cx);
let settings = language_settings(
self.language_at(position).map(|l| l.name()),
self.file(),
cx,
);
if settings.hard_tabs {
IndentSize::tab()
} else {
@@ -2823,11 +2828,15 @@ impl BufferSnapshot {
/// Returns the settings for the language at the given location.
pub fn settings_at<'a, D: ToOffset>(
&self,
&'a self,
position: D,
cx: &'a AppContext,
) -> &'a LanguageSettings {
language_settings(self.language_at(position), self.file.as_ref(), cx)
) -> Cow<'a, LanguageSettings> {
language_settings(
self.language_at(position).map(|l| l.name()),
self.file.as_ref(),
cx,
)
}
pub fn char_classifier_at<T: ToOffset>(&self, point: T) -> CharClassifier {
@@ -3529,7 +3538,8 @@ impl BufferSnapshot {
ignore_disabled_for_language: bool,
cx: &AppContext,
) -> Vec<IndentGuide> {
let language_settings = language_settings(self.language(), self.file.as_ref(), cx);
let language_settings =
language_settings(self.language().map(|l| l.name()), self.file.as_ref(), cx);
let settings = language_settings.indent_guides;
if !ignore_disabled_for_language && !settings.enabled {
return Vec::new();

View File

@@ -4,6 +4,10 @@ use crate::{File, Language, LanguageName, LanguageServerName};
use anyhow::Result;
use collections::{HashMap, HashSet};
use core::slice;
use ec4rs::{
property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
Properties as EditorconfigProperties,
};
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::AppContext;
use itertools::{Either, Itertools};
@@ -16,8 +20,10 @@ use serde::{
Deserialize, Deserializer, Serialize,
};
use serde_json::Value;
use settings::{add_references_to_properties, Settings, SettingsLocation, SettingsSources};
use std::{num::NonZeroU32, path::Path, sync::Arc};
use settings::{
add_references_to_properties, Settings, SettingsLocation, SettingsSources, SettingsStore,
};
use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc};
use util::serde::default_true;
/// Initializes the language settings.
@@ -27,17 +33,20 @@ pub fn init(cx: &mut AppContext) {
/// Returns the settings for the specified language from the provided file.
pub fn language_settings<'a>(
language: Option<&Arc<Language>>,
file: Option<&Arc<dyn File>>,
language: Option<LanguageName>,
file: Option<&'a Arc<dyn File>>,
cx: &'a AppContext,
) -> &'a LanguageSettings {
let language_name = language.map(|l| l.name());
all_language_settings(file, cx).language(language_name.as_ref())
) -> Cow<'a, LanguageSettings> {
let location = file.map(|f| SettingsLocation {
worktree_id: f.worktree_id(cx),
path: f.path().as_ref(),
});
AllLanguageSettings::get(location, cx).language(location, language.as_ref(), cx)
}
/// Returns the settings for all languages from the provided file.
pub fn all_language_settings<'a>(
file: Option<&Arc<dyn File>>,
file: Option<&'a Arc<dyn File>>,
cx: &'a AppContext,
) -> &'a AllLanguageSettings {
let location = file.map(|f| SettingsLocation {
@@ -810,13 +819,27 @@ impl InlayHintSettings {
impl AllLanguageSettings {
/// Returns the [`LanguageSettings`] for the language with the specified name.
pub fn language<'a>(&'a self, language_name: Option<&LanguageName>) -> &'a LanguageSettings {
if let Some(name) = language_name {
if let Some(overrides) = self.languages.get(name) {
return overrides;
}
pub fn language<'a>(
&'a self,
location: Option<SettingsLocation<'a>>,
language_name: Option<&LanguageName>,
cx: &'a AppContext,
) -> Cow<'a, LanguageSettings> {
let settings = language_name
.and_then(|name| self.languages.get(name))
.unwrap_or(&self.defaults);
let editorconfig_properties = location.and_then(|location| {
cx.global::<SettingsStore>()
.editorconfg_properties(location.worktree_id, location.path)
});
if let Some(editorconfig_properties) = editorconfig_properties {
let mut settings = settings.clone();
merge_with_editorconfig(&mut settings, &editorconfig_properties);
Cow::Owned(settings)
} else {
Cow::Borrowed(settings)
}
&self.defaults
}
/// Returns whether inline completions are enabled for the given path.
@@ -833,6 +856,7 @@ impl AllLanguageSettings {
&self,
language: Option<&Arc<Language>>,
path: Option<&Path>,
cx: &AppContext,
) -> bool {
if let Some(path) = path {
if !self.inline_completions_enabled_for_path(path) {
@@ -840,11 +864,64 @@ impl AllLanguageSettings {
}
}
self.language(language.map(|l| l.name()).as_ref())
self.language(None, language.map(|l| l.name()).as_ref(), cx)
.show_inline_completions
}
}
fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
let max_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
MaxLineLen::Value(u) => Some(u as u32),
MaxLineLen::Off => None,
});
let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
IndentSize::Value(u) => NonZeroU32::new(u as u32),
IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
TabWidth::Value(u) => NonZeroU32::new(u as u32),
}),
});
let hard_tabs = cfg
.get::<IndentStyle>()
.map(|v| v.eq(&IndentStyle::Tabs))
.ok();
let ensure_final_newline_on_save = cfg
.get::<FinalNewline>()
.map(|v| match v {
FinalNewline::Value(b) => b,
})
.ok();
let remove_trailing_whitespace_on_save = cfg
.get::<TrimTrailingWs>()
.map(|v| match v {
TrimTrailingWs::Value(b) => b,
})
.ok();
let preferred_line_length = max_line_length;
let soft_wrap = if max_line_length.is_some() {
Some(SoftWrap::PreferredLineLength)
} else {
None
};
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;
}
}
merge(&mut settings.tab_size, tab_size);
merge(&mut settings.hard_tabs, hard_tabs);
merge(
&mut settings.remove_trailing_whitespace_on_save,
remove_trailing_whitespace_on_save,
);
merge(
&mut settings.ensure_final_newline_on_save,
ensure_final_newline_on_save,
);
merge(&mut settings.preferred_line_length, preferred_line_length);
merge(&mut settings.soft_wrap, soft_wrap);
}
/// The kind of an inlay hint.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InlayHintKind {

View File

@@ -1,6 +1,6 @@
name = "CSS"
grammar = "css"
path_suffixes = ["css"]
path_suffixes = ["css", "postcss"]
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },

View File

@@ -6,7 +6,6 @@ use futures::{io::BufReader, StreamExt};
use gpui::{AppContext, AsyncAppContext};
use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
pub use language::*;
use language_settings::all_language_settings;
use lsp::LanguageServerBinary;
use regex::Regex;
use smol::fs::{self, File};
@@ -21,6 +20,8 @@ use std::{
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
use util::{fs::remove_matching, maybe, ResultExt};
use crate::language_settings::language_settings;
pub struct RustLspAdapter;
impl RustLspAdapter {
@@ -424,13 +425,13 @@ impl ContextProvider for RustContextProvider {
cx: &AppContext,
) -> Option<TaskTemplates> {
const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN";
let package_to_run = all_language_settings(file.as_ref(), cx)
.language(Some(&"Rust".into()))
let package_to_run = language_settings(Some("Rust".into()), file.as_ref(), cx)
.tasks
.variables
.get(DEFAULT_RUN_NAME_STR);
.get(DEFAULT_RUN_NAME_STR)
.cloned();
let run_task_args = if let Some(package_to_run) = package_to_run {
vec!["run".into(), "-p".into(), package_to_run.clone()]
vec!["run".into(), "-p".into(), package_to_run]
} else {
vec!["run".into()]
};

View File

@@ -101,7 +101,7 @@ impl LspAdapter for YamlLspAdapter {
let tab_size = cx.update(|cx| {
AllLanguageSettings::get(Some(location), cx)
.language(Some(&"YAML".into()))
.language(Some(location), Some(&"YAML".into()), cx)
.tab_size
})?;
let mut options = serde_json::json!({"[yaml]": {"editor.tabSize": tab_size}});

View File

@@ -102,6 +102,8 @@ impl<'a> MarkdownParser<'a> {
while !self.eof() {
if let Some(block) = self.parse_block().await {
self.parsed.extend(block);
} else {
self.cursor += 1;
}
}
self
@@ -163,20 +165,14 @@ impl<'a> MarkdownParser<'a> {
let code_block = self.parse_code_block(language).await;
Some(vec![ParsedMarkdownElement::CodeBlock(code_block)])
}
_ => {
self.cursor += 1;
None
}
_ => None,
},
Event::Rule => {
let source_range = source_range.clone();
self.cursor += 1;
Some(vec![ParsedMarkdownElement::HorizontalRule(source_range)])
}
_ => {
self.cursor += 1;
None
}
_ => None,
}
}
@@ -1000,6 +996,8 @@ Some other content
- Inner
- Inner
2. Goodbyte
- Next item empty
-
* Last
",
)
@@ -1021,8 +1019,10 @@ Some other content
list_item(97..116, 3, Ordered(1), vec![p("Goodbyte", 100..108)]),
list_item(117..124, 4, Unordered, vec![p("Inner", 119..124)]),
list_item(133..140, 4, Unordered, vec![p("Inner", 135..140)]),
list_item(143..154, 2, Ordered(2), vec![p("Goodbyte", 146..154)]),
list_item(155..161, 1, Unordered, vec![p("Last", 157..161)]),
list_item(143..159, 2, Ordered(2), vec![p("Goodbyte", 146..154)]),
list_item(160..180, 3, Unordered, vec![p("Next item empty", 165..180)]),
list_item(186..190, 3, Unordered, vec![]),
list_item(191..197, 1, Unordered, vec![p("Last", 193..197)]),
]
);
}

View File

@@ -189,6 +189,7 @@ pub struct MultiBufferSnapshot {
show_headers: bool,
}
#[derive(Clone)]
pub struct ExcerptInfo {
pub id: ExcerptId,
pub buffer: BufferSnapshot,
@@ -201,6 +202,7 @@ impl std::fmt::Debug for ExcerptInfo {
f.debug_struct(type_name::<Self>())
.field("id", &self.id)
.field("buffer_id", &self.buffer_id)
.field("path", &self.buffer.file().map(|f| f.path()))
.field("range", &self.range)
.finish()
}
@@ -1776,7 +1778,7 @@ impl MultiBuffer {
&self,
point: T,
cx: &'a AppContext,
) -> &'a LanguageSettings {
) -> Cow<'a, LanguageSettings> {
let mut language = None;
let mut file = None;
if let Some((buffer, offset, _)) = self.point_to_buffer_offset(point, cx) {
@@ -1784,7 +1786,7 @@ impl MultiBuffer {
language = buffer.language_at(offset);
file = buffer.file();
}
language_settings(language.as_ref(), file, cx)
language_settings(language.map(|l| l.name()), file, cx)
}
pub fn for_each_buffer(&self, mut f: impl FnMut(&Model<Buffer>)) {
@@ -3578,14 +3580,14 @@ impl MultiBufferSnapshot {
&'a self,
point: T,
cx: &'a AppContext,
) -> &'a LanguageSettings {
) -> Cow<'a, LanguageSettings> {
let mut language = None;
let mut file = None;
if let Some((buffer, offset)) = self.point_to_buffer_offset(point) {
language = buffer.language_at(offset);
file = buffer.file();
}
language_settings(language, file, cx)
language_settings(language.map(|l| l.name()), file, cx)
}
pub fn language_scope_at<T: ToOffset>(&self, point: T) -> Option<LanguageScope> {

View File

@@ -293,3 +293,6 @@ pub fn local_tasks_file_relative_path() -> &'static Path {
pub fn local_vscode_tasks_file_relative_path() -> &'static Path {
Path::new(".vscode/tasks.json")
}
/// A default editorconfig file name to use when resolving project settings.
pub const EDITORCONFIG_NAME: &str = ".editorconfig";

View File

@@ -205,7 +205,7 @@ impl Prettier {
let params = buffer
.update(cx, |buffer, cx| {
let buffer_language = buffer.language();
let language_settings = language_settings(buffer_language, buffer.file(), cx);
let language_settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx);
let prettier_settings = &language_settings.prettier;
anyhow::ensure!(
prettier_settings.allowed,

View File

@@ -2303,7 +2303,9 @@ impl LspCommand for OnTypeFormatting {
.await?;
let options = buffer.update(&mut cx, |buffer, cx| {
lsp_formatting_options(language_settings(buffer.language(), buffer.file(), cx))
lsp_formatting_options(
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx).as_ref(),
)
})?;
Ok(Self {

View File

@@ -30,8 +30,7 @@ use gpui::{
use http_client::HttpClient;
use language::{
language_settings::{
all_language_settings, language_settings, AllLanguageSettings, FormatOnSave, Formatter,
LanguageSettings, SelectedFormatter,
language_settings, FormatOnSave, Formatter, LanguageSettings, SelectedFormatter,
},
markdown, point_to_lsp, prepare_completion_documentation,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
@@ -223,7 +222,8 @@ impl LocalLspStore {
})?;
let settings = buffer.handle.update(&mut cx, |buffer, cx| {
language_settings(buffer.language(), buffer.file(), cx).clone()
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
.into_owned()
})?;
let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
@@ -280,7 +280,7 @@ impl LocalLspStore {
.zip(buffer.abs_path.as_ref());
let prettier_settings = buffer.handle.read_with(&cx, |buffer, cx| {
language_settings(buffer.language(), buffer.file(), cx)
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
.prettier
.clone()
})?;
@@ -1225,7 +1225,8 @@ impl LspStore {
});
let buffer_file = buffer.read(cx).file().cloned();
let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
let settings =
language_settings(Some(new_language.name()), buffer_file.as_ref(), cx).into_owned();
let buffer_file = File::from_dyn(buffer_file.as_ref());
let worktree_id = if let Some(file) = buffer_file {
@@ -1400,15 +1401,17 @@ impl LspStore {
let buffer = buffer.read(cx);
let buffer_file = File::from_dyn(buffer.file());
let buffer_language = buffer.language();
let settings = language_settings(buffer_language, buffer.file(), cx);
let settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx);
if let Some(language) = buffer_language {
if settings.enable_language_server {
if let Some(file) = buffer_file {
language_servers_to_start.push((file.worktree.clone(), language.name()));
}
}
language_formatters_to_check
.push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
language_formatters_to_check.push((
buffer_file.map(|f| f.worktree_id(cx)),
settings.into_owned(),
));
}
}
@@ -1433,10 +1436,13 @@ impl LspStore {
});
if let Some((language, adapter)) = language {
let worktree = self.worktree_for_id(worktree_id, cx).ok();
let file = worktree.as_ref().and_then(|tree| {
tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
let root_file = worktree.as_ref().and_then(|worktree| {
worktree
.update(cx, |tree, cx| tree.root_file(cx))
.map(|f| f as _)
});
if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
let settings = language_settings(Some(language.name()), root_file.as_ref(), cx);
if !settings.enable_language_server {
language_servers_to_stop.push((worktree_id, started_lsp_name.clone()));
} else if let Some(worktree) = worktree {
let server_name = &adapter.name;
@@ -1753,10 +1759,9 @@ impl LspStore {
})
.filter(|_| {
maybe!({
let language_name = buffer.read(cx).language_at(position)?.name();
let language = buffer.read(cx).language_at(position)?;
Some(
AllLanguageSettings::get_global(cx)
.language(Some(&language_name))
language_settings(Some(language.name()), buffer.read(cx).file(), cx)
.linked_edits,
)
}) == Some(true)
@@ -1850,11 +1855,14 @@ impl LspStore {
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> {
let options = buffer.update(cx, |buffer, cx| {
lsp_command::lsp_formatting_options(language_settings(
buffer.language_at(position).as_ref(),
buffer.file(),
cx,
))
lsp_command::lsp_formatting_options(
language_settings(
buffer.language_at(position).map(|l| l.name()),
buffer.file(),
cx,
)
.as_ref(),
)
});
self.request_lsp(
buffer.clone(),
@@ -5288,23 +5296,16 @@ impl LspStore {
})
}
fn language_settings<'a>(
&'a self,
worktree: &'a Model<Worktree>,
language: &LanguageName,
cx: &'a mut ModelContext<Self>,
) -> &'a LanguageSettings {
let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx));
all_language_settings(root_file.map(|f| f as _).as_ref(), cx).language(Some(language))
}
pub fn start_language_servers(
&mut self,
worktree: &Model<Worktree>,
language: LanguageName,
cx: &mut ModelContext<Self>,
) {
let settings = self.language_settings(worktree, &language, cx);
let root_file = worktree
.update(cx, |tree, cx| tree.root_file(cx))
.map(|f| f as _);
let settings = language_settings(Some(language.clone()), root_file.as_ref(), cx);
if !settings.enable_language_server || self.mode.is_remote() {
return;
}
@@ -6057,6 +6058,16 @@ impl LspStore {
);
})?;
}
"textDocument/rename" => {
this.update(&mut cx, |this, _| {
if let Some(server) = this.language_server_for_id(server_id)
{
server.update_capabilities(|capabilities| {
capabilities.rename_provider = None
})
}
})?;
}
"textDocument/rangeFormatting" => {
this.update(&mut cx, |this, _| {
if let Some(server) = this.language_server_for_id(server_id)

View File

@@ -1243,6 +1243,10 @@ impl Project {
self.client.clone()
}
pub fn ssh_client(&self) -> Option<Model<SshRemoteClient>> {
self.ssh_client.clone()
}
pub fn user_store(&self) -> Model<UserStore> {
self.user_store.clone()
}

View File

@@ -5,7 +5,7 @@ use gpui::{AppContext, AsyncAppContext, BorrowAppContext, EventEmitter, Model, M
use language::LanguageServerName;
use paths::{
local_settings_file_relative_path, local_tasks_file_relative_path,
local_vscode_tasks_file_relative_path,
local_vscode_tasks_file_relative_path, EDITORCONFIG_NAME,
};
use rpc::{proto, AnyProtoClient, TypedEnvelope};
use schemars::JsonSchema;
@@ -287,14 +287,29 @@ impl SettingsObserver {
let store = cx.global::<SettingsStore>();
for worktree in self.worktree_store.read(cx).worktrees() {
let worktree_id = worktree.read(cx).id().to_proto();
for (path, kind, content) in store.local_settings(worktree.read(cx).id()) {
for (path, content) in store.local_settings(worktree.read(cx).id()) {
downstream_client
.send(proto::UpdateWorktreeSettings {
project_id,
worktree_id,
path: path.to_string_lossy().into(),
content: Some(content),
kind: Some(local_settings_kind_to_proto(kind).into()),
kind: Some(
local_settings_kind_to_proto(LocalSettingsKind::Settings).into(),
),
})
.log_err();
}
for (path, content, _) in store.local_editorconfig_settings(worktree.read(cx).id()) {
downstream_client
.send(proto::UpdateWorktreeSettings {
project_id,
worktree_id,
path: path.to_string_lossy().into(),
content: Some(content),
kind: Some(
local_settings_kind_to_proto(LocalSettingsKind::Editorconfig).into(),
),
})
.log_err();
}
@@ -453,6 +468,11 @@ impl SettingsObserver {
.unwrap(),
);
(settings_dir, LocalSettingsKind::Tasks)
} else if path.ends_with(EDITORCONFIG_NAME) {
let Some(settings_dir) = path.parent().map(Arc::from) else {
continue;
};
(settings_dir, LocalSettingsKind::Editorconfig)
} else {
continue;
};

View File

@@ -4,7 +4,9 @@ use futures::{future, StreamExt};
use gpui::{AppContext, SemanticVersion, UpdateGlobal};
use http_client::Url;
use language::{
language_settings::{language_settings, AllLanguageSettings, LanguageSettingsContent},
language_settings::{
language_settings, AllLanguageSettings, LanguageSettingsContent, SoftWrap,
},
tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter,
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,
};
@@ -15,7 +17,7 @@ use serde_json::json;
#[cfg(not(windows))]
use std::os;
use std::{mem, ops::Range, task::Poll};
use std::{mem, num::NonZeroU32, ops::Range, task::Poll};
use task::{ResolvedTask, TaskContext};
use unindent::Unindent as _;
use util::{assert_set_eq, paths::PathMatcher, test::temp_tree, TryFutureExt as _};
@@ -91,6 +93,107 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
});
}
#[gpui::test]
async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) {
init_test(cx);
let dir = temp_tree(json!({
".editorconfig": r#"
root = true
[*.rs]
indent_style = tab
indent_size = 3
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 80
[*.js]
tab_width = 10
"#,
".zed": {
"settings.json": r#"{
"tab_size": 8,
"hard_tabs": false,
"ensure_final_newline_on_save": false,
"remove_trailing_whitespace_on_save": false,
"preferred_line_length": 64,
"soft_wrap": "editor_width"
}"#,
},
"a.rs": "fn a() {\n A\n}",
"b": {
".editorconfig": r#"
[*.rs]
indent_size = 2
max_line_length = off
"#,
"b.rs": "fn b() {\n B\n}",
},
"c.js": "def c\n C\nend",
"README.json": "tabs are better\n",
}));
let path = dir.path();
let fs = FakeFs::new(cx.executor());
fs.insert_tree_from_real_fs(path, path).await;
let project = Project::test(fs, [path], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(js_lang());
language_registry.add(json_lang());
language_registry.add(rust_lang());
let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
cx.executor().run_until_parked();
cx.update(|cx| {
let tree = worktree.read(cx);
let settings_for = |path: &str| {
let file_entry = tree.entry_for_path(path).unwrap().clone();
let file = File::for_entry(file_entry, worktree.clone());
let file_language = project
.read(cx)
.languages()
.language_for_file_path(file.path.as_ref());
let file_language = cx
.background_executor()
.block(file_language)
.expect("Failed to get file language");
let file = file as _;
language_settings(Some(file_language.name()), Some(&file), cx).into_owned()
};
let settings_a = settings_for("a.rs");
let settings_b = settings_for("b/b.rs");
let settings_c = settings_for("c.js");
let settings_readme = settings_for("README.json");
// .editorconfig overrides .zed/settings
assert_eq!(Some(settings_a.tab_size), NonZeroU32::new(3));
assert_eq!(settings_a.hard_tabs, true);
assert_eq!(settings_a.ensure_final_newline_on_save, true);
assert_eq!(settings_a.remove_trailing_whitespace_on_save, true);
assert_eq!(settings_a.preferred_line_length, 80);
// "max_line_length" also sets "soft_wrap"
assert_eq!(settings_a.soft_wrap, SoftWrap::PreferredLineLength);
// .editorconfig in b/ overrides .editorconfig in root
assert_eq!(Some(settings_b.tab_size), NonZeroU32::new(2));
// "indent_size" is not set, so "tab_width" is used
assert_eq!(Some(settings_c.tab_size), NonZeroU32::new(10));
// When max_line_length is "off", default to .zed/settings.json
assert_eq!(settings_b.preferred_line_length, 64);
assert_eq!(settings_b.soft_wrap, SoftWrap::EditorWidth);
// README.md should not be affected by .editorconfig's globe "*.rs"
assert_eq!(Some(settings_readme.tab_size), NonZeroU32::new(8));
});
}
#[gpui::test]
async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
init_test(cx);
@@ -146,26 +249,16 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
.update(|cx| {
let tree = worktree.read(cx);
let settings_a = language_settings(
None,
Some(
&(File::for_entry(
tree.entry_for_path("a/a.rs").unwrap().clone(),
worktree.clone(),
) as _),
),
cx,
);
let settings_b = language_settings(
None,
Some(
&(File::for_entry(
tree.entry_for_path("b/b.rs").unwrap().clone(),
worktree.clone(),
) as _),
),
cx,
);
let file_a = File::for_entry(
tree.entry_for_path("a/a.rs").unwrap().clone(),
worktree.clone(),
) as _;
let settings_a = language_settings(None, Some(&file_a), cx);
let file_b = File::for_entry(
tree.entry_for_path("b/b.rs").unwrap().clone(),
worktree.clone(),
) as _;
let settings_b = language_settings(None, Some(&file_b), cx);
assert_eq!(settings_a.tab_size.get(), 8);
assert_eq!(settings_b.tab_size.get(), 2);

View File

@@ -91,7 +91,6 @@ struct EditState {
entry_id: ProjectEntryId,
is_new_entry: bool,
is_dir: bool,
is_symlink: bool,
depth: usize,
processing_filename: Option<String>,
}
@@ -987,7 +986,6 @@ impl ProjectPanel {
is_new_entry: true,
is_dir,
processing_filename: None,
is_symlink: false,
depth: 0,
});
self.filename_editor.update(cx, |editor, cx| {
@@ -1027,7 +1025,6 @@ impl ProjectPanel {
is_new_entry: false,
is_dir: entry.is_dir(),
processing_filename: None,
is_symlink: entry.is_symlink,
depth: 0,
});
let file_name = entry
@@ -1533,16 +1530,15 @@ impl ProjectPanel {
fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
if let Some((worktree, entry)) = self.selected_sub_entry(cx) {
let abs_path = worktree.abs_path().join(&entry.path);
let abs_path = match &entry.canonical_path {
Some(canonical_path) => Some(canonical_path.to_path_buf()),
None => worktree.absolutize(&entry.path).ok(),
};
let working_directory = if entry.is_dir() {
Some(abs_path)
abs_path
} else {
if entry.is_symlink {
abs_path.canonicalize().ok()
} else {
Some(abs_path)
}
.and_then(|path| Some(path.parent()?.to_path_buf()))
abs_path.and_then(|path| Some(path.parent()?.to_path_buf()))
};
if let Some(working_directory) = working_directory {
cx.dispatch_action(workspace::OpenTerminal { working_directory }.boxed_clone())
@@ -1830,7 +1826,6 @@ impl ProjectPanel {
.unwrap_or_default();
if let Some(edit_state) = &mut self.edit_state {
if edit_state.entry_id == entry.id {
edit_state.is_symlink = entry.is_symlink;
edit_state.depth = depth;
}
}
@@ -1861,7 +1856,6 @@ impl ProjectPanel {
is_private: false,
git_status: entry.git_status,
canonical_path: entry.canonical_path.clone(),
is_symlink: entry.is_symlink,
char_bag: entry.char_bag,
is_fifo: entry.is_fifo,
});
@@ -1920,7 +1914,7 @@ impl ProjectPanel {
let width_estimate = item_width_estimate(
depth,
path.to_string_lossy().chars().count(),
entry.is_symlink,
entry.canonical_path.is_some(),
);
match max_width_item.as_mut() {

View File

@@ -12,6 +12,7 @@ message Envelope {
uint32 id = 1;
optional uint32 responding_to = 2;
optional PeerId original_sender_id = 3;
optional uint32 ack_id = 266;
oneof payload {
Hello hello = 4;
@@ -295,7 +296,9 @@ message Envelope {
OpenServerSettings open_server_settings = 263;
GetPermalinkToLine get_permalink_to_line = 264;
GetPermalinkToLineResponse get_permalink_to_line_response = 265; // current max
GetPermalinkToLineResponse get_permalink_to_line_response = 265;
FlushBufferedMessages flush_buffered_messages = 267;
}
reserved 87 to 88;
@@ -1867,12 +1870,13 @@ message Entry {
string path = 3;
uint64 inode = 4;
Timestamp mtime = 5;
bool is_symlink = 6;
bool is_ignored = 7;
bool is_external = 8;
reserved 6;
optional GitStatus git_status = 9;
bool is_fifo = 10;
optional uint64 size = 11;
optional string canonical_path = 12;
}
message RepositoryEntry {
@@ -2521,3 +2525,6 @@ message GetPermalinkToLine {
message GetPermalinkToLineResponse {
string permalink = 1;
}
message FlushBufferedMessages {}
message FlushBufferedMessagesResponse {}

View File

@@ -32,6 +32,7 @@ macro_rules! messages {
responding_to,
original_sender_id,
payload: Some(envelope::Payload::$name(self)),
ack_id: None,
}
}

View File

@@ -372,6 +372,7 @@ messages!(
(OpenServerSettings, Foreground),
(GetPermalinkToLine, Foreground),
(GetPermalinkToLineResponse, Foreground),
(FlushBufferedMessages, Foreground),
);
request_messages!(
@@ -498,6 +499,7 @@ request_messages!(
(RemoveWorktree, Ack),
(OpenServerSettings, OpenBufferResponse),
(GetPermalinkToLine, GetPermalinkToLineResponse),
(FlushBufferedMessages, Ack),
);
entity_messages!(

View File

@@ -175,7 +175,7 @@ impl Render for SshPrompt {
.child(
h_flex()
.p_2()
.flex_wrap()
.flex()
.child(if self.error_message.is_some() {
Icon::new(IconName::XCircle)
.size(IconSize::Medium)
@@ -195,6 +195,7 @@ impl Render for SshPrompt {
})
.child(
div()
.ml_1()
.text_ellipsis()
.overflow_x_hidden()
.when_some(self.error_message.as_ref(), |el, error| {
@@ -205,7 +206,7 @@ impl Render for SshPrompt {
|el| {
el.child(
Label::new(format!(
"{}",
"{}",
self.status_message.clone().unwrap()
))
.size(LabelSize::Small),

View File

@@ -19,6 +19,7 @@ test-support = ["fs/test-support"]
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
collections.workspace = true
fs.workspace = true
futures.workspace = true

View File

@@ -2,7 +2,6 @@ use anyhow::Result;
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use prost::Message as _;
use rpc::proto::Envelope;
use std::mem::size_of;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct MessageId(pub u32);
@@ -30,8 +29,10 @@ pub async fn read_message<S: AsyncRead + Unpin>(
) -> Result<Envelope> {
buffer.resize(MESSAGE_LEN_SIZE, 0);
stream.read_exact(buffer).await?;
let len = message_len_from_buffer(buffer);
read_message_with_len(stream, buffer, len).await
let result = read_message_with_len(stream, buffer, len).await;
result
}
pub async fn write_message<S: AsyncWrite + Unpin>(

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ use fs::{FakeFs, Fs};
use gpui::{Context, Model, TestAppContext};
use http_client::{BlockedHttpClient, FakeHttpClient};
use language::{
language_settings::{all_language_settings, AllLanguageSettings},
language_settings::{language_settings, AllLanguageSettings},
Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LanguageServerName,
LineEnding,
};
@@ -208,7 +208,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
server_cx.read(|cx| {
assert_eq!(
AllLanguageSettings::get_global(cx)
.language(Some(&"Rust".into()))
.language(None, Some(&"Rust".into()), cx)
.language_servers,
["from-local-settings".to_string()]
)
@@ -228,7 +228,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
server_cx.read(|cx| {
assert_eq!(
AllLanguageSettings::get_global(cx)
.language(Some(&"Rust".into()))
.language(None, Some(&"Rust".into()), cx)
.language_servers,
["from-server-settings".to_string()]
)
@@ -287,7 +287,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
}),
cx
)
.language(Some(&"Rust".into()))
.language(None, Some(&"Rust".into()), cx)
.language_servers,
["override-rust-analyzer".to_string()]
)
@@ -296,9 +296,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo
cx.read(|cx| {
let file = buffer.read(cx).file();
assert_eq!(
all_language_settings(file, cx)
.language(Some(&"Rust".into()))
.language_servers,
language_settings(Some("Rust".into()), file, cx).language_servers,
["override-rust-analyzer".to_string()]
)
});
@@ -379,9 +377,7 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
cx.read(|cx| {
let file = buffer.read(cx).file();
assert_eq!(
all_language_settings(file, cx)
.language(Some(&"Rust".into()))
.language_servers,
language_settings(Some("Rust".into()), file, cx).language_servers,
["rust-analyzer".to_string()]
)
});
@@ -641,6 +637,47 @@ async fn test_open_server_settings(cx: &mut TestAppContext, server_cx: &mut Test
})
}
#[gpui::test(iterations = 20)]
async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let (project, _headless, fs) = init_test(cx, server_cx).await;
let (worktree, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx)
})
.await
.unwrap();
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
let buffer = project
.update(cx, |project, cx| {
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
})
.await
.unwrap();
buffer.update(cx, |buffer, cx| {
assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
let ix = buffer.text().find('1').unwrap();
buffer.edit([(ix..ix + 1, "100")], None, cx);
});
let client = cx.read(|cx| project.read(cx).ssh_client().unwrap());
client
.update(cx, |client, cx| client.simulate_disconnect(cx))
.detach();
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
assert_eq!(
fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(),
"fn one() -> usize { 100 }"
);
}
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::try_init().ok();
@@ -651,9 +688,9 @@ async fn init_test(
cx: &mut TestAppContext,
server_cx: &mut TestAppContext,
) -> (Model<Project>, Model<HeadlessProject>, Arc<FakeFs>) {
let (ssh_remote_client, ssh_server_client) = SshRemoteClient::fake(cx, server_cx);
init_logger();
let (forwarder, ssh_server_client) = SshRemoteClient::fake_server(cx, server_cx);
let fs = FakeFs::new(server_cx.executor());
fs.insert_tree(
"/code",
@@ -694,8 +731,9 @@ async fn init_test(
cx,
)
});
let project = build_project(ssh_remote_client, cx);
let ssh = SshRemoteClient::fake_client(forwarder, cx).await;
let project = build_project(ssh, cx);
project
.update(cx, {
let headless = headless.clone();

View File

@@ -12,6 +12,7 @@ use language::LanguageRegistry;
use node_runtime::{NodeBinaryOptions, NodeRuntime};
use paths::logs_dir;
use project::project_settings::ProjectSettings;
use remote::proxy::ProxyLaunchError;
use remote::ssh_session::ChannelClient;
use remote::{
@@ -213,19 +214,27 @@ fn start_server(
let mut input_buffer = Vec::new();
let mut output_buffer = Vec::new();
let (mut stdin_msg_tx, mut stdin_msg_rx) = mpsc::unbounded::<Envelope>();
cx.background_executor().spawn(async move {
while let Ok(msg) = read_message(&mut stdin_stream, &mut input_buffer).await {
if let Err(_) = stdin_msg_tx.send(msg).await {
break;
}
}
}).detach();
loop {
select_biased! {
_ = app_quit_rx.next().fuse() => {
return anyhow::Ok(());
}
stdin_message = read_message(&mut stdin_stream, &mut input_buffer).fuse() => {
let message = match stdin_message {
Ok(message) => message,
Err(error) => {
log::warn!("error reading message on stdin: {}. exiting.", error);
break;
}
stdin_message = stdin_msg_rx.next().fuse() => {
let Some(message) = stdin_message else {
log::warn!("error reading message on stdin. exiting.");
break;
};
if let Err(error) = incoming_tx.unbounded_send(message) {
log::error!("failed to send message to application: {:?}. exiting.", error);
@@ -270,7 +279,7 @@ fn start_server(
})
.detach();
ChannelClient::new(incoming_rx, outgoing_tx, cx)
ChannelClient::new(incoming_rx, outgoing_tx, cx, "server")
}
fn init_paths() -> anyhow::Result<()> {

View File

@@ -17,8 +17,7 @@ use editor::{
use futures::io::BufReader;
use futures::{AsyncBufReadExt as _, FutureExt as _, StreamExt as _};
use gpui::{
div, prelude::*, EntityId, EventEmitter, Model, Render, Subscription, Task, View, ViewContext,
WeakView,
div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView,
};
use language::Point;
use project::Fs;
@@ -149,23 +148,21 @@ impl EditorBlock {
.w(text_line_height)
.h(text_line_height)
.child(
IconButton::new(
("close_output_area", EntityId::from(cx.block_id)),
IconName::Close,
)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Close output area", cx))
.on_click(move |_, cx| {
if let BlockId::Custom(block_id) = block_id {
(on_close)(block_id, cx)
}
}),
IconButton::new("close_output_area", IconName::Close)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Close output area", cx))
.on_click(move |_, cx| {
if let BlockId::Custom(block_id) = block_id {
(on_close)(block_id, cx)
}
}),
);
div()
.id(cx.block_id)
.flex()
.items_start()
.min_h(text_line_height)

View File

@@ -44,8 +44,12 @@ impl ReqwestClient {
let mut client = reqwest::Client::builder()
.use_rustls_tls()
.default_headers(map);
if let Some(proxy) = proxy.clone() {
client = client.proxy(reqwest::Proxy::all(proxy.to_string())?);
if let Some(proxy) = proxy.clone().and_then(|proxy_uri| {
reqwest::Proxy::all(proxy_uri.to_string())
.inspect_err(|e| log::error!("Failed to parse proxy URI {}: {}", proxy_uri, e))
.ok()
}) {
client = client.proxy(proxy);
}
let client = client.build()?;
let mut client: ReqwestClient = client.into();
@@ -232,3 +236,47 @@ impl http_client::HttpClient for ReqwestClient {
.boxed()
}
}
#[cfg(test)]
mod tests {
use http_client::{http, HttpClient};
use crate::ReqwestClient;
#[test]
fn test_proxy_uri() {
let client = ReqwestClient::new();
assert_eq!(client.proxy(), None);
let proxy = http::Uri::from_static("http://localhost:10809");
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
assert_eq!(client.proxy(), Some(&proxy));
let proxy = http::Uri::from_static("https://localhost:10809");
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
assert_eq!(client.proxy(), Some(&proxy));
let proxy = http::Uri::from_static("socks4://localhost:10808");
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
assert_eq!(client.proxy(), Some(&proxy));
let proxy = http::Uri::from_static("socks4a://localhost:10808");
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
assert_eq!(client.proxy(), Some(&proxy));
let proxy = http::Uri::from_static("socks5://localhost:10808");
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
assert_eq!(client.proxy(), Some(&proxy));
let proxy = http::Uri::from_static("socks5h://localhost:10808");
let client = ReqwestClient::proxy_and_user_agent(Some(proxy.clone()), "test").unwrap();
assert_eq!(client.proxy(), Some(&proxy));
}
#[test]
#[should_panic]
fn test_invalid_proxy_uri() {
let proxy = http::Uri::from_static("file:///etc/hosts");
ReqwestClient::proxy_and_user_agent(Some(proxy), "test").unwrap();
}
}

View File

@@ -18,6 +18,7 @@ test-support = ["gpui/test-support", "fs/test-support"]
[dependencies]
anyhow.workspace = true
collections.workspace = true
ec4rs.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true

View File

@@ -1,9 +1,10 @@
use anyhow::{anyhow, Context, Result};
use collections::{btree_map, hash_map, BTreeMap, HashMap};
use ec4rs::{ConfigParser, PropertiesSource, Section};
use fs::Fs;
use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt};
use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal};
use paths::local_settings_file_relative_path;
use paths::{local_settings_file_relative_path, EDITORCONFIG_NAME};
use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
use smallvec::SmallVec;
@@ -12,12 +13,14 @@ use std::{
fmt::Debug,
ops::Range,
path::{Path, PathBuf},
str,
str::{self, FromStr},
sync::{Arc, LazyLock},
};
use tree_sitter::Query;
use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};
pub type EditorconfigProperties = ec4rs::Properties;
use crate::{SettingsJsonSchemaParams, WorktreeId};
/// A value that can be defined as a user setting.
@@ -167,8 +170,8 @@ pub struct SettingsStore {
raw_user_settings: serde_json::Value,
raw_server_settings: Option<serde_json::Value>,
raw_extension_settings: serde_json::Value,
raw_local_settings:
BTreeMap<(WorktreeId, Arc<Path>), HashMap<LocalSettingsKind, serde_json::Value>>,
raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), serde_json::Value>,
raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>,
tab_size_callback: Option<(
TypeId,
Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>,
@@ -179,6 +182,26 @@ pub struct SettingsStore {
>,
}
#[derive(Clone)]
pub struct Editorconfig {
pub is_root: bool,
pub sections: SmallVec<[Section; 5]>,
}
impl FromStr for Editorconfig {
type Err = anyhow::Error;
fn from_str(contents: &str) -> Result<Self, Self::Err> {
let parser = ConfigParser::new_buffered(contents.as_bytes())
.context("creating editorconfig parser")?;
let is_root = parser.is_root;
let sections = parser
.collect::<Result<SmallVec<_>, _>>()
.context("parsing editorconfig sections")?;
Ok(Self { is_root, sections })
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum LocalSettingsKind {
Settings,
@@ -226,6 +249,7 @@ impl SettingsStore {
raw_server_settings: None,
raw_extension_settings: serde_json::json!({}),
raw_local_settings: Default::default(),
raw_editorconfig_settings: BTreeMap::default(),
tab_size_callback: Default::default(),
setting_file_updates_tx,
_setting_file_updates: cx.spawn(|cx| async move {
@@ -567,33 +591,91 @@ impl SettingsStore {
settings_content: Option<&str>,
cx: &mut AppContext,
) -> std::result::Result<(), InvalidSettingsError> {
debug_assert!(
kind != LocalSettingsKind::Tasks,
"Attempted to submit tasks into the settings store"
);
let raw_local_settings = self
.raw_local_settings
.entry((root_id, directory_path.clone()))
.or_default();
let changed = if settings_content.is_some_and(|content| !content.is_empty()) {
let new_contents =
parse_json_with_comments(settings_content.unwrap()).map_err(|e| {
InvalidSettingsError::LocalSettings {
let mut zed_settings_changed = false;
match (
kind,
settings_content
.map(|content| content.trim())
.filter(|content| !content.is_empty()),
) {
(LocalSettingsKind::Tasks, _) => {
return Err(InvalidSettingsError::Tasks {
message: "Attempted to submit tasks into the settings store".to_string(),
})
}
(LocalSettingsKind::Settings, None) => {
zed_settings_changed = self
.raw_local_settings
.remove(&(root_id, directory_path.clone()))
.is_some()
}
(LocalSettingsKind::Editorconfig, None) => {
self.raw_editorconfig_settings
.remove(&(root_id, directory_path.clone()));
}
(LocalSettingsKind::Settings, Some(settings_contents)) => {
let new_settings = parse_json_with_comments::<serde_json::Value>(settings_contents)
.map_err(|e| InvalidSettingsError::LocalSettings {
path: directory_path.join(local_settings_file_relative_path()),
message: e.to_string(),
})?;
match self
.raw_local_settings
.entry((root_id, directory_path.clone()))
{
btree_map::Entry::Vacant(v) => {
v.insert(new_settings);
zed_settings_changed = true;
}
})?;
if Some(&new_contents) == raw_local_settings.get(&kind) {
false
} else {
raw_local_settings.insert(kind, new_contents);
true
btree_map::Entry::Occupied(mut o) => {
if o.get() != &new_settings {
o.insert(new_settings);
zed_settings_changed = true;
}
}
}
}
(LocalSettingsKind::Editorconfig, Some(editorconfig_contents)) => {
match self
.raw_editorconfig_settings
.entry((root_id, directory_path.clone()))
{
btree_map::Entry::Vacant(v) => match editorconfig_contents.parse() {
Ok(new_contents) => {
v.insert((editorconfig_contents.to_owned(), Some(new_contents)));
}
Err(e) => {
v.insert((editorconfig_contents.to_owned(), None));
return Err(InvalidSettingsError::Editorconfig {
message: e.to_string(),
path: directory_path.join(EDITORCONFIG_NAME),
});
}
},
btree_map::Entry::Occupied(mut o) => {
if o.get().0 != editorconfig_contents {
match editorconfig_contents.parse() {
Ok(new_contents) => {
o.insert((
editorconfig_contents.to_owned(),
Some(new_contents),
));
}
Err(e) => {
o.insert((editorconfig_contents.to_owned(), None));
return Err(InvalidSettingsError::Editorconfig {
message: e.to_string(),
path: directory_path.join(EDITORCONFIG_NAME),
});
}
}
}
}
}
}
} else {
raw_local_settings.remove(&kind).is_some()
};
if changed {
if zed_settings_changed {
self.recompute_values(Some((root_id, &directory_path)), cx)?;
}
Ok(())
@@ -605,13 +687,10 @@ impl SettingsStore {
cx: &mut AppContext,
) -> Result<()> {
let settings: serde_json::Value = serde_json::to_value(content)?;
if settings.is_object() {
self.raw_extension_settings = settings;
self.recompute_values(None, cx)?;
Ok(())
} else {
Err(anyhow!("settings must be an object"))
}
anyhow::ensure!(settings.is_object(), "settings must be an object");
self.raw_extension_settings = settings;
self.recompute_values(None, cx)?;
Ok(())
}
/// Add or remove a set of local settings via a JSON string.
@@ -625,7 +704,7 @@ impl SettingsStore {
pub fn local_settings(
&self,
root_id: WorktreeId,
) -> impl '_ + Iterator<Item = (Arc<Path>, LocalSettingsKind, String)> {
) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
self.raw_local_settings
.range(
(root_id, Path::new("").into())
@@ -634,11 +713,23 @@ impl SettingsStore {
Path::new("").into(),
),
)
.flat_map(|((_, path), content)| {
content.iter().filter_map(|(&kind, raw_content)| {
let parsed_content = serde_json::to_string(raw_content).log_err()?;
Some((path.clone(), kind, parsed_content))
})
.map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
}
pub fn local_editorconfig_settings(
&self,
root_id: WorktreeId,
) -> impl '_ + Iterator<Item = (Arc<Path>, String, Option<Editorconfig>)> {
self.raw_editorconfig_settings
.range(
(root_id, Path::new("").into())
..(
WorktreeId::from_usize(root_id.to_usize() + 1),
Path::new("").into(),
),
)
.map(|((_, path), (content, parsed_content))| {
(path.clone(), content.clone(), parsed_content.clone())
})
}
@@ -753,7 +844,7 @@ impl SettingsStore {
&mut self,
changed_local_path: Option<(WorktreeId, &Path)>,
cx: &mut AppContext,
) -> Result<(), InvalidSettingsError> {
) -> std::result::Result<(), InvalidSettingsError> {
// Reload the global and local values for every setting.
let mut project_settings_stack = Vec::<DeserializedSetting>::new();
let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
@@ -819,69 +910,90 @@ impl SettingsStore {
paths_stack.clear();
project_settings_stack.clear();
for ((root_id, directory_path), local_settings) in &self.raw_local_settings {
if let Some(local_settings) = local_settings.get(&LocalSettingsKind::Settings) {
// Build a stack of all of the local values for that setting.
while let Some(prev_entry) = paths_stack.last() {
if let Some((prev_root_id, prev_path)) = prev_entry {
if root_id != prev_root_id || !directory_path.starts_with(prev_path) {
paths_stack.pop();
project_settings_stack.pop();
continue;
}
// Build a stack of all of the local values for that setting.
while let Some(prev_entry) = paths_stack.last() {
if let Some((prev_root_id, prev_path)) = prev_entry {
if root_id != prev_root_id || !directory_path.starts_with(prev_path) {
paths_stack.pop();
project_settings_stack.pop();
continue;
}
break;
}
break;
}
match setting_value.deserialize_setting(local_settings) {
Ok(local_settings) => {
paths_stack.push(Some((*root_id, directory_path.as_ref())));
project_settings_stack.push(local_settings);
match setting_value.deserialize_setting(local_settings) {
Ok(local_settings) => {
paths_stack.push(Some((*root_id, directory_path.as_ref())));
project_settings_stack.push(local_settings);
// If a local settings file changed, then avoid recomputing local
// settings for any path outside of that directory.
if changed_local_path.map_or(
false,
|(changed_root_id, changed_local_path)| {
*root_id != changed_root_id
|| !directory_path.starts_with(changed_local_path)
// If a local settings file changed, then avoid recomputing local
// settings for any path outside of that directory.
if changed_local_path.map_or(
false,
|(changed_root_id, changed_local_path)| {
*root_id != changed_root_id
|| !directory_path.starts_with(changed_local_path)
},
) {
continue;
}
if let Some(value) = setting_value
.load_setting(
SettingsSources {
default: &default_settings,
extensions: extension_settings.as_ref(),
user: user_settings.as_ref(),
release_channel: release_channel_settings.as_ref(),
server: server_settings.as_ref(),
project: &project_settings_stack.iter().collect::<Vec<_>>(),
},
) {
continue;
}
if let Some(value) = setting_value
.load_setting(
SettingsSources {
default: &default_settings,
extensions: extension_settings.as_ref(),
user: user_settings.as_ref(),
release_channel: release_channel_settings.as_ref(),
server: server_settings.as_ref(),
project: &project_settings_stack.iter().collect::<Vec<_>>(),
},
cx,
)
.log_err()
{
setting_value.set_local_value(
*root_id,
directory_path.clone(),
value,
);
}
}
Err(error) => {
return Err(InvalidSettingsError::LocalSettings {
path: directory_path.join(local_settings_file_relative_path()),
message: error.to_string(),
});
cx,
)
.log_err()
{
setting_value.set_local_value(*root_id, directory_path.clone(), value);
}
}
Err(error) => {
return Err(InvalidSettingsError::LocalSettings {
path: directory_path.join(local_settings_file_relative_path()),
message: error.to_string(),
});
}
}
}
}
Ok(())
}
pub fn editorconfg_properties(
&self,
for_worktree: WorktreeId,
for_path: &Path,
) -> Option<EditorconfigProperties> {
let mut properties = EditorconfigProperties::new();
for (directory_with_config, _, parsed_editorconfig) in
self.local_editorconfig_settings(for_worktree)
{
if !for_path.starts_with(&directory_with_config) {
properties.use_fallbacks();
return Some(properties);
}
let parsed_editorconfig = parsed_editorconfig?;
if parsed_editorconfig.is_root {
properties = EditorconfigProperties::new();
}
for section in parsed_editorconfig.sections {
section.apply_to(&mut properties, for_path).log_err()?;
}
}
properties.use_fallbacks();
Some(properties)
}
}
#[derive(Debug, Clone, PartialEq)]
@@ -890,6 +1002,8 @@ pub enum InvalidSettingsError {
UserSettings { message: String },
ServerSettings { message: String },
DefaultSettings { message: String },
Editorconfig { path: PathBuf, message: String },
Tasks { message: String },
}
impl std::fmt::Display for InvalidSettingsError {
@@ -898,8 +1012,10 @@ impl std::fmt::Display for InvalidSettingsError {
InvalidSettingsError::LocalSettings { message, .. }
| InvalidSettingsError::UserSettings { message }
| InvalidSettingsError::ServerSettings { message }
| InvalidSettingsError::DefaultSettings { message } => {
write!(f, "{}", message)
| InvalidSettingsError::DefaultSettings { message }
| InvalidSettingsError::Tasks { message }
| InvalidSettingsError::Editorconfig { message, .. } => {
write!(f, "{message}")
}
}
}

View File

@@ -121,7 +121,7 @@ impl InlineCompletionProvider for SupermavenCompletionProvider {
let file = buffer.file();
let language = buffer.language_at(cursor_position);
let settings = all_language_settings(file, cx);
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()))
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx)
}
fn refresh(

View File

@@ -17,6 +17,8 @@ gpui.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true
workspace.workspace = true
menu.workspace = true
[features]
default = []

View File

@@ -1,3 +1,5 @@
#![allow(unused, dead_code)]
//! # UI Text Field
//!
//! This crate provides a text field component that can be used to create text fields like search inputs, form fields, etc.
@@ -5,11 +7,14 @@
//! It can't be located in the `ui` crate because it depends on `editor`.
//!
use std::default;
use editor::*;
use gpui::*;
use settings::Settings;
use theme::ThemeSettings;
use ui::*;
use ui::{List, *};
use workspace::{ModalView, Workspace};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FieldLabelLayout {
@@ -187,3 +192,243 @@ impl Render for TextField {
)
}
}
// -------------------------------------------------------------------------------------------------
actions!(quick_commit, [ToggleStageAll]);
pub const MODAL_WIDTH: f32 = 700.0;
pub const MODAL_HEIGHT: f32 = 300.0;
fn test_files() -> Vec<ChangedFile> {
vec![
ChangedFile {
id: 0,
state: FileVCSState::Modified,
file_name: "file1.txt".into(),
file_path: "/path/to/file1.txt".into(),
},
ChangedFile {
id: 1,
state: FileVCSState::Deleted,
file_name: "file2.txt".into(),
file_path: "/path/to/file2.txt".into(),
},
ChangedFile {
id: 2,
state: FileVCSState::Created,
file_name: "file3.txt".into(),
file_path: "/path/to/file3.txt".into(),
},
]
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
enum FileVCSState {
Deleted,
Modified,
Created,
}
struct ChangedFileId(usize);
impl ChangedFileId {
fn new(id: usize) -> Self {
Self(id)
}
}
// placeholder for ui
#[derive(Debug, Clone)]
struct ChangedFile {
id: usize,
state: FileVCSState,
file_name: SharedString,
file_path: SharedString,
}
struct QuickCommitState {
placeholder_text: SharedString,
tracked_files: Vec<ChangedFile>,
staged_files: Vec<usize>,
active_participant_handles: Vec<SharedString>,
editor: View<Editor>,
workspace: WeakView<Workspace>,
}
impl QuickCommitState {
fn init(
editor: View<Editor>,
workspace: WeakView<Workspace>,
cx: &mut ModelContext<Self>,
) -> Self {
let workspace = workspace.clone();
Self {
placeholder_text: "Add a message".into(),
tracked_files: Default::default(),
staged_files: Default::default(),
active_participant_handles: Default::default(),
editor,
workspace,
}
}
fn stage_state(&self) -> Selection {
let staged_files = self.staged_files.clone();
let tracked_files = self.tracked_files.clone();
if staged_files.len() == tracked_files.len() {
Selection::Selected
} else if staged_files.is_empty() {
Selection::Unselected
} else {
Selection::Indeterminate
}
}
fn stage_all(&mut self) -> &mut Self {
let tracked_files = self.tracked_files.clone();
self.staged_files = tracked_files.iter().map(|file| file.id).collect();
self
}
fn toggle_stage_all(&mut self) {
let stage_state = self.stage_state();
let staged_files = self.staged_files.clone();
let tracked_files = self.tracked_files.clone();
match stage_state {
Selection::Selected => {
self.staged_files.clear();
}
Selection::Unselected | Selection::Indeterminate => {
self.stage_all();
}
}
}
fn toggle_file_staged(&mut self, file_id: usize) {
if let Some(pos) = self.staged_files.iter().position() {
self.staged_files.swap_remove(pos);
} else {
self.staged_files.push(file_id);
}
}
}
pub struct QuickCommit {
state: Model<QuickCommitState>,
}
impl QuickCommit {
pub fn init(workspace: WeakView<Workspace>, cx: &mut WindowContext) -> View<Self> {
let editor = cx.new_view(|cx| {
let mut editor = Editor::multi_line(cx);
editor.set_show_gutter(false, cx);
editor
});
cx.new_view(|cx| {
let state = cx
.new_model(move |cx| QuickCommitState::init(editor.clone(), workspace.clone(), cx));
Self { state }
})
}
fn stage_state(&self, cx: &ViewContext<Self>) -> Selection {
self.state.read(cx).stage_state()
}
fn toggle_stage_all(&mut self, _: &ToggleStageAll, cx: &mut ViewContext<Self>) {
self.state.update(cx, |state, _| state.toggle_stage_all());
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent)
}
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.state.read(cx).editor.focus_handle(cx)
}
}
impl QuickCommit {
fn render_file_list(&mut self, cx: &mut ViewContext<Self>) -> List {
List::new().empty_message("No changes")
}
}
impl Render for QuickCommit {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let staged_files = self.state.read(cx).staged_files.clone();
let total_tracked_files = self.state.read(cx).tracked_files.clone();
let staged_state = self.stage_state(cx);
h_flex()
.id("quick_commit_modal")
.key_context("quick_commit")
.track_focus(&self.focus_handle(cx))
.on_action(cx.listener(Self::cancel))
.occlude()
.h(px(MODAL_HEIGHT))
.w(px(MODAL_WIDTH))
.child(
// commit editor
div()
.h_full()
.flex_1()
// .child(self.editor.clone())
.child(
div()
.absolute()
.bottom_2()
.right_2()
.child(Button::new("submit_commit", "Commit")),
),
)
.child(
// file list
div()
.w(relative(0.42))
.h_full()
.border_l_1()
.border_color(cx.theme().colors().border)
// sticky header
.child(
h_flex()
.h_10()
.w_full()
.child(Label::new(format!(
"Staged Files: {}/{}",
staged_files.len(),
total_tracked_files.len()
)))
.child(Checkbox::new("toggle-stage-all", staged_state).on_click(
|_, cx| {
cx.dispatch_action(ToggleStageAll.boxed_clone());
},
)),
)
// file list
.child(self.render_file_list(cx)),
)
}
}
impl EventEmitter<DismissEvent> for QuickCommit {}
impl FocusableView for QuickCommit {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
// TODO: Not sure this is right
self.focus_handle(cx)
}
}
impl ModalView for QuickCommit {
fn fade_out_background(&self) -> bool {
true
}
}

View File

@@ -98,32 +98,40 @@ impl Vim {
}
}
fn increment_decimal_string(mut num: &str, mut delta: i64) -> String {
let mut negative = false;
if num.chars().next() == Some('-') {
negative = true;
delta = 0 - delta;
num = &num[1..];
}
let result = if let Ok(value) = u64::from_str_radix(num, 10) {
let wrapped = value.wrapping_add_signed(delta);
if delta < 0 && wrapped > value {
negative = !negative;
(u64::MAX - wrapped).wrapping_add(1)
} else if delta > 0 && wrapped < value {
negative = !negative;
u64::MAX - wrapped
} else {
wrapped
fn increment_decimal_string(num: &str, delta: i64) -> String {
let (negative, delta, num_str) = match num.strip_prefix('-') {
Some(n) => (true, -delta, n),
None => (false, delta, num),
};
let num_length = num_str.len();
let leading_zero = num_str.starts_with('0');
let (result, new_negative) = match u64::from_str_radix(num_str, 10) {
Ok(value) => {
let wrapped = value.wrapping_add_signed(delta);
if delta < 0 && wrapped > value {
((u64::MAX - wrapped).wrapping_add(1), !negative)
} else if delta > 0 && wrapped < value {
(u64::MAX - wrapped, !negative)
} else {
(wrapped, negative)
}
}
} else {
u64::MAX
Err(_) => (u64::MAX, negative),
};
if result == 0 || !negative {
format!("{}", result)
let formatted = format!("{}", result);
let new_significant_digits = formatted.len();
let padding = if leading_zero {
num_length.saturating_sub(new_significant_digits)
} else {
format!("-{}", result)
0
};
if new_negative && result != 0 {
format!("-{}{}", "0".repeat(padding), formatted)
} else {
format!("{}{}", "0".repeat(padding), formatted)
}
}
@@ -286,6 +294,63 @@ mod test {
"});
}
#[gpui::test]
async fn test_increment_with_leading_zeros(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
000ˇ9
"})
.await;
cx.simulate_shared_keystrokes("ctrl-a").await;
cx.shared_state().await.assert_eq(indoc! {"
001ˇ0
"});
cx.simulate_shared_keystrokes("2 ctrl-x").await;
cx.shared_state().await.assert_eq(indoc! {"
000ˇ8
"});
}
#[gpui::test]
async fn test_increment_with_leading_zeros_and_zero(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
01ˇ1
"})
.await;
cx.simulate_shared_keystrokes("ctrl-a").await;
cx.shared_state().await.assert_eq(indoc! {"
01ˇ2
"});
cx.simulate_shared_keystrokes("1 2 ctrl-x").await;
cx.shared_state().await.assert_eq(indoc! {"
00ˇ0
"});
}
#[gpui::test]
async fn test_increment_with_changing_leading_zeros(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
099ˇ9
"})
.await;
cx.simulate_shared_keystrokes("ctrl-a").await;
cx.shared_state().await.assert_eq(indoc! {"
100ˇ0
"});
cx.simulate_shared_keystrokes("2 ctrl-x").await;
cx.shared_state().await.assert_eq(indoc! {"
99ˇ8
"});
}
#[gpui::test]
async fn test_increment_with_two_dots(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
@@ -322,6 +387,27 @@ mod test {
"});
}
#[gpui::test]
async fn test_increment_sign_change_with_leading_zeros(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {"
00ˇ1
"})
.await;
cx.simulate_shared_keystrokes("ctrl-x").await;
cx.shared_state().await.assert_eq(indoc! {"
00ˇ0
"});
cx.simulate_shared_keystrokes("ctrl-x").await;
cx.shared_state().await.assert_eq(indoc! {"
-00ˇ1
"});
cx.simulate_shared_keystrokes("2 ctrl-a").await;
cx.shared_state().await.assert_eq(indoc! {"
00ˇ1
"});
}
#[gpui::test]
async fn test_increment_bin_wrapping_and_padding(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;

View File

@@ -0,0 +1,8 @@
{"Put":{"state":"00ˇ1\n"}}
{"Key":"ctrl-x"}
{"Get":{"state":"00ˇ0\n","mode":"Normal"}}
{"Key":"ctrl-x"}
{"Get":{"state":"-00ˇ1\n","mode":"Normal"}}
{"Key":"2"}
{"Key":"ctrl-a"}
{"Get":{"state":"00ˇ1\n","mode":"Normal"}}

View File

@@ -0,0 +1,6 @@
{"Put":{"state":"099ˇ9\n"}}
{"Key":"ctrl-a"}
{"Get":{"state":"100ˇ0\n","mode":"Normal"}}
{"Key":"2"}
{"Key":"ctrl-x"}
{"Get":{"state":"99ˇ8\n","mode":"Normal"}}

View File

@@ -0,0 +1,6 @@
{"Put":{"state":"000ˇ9\n"}}
{"Key":"ctrl-a"}
{"Get":{"state":"001ˇ0\n","mode":"Normal"}}
{"Key":"2"}
{"Key":"ctrl-x"}
{"Get":{"state":"000ˇ8\n","mode":"Normal"}}

View File

@@ -0,0 +1,7 @@
{"Put":{"state":"01ˇ1\n"}}
{"Key":"ctrl-a"}
{"Get":{"state":"01ˇ2\n","mode":"Normal"}}
{"Key":"1"}
{"Key":"2"}
{"Key":"ctrl-x"}
{"Get":{"state":"00ˇ0\n","mode":"Normal"}}

View File

@@ -0,0 +1,4 @@
{"Put":{"state":"001ˇ0\n"}}
{"Key":"10"}
{"Key":"ctrl-x"}
{"Get":{"state":"000ˇ9\n","mode":"Normal"}}

View File

@@ -1739,11 +1739,9 @@ impl Pane {
.worktree_for_entry(entry, cx)?
.read(cx);
let entry = worktree.entry_for_id(entry)?;
let abs_path = worktree.absolutize(&entry.path).ok()?;
if entry.is_symlink {
abs_path.canonicalize().ok()
} else {
Some(abs_path)
match &entry.canonical_path {
Some(canonical_path) => Some(canonical_path.to_path_buf()),
None => worktree.absolutize(&entry.path).ok(),
}
}

View File

@@ -3203,7 +3203,6 @@ pub struct Entry {
pub mtime: Option<SystemTime>,
pub canonical_path: Option<Box<Path>>,
pub is_symlink: bool,
/// Whether this entry is ignored by Git.
///
/// We only scan ignored entries once the directory is expanded and
@@ -3280,7 +3279,6 @@ impl Entry {
mtime: Some(metadata.mtime),
size: metadata.len,
canonical_path,
is_symlink: metadata.is_symlink,
is_ignored: false,
is_external: false,
is_private: false,
@@ -5249,12 +5247,15 @@ impl<'a> From<&'a Entry> for proto::Entry {
path: entry.path.to_string_lossy().into(),
inode: entry.inode,
mtime: entry.mtime.map(|time| time.into()),
is_symlink: entry.is_symlink,
is_ignored: entry.is_ignored,
is_external: entry.is_external,
git_status: entry.git_status.map(git_status_to_proto),
is_fifo: entry.is_fifo,
size: Some(entry.size),
canonical_path: entry
.canonical_path
.as_ref()
.map(|path| path.to_string_lossy().to_string()),
}
}
}
@@ -5277,12 +5278,13 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
inode: entry.inode,
mtime: entry.mtime.map(|time| time.into()),
size: entry.size.unwrap_or(0),
canonical_path: None,
canonical_path: entry
.canonical_path
.map(|path_string| Box::from(Path::new(&path_string))),
is_ignored: entry.is_ignored,
is_external: entry.is_external,
git_status: git_status_from_proto(entry.git_status),
is_private: false,
is_symlink: entry.is_symlink,
char_bag,
is_fifo: entry.is_fifo,
})

View File

@@ -32,6 +32,7 @@ Zed supports hundreds of programming languages and text formats. Some work out-o
- [JavaScript](./languages/javascript.md)
- [Julia](./languages/julia.md)
- [JSON](./languages/json.md)
- [Jsonnet](./languages/jsonnet.md)
- [Kotlin](./languages/kotlin.md)
- [Lua](./languages/lua.md)
- [Luau](./languages/luau.md)
@@ -104,7 +105,6 @@ Zed supports hundreds of programming languages and text formats. Some work out-o
- [Groq](https://github.com/juice49/zed-groq)
- [INI](https://github.com/bajrangCoder/zed-ini)
- [Java](https://github.com/zed-extensions/java)
- [Jsonnet](https://github.com/narqo/zed-jsonnet)
- [Justfiles](https://github.com/jackTabsCode/zed-just)
- [LaTeX](https://github.com/rzukic/zed-latex)
- [Ledger](https://github.com/mrkstwrt/zed-ledger)

View File

@@ -24,7 +24,7 @@ To use a binary in a custom location, add the following to your `settings.json`:
}
```
If you want to disable Zed looking for a `clangd` binary, you can set `ignore_system-version` to `true`:
If you want to disable Zed looking for a `clangd` binary, you can set `ignore_system_version` to `true`:
```json
{

View File

@@ -17,7 +17,21 @@ The `OmniSharp` binary can be configured in a Zed settings file with:
"omnisharp": {
"binary": {
"path": "/path/to/OmniSharp",
"args": ["optional", "additional", "args", "-lsp"]
"arguments": ["optional", "additional", "args", "-lsp"]
}
}
}
}
```
If you want to disable Zed looking for a `omnisharp` binary, you can set `ignore_system_version` to `true`:
```json
{
"lsp": {
"omnisharp": {
"binary": {
"ignore_system_version": true
}
}
}

View File

@@ -10,9 +10,7 @@ Both use:
- Tree Sitter: [tree-sitter/tree-sitter-java](https://github.com/tree-sitter/tree-sitter-java)
- Language Server: [eclipse-jdtls/eclipse.jdt.ls](https://github.com/eclipse-jdtls/eclipse.jdt.ls)
## Pre-requisites
### Install OpenJDK
## Install OpenJDK
You will need to install a Java runtime (OpenJDK).
@@ -23,30 +21,19 @@ You will need to install a Java runtime (OpenJDK).
Or manually download and install [OpenJDK 23](https://jdk.java.net/23/).
### (Optional) Install JDTLS
If you are using Java with Eclipse JDTLS, you can skip this section as it will automatically download a binary for you.
If you are using Zed Java you need to install your own copy of Eclipse JDT Language Server (`eclipse.jdt.ls`).
- MacOS: `brew install jdtls`
- Arch: [`jdtls` from AUR](https://aur.archlinux.org/packages/jdtls)
Or manually download install:
- [JDTLS Milestone Builds](http://download.eclipse.org/jdtls/milestones/) (updated every two weeks)
- [JDTLS Snapshot Builds](https://download.eclipse.org/jdtls/snapshots/) (frequent updates)
## Extension Install
You can install either by opening {#action zed::Extensions}({#kb zed::Extensions}) and searching for `java`.
We recommend you install one or the other and not both.
## Settings / Initialization Options
See [JDTLS Language Server Settings & Capabilities](https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Language-Server-Settings-&-Capabilities) for a complete list of settings.
Both extensions will automatically download the language server, see: [Manual JDTLS Install](#manual-jdts-install) below if you'd prefer to manage that yourself.
Add the following to your Zed Settings by launching {#action zed::OpenSettings}({#kb zed::OpenSettings}).
For available `initialization_options` please see the [Initialize Request section of the Eclipse.jdt.ls Wiki](https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request).
You can add these customizations to your Zed Settings by launching {#action zed::OpenSettings}({#kb zed::OpenSettings}) or by using a `.zed/setting.json` inside your project.
### Zed Java Settings
@@ -54,7 +41,10 @@ Add the following to your Zed Settings by launching {#action zed::OpenSettings}(
{
"lsp": {
"jdtls": {
"settings": {},
"settings": {
"version": "1.40.0", // jdtls version to download and use
"classpath": "/path/to/classes.jar:/path/to/more/classes/"
},
"initialization_options": {}
}
}
@@ -74,36 +64,8 @@ Add the following to your Zed Settings by launching {#action zed::OpenSettings}(
}
```
## See also
- [Zed Java Readme](https://github.com/zed-extensions/java)
- [Java with Eclipse JDTLS Readme](https://github.com/ABckh/zed-java-eclipse-jdtls)
## Support
If you have issues with either of these plugins, please open issues on their respective repositories:
- [Zed Java Issues](https://github.com/zed-extensions/java/issues)
- [Java with Eclipse JDTLS Issues](https://github.com/ABckh/zed-java-eclipse-jdtls/issues)
## Example Configs
### Zed Java Classpath
You can optionally configure the class path that JDTLS uses with:
```json
{
"lsp": {
"jdtls": {
"settings": {
"classpath": "/path/to/classes.jar:/path/to/more/classes/"
}
}
}
}
```
### Zed Java Initialization Options
There are also many more options you can pass directly to the language server, for example:
@@ -204,3 +166,27 @@ For example, to enable [Lombok Support](https://github.com/redhat-developer/vsco
}
}
```
## Manual JDTLS Install
If you prefer, you can install JDTLS yourself and both extensions can be configured to use that instead.
- MacOS: `brew install jdtls`
- Arch: [`jdtls` from AUR](https://aur.archlinux.org/packages/jdtls)
Or manually download install:
- [JDTLS Milestone Builds](http://download.eclipse.org/jdtls/milestones/) (updated every two weeks)
- [JDTLS Snapshot Builds](https://download.eclipse.org/jdtls/snapshots/) (frequent updates)
## See also
- [Zed Java Readme](https://github.com/zed-extensions/java)
- [Java with Eclipse JDTLS Readme](https://github.com/ABckh/zed-java-eclipse-jdtls)
## Support
If you have issues with either of these plugins, please open issues on their respective repositories:
- [Zed Java Issues](https://github.com/zed-extensions/java/issues)
- [Java with Eclipse JDTLS Issues](https://github.com/ABckh/zed-java-eclipse-jdtls/issues)

View File

@@ -0,0 +1,24 @@
# Jsonnet
Jsonnet language support in Zed is provided by the community-maintained [Jsonnet extension](https://github.com/narqo/zed-jsonnet).
- Tree Sitter: [sourcegraph/tree-sitter-jsonnet](https://github.com/sourcegraph/tree-sitter-jsonnet)
- Language Server: [grafana/jsonnet-language-server](https://github.com/grafana/jsonnet-language-server)
## Configuration
Workspace configuration options can be passed to the language server via the `lsp` settings of the `settings.json`.
The following example enables support for resolving [tanka](https://tanka.dev) import paths in `jsonnet-language-server`:
```json
{
"lsp": {
"jsonnet-language-server": {
"settings": {
"resolve_paths_with_tanka": true
}
}
}
}
```

View File

@@ -1,6 +1,6 @@
# Vue
Vue support is available through the [Vue extension](https://github.com/zed-industries/zed/tree/main/extensions/vue).
Vue support is available through the [Vue extension](https://github.com/zed-extensions/vue).
- Tree Sitter: [tree-sitter-grammars/tree-sitter-vue](https://github.com/tree-sitter-grammars/tree-sitter-vue)
- Language Server: [vuejs/language-tools/](https://github.com/vuejs/language-tools/)

View File

@@ -1,6 +1,6 @@
[package]
name = "zed_elixir"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
publish = false
license = "Apache-2.0"

View File

@@ -1,7 +1,7 @@
id = "elixir"
name = "Elixir"
description = "Elixir support."
version = "0.1.0"
version = "0.1.1"
schema_version = 1
authors = ["Marshall Bowers <elliott.codes@gmail.com>"]
repository = "https://github.com/zed-industries/zed"

View File

@@ -3,6 +3,16 @@
(arguments (alias) @name)
(#match? @context "^(defmodule|defprotocol)$")) @item
(call
target: (identifier) @context
(arguments (_) @name)?
(#match? @context "^(setup|setup_all)$")) @item
(call
target: (identifier) @context
(arguments (string) @name)
(#match? @context "^(describe|test)$")) @item
(unary_operator
operator: "@" @name
operand: (call

View File

@@ -1,3 +0,0 @@
target
*.wasm
grammars

View File

@@ -1,16 +0,0 @@
[package]
name = "zed_svelte"
version = "0.2.0"
edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/svelte.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.1.0"

View File

@@ -1 +0,0 @@
../../LICENSE-APACHE

View File

@@ -1,15 +0,0 @@
id = "svelte"
name = "Svelte"
description = "Svelte support"
version = "0.2.0"
schema_version = 1
authors = []
repository = "https://github.com/zed-extensions/svelte"
[language_servers.svelte-language-server]
name = "Svelte Language Server"
language = "Svelte"
[grammars.svelte]
repository = "https://github.com/tree-sitter-grammars/tree-sitter-svelte"
commit = "3f06f705410683adb17d146b5eca28c62fe81ba6"

View File

@@ -1,7 +0,0 @@
("<" @open ">" @close)
("{" @open "}" @close)
("'" @open "'" @close)
("\"" @open "\"" @close)
("(" @open ")" @close)
; ("[" @open "]" @close)
; ("`" @open "`" @close)

View File

@@ -1,22 +0,0 @@
name = "Svelte"
grammar = "svelte"
path_suffixes = ["svelte"]
block_comment = ["<!-- ", " -->"]
autoclose_before = ":\"'}]>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "<", end = ">", close = true, newline = true, not_in = ["string"] },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "!--", end = " --", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = true, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = true, not_in = ["string"] },
{ start = "`", end = "`", close = true, newline = true, not_in = ["string"] },
]
scope_opt_in_language_servers = ["tailwindcss-language-server"]
prettier_parser_name = "svelte"
prettier_plugins = ["prettier-plugin-svelte"]
[overrides.string]
word_characters = ["-"]
opt_into_language_servers = ["tailwindcss-language-server"]

View File

@@ -1,107 +0,0 @@
; comments
(comment) @comment
; property attribute
(attribute_directive) @attribute.function
(attribute_identifier) @attribute
(attribute_modifier) @attribute.special
; Style component attributes as @property
(start_tag
(
(tag_name) @_tag_name
(#match? @_tag_name "^[A-Z]")
)
(attribute
(attribute_name
(attribute_identifier) @tag.property
)
)
)
(self_closing_tag
(
(tag_name) @_tag_name
(#match? @_tag_name "^[A-Z]")
)
(attribute
(attribute_name
(attribute_identifier) @tag.property
)
)
)
; style elements starting with lowercase letters as tags
(
(tag_name) @tag
(#match? @tag "^[a-z]")
)
; style elements starting with uppercase letters as components (types)
; Also valid might be to treat them as constructors
(
(tag_name) @tag @tag.component.type.constructor
(#match? @tag "^[A-Z]")
)
[
"<"
">"
"</"
"/>"
] @tag.punctuation.bracket
[
"{"
"}"
] @punctuation.bracket
[
"|"
] @punctuation.delimiter
[
"@"
"#"
":"
"/"
] @tag.punctuation.special
"=" @operator
; Treating (if, each, ...) as a keyword inside of blocks
; like {#if ...} or {#each ...}
(block_start_tag
tag: _ @tag.keyword
)
(block_tag
tag: _ @tag.keyword
)
(block_end_tag
tag: _ @tag.keyword
)
(expression_tag
tag: _ @tag.keyword
)
; Style quoted string attribute values
(quoted_attribute_value) @string
; Highlight the `as` keyword in each blocks
(each_start
("as") @tag.keyword
)
; Highlight the snippet name as a function
; (e.g. {#snippet foo(bar)}
(snippet_name) @function

View File

@@ -1,9 +0,0 @@
[
(element)
(if_statement)
(each_statement)
(await_statement)
(snippet_statement)
(script_element)
(style_element)
] @indent

View File

@@ -1,86 +0,0 @@
; ; injections.scm
; ; --------------
; Match script tags with a lang attribute
(script_element
(start_tag
(attribute
(attribute_name) @_attr_name
(#eq? @_attr_name "lang")
(quoted_attribute_value
(attribute_value) @language
)
)
)
(raw_text) @content
)
; Match script tags without a lang attribute
(script_element
(start_tag
(attribute
(attribute_name) @_attr_name
)*
)
(raw_text) @content
(#not-any-of? @_attr_name "lang")
(#set! language "javascript")
)
; Match the contents of the script's generics="T extends string" as typescript code
;
; Disabled for the time-being because tree-sitter is treating the generics
; attribute as a top-level typescript statement, where `T extends string` is
; not a valid top-level typescript statement.
;
; (script_element
; (start_tag
; (attribute
; (attribute_name) @_attr_name
; (#eq? @_attr_name "generics")
; (quoted_attribute_value
; (attribute_value) @content
; )
; )
; )
; (#set! language "typescript")
; )
; Mark everything as typescript because it's
; a more generic superset of javascript
; Not sure if it's possible to somehow refer to the
; script's language attribute here.
((svelte_raw_text) @content
(#set! "language" "ts")
)
; Match style tags with a lang attribute
(style_element
(start_tag
(attribute
(attribute_name) @_attr_name
(#eq? @_attr_name "lang")
(quoted_attribute_value
(attribute_value) @language
)
)
)
(raw_text) @content
)
; Match style tags without a lang attribute
(style_element
(start_tag
(attribute
(attribute_name) @_attr_name
)*
)
(raw_text) @content
(#not-any-of? @_attr_name "lang")
(#set! language "css")
)
; Downstream TODO: Style highlighting for `style:background="red"` and `style="background: red"` strings
; Downstream TODO: Style component comments as markdown

View File

@@ -1,69 +0,0 @@
(script_element
(start_tag) @name
(raw_text) @context @item
)
(script_element
(end_tag) @name @item
)
(style_element
(start_tag) @name
(raw_text) @context
) @item
(document) @item
(comment) @annotation
(if_statement
(if_start) @name
) @item
(else_block
(else_start) @name
) @item
(else_if_block
(else_if_start) @name
) @item
(element
(start_tag) @name
) @item
(element
(self_closing_tag) @name
) @item
; (if_end) @name @item
(each_statement
(each_start) @name
) @item
(snippet_statement
(snippet_start) @name
) @item
(snippet_end) @name @item
(html_tag) @name @item
(const_tag) @name @item
(await_statement
(await_start) @name
) @item
(then_block
(then_start) @name
) @item
(catch_block
(catch_start) @name
) @item

View File

@@ -1,7 +0,0 @@
(comment) @comment
[
(raw_text)
(attribute_value)
(quoted_attribute_value)
] @string

View File

@@ -1,124 +0,0 @@
use std::{env, fs};
use zed_extension_api::{self as zed, serde_json, Result};
struct SvelteExtension {
did_find_server: bool,
}
const SERVER_PATH: &str = "node_modules/svelte-language-server/bin/server.js";
const PACKAGE_NAME: &str = "svelte-language-server";
impl SvelteExtension {
fn server_exists(&self) -> bool {
fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file())
}
fn server_script_path(&mut self, id: &zed::LanguageServerId) -> Result<String> {
let server_exists = self.server_exists();
if self.did_find_server && server_exists {
return Ok(SERVER_PATH.to_string());
}
zed::set_language_server_installation_status(
id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
);
let version = zed::npm_package_latest_version(PACKAGE_NAME)?;
if !server_exists
|| zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version)
{
zed::set_language_server_installation_status(
id,
&zed::LanguageServerInstallationStatus::Downloading,
);
let result = zed::npm_install_package(PACKAGE_NAME, &version);
match result {
Ok(()) => {
if !self.server_exists() {
Err(format!(
"installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'",
))?;
}
}
Err(error) => {
if !self.server_exists() {
Err(error)?;
}
}
}
}
self.did_find_server = true;
Ok(SERVER_PATH.to_string())
}
}
impl zed::Extension for SvelteExtension {
fn new() -> Self {
Self {
did_find_server: false,
}
}
fn language_server_command(
&mut self,
id: &zed::LanguageServerId,
_: &zed::Worktree,
) -> Result<zed::Command> {
let server_path = self.server_script_path(id)?;
Ok(zed::Command {
command: zed::node_binary_path()?,
args: vec![
env::current_dir()
.unwrap()
.join(&server_path)
.to_string_lossy()
.to_string(),
"--stdio".to_string(),
],
env: Default::default(),
})
}
fn language_server_initialization_options(
&mut self,
_: &zed::LanguageServerId,
_: &zed::Worktree,
) -> Result<Option<serde_json::Value>> {
let config = serde_json::json!({
"inlayHints": {
"parameterNames": {
"enabled": "all",
"suppressWhenArgumentMatchesName": false
},
"parameterTypes": {
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": false
},
"propertyDeclarationTypes": {
"enabled": true
},
"functionLikeReturnTypes": {
"enabled": true
},
"enumMemberValues": {
"enabled": true
}
}
});
Ok(Some(serde_json::json!({
"provideFormatter": true,
"configuration": {
"typescript": config,
"javascript": config
}
})))
}
}
zed::register_extension!(SvelteExtension);

View File

@@ -1,17 +0,0 @@
[package]
name = "zed_vue"
version = "0.1.0"
edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/vue.rs"
crate-type = ["cdylib"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
zed_extension_api = "0.1.0"

View File

@@ -1 +0,0 @@
../../LICENSE-APACHE

View File

@@ -1,19 +0,0 @@
id = "vue"
name = "Vue"
description = "Vue support."
version = "0.1.0"
schema_version = 1
authors = ["Piotr Osiewicz <piotr@zed.dev>"]
repository = "https://github.com/zed-industries/zed"
[language_servers.vue-language-server]
name = "Vue Language Server"
language = "Vue.js"
language_ids = { "Vue.js" = "vue" }
# REFACTOR is explicitly disabled, as vue-lsp does not adhere to LSP protocol for code actions with these - it
# sends back a CodeAction with neither `command` nor `edits` fields set, which is against the spec.
code_action_kinds = ["", "quickfix", "refactor.rewrite"]
[grammars.vue]
repository = "https://github.com/tree-sitter-grammars/tree-sitter-vue"
commit = "7e48557b903a9db9c38cea3b7839ef7e1f36c693"

Some files were not shown because too many files have changed in this diff Show More