Compare commits

...

34 Commits

Author SHA1 Message Date
Thorsten Ball
eae06aaca9 zed 0.125.4 2024-03-12 12:32:41 +01:00
Thorsten Ball
8ebee1f31d Fix clippy warning after cherry-picking fix into 0.125.x 2024-03-12 12:32:13 +01:00
Thorsten Ball
bc4230a6b1 Fix broken ESLint by pinning to 2.2.20-Insiders release (#9215)
This fixes #9213 by pinning ESLint to `2.2.20-Insiders` which is the
last known version to work well with Zed.

Once this fix is out, we can take a closer look at upgrading to 2.4.x or
even 3.x once that's out of prerelease.

Release Notes:

- Fixed ESLint integration being broken after Mar 7 2024 due to ESLint
3.0.1 alpha release being pushed.
([#9213](https://github.com/zed-industries/zed/issues/9213)).
2024-03-12 12:18:55 +01:00
Joseph T. Lyons
430d197e91 v0.125.x stable 2024-03-06 12:29:47 -05:00
Joseph T. Lyons
a9696c59cc zed 0.125.3 2024-03-06 11:39:44 -05:00
gcp-cherry-pick-bot[bot]
3afc9d8607 Throttle the sending of UpdateFollowers messages (cherry-pick #8918) (#8946)
Cherry-picked Throttle the sending of UpdateFollowers messages (#8918)

## Problem

We're trying to figure out why we sometimes see high latency when
collaborating, even though the collab server logs indicate that messages
are not taking long to process.

We think that high volumes of certain types of messages, including
`UpdateFollowers` may cause a lot of messages to queue up, causing
delays before collab sees certain messages.

## Fix

This PR reduces the number of `UpdateFollowers` messages that clients
send to collab when scrolling around or moving the cursor, using a
time-based throttle.

The downside of this change is that scrolling will not be as smooth when
following someone. The advantage is that it will be much easier to keep
up with the stream of updates, since they will be sent much less
frequently.

## Release Notes:

- Fixed slowness that could occur when collaborating due to excessive
messages being sent to support following.

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Thorsten <thorsten@zed.dev>
Co-authored-by: Thorsten Ball <mrnugget@gmail.com>

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Thorsten <thorsten@zed.dev>
Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-03-06 15:41:19 +01:00
Kirill Bulatov
1c9ba85146 Always resolve code action if needed (#8904)
Follow-up of https://github.com/zed-industries/zed/pull/8874 and
https://github.com/zed-industries/zed/pull/7635
Closes https://github.com/zed-industries/zed/issues/7609

* mentions all `lsp::CodeActions` properties in the Zed client resolve
capabilities to remove more json out of general actions request
potentially
* removes odd `CodeActions.data` field checks, as that field is opaque
and is intended to store data, needed by the langserver to resolve this
code action
* if any `CodeAction` lacks either `command` or `edits` fields, tries to
resolve the action

This all effectively causes Zed to always fire an action resolve
request, since we update actions list (replacing the resolved actions
with the new, unresolved ones) via `refresh_code_actions`

9e66d48ccd/crates/editor/src/editor.rs (L3650)
that is being called on selections change and the actions menu open.

Yet, we do not query the resolve until the action is either applied
(selected in the list), or called for formatting, so it seems to be fine
to resolve them always, as it's not a frequent operation such as
reacting to every keystroke.

Release Notes:

- Fixed certain code actions not being resolved properly ([7609](https://github.com/zed-industries/zed/issues/7609))

---------

Co-authored-by: Derrick Laird <swampdonk@gmail.com>
2024-03-06 09:02:58 +02:00
Conrad Irwin
80ebf5aff9 Fix panic in enclosing bracket ranges (#8870)
This function was operating in the wrong co-ordinate space (c.f. #8081)

Release Notes:

- Fixed a panic on `ctrl-m` in a multibuffer
2024-03-05 15:23:50 -07:00
Conrad Irwin
acd21f5c15 Upload crashes to collab directly (#8649)
This lets us run rustc_demangle on the backtrace, which helps the Slack
view significantly.

We're also now uploading files to digital ocean's S3 equivalent (with a
1 month expiry) instead of to Slack.

This PR paves the way for (but does not yet implement) sending this data
to clickhouse too.

Release Notes:

- N/A
2024-03-05 15:23:33 -07:00
Conrad Irwin
608ad1636d Revert "Upload crashes to collab directly (#8649)"
This reverts commit c05f8154be.
2024-03-05 15:22:50 -07:00
Conrad Irwin
c05f8154be Upload crashes to collab directly (#8649)
This lets us run rustc_demangle on the backtrace, which helps the Slack
view significantly.

We're also now uploading files to digital ocean's S3 equivalent (with a
1 month expiry) instead of to Slack.

This PR paves the way for (but does not yet implement) sending this data
to clickhouse too.

Release Notes:

- N/A
2024-03-04 17:07:58 -07:00
Kirill Bulatov
de71d7c0a0 zed 0.125.2 2024-03-04 20:08:08 +02:00
Jason Lee
da671d9771 Return "open in new window" as default in recent projects (#8798)
https://github.com/zed-industries/zed/assets/5518/8bbd13a7-9144-48b0-9bc8-6651725476f8

Closes https://github.com/zed-industries/zed/issues/8651

Reworks `recent_projects::OpenRecent` action with collab projects in mind:
* keep the "open in new window" behavior for corresponding menu and command entries
* use new, "reuse current window" behavior in the recent projects picker up in the toolbar

This way, old Zed behavior is not customizable, kept as original in all main use cases — so that projects shared via remote entities: a channel and a call, are never accidentally closed, breaking the sharing. 

Release Notes:

- Return "open in new window" as default in recent projects
2024-03-04 11:43:11 +02:00
Kirill Bulatov
993c001fc4 When clicking the checkbox, toggle open the LSP trace logs (#8689)
Before this change, enabling LSP trace checkbox closed the panel and
toggled the server logs on.
Now, the newly enabled trace logs are shown instead.

Release Notes:

- Improved LSP logs checkbox behavior
2024-03-02 02:04:03 +02:00
Kirill Bulatov
7e501f6c1b Add a way to change what menu::Confirm does in the recent projects modal (#8688)
Follow-up of
https://github.com/zed-industries/zed/issues/8651#issuecomment-1973411072

Zed current default is still to reuse the current window, but now it's
possible to do
```json
"alt-cmd-o": [
  "projects::OpenRecent",
  {
    "create_new_window": true
  }
]
```
and change this.

menu::Secondary confirm does the action with opposite window creation
strategy.

Release Notes:

- Improved open recent projects flexibility: settings can change whether
`menu::Confirm` opens a new window or reuses the old one
2024-03-02 00:32:59 +02:00
Kirill Bulatov
3a0f842f3d Prompt to save files on recent project selection (#8673) 2024-03-01 22:01:02 +02:00
Conrad Irwin
38564c5ab2 zed 0.125.1 2024-02-29 10:35:20 -07:00
gcp-cherry-pick-bot[bot]
023498ef53 Ensure panel and pane sizes are integral (cherry-pick #8619) (#8620)
Cherry-picked Ensure panel and pane sizes are integral (#8619)

Fixes: #8050

For some reason that we didn't investigate, if you have view caching
enabled,
and you have non-integer sized bounds, and you are right aligning
things, the
co-ordinates can differ by +/- 1px when using the cached view.

The easiest fix for now is to just not do that.

Co-Authored-By: Antonio <as-cii@zed.dev>

Release Notes:

- Fixed the pane icons flickering
([#8050](https://github.com/zed-industries/zed/issues/8050)).

Co-authored-by: Antonio <as-cii@zed.dev>

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Antonio <as-cii@zed.dev>
2024-02-29 10:03:11 -07:00
gcp-cherry-pick-bot[bot]
1f61804ae0 Revert "Introduce a new ToggleGraphicsProfiler command (#7607)" (cherry-pick #8567) (#8569)
Cherry-picked Revert "Introduce a new `ToggleGraphicsProfiler` command
(#7607)" (#8567)

This reverts commit 0cebf68306.

Although this thing is very cool, it is a top source of crashes.

Example crash:
```
Segmentation fault: 11 on thread 26
  objc_retain +16
  invocation function for block in Overlay::onCommandBufferCommit(id<MTLCommandBuffer>) +60
  MTLDispatchListApply +52
```

Release Notes:

- Removed "Toggle Graphics Profiler" as it crashes too much.

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-02-28 19:06:09 -07:00
gcp-cherry-pick-bot[bot]
d229b39621 Avoid an unwrap when loading languages (cherry-pick #8562) (#8566)
Cherry-picked Avoid an unwrap when loading languages (#8562)

We couldn't reproduce the panic, but I believe it was possible when
uninstalling an extension while one if its grammars was still loading.

Release Notes:
- Fixed a crash that could happen when uninstalling a language extension
while its grammar was loading.

---------

Co-authored-by: Conrad <conrad@zed.dev>

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Conrad <conrad@zed.dev>
2024-02-28 15:51:52 -07:00
Joseph T. Lyons
ae2326c781 Fix view release notes locally (#8553)
🤦‍♂️

Release Notes:

- N/A
2024-02-28 15:50:41 -05:00
Joseph T. Lyons
c4d1855824 v0.125.x preview 2024-02-28 13:13:28 -05:00
Antonio Scandurra
57f5f128f3 Fix flickering cursor style when a pane was zoomed (#8546)
Release Notes:

- N/A

Co-authored-by: Max Brunsfeld <max@zed.dev>
2024-02-28 18:50:48 +01:00
Thorsten Ball
7efa8d079d Do not set rules by default for eslint (#8545)
Follow-up to and fix for #8537.

Turns out that if you set `rules: []` it doesn't mean "no matchers", but
it means "no rules". So let's not set a default here.

Release Notes:

- N/A, see #8537

Co-authored-by: Conrad <conrad@zed.dev>
2024-02-28 18:39:54 +01:00
Andrew Lygin
d0ffd51bb1 Optimize file finder subscriptions (#8343)
Fixes #7519 

Optimizes file finder subscriptions &mdash; it now only subscribes to
worktrees updates instead of all project updates.

Project panel could also be optimized this way, I guess.

Release Notes:

- Fix selection resets in the file finder during language server
startup ([7519](https://github.com/zed-industries/zed/issues/7519))
2024-02-28 19:02:21 +02:00
Antonio Scandurra
7aba9eb4b7 Introduce a short-term solution for flickering (#8542)
This uses bounds checking alone to determine hover state to avoid
flicker. It's a short-term solution because the rendering is incorrect.
We think this is better than flickering though and buys us some time as
we work on a more robust solution overall.

Release Notes:

- Fixed flickering when hovering.

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-02-28 17:57:20 +01:00
Hugo Urías
517ea734ee Add Coffeescript, Scala, FSharp, TCL and Nim icons and add SQL as "storage" (#8447)
I would like to add these file icons all from the source svgrepo.com and
with a size of 14x14. Also I've modified file_types.json in order to add
the file types and path to the image aswell as added SQL as a storage
type so it's linked to an icon.

Here is how these new changes would look like:
<img width="240" alt="Captura de pantalla 2024-02-26 a las 19 30 33"
src="https://github.com/zed-industries/zed/assets/93369643/73e50e4a-bfe8-4239-b919-280150051e36">

Release Notes:

- Added icons for Coffeescript, F#, Nim, Scala, and TCL files.
- Updated icon for SQL files.
2024-02-28 11:09:29 -05:00
Thorsten Ball
a52177fd39 Allow users to configure ESLint codeActionOnSave settings (#8537)
This fixes #8533 by allowing users to specify the settings that are
passed to ESLint on workspace initialization.

Example Zed `settings.json` to enable `fixAll` for eslint when
saving/formatting, but only for the `import/order` rule:

```json
{
  "languages": {
    "JavaScript": {
      "code_actions_on_format": {
        "source.fixAll.eslint": true
      }
    }
  },
  "lsp": {
    "eslint": {
      "settings": {
        "codeActionOnSave": {
          "rules": ["import/order"]
        }
      }
    },
  }
}
```

The possible settings are described in the README of `vscode-eslint`
here:
https://github.com/Microsoft/vscode-eslint?tab=readme-ov-file#settings-options

- `eslint.codeActionsOnSave.enable` (default: `true`, config key in Zed:
`lsp.eslint.settings.codeActionOnSave.enable`)
- `eslint.codeActionsOnSave.mode` (default: not set by Zed, config key
in Zed: `lsp.eslint.settings.codeActionOnSave.mode`)
- `eslint.codeActionsOnSave.rules` (default: `[]`, config key in Zed:
`lsp.eslint.settings.codeActionOnSave.rules`)

Yes, in the readme it's plural: `codeActionsOnSave`, but since
`eslint-vscode` we're using this old release:


https://github.com/microsoft/vscode-eslint/releases/tag/release%2F2.2.20-Insider

We use the singular version:
https://github.com/microsoft/vscode-eslint/blob/release/2.2.20-Insider/server/src/eslintServer.ts#L461

Our schema looks like this:

```json
{
  "lsp": {
    "eslint": {
      "settings": {
        "codeActionOnSave": {
          "enable": true,
          "rules": ["import/order"],
          "mode": "all"
        }
      }
    },
  }
}
```

We should probably fix this and upgrade to the newest version of ESLint.

Release Notes:

- Added ability for users to configure settings for ESLint's
`codeActionOnSave`, e.g. specifying `rules` that should be respected
when also using `"code_actions_on_format": {"source.fixAll.eslint":
true}`. These settings can be passed to ESLint as part of the `"lsp"`
part of the Zed settings. Example: `{"lsp": {"eslint": {"settings":
{"codeActionOnSave": { "rules": ["import/order"] }}}}}`
([#8533](https://github.com/zed-industries/zed/issues/8533)).

Demo:


https://github.com/zed-industries/zed/assets/1185253/5c0cf900-9acb-4a70-b89d-49b6eeb6f0e4
2024-02-28 17:04:36 +01:00
Dedar Alam
893e55ff96 Downscale source file icons to 14x14 (#8521)
Update file icon size to 14 * 14
- css 
- bun 
- erlang 
- terraform 
- php 
- haskell

Release Notes:

- N/A
2024-02-28 11:04:27 -05:00
Thorsten Ball
f8959834c4 Add ability to run ESLint (and other non-primary language server) code actions on format (#8496)
This PR does two things to fix
https://github.com/zed-industries/zed/issues/4325:

1. It changes the way `code_actions_on_format` works to send the
possibly configured code actions to _all_ (and not just the primary)
languages servers. That means configured code actions can now be sent to
ESLint, tailwind, ... and other language servers.
2. It enables `codeActionsOnSave` by default for ESLint. That does
**not** mean that by default we will run something on save, but only
that we enable it for ESLint.

Users can then configure their Zed to run the `eslint` code action on
format. Example, for JavaScript:

```json
    {
      "languages": {
        "JavaScript": {
          "code_actions_on_format": {
            "source.fixAll.eslint": true
          }
        },
      }
    }
```

Release Notes:

- Added ability to run ESLint fixes when formatting a buffer. Code
actions configured in
[`code_actions_on_format`](https://zed.dev/docs/configuring-zed#code-actions-on-format)
are now being sent to _all_ language servers connected to a buffer, not
just the primary one. So if a user now sets `"code_actions_on_format": {
"source.fixAll.eslint": true }` in their Zed settings, the
`source.fixAll.eslint` code action will be sent to ESLint, which is not
a primary language server. Since the formatter (prettier, or external
commands, or another language server, ...) still runs, it's important
that these code actions and the formatter don't clash.
([#4325](https://github.com/zed-industries/zed/issues/4325)).

Demo:



https://github.com/zed-industries/zed/assets/1185253/9ef03ad5-1f5c-4d46-b72a-eef611e32f39
2024-02-28 13:55:20 +01:00
Kirill Bulatov
2e516261fe Add tests on inventory task sorting 2024-02-28 14:13:40 +02:00
Kirill Bulatov
ca092fb694 Move NumericPrefixWithSuffix into utils 2024-02-28 14:13:40 +02:00
Kirill Bulatov
96d9df073e Sort tasks modal entries by last used time 2024-02-28 14:13:40 +02:00
Thorsten Ball
9f7e625d37 Fix formatting of code in prettier crate (#8526)
Came across this code, saw lots of blue squiggly lines, saw a chance to
simplify the code a little bit and reduce indentation.

(Kinda ironic that I'm the one formatting the prettier code, right?)

Release Notes:

- N/A
2024-02-28 12:51:08 +01:00
77 changed files with 1480 additions and 508 deletions

9
Cargo.lock generated
View File

@@ -6374,6 +6374,7 @@ dependencies = [
name = "picker"
version = "0.1.0"
dependencies = [
"anyhow",
"ctor",
"editor",
"env_logger",
@@ -7143,11 +7144,16 @@ dependencies = [
name = "recent_projects"
version = "0.1.0"
dependencies = [
"editor",
"fuzzy",
"gpui",
"language",
"menu",
"ordered-float 2.10.0",
"picker",
"project",
"serde",
"serde_json",
"smol",
"ui",
"util",
@@ -10487,6 +10493,7 @@ dependencies = [
"take-until",
"tempfile",
"tendril",
"unicase",
"url",
]
@@ -11602,7 +11609,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.125.0"
version = "0.125.4"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -294,6 +294,7 @@ tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", r
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
unindent = "0.1.7"
unicase = "2.6"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4"] }
wasmtime = "18.0"

View File

@@ -1,3 +1,6 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z"/>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7 13.179688 C 10.867188 13.179688 14 10.652344 14 7.53125 C 14 5.59375 12.800781 3.886719 10.957031 2.871094 C 10.207031 2.453125 9.617188 2.078125 9.125 1.769531 C 8.199219 1.179688 7.628906 0.820312 7 0.820312 C 6.359375 0.820312 5.636719 1.277344 4.6875 1.882812 C 4.148438 2.230469 3.601562 2.558594 3.042969 2.871094 C 1.199219 3.886719 0 5.59375 0 7.53125 C 0 10.652344 3.132812 13.179688 7 13.179688 Z M 6.183594 2.75 C 6.378906 2.308594 6.476562 1.828125 6.472656 1.34375 C 6.472656 1.261719 6.589844 1.234375 6.605469 1.328125 C 6.992188 2.953125 6.082031 3.757812 5.40625 4.027344 C 5.335938 4.054688 5.292969 3.953125 5.347656 3.902344 C 5.703125 3.582031 5.988281 3.191406 6.183594 2.75 Z M 7.382812 2.691406 C 7.328125 2.214844 7.171875 1.757812 6.925781 1.347656 L 6.925781 1.335938 C 6.886719 1.265625 6.976562 1.183594 7.035156 1.234375 C 8.179688 2.46875 7.796875 3.609375 7.359375 4.183594 C 7.3125 4.242188 7.226562 4.179688 7.25 4.109375 C 7.394531 3.652344 7.4375 3.167969 7.382812 2.691406 Z M 8.417969 2.363281 C 8.183594 1.949219 7.863281 1.589844 7.480469 1.308594 L 7.480469 1.300781 C 7.414062 1.253906 7.464844 1.140625 7.546875 1.175781 C 9.058594 1.808594 9.164062 3.03125 8.980469 3.746094 C 8.976562 3.761719 8.964844 3.777344 8.953125 3.785156 C 8.921875 3.808594 8.882812 3.800781 8.863281 3.773438 C 8.851562 3.757812 8.847656 3.742188 8.847656 3.722656 C 8.800781 3.246094 8.65625 2.78125 8.417969 2.363281 Z M 5.453125 2.691406 C 5.09375 3.007812 4.703125 3.132812 4.25 3.273438 C 4.179688 3.273438 4.132812 3.230469 4.15625 3.167969 C 5.179688 2.636719 5.542969 2.207031 5.90625 1.546875 C 5.90625 1.546875 5.996094 1.480469 6.015625 1.597656 C 6.015625 1.773438 5.8125 2.371094 5.453125 2.691406 Z M 8.335938 9.246094 C 8.253906 9.597656 8.0625 9.914062 7.789062 10.152344 C 7.589844 10.355469 7.324219 10.480469 7.039062 10.511719 C 6.746094 10.484375 6.472656 10.359375 6.265625 10.152344 C 5.996094 9.914062 5.808594 9.597656 5.726562 9.246094 C 5.71875 9.203125 5.734375 9.160156 5.761719 9.128906 C 5.792969 9.101562 5.835938 9.085938 5.875 9.089844 L 8.1875 9.089844 C 8.230469 9.085938 8.269531 9.101562 8.300781 9.132812 C 8.328125 9.160156 8.34375 9.203125 8.335938 9.246094 Z M 5.152344 7.976562 C 4.714844 8.273438 4.128906 8.210938 3.761719 7.832031 C 3.390625 7.445312 3.335938 6.855469 3.625 6.40625 C 3.746094 6.21875 3.917969 6.074219 4.121094 5.992188 C 4.320312 5.90625 4.542969 5.882812 4.757812 5.925781 C 4.972656 5.96875 5.167969 6.078125 5.320312 6.234375 C 5.636719 6.5625 5.730469 7.046875 5.558594 7.46875 C 5.476562 7.675781 5.335938 7.851562 5.152344 7.976562 Z M 10.109375 7.980469 C 9.671875 8.273438 9.085938 8.210938 8.71875 7.832031 C 8.511719 7.617188 8.398438 7.332031 8.398438 7.035156 C 8.398438 6.8125 8.464844 6.589844 8.585938 6.40625 C 8.707031 6.21875 8.878906 6.074219 9.082031 5.988281 C 9.28125 5.90625 9.503906 5.882812 9.71875 5.925781 C 9.933594 5.972656 10.128906 6.078125 10.285156 6.238281 C 10.597656 6.566406 10.691406 7.050781 10.515625 7.472656 C 10.433594 7.679688 10.292969 7.855469 10.109375 7.980469 Z M 10.109375 7.980469 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -1,4 +1,6 @@
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M24.235 6.519l-16.47-0.004 0.266 3.277 12.653 0.002-0.319 3.394h-8.298l0.3 3.215h7.725l-0.457 4.403-3.636 1.005-3.694-1.012-0.235-2.637h-3.262l0.362 4.817 6.829 2.128 6.714-1.912 1.521-16.675zM2.879 1.004h26.242l-2.387 26.946-10.763 3.045-10.703-3.047z"></path>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.601562 2.851562 L 3.398438 2.851562 L 3.511719 4.285156 L 9.050781 4.285156 L 8.910156 5.769531 L 5.28125 5.769531 L 5.410156 7.175781 L 8.789062 7.175781 L 8.589844 9.101562 L 7 9.542969 L 5.382812 9.097656 L 5.28125 7.945312 L 3.851562 7.945312 L 4.011719 10.054688 L 7 10.984375 L 9.9375 10.148438 Z M 1.257812 0.4375 L 12.742188 0.4375 L 11.695312 12.226562 L 6.988281 13.558594 L 2.304688 12.226562 Z M 1.257812 0.4375 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 730 B

View File

@@ -1 +1,12 @@
<svg height="64" viewBox="0 0 128 128" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="0" x2="128" y1="128" y2="0"><stop offset="0" stop-color="#333"/><stop offset="1" stop-color="#5d5d5d"/></linearGradient><path d="m12.239265 30.664279h14.960911c-5.59432 5.460938-7.654216 10.692785-10.342106 18.023379-3.200764 8.729348-.549141 29.987457 3.815534 37.55289 2.943384 5.101853 6.282685 8.994876 8.233522 11.095173h-16.667861zm89.614855 0h13.90661v66.671442h-13.55518c1.31391-1.750328 3.43934-4.534454 5.12085-6.426163 2.32782-2.618784 4.97023-6.978412 4.97023-6.978412l-16.015202-8.133112s-5.48977 11.600331-15.964999 15.964998c-10.475214 4.364666-19.784679-.838179-25.604243-7.530659-5.819578-6.692502-5.82371-22.14014-5.82371-22.14014h60.797524c1.16391-14.839892-2.63216-21.249816-4.66901-25.90547-.91799-2.098266-1.89261-3.810819-3.16287-5.522484zm-38.356164 1.757154c.35429-.01632.731685-.0092 1.104497 0 11.930114.290977 13.053143 12.802122 13.053143 12.802122h-27.311192s2.170772-12.298638 13.153552-12.802122z" fill="url(#a)"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<defs>
<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="0" y1="128" x2="128" y2="0" gradientTransform="matrix(0.109375,0,0,0.109375,0,0)">
<stop offset="0" style="stop-color:rgb(20%,20%,20%);stop-opacity:1;"/>
<stop offset="1" style="stop-color:rgb(36.470588%,36.470588%,36.470588%);stop-opacity:1;"/>
</linearGradient>
</defs>
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 1.339844 3.355469 L 2.976562 3.355469 C 2.363281 3.953125 2.136719 4.523438 1.84375 5.324219 C 1.492188 6.28125 1.785156 8.605469 2.261719 9.433594 C 2.582031 9.992188 2.949219 10.417969 3.160156 10.644531 L 1.339844 10.644531 Z M 11.140625 3.355469 L 12.660156 3.355469 L 12.660156 10.644531 L 11.179688 10.644531 C 11.324219 10.453125 11.554688 10.148438 11.738281 9.941406 C 11.992188 9.65625 12.28125 9.179688 12.28125 9.179688 L 10.53125 8.289062 C 10.53125 8.289062 9.929688 9.558594 8.785156 10.035156 C 7.640625 10.515625 6.621094 9.945312 5.984375 9.214844 C 5.347656 8.480469 5.347656 6.792969 5.347656 6.792969 L 11.996094 6.792969 C 12.125 5.167969 11.710938 4.46875 11.484375 3.957031 C 11.386719 3.726562 11.277344 3.542969 11.140625 3.355469 Z M 6.945312 3.546875 C 6.984375 3.542969 7.023438 3.546875 7.066406 3.546875 C 8.371094 3.578125 8.492188 4.945312 8.492188 4.945312 L 5.507812 4.945312 C 5.507812 4.945312 5.742188 3.601562 6.945312 3.546875 Z M 6.945312 3.546875 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -27,6 +27,7 @@
"css": "css",
"csv": "storage",
"cts": "typescript",
"coffee": "coffeescript",
"dart": "dart",
"dat": "storage",
"db": "storage",
@@ -48,6 +49,7 @@
"fmp": "storage",
"fp7": "storage",
"frm": "storage",
"fs": "fsharp",
"gdb": "storage",
"gif": "image",
"gitattributes": "vcs",
@@ -104,6 +106,7 @@
"myd": "storage",
"myi": "storage",
"nu": "terminal",
"nim": "nim",
"odp": "document",
"ods": "document",
"odt": "document",
@@ -138,6 +141,9 @@
"sqlite": "storage",
"svelte": "template",
"svg": "image",
"sc": "scala",
"scala": "scala",
"sql": "storage",
"swift": "swift",
"tf": "terraform",
"tfvars": "terraform",
@@ -148,6 +154,7 @@
"ttf": "font",
"tsx": "code",
"txt": "document",
"tcl": "tcl",
"vue": "vue",
"wav": "audio",
"webm": "video",
@@ -189,6 +196,9 @@
"css": {
"icon": "icons/file_icons/css.svg"
},
"coffeescript": {
"icon": "icons/file_icons/coffeescript.svg"
},
"dart": {
"icon": "icons/file_icons/dart.svg"
},
@@ -222,6 +232,9 @@
"font": {
"icon": "icons/file_icons/font.svg"
},
"fsharp": {
"icon": "icons/file_icons/fsharp.svg"
},
"haskell": {
"icon": "icons/file_icons/haskell.svg"
},
@@ -258,6 +271,9 @@
"ocaml": {
"icon": "icons/file_icons/ocaml.svg"
},
"nim": {
"icon": "icons/file_icons/nim.svg"
},
"phoenix": {
"icon": "icons/file_icons/phoenix.svg"
},
@@ -288,6 +304,9 @@
"storage": {
"icon": "icons/file_icons/database.svg"
},
"scala": {
"icon": "icons/file_icons/scala.svg"
},
"swift": {
"icon": "icons/file_icons/swift.svg"
},
@@ -306,6 +325,9 @@
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"tcl": {
"icon": "icons/file_icons/tcl.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 3.324219 3.496094 C 1.507812 5.40625 0.0273438 6.984375 0.0273438 6.996094 C 0.0273438 7.015625 1.511719 8.59375 3.332031 10.503906 L 6.632812 13.980469 L 6.632812 10.472656 L 4.984375 8.734375 L 3.332031 6.996094 L 4.984375 5.257812 L 6.632812 3.519531 L 6.628906 1.773438 L 6.621094 0.0273438 Z M 3.324219 3.496094 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.125 1.773438 L 7.125 3.492188 L 8.769531 5.222656 C 9.675781 6.171875 10.421875 6.96875 10.425781 6.984375 C 10.433594 7.003906 9.691406 7.800781 8.78125 8.761719 L 7.125 10.503906 L 7.125 13.972656 L 7.214844 13.890625 C 7.296875 13.820312 8.203125 12.90625 9.167969 11.910156 C 9.398438 11.671875 9.605469 11.464844 9.621094 11.449219 C 9.671875 11.402344 11.261719 9.789062 11.601562 9.4375 C 11.773438 9.261719 11.957031 9.082031 12 9.035156 C 12.046875 8.988281 12.433594 8.59375 12.863281 8.160156 C 13.289062 7.726562 13.722656 7.289062 13.824219 7.183594 L 14.007812 6.996094 L 13.808594 6.796875 C 13.179688 6.167969 12.527344 5.503906 11.820312 4.785156 C 11.574219 4.53125 11.105469 4.058594 10.785156 3.734375 C 10.460938 3.414062 9.871094 2.8125 9.472656 2.402344 C 9.074219 1.996094 8.609375 1.515625 8.4375 1.339844 C 8.265625 1.160156 7.910156 0.800781 7.652344 0.539062 C 7.394531 0.273438 7.167969 0.0585938 7.15625 0.0585938 C 7.132812 0.0585938 7.125 0.59375 7.125 1.773438 Z M 7.125 1.773438 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.453125 5.757812 C 4.308594 6.964844 4.285156 6.992188 4.332031 7.050781 C 4.359375 7.082031 4.886719 7.636719 5.5 8.289062 L 6.621094 9.464844 L 6.628906 8.222656 C 6.632812 7.539062 6.632812 6.429688 6.628906 5.753906 L 6.621094 4.527344 Z M 5.453125 5.757812 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,13 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg977"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs981" />
<path
id="path973"
d="M 10.699219 8.9003906 L 10.699219 9 C 12.199219 11.3 13.800781 13.600391 15.300781 15.900391 L 15.300781 16 C 13.800781 18.3 12.199219 20.600391 10.699219 22.900391 L 10.699219 23 L 14.199219 23 L 14.300781 22.900391 C 15.200781 21.500391 16.199609 20.099219 17.099609 18.699219 C 17.199609 18.599219 17.099219 18.599219 17.199219 18.699219 C 18.099219 20.099219 19.1 21.500391 20 22.900391 L 20.099609 23 L 23.599609 23 C 21.699609 20 19.699219 17.099609 17.699219 14.099609 C 16.499219 12.399609 15.399219 10.600391 14.199219 8.9003906 L 10.699219 8.9003906 z M 6 9 C 7.6 11.3 9.0996094 13.6 10.599609 16 L 10.599609 16.099609 C 9.4996094 17.799609 8.4007813 19.399609 7.3007812 21.099609 C 6.8007813 21.699609 6.4 22.4 6 23 L 6 23.099609 L 9.5 23.099609 C 11.1 20.699609 12.699219 18.399609 14.199219 16.099609 L 14.199219 16 C 13.499219 14.8 12.700391 13.7 11.900391 12.5 C 11.100391 11.4 10.399609 10.199609 9.5996094 9.0996094 L 9.5 9 L 6 9 z M 18.199219 13 L 18.199219 13.099609 C 18.699219 13.899609 19.199219 14.600391 19.699219 15.400391 L 26 15.400391 L 26 13 L 18.199219 13 z M 20.5 16.599609 L 20.5 16.699219 C 21 17.499219 21.5 18.2 22 19 L 26 19 L 26 16.599609 L 20.5 16.599609 z " />
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 4.679688 3.894531 L 4.679688 3.9375 C 5.335938 4.945312 6.039062 5.949219 6.695312 6.957031 L 6.695312 7 C 6.039062 8.007812 5.335938 9.011719 4.679688 10.019531 L 4.679688 10.0625 L 6.210938 10.0625 L 6.257812 10.019531 C 6.648438 9.40625 7.085938 8.792969 7.480469 8.179688 C 7.523438 8.136719 7.480469 8.136719 7.523438 8.179688 C 7.917969 8.792969 8.355469 9.40625 8.75 10.019531 L 8.792969 10.0625 L 10.324219 10.0625 C 9.492188 8.75 8.617188 7.480469 7.742188 6.167969 C 7.21875 5.425781 6.738281 4.636719 6.210938 3.894531 Z M 2.625 3.9375 C 3.324219 4.945312 3.980469 5.949219 4.636719 7 L 4.636719 7.042969 C 4.15625 7.789062 3.675781 8.488281 3.195312 9.230469 C 2.976562 9.492188 2.800781 9.800781 2.625 10.0625 L 2.625 10.105469 L 4.15625 10.105469 C 4.855469 9.054688 5.554688 8.050781 6.210938 7.042969 L 6.210938 7 C 5.90625 6.476562 5.554688 5.992188 5.207031 5.46875 C 4.855469 4.988281 4.550781 4.460938 4.199219 3.980469 L 4.15625 3.9375 Z M 7.960938 5.6875 L 7.960938 5.730469 C 8.179688 6.082031 8.398438 6.386719 8.617188 6.738281 L 11.375 6.738281 L 11.375 5.6875 Z M 8.96875 7.261719 L 8.96875 7.304688 C 9.1875 7.65625 9.40625 7.960938 9.625 8.3125 L 11.375 8.3125 L 11.375 7.261719 Z M 8.96875 7.261719 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.054688 1.691406 C 7.054688 1.691406 6.519531 2.148438 5.972656 2.59375 C 5.410156 2.578125 4.304688 2.710938 3.710938 2.945312 C 3.15625 2.570312 2.671875 2.15625 2.671875 2.15625 C 2.671875 2.15625 2.257812 2.917969 2 3.367188 C 1.613281 3.585938 1.226562 3.832031 0.882812 4.160156 C 0.480469 3.988281 0.015625 3.78125 0 3.777344 C 0.53125 4.921875 0.886719 6.070312 1.863281 6.761719 C 3.410156 4.144531 10.601562 4.386719 12.183594 6.746094 C 13.203125 6.175781 13.597656 4.953125 14 3.820312 C 13.957031 3.835938 13.410156 4.03125 13.058594 4.175781 C 12.84375 3.929688 12.347656 3.550781 12.0625 3.367188 C 11.847656 2.949219 11.628906 2.539062 11.402344 2.128906 C 11.402344 2.128906 10.941406 2.496094 10.402344 2.902344 C 9.675781 2.757812 8.800781 2.585938 8.0625 2.628906 C 7.71875 2.320312 7.382812 2.011719 7.054688 1.691406 Z M 0.550781 6.210938 L 1.828125 9.519531 C 4.046875 12.652344 9.707031 12.867188 12.175781 9.582031 C 12.757812 8.171875 13.546875 6.191406 13.546875 6.191406 C 12.914062 7.199219 11.882812 7.890625 11.246094 8.261719 C 10.796875 8.527344 9.757812 8.6875 9.757812 8.6875 L 7.023438 7.171875 L 4.277344 8.65625 C 4.277344 8.65625 3.25 8.480469 2.785156 8.25 C 1.847656 7.714844 1.214844 7.078125 0.550781 6.210938 Z M 0.550781 6.210938 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +1,6 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512"><path d="M170.322 349.808c-2.4-15.66-9-28.38-25.020-34.531-6.27-2.4-11.7-6.78-17.88-9.54-7.020-3.15-14.16-6.15-21.57-8.1-5.61-1.5-10.83 1.020-14.16 5.94-3.15 4.62-0.87 8.97 1.77 12.84 2.97 4.35 6.27 8.49 9.6 12.57 5.52 6.78 11.37 13.29 16.74 20.161 5.13 6.57 9.51 13.86 8.76 22.56-1.65 19.080-10.29 34.891-24.21 47.76-1.53 1.38-4.23 2.37-6.21 2.19-8.88-0.96-16.95-4.32-23.46-10.53-7.47-7.11-6.33-15.48 2.61-20.67 2.13-1.23 4.35-2.37 6.3-3.87 5.46-4.11 7.29-11.13 4.32-17.22-1.41-2.94-3-6.12-5.34-8.25-11.43-10.41-22.651-21.151-34.891-30.63-29.671-23.041-44.91-53.52-47.251-90.421-2.64-40.981 6.87-79.231 28.5-114.242 8.19-13.29 17.73-25.951 32.37-32.52 9.96-4.47 20.88-6.99 31.531-9.78 29.311-7.71 58.89-13.5 89.401-8.34 26.28 4.41 45.511 17.94 54.331 43.77 5.79 16.89 7.17 34.35 5.37 52.231-3.54 35.131-29.49 66.541-63.331 75.841-14.67 4.020-22.68 1.77-31.5-10.44-6.33-8.79-11.58-18.36-17.25-27.631-0.84-1.38-1.44-2.97-2.16-4.44-0.69-1.47-1.44-2.88-2.16-4.35 2.13 15.24 5.67 29.911 13.98 42.99 4.5 7.11 10.5 12.36 19.29 13.14 32.34 2.91 59.641-7.71 79.021-33.721 21.69-29.101 26.461-62.581 20.19-97.831-1.23-6.96-3.3-13.77-4.77-20.7-0.99-4.47 0.78-7.77 5.19-9.33 2.040-0.69 4.14-1.26 6.18-1.68 26.461-5.7 53.221-7.59 80.191-4.86 30.601 3.060 59.551 11.46 85.441 28.471 40.531 26.67 65.641 64.621 79.291 110.522 1.98 6.66 2.28 13.95 2.46 20.971 0.12 4.68-2.88 5.91-6.45 2.97-3.93-3.21-7.53-6.87-10.92-10.65-3.15-3.57-5.67-7.65-8.73-11.4-2.37-2.94-4.44-2.49-5.58 1.17-0.72 2.22-1.35 4.41-1.98 6.63-7.080 25.26-18.24 48.3-36.33 67.711-2.52 2.73-4.77 6.78-5.070 10.38-0.78 9.96-1.35 20.13-0.39 30.060 1.98 21.331 5.070 42.57 7.47 63.871 1.35 12.030-2.52 19.11-13.83 23.281-7.95 2.91-16.47 5.040-24.87 5.64-13.38 0.93-26.88 0.27-40.32 0.27-0.36-15 0.93-29.731-13.17-37.771 2.73-11.13 5.88-21.69 7.77-32.49 1.56-8.97 0.24-17.79-6.060-25.14-5.91-6.93-13.32-8.82-20.101-4.86-20.43 11.91-41.671 11.97-63.301 4.17-9.93-3.6-16.86-1.56-22.351 7.5-5.91 9.75-8.4 20.7-7.74 31.771 0.84 13.95 3.27 27.75 5.13 41.64 1.020 7.77 0.15 9.78-7.56 11.76-17.13 4.35-34.56 4.83-52.081 3.42-0.93-0.090-1.86-0.48-2.46-0.63-0.87-14.55 0.66-29.671-16.68-37.411 7.68-16.29 6.63-33.18 3.99-50.070l-0.060-0.15zM66.761 292.718c2.55-2.4 4.59-6.15 5.31-9.6 1.8-8.64-4.68-20.22-12.18-23.43-3.99-1.74-7.47-1.11-10.29 2.070-6.87 7.77-13.65 15.63-20.401 23.521-1.14 1.35-2.16 2.94-2.97 4.53-2.7 5.19-1.11 8.97 4.65 10.38 3.48 0.87 7.080 1.050 10.65 1.56 9.3-0.9 18.3-2.46 25.23-9v-0.030zM67.541 206.347c-0.030-6.18-5.19-11.34-11.28-11.37-6.27-0.030-11.67 5.58-11.46 11.76 0.27 6.21 5.43 11.19 11.61 11.070 6.24-0.090 11.22-5.19 11.16-11.43l-0.030-0.030z"></path></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 4.65625 9.566406 C 4.589844 9.136719 4.410156 8.789062 3.972656 8.621094 C 3.800781 8.554688 3.652344 8.433594 3.484375 8.359375 C 3.292969 8.273438 3.097656 8.191406 2.894531 8.136719 C 2.742188 8.097656 2.597656 8.167969 2.507812 8.300781 C 2.421875 8.425781 2.484375 8.546875 2.554688 8.652344 C 2.636719 8.769531 2.726562 8.882812 2.816406 8.996094 C 2.96875 9.179688 3.128906 9.359375 3.277344 9.546875 C 3.417969 9.726562 3.535156 9.925781 3.515625 10.164062 C 3.46875 10.6875 3.234375 11.117188 2.851562 11.46875 C 2.8125 11.507812 2.738281 11.535156 2.683594 11.53125 C 2.441406 11.503906 2.21875 11.410156 2.042969 11.242188 C 1.835938 11.046875 1.867188 10.820312 2.113281 10.675781 C 2.171875 10.644531 2.230469 10.613281 2.285156 10.570312 C 2.433594 10.457031 2.484375 10.265625 2.402344 10.101562 C 2.367188 10.019531 2.320312 9.933594 2.257812 9.875 C 1.945312 9.589844 1.636719 9.296875 1.304688 9.035156 C 0.492188 8.40625 0.0742188 7.574219 0.0117188 6.5625 C -0.0585938 5.445312 0.199219 4.398438 0.792969 3.441406 C 1.015625 3.078125 1.277344 2.730469 1.675781 2.550781 C 1.949219 2.429688 2.246094 2.359375 2.539062 2.285156 C 3.339844 2.074219 4.148438 1.914062 4.984375 2.054688 C 5.703125 2.175781 6.226562 2.546875 6.46875 3.253906 C 6.625 3.714844 6.664062 4.191406 6.617188 4.679688 C 6.519531 5.640625 5.808594 6.5 4.882812 6.753906 C 4.484375 6.863281 4.261719 6.804688 4.023438 6.46875 C 3.847656 6.230469 3.707031 5.96875 3.550781 5.714844 C 3.527344 5.675781 3.511719 5.632812 3.492188 5.59375 C 3.472656 5.550781 3.453125 5.511719 3.433594 5.472656 C 3.492188 5.890625 3.585938 6.292969 3.816406 6.648438 C 3.9375 6.84375 4.101562 6.988281 4.34375 7.007812 C 5.226562 7.085938 5.972656 6.796875 6.503906 6.085938 C 7.097656 5.289062 7.226562 4.375 7.054688 3.410156 C 7.019531 3.21875 6.964844 3.035156 6.925781 2.84375 C 6.898438 2.722656 6.945312 2.632812 7.066406 2.589844 C 7.121094 2.570312 7.179688 2.554688 7.234375 2.542969 C 7.960938 2.386719 8.691406 2.335938 9.429688 2.410156 C 10.265625 2.496094 11.054688 2.722656 11.765625 3.191406 C 12.871094 3.917969 13.558594 4.957031 13.933594 6.210938 C 13.988281 6.394531 13.996094 6.59375 14 6.785156 C 14.003906 6.914062 13.921875 6.945312 13.824219 6.867188 C 13.714844 6.777344 13.617188 6.679688 13.523438 6.574219 C 13.4375 6.476562 13.371094 6.367188 13.285156 6.261719 C 13.222656 6.183594 13.164062 6.195312 13.132812 6.296875 C 13.113281 6.355469 13.097656 6.414062 13.078125 6.476562 C 12.886719 7.167969 12.582031 7.796875 12.085938 8.328125 C 12.015625 8.402344 11.957031 8.511719 11.949219 8.613281 C 11.925781 8.882812 11.910156 9.164062 11.9375 9.433594 C 11.992188 10.015625 12.074219 10.597656 12.140625 11.179688 C 12.179688 11.507812 12.070312 11.703125 11.761719 11.816406 C 11.546875 11.894531 11.3125 11.953125 11.082031 11.972656 C 10.71875 11.996094 10.347656 11.976562 9.980469 11.976562 C 9.96875 11.566406 10.003906 11.164062 9.621094 10.945312 C 9.695312 10.640625 9.78125 10.351562 9.832031 10.058594 C 9.875 9.8125 9.839844 9.570312 9.667969 9.371094 C 9.503906 9.179688 9.304688 9.128906 9.117188 9.238281 C 8.558594 9.5625 7.976562 9.5625 7.386719 9.351562 C 7.113281 9.253906 6.925781 9.308594 6.773438 9.554688 C 6.613281 9.824219 6.546875 10.121094 6.5625 10.425781 C 6.585938 10.804688 6.652344 11.183594 6.703125 11.5625 C 6.730469 11.777344 6.707031 11.832031 6.496094 11.886719 C 6.027344 12.003906 5.550781 12.015625 5.074219 11.976562 C 5.046875 11.976562 5.023438 11.964844 5.007812 11.960938 C 4.980469 11.5625 5.023438 11.148438 4.550781 10.9375 C 4.761719 10.492188 4.730469 10.03125 4.660156 9.570312 Z M 1.824219 8.003906 C 1.894531 7.9375 1.949219 7.835938 1.96875 7.742188 C 2.019531 7.503906 1.84375 7.1875 1.636719 7.101562 C 1.527344 7.054688 1.433594 7.070312 1.355469 7.15625 C 1.167969 7.371094 0.984375 7.585938 0.796875 7.800781 C 0.765625 7.835938 0.738281 7.882812 0.71875 7.925781 C 0.644531 8.066406 0.6875 8.167969 0.84375 8.207031 C 0.941406 8.230469 1.039062 8.238281 1.136719 8.25 C 1.390625 8.226562 1.636719 8.183594 1.824219 8.003906 Z M 1.847656 5.640625 C 1.847656 5.472656 1.703125 5.332031 1.539062 5.332031 C 1.367188 5.332031 1.21875 5.484375 1.226562 5.652344 C 1.230469 5.824219 1.375 5.957031 1.542969 5.957031 C 1.714844 5.953125 1.847656 5.8125 1.847656 5.644531 Z M 1.847656 5.640625 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 11.214844 0.015625 C 11.214844 1.125 5.859375 1.976562 2.785156 2.179688 L 2.785156 5.335938 C 4.082031 5.421875 5.789062 5.628906 7.324219 5.917969 C 5.789062 6.207031 4.082031 6.410156 2.785156 6.492188 L 2.785156 9.648438 C 4.082031 9.734375 5.785156 9.9375 7.320312 10.226562 C 5.785156 10.515625 4.082031 10.71875 2.785156 10.804688 L 2.785156 13.988281 L 4.308594 13.765625 C 7.152344 13.339844 10.285156 12.53125 11.214844 11.820312 L 11.214844 8.632812 C 11.214844 8.433594 10.851562 8.21875 10.265625 8 C 10.675781 7.832031 11.003906 7.667969 11.214844 7.507812 L 11.214844 4.324219 C 11.214844 4.125 10.855469 3.90625 10.277344 3.6875 C 10.683594 3.523438 11.003906 3.355469 11.214844 3.195312 Z M 10.269531 8.339844 C 10.6875 8.503906 10.839844 8.617188 10.890625 8.664062 C 10.855469 8.734375 10.726562 8.871094 10.359375 9.050781 C 10.300781 9.078125 10.238281 9.105469 10.183594 9.128906 C 9.550781 9.402344 8.570312 9.667969 7.320312 9.90625 C 6.707031 9.792969 6.046875 9.6875 5.382812 9.597656 C 7.25 9.261719 9.09375 8.796875 10.269531 8.339844 Z M 10.277344 4.027344 C 10.691406 4.195312 10.839844 4.308594 10.890625 4.355469 C 10.863281 4.40625 10.78125 4.5 10.578125 4.625 L 10.574219 4.625 C 10.535156 4.648438 10.488281 4.675781 10.441406 4.699219 C 9.839844 5.011719 8.765625 5.324219 7.335938 5.59375 C 6.71875 5.480469 6.058594 5.375 5.394531 5.285156 C 7.269531 4.953125 9.097656 4.492188 10.277344 4.027344 Z M 10.277344 4.027344 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style="fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 21.946429 2.875 C 21.982143 5.348214 21.910714 7.785714 19.776786 10.107143 L 19.696429 10.196429 L 19.8125 10.196429 L 20.6875 10.205357 C 19.267857 13.160714 18.348214 16.098214 16.303571 19.035714 L 16.232143 19.142857 L 16.357143 19.125 L 17.4375 18.919643 C 16.883929 20.598214 15.607143 21.946429 13.955357 22.571429 C 13.5625 17.116071 16.285714 12.303571 18.598214 7.5 L 18.607143 7.491071 L 18.517857 7.428571 C 14.732143 11.660714 13.026786 17.625 12.383929 22.553571 C 11.285714 21.901786 10.5 20.821429 10.241071 19.5625 L 11.133929 19.946429 L 11.232143 19.982143 L 11.214286 19.883929 C 10.526786 16.857143 11.589286 14.678571 12.607143 11.830357 L 13.348214 12.321429 L 13.4375 12.383929 L 13.4375 12.276786 C 13.375 9.964286 14.9375 7.633929 17.008929 5.553571 L 17.294643 6.321429 L 17.339286 6.419643 L 17.392857 6.321429 L 18.026786 5.267857 C 18.973214 3.991071 20.375 3.133929 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
<path style="fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 21.946429 2.875 C 20.375 3.133929 18.973214 3.991071 18.017857 5.258929 L 18.017857 5.267857 L 17.392857 6.321429 L 17.339286 6.419643 L 17.294643 6.321429 L 17 5.544643 C 14.928571 7.625 13.366071 9.955357 13.419643 12.267857 L 13.419643 12.375 L 13.339286 12.3125 L 12.598214 11.821429 C 11.571429 14.669643 10.517857 16.848214 11.196429 19.875 L 11.223214 19.973214 L 11.125 19.9375 L 10.241071 19.5625 C 10.241071 19.580357 10.25 19.598214 10.25 19.616071 C 10.517857 20.839286 11.294643 21.901786 12.375 22.544643 C 12.428571 22.160714 12.482143 21.776786 12.544643 21.383929 C 11 17.767857 12.348214 15.107143 12.955357 12.723214 L 13.892857 13.258929 C 13.758929 11.026786 15.080357 8.607143 16.785714 6.508929 L 17.285714 7.375 C 18.553571 4.767857 19.5625 3.723214 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 22.517857 2 L 22.464286 2.008929 C 20.383929 2.375 18.339286 3.133929 17.446429 4.973214 L 17.071429 4.3125 L 17.035714 4.25 L 16.991071 4.303571 C 15.883929 5.357143 14.892857 6.526786 14.044643 7.803571 C 13.339286 8.723214 12.919643 9.839286 12.839286 11 L 12.303571 10.339286 L 12.25 10.267857 L 12.214286 10.348214 C 11.508929 11.857143 10.9375 13.428571 10.517857 15.044643 C 10.116071 16.25 10.0625 17.535714 10.366071 18.767857 L 9.482143 18.258929 L 9.410714 18.214286 L 9.401786 18.294643 C 9.223214 20.151786 9.982143 21.973214 11.419643 23.142857 L 10.455357 23.383929 L 10.25 23.428571 L 10.455357 23.482143 C 10.973214 23.598214 11.464286 23.794643 11.901786 24.089286 C 12.3125 24.383929 12.508929 24.892857 12.419643 25.383929 L 12.419643 28.098214 L 12.428571 28.116071 L 13.651786 29.857143 L 13.75 30 L 13.75 25.723214 C 13.839286 25.178571 14.053571 24.678571 14.366071 24.232143 C 14.660714 23.910714 15.071429 23.723214 15.5 23.696429 L 15.678571 23.678571 L 15.517857 23.598214 L 14.875 23.303571 C 16.714286 22.035714 18.035714 20.142857 18.571429 17.982143 L 18.589286 17.892857 L 18.508929 17.919643 L 17.714286 18.133929 C 18.607143 17.098214 19.3125 15.910714 19.803571 14.633929 C 20.508929 13 21.178571 11.169643 21.732143 9.696429 L 21.758929 9.625 L 21.678571 9.625 L 21.0625 9.669643 C 21.857143 8.651786 22.339286 7.428571 22.446429 6.142857 C 22.633929 4.785714 22.660714 3.419643 22.526786 2.053571 Z M 21.946429 2.875 C 21.982143 5.348214 21.910714 7.785714 19.776786 10.107143 L 19.696429 10.196429 L 19.8125 10.196429 L 20.6875 10.205357 C 19.267857 13.160714 18.348214 16.098214 16.303571 19.035714 L 16.232143 19.142857 L 16.357143 19.125 L 17.4375 18.919643 C 16.883929 20.598214 15.607143 21.946429 13.955357 22.571429 C 13.5625 17.116071 16.285714 12.303571 18.598214 7.5 L 18.607143 7.491071 L 18.517857 7.428571 C 14.732143 11.660714 13.026786 17.625 12.383929 22.553571 C 11.285714 21.901786 10.508929 20.821429 10.241071 19.5625 L 11.142857 19.946429 L 11.232143 19.982143 L 11.214286 19.883929 C 10.526786 16.857143 11.589286 14.678571 12.616071 11.830357 L 13.348214 12.321429 L 13.4375 12.383929 L 13.4375 12.276786 C 13.375 9.964286 14.9375 7.633929 17.008929 5.553571 L 17.303571 6.321429 L 17.339286 6.419643 L 17.392857 6.321429 L 18.026786 5.267857 C 18.973214 3.991071 20.375 3.133929 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,6 +1,9 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 8.01531L19.2375 12.1073V20.2894L12.5066 16.1992V8.01531Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0294 12.1073V20.2894L27.1563 16.1992V8.01531L20.0294 12.1073Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58781 3.66V11.5787L11.7147 15.5381V7.61937L4.58781 3.66Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 25.04L19.2375 29V21.1348V21.0818L12.5066 17.1219V25.04Z" fill="black"/>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.472656 3.507812 L 8.417969 5.296875 L 8.417969 8.875 L 5.472656 7.085938 Z M 5.472656 3.507812 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 8.761719 5.296875 L 8.761719 8.875 L 11.882812 7.085938 L 11.882812 3.507812 Z M 8.761719 5.296875 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2.007812 1.601562 L 2.007812 5.066406 L 5.125 6.796875 L 5.125 3.332031 Z M 2.007812 1.601562 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.472656 10.953125 L 8.417969 12.6875 L 8.417969 9.222656 L 5.472656 7.492188 Z M 5.472656 10.953125 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 961 B

View File

@@ -341,6 +341,13 @@
{
"context": "Workspace",
"bindings": {
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-cmd-o": [
// "projects::OpenRecent",
// {
// "create_new_window": true
// }
// ]
"ctrl-alt-o": "projects::OpenRecent",
"ctrl-alt-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",

View File

@@ -383,6 +383,13 @@
{
"context": "Workspace",
"bindings": {
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-cmd-o": [
// "projects::OpenRecent",
// {
// "create_new_window": true
// }
// ]
"alt-cmd-o": "projects::OpenRecent",
"alt-cmd-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",

View File

@@ -20,7 +20,7 @@ use smol::io::AsyncReadExt;
use settings::{Settings, SettingsStore};
use smol::{fs::File, process::Command};
use release_channel::{AppCommitSha, ReleaseChannel};
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use std::{
env::consts::{ARCH, OS},
ffi::OsString,
@@ -190,7 +190,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let release_channel = ReleaseChannel::global(cx);
let version = env!("CARGO_PKG_VERSION");
let version = AppVersion::global(cx).to_string();
let client = client::Client::global(cx).http_client();
let url = client.build_url(&format!(

View File

@@ -373,8 +373,10 @@ async fn test_basic_following(
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
cx_b.background_executor.run_until_parked();
editor_b1.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
});
@@ -387,6 +389,7 @@ async fn test_basic_following(
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
editor.set_scroll_position(point(0., 100.), cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
editor_b1.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), &[3..3]);
@@ -1600,6 +1603,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
cx_a.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), vec![1..1])
@@ -1618,6 +1623,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([2..2]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
cx_a.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), vec![1..1])
@@ -1722,6 +1729,7 @@ async fn test_following_into_excluded_file(
// When client B starts following client A, currently visible file is replicated
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
@@ -1743,6 +1751,7 @@ async fn test_following_into_excluded_file(
editor_for_excluded_a.update(cx_a, |editor, cx| {
editor.select_right(&Default::default(), cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
// Changes from B to the excluded file are replicated in A's editor

View File

@@ -204,7 +204,7 @@ impl ChatPanel {
let panel = Self::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width;
panel.width = serialized_panel.width.map(|r| r.round());
cx.notify();
});
}

View File

@@ -327,7 +327,7 @@ impl CollabPanel {
let panel = CollabPanel::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width;
panel.width = serialized_panel.width.map(|w| w.round());
panel.collapsed_channels = serialized_panel
.collapsed_channels
.unwrap_or_else(|| Vec::new())

View File

@@ -183,7 +183,7 @@ impl NotificationPanel {
let panel = Self::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width;
panel.width = serialized_panel.width.map(|w| w.round());
cx.notify();
});
}

View File

@@ -8098,6 +8098,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
project_settings.lsp.insert(
"Some other server name".into(),
LspSettings {
settings: None,
initialization_options: Some(json!({
"some other init value": false
})),
@@ -8115,6 +8116,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
project_settings.lsp.insert(
language_server_name.into(),
LspSettings {
settings: None,
initialization_options: Some(json!({
"anotherInitValue": false
})),
@@ -8132,6 +8134,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
project_settings.lsp.insert(
language_server_name.into(),
LspSettings {
settings: None,
initialization_options: Some(json!({
"anotherInitValue": false
})),
@@ -8149,6 +8152,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
project_settings.lsp.insert(
language_server_name.into(),
LspSettings {
settings: None,
initialization_options: None,
},
);

View File

@@ -716,6 +716,11 @@ impl EditorElement {
let scroll_position = layout.position_map.snapshot.scroll_position();
let scroll_top = scroll_position.y * line_height;
if bounds.contains(&cx.mouse_position()) {
let stacking_order = cx.stacking_order().clone();
cx.set_cursor_style(CursorStyle::Arrow, stacking_order);
}
let show_git_gutter = matches!(
ProjectSettings::get_global(cx).git.git_gutter,
Some(GitGutterSetting::TrackedFiles)
@@ -915,7 +920,7 @@ impl EditorElement {
bounds: text_bounds,
stacking_order: cx.stacking_order().clone(),
};
if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
if text_bounds.contains(&cx.mouse_position()) {
if self
.editor
.read(cx)
@@ -923,9 +928,15 @@ impl EditorElement {
.as_ref()
.is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
{
cx.set_cursor_style(CursorStyle::PointingHand);
cx.set_cursor_style(
CursorStyle::PointingHand,
interactive_text_bounds.stacking_order.clone(),
);
} else {
cx.set_cursor_style(CursorStyle::IBeam);
cx.set_cursor_style(
CursorStyle::IBeam,
interactive_text_bounds.stacking_order.clone(),
);
}
}
@@ -1551,8 +1562,11 @@ impl EditorElement {
stacking_order: cx.stacking_order().clone(),
};
let mut mouse_position = cx.mouse_position();
if interactive_track_bounds.visibly_contains(&mouse_position, cx) {
cx.set_cursor_style(CursorStyle::Arrow);
if track_bounds.contains(&mouse_position) {
cx.set_cursor_style(
CursorStyle::Arrow,
interactive_track_bounds.stacking_order.clone(),
);
}
cx.on_mouse_event({

View File

@@ -365,14 +365,7 @@ impl FileFinderDelegate {
history_items: Vec<FoundPath>,
cx: &mut ViewContext<FileFinder>,
) -> Self {
cx.observe(&project, |file_finder, _, cx| {
//todo We should probably not re-render on every project anything
file_finder
.picker
.update(cx, |picker, cx| picker.refresh(cx))
})
.detach();
Self::subscribe_to_updates(&project, cx);
Self {
file_finder,
workspace,
@@ -389,6 +382,20 @@ impl FileFinderDelegate {
}
}
fn subscribe_to_updates(project: &Model<Project>, cx: &mut ViewContext<FileFinder>) {
cx.subscribe(project, |file_finder, _, event, cx| {
match event {
project::Event::WorktreeUpdatedEntries(_, _)
| project::Event::WorktreeAdded
| project::Event::WorktreeRemoved(_) => file_finder
.picker
.update(cx, |picker, cx| picker.refresh(cx)),
_ => {}
};
})
.detach();
}
fn spawn_search(
&mut self,
query: PathLikeWithPosition<FileSearchQuery>,

View File

@@ -1,9 +1,10 @@
use std::{assert_eq, path::Path, time::Duration};
use std::{assert_eq, future::IntoFuture, path::Path, time::Duration};
use super::*;
use editor::Editor;
use gpui::{Entity, TestAppContext, VisualTestContext};
use menu::{Confirm, SelectNext};
use project::worktree::FS_WATCH_LATENCY;
use serde_json::json;
use workspace::{AppState, Workspace};
@@ -1337,6 +1338,137 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
});
}
#[gpui::test]
async fn test_search_results_refreshed_on_worktree_updates(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/src",
json!({
"lib.rs": "// Lib file",
"main.rs": "// Bar file",
"read.me": "// Readme file",
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
// Initial state
let picker = open_file_picker(&workspace, cx);
cx.simulate_input("rs");
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 2);
assert_match_at_position(finder, 0, "lib.rs");
assert_match_at_position(finder, 1, "main.rs");
});
// Delete main.rs
app_state
.fs
.remove_file("/src/main.rs".as_ref(), Default::default())
.await
.expect("unable to remove file");
cx.executor().advance_clock(FS_WATCH_LATENCY);
// main.rs is in not among search results anymore
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 1);
assert_match_at_position(finder, 0, "lib.rs");
});
// Create util.rs
app_state
.fs
.create_file("/src/util.rs".as_ref(), Default::default())
.await
.expect("unable to create file");
cx.executor().advance_clock(FS_WATCH_LATENCY);
// util.rs is among search results
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 2);
assert_match_at_position(finder, 0, "lib.rs");
assert_match_at_position(finder, 1, "util.rs");
});
}
#[gpui::test]
async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
cx: &mut gpui::TestAppContext,
) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/test",
json!({
"project_1": {
"bar.rs": "// Bar file",
"lib.rs": "// Lib file",
},
"project_2": {
"Cargo.toml": "// Cargo file",
"main.rs": "// Main file",
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/test/project_1".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let worktree_1_id = project.update(cx, |project, cx| {
let worktree = project.worktrees().last().expect("worktree not found");
worktree.read(cx).id()
});
// Initial state
let picker = open_file_picker(&workspace, cx);
cx.simulate_input("rs");
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 2);
assert_match_at_position(finder, 0, "bar.rs");
assert_match_at_position(finder, 1, "lib.rs");
});
// Add new worktree
project
.update(cx, |project, cx| {
project
.find_or_create_local_worktree("/test/project_2", true, cx)
.into_future()
})
.await
.expect("unable to create workdir");
cx.executor().advance_clock(FS_WATCH_LATENCY);
// main.rs is among search results
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "bar.rs");
assert_match_at_position(finder, 1, "lib.rs");
assert_match_at_position(finder, 2, "main.rs");
});
// Remove the first worktree
project.update(cx, |project, cx| {
project.remove_worktree(worktree_1_id, cx);
});
cx.executor().advance_clock(FS_WATCH_LATENCY);
// Files from the first worktree are not in the search results anymore
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 1);
assert_match_at_position(finder, 0, "main.rs");
});
}
async fn open_close_queried_buffer(
input: &str,
expected_matches: usize,

View File

@@ -1221,7 +1221,8 @@ pub struct InteractiveBounds {
}
impl InteractiveBounds {
/// Checks whether this point was inside these bounds, and that these bounds where the topmost layer
/// Checks whether this point was inside these bounds in the rendered frame, and that these bounds where the topmost layer
/// Never call this during paint to perform hover calculations. It will reference the previous frame and could cause flicker.
pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
self.bounds.contains(point) && cx.was_top_layer(point, &self.stacking_order)
}
@@ -1449,11 +1450,12 @@ impl Interactivity {
if !cx.has_active_drag() {
if let Some(mouse_cursor) = style.mouse_cursor {
let mouse_position = &cx.mouse_position();
let hovered =
interactive_bounds.visibly_contains(mouse_position, cx);
let hovered = bounds.contains(&cx.mouse_position());
if hovered {
cx.set_cursor_style(mouse_cursor);
cx.set_cursor_style(
mouse_cursor,
interactive_bounds.stacking_order.clone(),
);
}
}
}
@@ -1955,9 +1957,7 @@ impl Interactivity {
if let Some(group_bounds) =
GroupBounds::get(&group_hover.group, cx.deref_mut())
{
if group_bounds.contains(&mouse_position)
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
{
if group_bounds.contains(&mouse_position) {
style.refine(&group_hover.style);
}
}
@@ -1967,7 +1967,6 @@ impl Interactivity {
if bounds
.intersect(&cx.content_mask().bounds)
.contains(&mouse_position)
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
{
style.refine(hover_style);
}

View File

@@ -427,9 +427,9 @@ impl Element for InteractiveText {
.clickable_ranges
.iter()
.any(|range| range.contains(&ix))
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
{
cx.set_cursor_style(crate::CursorStyle::PointingHand)
let stacking_order = cx.stacking_order().clone();
cx.set_cursor_style(crate::CursorStyle::PointingHand, stacking_order);
}
}

View File

@@ -195,8 +195,8 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
fn draw(&self, scene: &Scene);
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
fn set_graphics_profiler_enabled(&self, enabled: bool);
#[cfg(any(test, feature = "test-support"))]
fn as_test(&mut self) -> Option<&mut TestWindow> {

View File

@@ -396,10 +396,6 @@ impl PlatformWindow for WaylandWindow {
let inner = self.0.inner.lock();
inner.renderer.sprite_atlas().clone()
}
fn set_graphics_profiler_enabled(&self, enabled: bool) {
//todo!(linux)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]

View File

@@ -513,8 +513,4 @@ impl PlatformWindow for X11Window {
let inner = self.0.inner.lock();
inner.renderer.sprite_atlas().clone()
}
fn set_graphics_profiler_enabled(&self, enabled: bool) {
unimplemented!("linux")
}
}

View File

@@ -1060,27 +1060,6 @@ impl PlatformWindow for MacWindow {
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.lock().renderer.sprite_atlas().clone()
}
/// Enables or disables the Metal HUD for debugging purposes. Note that this only works
/// when the app is bundled and it has the `MetalHudEnabled` key set to true in Info.plist.
fn set_graphics_profiler_enabled(&self, enabled: bool) {
let this_lock = self.0.lock();
let layer = this_lock.renderer.layer();
unsafe {
if enabled {
let hud_properties = NSDictionary::dictionaryWithObject_forKey_(
nil,
ns_string("default"),
ns_string("mode"),
);
let _: () = msg_send![layer, setDeveloperHUDProperties: hud_properties];
} else {
let _: () =
msg_send![layer, setDeveloperHUDProperties: NSDictionary::dictionary(nil)];
}
}
}
}
impl HasWindowHandle for MacWindow {

View File

@@ -251,8 +251,6 @@ impl PlatformWindow for TestWindow {
self.0.lock().sprite_atlas.clone()
}
fn set_graphics_profiler_enabled(&self, _enabled: bool) {}
fn as_test(&mut self) -> Option<&mut TestWindow> {
Some(self)
}

View File

@@ -46,10 +46,10 @@ pub fn run_test(
let starting_seed = env::var("SEED")
.map(|seed| seed.parse().expect("invalid SEED variable"))
.unwrap_or(0);
let is_randomized = num_iterations > 1;
if let Ok(iterations) = env::var("ITERATIONS") {
num_iterations = iterations.parse().expect("invalid ITERATIONS variable");
}
let is_randomized = num_iterations > 1;
for seed in starting_seed..starting_seed + num_iterations {
let mut retry = 0;

View File

@@ -280,7 +280,6 @@ pub struct Window {
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
pending_input: Option<PendingInput>,
graphics_profiler_enabled: bool,
}
#[derive(Default, Debug)]
@@ -474,7 +473,6 @@ impl Window {
focus: None,
focus_enabled: true,
pending_input: None,
graphics_profiler_enabled: false,
}
}
fn new_focus_listener(
@@ -1023,13 +1021,10 @@ impl<'a> WindowContext<'a> {
self.window.root_view = Some(root_view);
// Set the cursor only if we're the active window.
let cursor_style = self
.window
.next_frame
.requested_cursor_style
.take()
.unwrap_or(CursorStyle::Arrow);
let cursor_style_request = self.window.next_frame.requested_cursor_style.take();
if self.is_window_active() {
let cursor_style =
cursor_style_request.map_or(CursorStyle::Arrow, |request| request.style);
self.platform.set_cursor_style(cursor_style);
}
@@ -1522,14 +1517,6 @@ impl<'a> WindowContext<'a> {
}
}
/// Toggle the graphics profiler to debug your application's rendering performance.
pub fn toggle_graphics_profiler(&mut self) {
self.window.graphics_profiler_enabled = !self.window.graphics_profiler_enabled;
self.window
.platform_window
.set_graphics_profiler_enabled(self.window.graphics_profiler_enabled);
}
/// Register the given handler to be invoked whenever the global of the given type
/// is updated.
pub fn observe_global<G: Global>(

View File

@@ -51,6 +51,12 @@ pub(crate) struct TooltipRequest {
pub(crate) tooltip: AnyTooltip,
}
#[derive(Clone)]
pub(crate) struct CursorStyleRequest {
pub(crate) style: CursorStyle,
stacking_order: StackingOrder,
}
pub(crate) struct Frame {
pub(crate) focus: Option<FocusId>,
pub(crate) window_active: bool,
@@ -66,8 +72,8 @@ pub(crate) struct Frame {
pub(crate) element_offset_stack: Vec<Point<Pixels>>,
pub(crate) requested_input_handler: Option<RequestedInputHandler>,
pub(crate) tooltip_request: Option<TooltipRequest>,
pub(crate) cursor_styles: FxHashMap<EntityId, CursorStyle>,
pub(crate) requested_cursor_style: Option<CursorStyle>,
pub(crate) cursor_styles: FxHashMap<EntityId, CursorStyleRequest>,
pub(crate) requested_cursor_style: Option<CursorStyleRequest>,
pub(crate) view_stack: Vec<EntityId>,
pub(crate) reused_views: FxHashSet<EntityId>,
@@ -346,9 +352,13 @@ impl<'a> ElementContext<'a> {
}
// Reuse the cursor styles previously requested during painting of the reused view.
if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) {
self.window.next_frame.cursor_styles.insert(view_id, style);
self.window.next_frame.requested_cursor_style = Some(style);
if let Some(cursor_style_request) =
self.window.rendered_frame.cursor_styles.remove(&view_id)
{
self.set_cursor_style(
cursor_style_request.style,
cursor_style_request.stacking_order,
);
}
}
@@ -387,10 +397,27 @@ impl<'a> ElementContext<'a> {
}
/// Updates the cursor style at the platform level.
pub fn set_cursor_style(&mut self, style: CursorStyle) {
pub fn set_cursor_style(&mut self, style: CursorStyle, stacking_order: StackingOrder) {
let view_id = self.parent_view_id();
self.window.next_frame.cursor_styles.insert(view_id, style);
self.window.next_frame.requested_cursor_style = Some(style);
let style_request = CursorStyleRequest {
style,
stacking_order,
};
if self
.window
.next_frame
.requested_cursor_style
.as_ref()
.map_or(true, |prev_style_request| {
style_request.stacking_order >= prev_style_request.stacking_order
})
{
self.window.next_frame.requested_cursor_style = Some(style_request.clone());
}
self.window
.next_frame
.cursor_styles
.insert(view_id, style_request);
}
/// Sets a tooltip to be rendered for the upcoming frame

View File

@@ -97,14 +97,14 @@ fn test_select_language() {
// matching file extension
assert_eq!(
registry
.language_for_file("zed/lib.rs", None)
.language_for_file("zed/lib.rs".as_ref(), None)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
Some("Rust".into())
);
assert_eq!(
registry
.language_for_file("zed/lib.mk", None)
.language_for_file("zed/lib.mk".as_ref(), None)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
Some("Make".into())
@@ -113,7 +113,7 @@ fn test_select_language() {
// matching filename
assert_eq!(
registry
.language_for_file("zed/Makefile", None)
.language_for_file("zed/Makefile".as_ref(), None)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
Some("Make".into())
@@ -122,21 +122,21 @@ fn test_select_language() {
// matching suffix that is not the full file extension or filename
assert_eq!(
registry
.language_for_file("zed/cars", None)
.language_for_file("zed/cars".as_ref(), None)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
None
);
assert_eq!(
registry
.language_for_file("zed/a.cars", None)
.language_for_file("zed/a.cars".as_ref(), None)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
None
);
assert_eq!(
registry
.language_for_file("zed/sumk", None)
.language_for_file("zed/sumk".as_ref(), None)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
None

View File

@@ -1522,16 +1522,16 @@ mod tests {
});
languages
.language_for_file("the/script", None)
.language_for_file("the/script".as_ref(), None)
.await
.unwrap_err();
languages
.language_for_file("the/script", Some(&"nothing".into()))
.language_for_file("the/script".as_ref(), Some(&"nothing".into()))
.await
.unwrap_err();
assert_eq!(
languages
.language_for_file("the/script", Some(&"#!/bin/env node".into()))
.language_for_file("the/script".as_ref(), Some(&"#!/bin/env node".into()))
.await
.unwrap()
.name()

View File

@@ -7,7 +7,7 @@ use collections::{hash_map, HashMap};
use futures::{
channel::{mpsc, oneshot},
future::Shared,
FutureExt as _, TryFutureExt as _,
Future, FutureExt as _, TryFutureExt as _,
};
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
use lsp::{LanguageServerBinary, LanguageServerId};
@@ -24,7 +24,7 @@ use sum_tree::Bias;
use text::{Point, Rope};
use theme::Theme;
use unicase::UniCase;
use util::{paths::PathExt, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
use util::{paths::PathExt, post_inc, ResultExt};
pub struct LanguageRegistry {
state: RwLock<LanguageRegistryState>,
@@ -291,35 +291,36 @@ impl LanguageRegistry {
pub fn language_for_name(
self: &Arc<Self>,
name: &str,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
) -> impl Future<Output = Result<Arc<Language>>> {
let name = UniCase::new(name);
self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name)
let rx = self.get_or_load_language(|language_name, _| UniCase::new(language_name) == name);
async move { rx.await? }
}
pub fn language_for_name_or_extension(
self: &Arc<Self>,
string: &str,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
) -> impl Future<Output = Result<Arc<Language>>> {
let string = UniCase::new(string);
self.get_or_load_language(|name, config| {
let rx = self.get_or_load_language(|name, config| {
UniCase::new(name) == string
|| config
.path_suffixes
.iter()
.any(|suffix| UniCase::new(suffix) == string)
})
});
async move { rx.await? }
}
pub fn language_for_file(
self: &Arc<Self>,
path: impl AsRef<Path>,
path: &Path,
content: Option<&Rope>,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let path = path.as_ref();
) -> impl Future<Output = Result<Arc<Language>>> {
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name();
let path_suffixes = [extension, filename];
self.get_or_load_language(|_, config| {
let rx = self.get_or_load_language(move |_, config| {
let path_matches = config
.path_suffixes
.iter()
@@ -334,13 +335,14 @@ impl LanguageRegistry {
},
);
path_matches || content_matches
})
});
async move { rx.await? }
}
fn get_or_load_language(
self: &Arc<Self>,
callback: impl Fn(&str, &LanguageMatcher) -> bool,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
) -> oneshot::Receiver<Result<Arc<Language>>> {
let (tx, rx) = oneshot::channel();
let mut state = self.state.write();
@@ -421,13 +423,13 @@ impl LanguageRegistry {
let _ = tx.send(Err(anyhow!("executor does not exist")));
}
rx.unwrap()
rx
}
fn get_or_load_grammar(
self: &Arc<Self>,
name: Arc<str>,
) -> UnwrapFuture<oneshot::Receiver<Result<tree_sitter::Language>>> {
) -> impl Future<Output = Result<tree_sitter::Language>> {
let (tx, rx) = oneshot::channel();
let mut state = self.state.write();
@@ -483,7 +485,7 @@ impl LanguageRegistry {
tx.send(Err(anyhow!("no such grammar {}", name))).ok();
}
rx.unwrap()
async move { rx.await? }
}
pub fn to_vec(&self) -> Vec<Arc<Language>> {

View File

@@ -823,7 +823,7 @@ impl Render for LspLogToolbarItemView {
selection,
Selection::Selected
);
view.toggle_logging_for_server(
view.toggle_rpc_logging_for_server(
row.server_id,
enabled,
cx,
@@ -887,7 +887,7 @@ impl LspLogToolbarItemView {
}
}
fn toggle_logging_for_server(
fn toggle_rpc_logging_for_server(
&mut self,
id: LanguageServerId,
enabled: bool,
@@ -899,6 +899,9 @@ impl LspLogToolbarItemView {
if !enabled && Some(id) == log_view.current_server_id {
log_view.show_logs_for_server(id, cx);
cx.notify();
} else if enabled {
log_view.show_rpc_trace_for_server(id, cx);
cx.notify();
}
cx.focus(&log_view.focus_handle);
});

View File

@@ -7,7 +7,9 @@ use gpui::AppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary};
use node_runtime::NodeRuntime;
use project::project_settings::ProjectSettings;
use serde_json::{json, Value};
use settings::Settings;
use smol::{fs, io::BufReader, stream::StreamExt};
use std::{
any::Any,
@@ -18,7 +20,7 @@ use std::{
use util::{
async_maybe,
fs::remove_matching,
github::{latest_github_release, GitHubLspBinaryVersion},
github::{github_release_with_tag, GitHubLspBinaryVersion},
ResultExt,
};
@@ -219,6 +221,7 @@ pub struct EsLintLspAdapter {
impl EsLintLspAdapter {
const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
const SERVER_NAME: &'static str = "eslint";
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
EsLintLspAdapter { node }
@@ -227,7 +230,34 @@ impl EsLintLspAdapter {
#[async_trait]
impl LspAdapter for EsLintLspAdapter {
fn workspace_configuration(&self, workspace_root: &Path, _: &mut AppContext) -> Value {
fn workspace_configuration(&self, workspace_root: &Path, cx: &mut AppContext) -> Value {
let eslint_user_settings = ProjectSettings::get_global(cx)
.lsp
.get(Self::SERVER_NAME)
.and_then(|s| s.settings.clone())
.unwrap_or_default();
let mut code_action_on_save = json!({
// We enable this, but without also configuring `code_actions_on_format`
// in the Zed configuration, it doesn't have an effect.
"enable": true,
});
if let Some(code_action_settings) = eslint_user_settings
.get("codeActionOnSave")
.and_then(|settings| settings.as_object())
{
if let Some(enable) = code_action_settings.get("enable") {
code_action_on_save["enable"] = enable.clone();
}
if let Some(mode) = code_action_settings.get("mode") {
code_action_on_save["mode"] = mode.clone();
}
if let Some(rules) = code_action_settings.get("rules") {
code_action_on_save["rules"] = rules.clone();
}
}
json!({
"": {
"validate": "on",
@@ -241,6 +271,7 @@ impl LspAdapter for EsLintLspAdapter {
.unwrap_or_else(|| workspace_root.as_os_str()),
},
"problems": {},
"codeActionOnSave": code_action_on_save,
"experimental": {
"useFlatConfig": workspace_root.join("eslint.config.js").is_file(),
},
@@ -249,7 +280,7 @@ impl LspAdapter for EsLintLspAdapter {
}
fn name(&self) -> LanguageServerName {
LanguageServerName("eslint".into())
LanguageServerName(Self::SERVER_NAME.into())
}
fn short_name(&self) -> &'static str {
@@ -260,13 +291,11 @@ impl LspAdapter for EsLintLspAdapter {
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
// At the time of writing the latest vscode-eslint release was released in 2020 and requires
// special custom LSP protocol extensions be handled to fully initialize. Download the latest
// prerelease instead to sidestep this issue
let release = latest_github_release(
// We're using this hardcoded release tag, because ESLint's API changed with
// >= 2.3 and we haven't upgraded yet.
let release = github_release_with_tag(
"microsoft/vscode-eslint",
false,
true,
"release/2.2.20-Insider",
delegate.http_client(),
)
.await?;

View File

@@ -541,7 +541,14 @@ impl LanguageServer {
}),
data_support: Some(true),
resolve_support: Some(CodeActionCapabilityResolveSupport {
properties: vec!["edit".to_string(), "command".to_string()],
properties: vec![
"kind".to_string(),
"diagnostics".to_string(),
"isPreferred".to_string(),
"disabled".to_string(),
"edit".to_string(),
"command".to_string(),
],
}),
..Default::default()
}),

View File

@@ -195,7 +195,7 @@ struct Excerpt {
///
/// Contains methods for getting the [`Buffer`] of the excerpt,
/// as well as mapping offsets to/from buffer and multibuffer coordinates.
#[derive(Copy, Clone)]
#[derive(Clone)]
pub struct MultiBufferExcerpt<'a> {
excerpt: &'a Excerpt,
excerpt_offset: usize,
@@ -2967,7 +2967,16 @@ impl MultiBufferSnapshot {
excerpt
.buffer()
.enclosing_bracket_ranges(excerpt.map_range_to_buffer(range))
.filter(move |(open, close)| excerpt.contains_buffer_range(open.start..close.end)),
.filter_map(move |(open, close)| {
if excerpt.contains_buffer_range(open.start..close.end) {
Some((
excerpt.map_range_from_buffer(open),
excerpt.map_range_from_buffer(close),
))
} else {
None
}
}),
)
}

View File

@@ -10,6 +10,7 @@ path = "src/picker.rs"
doctest = false
[dependencies]
anyhow.workspace = true
editor.workspace = true
gpui.workspace = true
menu.workspace = true

View File

@@ -1,3 +1,4 @@
use anyhow::Result;
use editor::Editor;
use gpui::{
div, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent, DismissEvent,
@@ -13,11 +14,16 @@ enum ElementContainer {
UniformList(UniformListScrollHandle),
}
struct PendingUpdateMatches {
delegate_update_matches: Option<Task<()>>,
_task: Task<Result<()>>,
}
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
element_container: ElementContainer,
editor: View<Editor>,
pending_update_matches: Option<Task<()>>,
pending_update_matches: Option<PendingUpdateMatches>,
confirm_on_update: Option<bool>,
width: Option<Length>,
max_height: Option<Length>,
@@ -268,15 +274,32 @@ impl<D: PickerDelegate> Picker<D> {
}
pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
let update = self.delegate.update_matches(query, cx);
let delegate_pending_update_matches = self.delegate.update_matches(query, cx);
self.matches_updated(cx);
self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
update.await;
this.update(&mut cx, |this, cx| {
this.matches_updated(cx);
})
.ok();
}));
// This struct ensures that we can synchronously drop the task returned by the
// delegate's `update_matches` method and the task that the picker is spawning.
// If we simply capture the delegate's task into the picker's task, when the picker's
// task gets synchronously dropped, the delegate's task would keep running until
// the picker's task has a chance of being scheduled, because dropping a task happens
// asynchronously.
self.pending_update_matches = Some(PendingUpdateMatches {
delegate_update_matches: Some(delegate_pending_update_matches),
_task: cx.spawn(|this, mut cx| async move {
let delegate_pending_update_matches = this.update(&mut cx, |this, _| {
this.pending_update_matches
.as_mut()
.unwrap()
.delegate_update_matches
.take()
.unwrap()
})?;
delegate_pending_update_matches.await;
this.update(&mut cx, |this, cx| {
this.matches_updated(cx);
})
}),
});
}
fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {

View File

@@ -1818,6 +1818,19 @@ impl LspCommand for GetCodeActions {
}
}
impl GetCodeActions {
pub fn can_resolve_actions(capabilities: &ServerCapabilities) -> bool {
capabilities
.code_action_provider
.as_ref()
.and_then(|options| match options {
lsp::CodeActionProviderCapability::Simple(_is_supported) => None,
lsp::CodeActionProviderCapability::Options(options) => options.resolve_provider,
})
.unwrap_or(false)
}
}
#[async_trait(?Send)]
impl LspCommand for OnTypeFormatting {
type Response = Option<Transaction>;

View File

@@ -522,6 +522,9 @@ impl Project {
buffer: &Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
if !self.is_local() {
return Task::ready(None);
}
let buffer = buffer.read(cx);
let buffer_file = buffer.file();
let Some(buffer_language) = buffer.language() else {
@@ -530,119 +533,105 @@ impl Project {
if buffer_language.prettier_parser_name().is_none() {
return Task::ready(None);
}
if self.is_local() {
let Some(node) = self.node.as_ref().map(Arc::clone) else {
return Task::ready(None);
};
match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx)))
{
Some((worktree_id, buffer_path)) => {
let fs = Arc::clone(&self.fs);
let installed_prettiers = self.prettier_instances.keys().cloned().collect();
return cx.spawn(|project, mut cx| async move {
match cx
.background_executor()
.spawn(async move {
Prettier::locate_prettier_installation(
fs.as_ref(),
&installed_prettiers,
&buffer_path,
)
.await
})
.await
{
Ok(ControlFlow::Break(())) => {
return None;
}
Ok(ControlFlow::Continue(None)) => {
let default_instance = project
.update(&mut cx, |project, cx| {
project
.prettiers_per_worktree
.entry(worktree_id)
.or_default()
.insert(None);
project.default_prettier.prettier_task(
&node,
Some(worktree_id),
cx,
)
})
.ok()?;
Some((None, default_instance?.log_err().await?))
}
Ok(ControlFlow::Continue(Some(prettier_dir))) => {
project
.update(&mut cx, |project, _| {
project
.prettiers_per_worktree
.entry(worktree_id)
.or_default()
.insert(Some(prettier_dir.clone()))
})
.ok()?;
if let Some(prettier_task) = project
.update(&mut cx, |project, cx| {
project.prettier_instances.get_mut(&prettier_dir).map(
|existing_instance| {
existing_instance.prettier_task(
&node,
Some(&prettier_dir),
Some(worktree_id),
cx,
)
},
)
})
.ok()?
{
log::debug!(
"Found already started prettier in {prettier_dir:?}"
);
return Some((
Some(prettier_dir),
prettier_task?.await.log_err()?,
));
}
log::info!("Found prettier in {prettier_dir:?}, starting.");
let new_prettier_task = project
.update(&mut cx, |project, cx| {
let new_prettier_task = start_prettier(
node,
prettier_dir.clone(),
Some(worktree_id),
cx,
);
project.prettier_instances.insert(
prettier_dir.clone(),
PrettierInstance {
attempt: 0,
prettier: Some(new_prettier_task.clone()),
},
);
new_prettier_task
})
.ok()?;
Some((Some(prettier_dir), new_prettier_task))
}
Err(e) => {
log::error!("Failed to determine prettier path for buffer: {e:#}");
return None;
}
}
});
}
None => {
let new_task = self.default_prettier.prettier_task(&node, None, cx);
return cx
.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) });
}
}
} else {
let Some(node) = self.node.as_ref().map(Arc::clone) else {
return Task::ready(None);
};
match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
Some((worktree_id, buffer_path)) => {
let fs = Arc::clone(&self.fs);
let installed_prettiers = self.prettier_instances.keys().cloned().collect();
cx.spawn(|project, mut cx| async move {
match cx
.background_executor()
.spawn(async move {
Prettier::locate_prettier_installation(
fs.as_ref(),
&installed_prettiers,
&buffer_path,
)
.await
})
.await
{
Ok(ControlFlow::Break(())) => None,
Ok(ControlFlow::Continue(None)) => {
let default_instance = project
.update(&mut cx, |project, cx| {
project
.prettiers_per_worktree
.entry(worktree_id)
.or_default()
.insert(None);
project.default_prettier.prettier_task(
&node,
Some(worktree_id),
cx,
)
})
.ok()?;
Some((None, default_instance?.log_err().await?))
}
Ok(ControlFlow::Continue(Some(prettier_dir))) => {
project
.update(&mut cx, |project, _| {
project
.prettiers_per_worktree
.entry(worktree_id)
.or_default()
.insert(Some(prettier_dir.clone()))
})
.ok()?;
if let Some(prettier_task) = project
.update(&mut cx, |project, cx| {
project.prettier_instances.get_mut(&prettier_dir).map(
|existing_instance| {
existing_instance.prettier_task(
&node,
Some(&prettier_dir),
Some(worktree_id),
cx,
)
},
)
})
.ok()?
{
log::debug!("Found already started prettier in {prettier_dir:?}");
return Some((Some(prettier_dir), prettier_task?.await.log_err()?));
}
log::info!("Found prettier in {prettier_dir:?}, starting.");
let new_prettier_task = project
.update(&mut cx, |project, cx| {
let new_prettier_task = start_prettier(
node,
prettier_dir.clone(),
Some(worktree_id),
cx,
);
project.prettier_instances.insert(
prettier_dir.clone(),
PrettierInstance {
attempt: 0,
prettier: Some(new_prettier_task.clone()),
},
);
new_prettier_task
})
.ok()?;
Some((Some(prettier_dir), new_prettier_task))
}
Err(e) => {
log::error!("Failed to determine prettier path for buffer: {e:#}");
None
}
}
})
}
None => {
let new_task = self.default_prettier.prettier_task(&node, None, cx);
cx.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) })
}
}
}

View File

@@ -35,11 +35,11 @@ use language::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
serialize_anchor, serialize_version, split_operations,
},
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName,
LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer,
PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeAction,
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation,
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
LspAdapterDelegate, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
ToOffset, ToPointUtf16, Transaction, Unclipped,
};
use log::error;
use lsp::{
@@ -4129,17 +4129,13 @@ impl Project {
cx: &mut ModelContext<Project>,
) -> Task<anyhow::Result<ProjectTransaction>> {
if self.is_local() {
let mut buffers_with_paths_and_servers = buffers
let mut buffers_with_paths = buffers
.into_iter()
.filter_map(|buffer_handle| {
let buffer = buffer_handle.read(cx);
let file = File::from_dyn(buffer.file())?;
let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
let (adapter, server) = self
.primary_language_server_for_buffer(buffer, cx)
.map(|(a, s)| (Some(a.clone()), Some(s.clone())))
.unwrap_or((None, None));
Some((buffer_handle, buffer_abs_path, adapter, server))
Some((buffer_handle, buffer_abs_path))
})
.collect::<Vec<_>>();
@@ -4147,7 +4143,7 @@ impl Project {
// Do not allow multiple concurrent formatting requests for the
// same buffer.
project.update(&mut cx, |this, cx| {
buffers_with_paths_and_servers.retain(|(buffer, _, _, _)| {
buffers_with_paths.retain(|(buffer, _)| {
this.buffers_being_formatted
.insert(buffer.read(cx).remote_id())
});
@@ -4156,10 +4152,10 @@ impl Project {
let _cleanup = defer({
let this = project.clone();
let mut cx = cx.clone();
let buffers = &buffers_with_paths_and_servers;
let buffers = &buffers_with_paths;
move || {
this.update(&mut cx, |this, cx| {
for (buffer, _, _, _) in buffers {
for (buffer, _) in buffers {
this.buffers_being_formatted
.remove(&buffer.read(cx).remote_id());
}
@@ -4169,9 +4165,14 @@ impl Project {
});
let mut project_transaction = ProjectTransaction::default();
for (buffer, buffer_abs_path, lsp_adapter, language_server) in
&buffers_with_paths_and_servers
{
for (buffer, buffer_abs_path) in &buffers_with_paths {
let adapters_and_servers: Vec<_> = project.update(&mut cx, |project, cx| {
project
.language_servers_for_buffer(&buffer.read(cx), cx)
.map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
.collect()
})?;
let settings = buffer.update(&mut cx, |buffer, cx| {
language_settings(buffer.language(), buffer.file(), cx).clone()
})?;
@@ -4202,9 +4203,7 @@ impl Project {
buffer.end_transaction(cx)
})?;
if let (Some(lsp_adapter), Some(language_server)) =
(lsp_adapter, language_server)
{
for (lsp_adapter, language_server) in adapters_and_servers.iter() {
// Apply the code actions on
let code_actions: Vec<lsp::CodeActionKind> = settings
.code_actions_on_format
@@ -4236,11 +4235,15 @@ impl Project {
})?
.await?;
for action in actions {
for mut action in actions {
Self::try_resolve_code_action(&language_server, &mut action)
.await
.context("resolving a formatting code action")?;
if let Some(edit) = action.lsp_action.edit {
if edit.changes.is_none() && edit.document_changes.is_none() {
continue;
}
let new = Self::deserialize_workspace_edit(
project
.upgrade()
@@ -4255,6 +4258,7 @@ impl Project {
project_transaction.0.extend(new.0);
}
// TODO kb here too:
if let Some(command) = action.lsp_action.command {
project.update(&mut cx, |this, _| {
this.last_workspace_edits_by_language_server
@@ -4284,17 +4288,23 @@ impl Project {
}
}
// Apply language-specific formatting using either a language server
// Apply language-specific formatting using either the primary language server
// or external command.
let primary_language_server = adapters_and_servers
.first()
.cloned()
.map(|(_, lsp)| lsp.clone());
let server_and_buffer = primary_language_server
.as_ref()
.zip(buffer_abs_path.as_ref());
let mut format_operation = None;
match (&settings.formatter, &settings.format_on_save) {
(_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {}
(Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off)
| (_, FormatOnSave::LanguageServer) => {
if let Some((language_server, buffer_abs_path)) =
language_server.as_ref().zip(buffer_abs_path.as_ref())
{
if let Some((language_server, buffer_abs_path)) = server_and_buffer {
format_operation = Some(FormatOperation::Lsp(
Self::format_via_lsp(
&project,
@@ -4338,7 +4348,7 @@ impl Project {
{
format_operation = Some(new_operation);
} else if let Some((language_server, buffer_abs_path)) =
language_server.as_ref().zip(buffer_abs_path.as_ref())
server_and_buffer
{
format_operation = Some(FormatOperation::Lsp(
Self::format_via_lsp(
@@ -5290,33 +5300,10 @@ impl Project {
} else {
return Task::ready(Ok(Default::default()));
};
let range = action.range.to_point_utf16(buffer);
cx.spawn(move |this, mut cx| async move {
if let Some(lsp_range) = action
.lsp_action
.data
.as_mut()
.and_then(|d| d.get_mut("codeActionParams"))
.and_then(|d| d.get_mut("range"))
{
*lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap();
action.lsp_action = lang_server
.request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
.await?;
} else {
let actions = this
.update(&mut cx, |this, cx| {
this.code_actions(&buffer_handle, action.range, cx)
})?
.await?;
action.lsp_action = actions
.into_iter()
.find(|a| a.lsp_action.title == action.lsp_action.title)
.ok_or_else(|| anyhow!("code action is outdated"))?
.lsp_action;
}
Self::try_resolve_code_action(&lang_server, &mut action)
.await
.context("resolving a code action")?;
if let Some(edit) = action.lsp_action.edit {
if edit.changes.is_some() || edit.document_changes.is_some() {
return Self::deserialize_workspace_edit(
@@ -8137,6 +8124,23 @@ impl Project {
})
}
async fn try_resolve_code_action(
lang_server: &LanguageServer,
action: &mut CodeAction,
) -> anyhow::Result<()> {
if GetCodeActions::can_resolve_actions(&lang_server.capabilities()) {
if action.lsp_action.data.is_some()
&& (action.lsp_action.command.is_none() || action.lsp_action.edit.is_none())
{
action.lsp_action = lang_server
.request::<lsp::request::CodeActionResolveRequest>(action.lsp_action.clone())
.await?;
}
}
anyhow::Ok(())
}
async fn handle_refresh_inlay_hints(
this: Model<Self>,
_: TypedEnvelope<proto::RefreshInlayHints>,

View File

@@ -2564,7 +2564,20 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
},
None,
);
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
let mut fake_language_servers = language
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
code_action_provider: Some(lsp::CodeActionProviderCapability::Options(
lsp::CodeActionOptions {
resolve_provider: Some(true),
..lsp::CodeActionOptions::default()
},
)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default()
}))
.await;
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
@@ -2591,16 +2604,14 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
Ok(Some(vec![
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
title: "The code action".into(),
command: Some(lsp::Command {
title: "The command".into(),
command: "_the/command".into(),
arguments: Some(vec![json!("the-argument")]),
}),
..Default::default()
data: Some(serde_json::json!({
"command": "_the/command",
})),
..lsp::CodeAction::default()
}),
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
title: "two".into(),
..Default::default()
..lsp::CodeAction::default()
}),
]))
})
@@ -2615,7 +2626,16 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
// Resolving the code action does not populate its edits. In absence of
// edits, we must execute the given command.
fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|action, _| async move { Ok(action) },
|mut action, _| async move {
if action.data.is_some() {
action.command = Some(lsp::Command {
title: "The command".into(),
command: "_the/command".into(),
arguments: Some(vec![json!("the-argument")]),
});
}
Ok(action)
},
);
// While executing the command, the language server sends the editor

View File

@@ -2,17 +2,20 @@
use std::{any::TypeId, path::Path, sync::Arc};
use collections::{HashMap, VecDeque};
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
use task::{Source, Task, TaskId};
use itertools::Itertools;
use task::{Task, TaskId, TaskSource};
use util::{post_inc, NumericPrefixWithSuffix};
/// Inventory tracks available tasks for a given project.
pub struct Inventory {
sources: Vec<SourceInInventory>,
pub last_scheduled_task: Option<TaskId>,
last_scheduled_tasks: VecDeque<TaskId>,
}
struct SourceInInventory {
source: Model<Box<dyn Source>>,
source: Model<Box<dyn TaskSource>>,
_subscription: Subscription,
type_id: TypeId,
}
@@ -21,12 +24,12 @@ impl Inventory {
pub(crate) fn new(cx: &mut AppContext) -> Model<Self> {
cx.new_model(|_| Self {
sources: Vec::new(),
last_scheduled_task: None,
last_scheduled_tasks: VecDeque::new(),
})
}
/// Registers a new tasks source, that would be fetched for available tasks.
pub fn add_source(&mut self, source: Model<Box<dyn Source>>, cx: &mut ModelContext<Self>) {
pub fn add_source(&mut self, source: Model<Box<dyn TaskSource>>, cx: &mut ModelContext<Self>) {
let _subscription = cx.observe(&source, |_, _, cx| {
cx.notify();
});
@@ -39,7 +42,8 @@ impl Inventory {
self.sources.push(source);
cx.notify();
}
pub fn source<T: Source>(&self) -> Option<Model<Box<dyn Source>>> {
pub fn source<T: TaskSource>(&self) -> Option<Model<Box<dyn TaskSource>>> {
let target_type_id = std::any::TypeId::of::<T>();
self.sources.iter().find_map(
|SourceInInventory {
@@ -55,25 +59,306 @@ impl Inventory {
}
/// Pulls its sources to list runanbles for the path given (up to the source to decide what to return for no path).
pub fn list_tasks(&self, path: Option<&Path>, cx: &mut AppContext) -> Vec<Arc<dyn Task>> {
let mut tasks = Vec::new();
for source in &self.sources {
tasks.extend(
pub fn list_tasks(
&self,
path: Option<&Path>,
lru: bool,
cx: &mut AppContext,
) -> Vec<Arc<dyn Task>> {
let mut lru_score = 0_u32;
let tasks_by_usage = if lru {
self.last_scheduled_tasks
.iter()
.rev()
.fold(HashMap::default(), |mut tasks, id| {
tasks.entry(id).or_insert_with(|| post_inc(&mut lru_score));
tasks
})
} else {
HashMap::default()
};
let not_used_score = post_inc(&mut lru_score);
self.sources
.iter()
.flat_map(|source| {
source
.source
.update(cx, |source, cx| source.tasks_for_path(path, cx)),
);
}
tasks
.update(cx, |source, cx| source.tasks_for_path(path, cx))
})
.map(|task| {
let usages = if lru {
tasks_by_usage
.get(&task.id())
.copied()
.unwrap_or(not_used_score)
} else {
not_used_score
};
(task, usages)
})
.sorted_unstable_by(|(task_a, usages_a), (task_b, usages_b)| {
usages_a.cmp(usages_b).then({
NumericPrefixWithSuffix::from_numeric_prefixed_str(task_a.name())
.cmp(&NumericPrefixWithSuffix::from_numeric_prefixed_str(
task_b.name(),
))
.then(task_a.name().cmp(task_b.name()))
})
})
.map(|(task, _)| task)
.collect()
}
/// Returns the last scheduled task, if any of the sources contains one with the matching id.
pub fn last_scheduled_task(&self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
self.last_scheduled_task.as_ref().and_then(|id| {
self.last_scheduled_tasks.back().and_then(|id| {
// TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future.
self.list_tasks(None, cx)
self.list_tasks(None, false, cx)
.into_iter()
.find(|task| task.id() == id)
})
}
/// Registers task "usage" as being scheduled to be used for LRU sorting when listing all tasks.
pub fn task_scheduled(&mut self, id: TaskId) {
self.last_scheduled_tasks.push_back(id);
if self.last_scheduled_tasks.len() > 5_000 {
self.last_scheduled_tasks.pop_front();
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use gpui::TestAppContext;
use super::*;
#[gpui::test]
fn test_task_list_sorting(cx: &mut TestAppContext) {
let inventory = cx.update(Inventory::new);
let initial_tasks = list_task_names(&inventory, None, true, cx);
assert!(
initial_tasks.is_empty(),
"No tasks expected for empty inventory, but got {initial_tasks:?}"
);
let initial_tasks = list_task_names(&inventory, None, false, cx);
assert!(
initial_tasks.is_empty(),
"No tasks expected for empty inventory, but got {initial_tasks:?}"
);
inventory.update(cx, |inventory, cx| {
inventory.add_source(TestSource::new(vec!["3_task".to_string()], cx), cx);
});
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TestSource::new(
vec![
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
],
cx,
),
cx,
);
});
let expected_initial_state = [
"1_a_task".to_string(),
"1_task".to_string(),
"2_task".to_string(),
"3_task".to_string(),
];
assert_eq!(
list_task_names(&inventory, None, false, cx),
&expected_initial_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
&expected_initial_state,
"Tasks with equal amount of usages should be sorted alphanumerically"
);
register_task_used(&inventory, "2_task", cx);
assert_eq!(
list_task_names(&inventory, None, false, cx),
&expected_initial_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
vec![
"2_task".to_string(),
"1_a_task".to_string(),
"1_task".to_string(),
"3_task".to_string()
],
);
register_task_used(&inventory, "1_task", cx);
register_task_used(&inventory, "1_task", cx);
register_task_used(&inventory, "1_task", cx);
register_task_used(&inventory, "3_task", cx);
assert_eq!(
list_task_names(&inventory, None, false, cx),
&expected_initial_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
vec![
"3_task".to_string(),
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
],
);
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TestSource::new(vec!["10_hello".to_string(), "11_hello".to_string()], cx),
cx,
);
});
let expected_updated_state = [
"1_a_task".to_string(),
"1_task".to_string(),
"2_task".to_string(),
"3_task".to_string(),
"10_hello".to_string(),
"11_hello".to_string(),
];
assert_eq!(
list_task_names(&inventory, None, false, cx),
&expected_updated_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
vec![
"3_task".to_string(),
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
"10_hello".to_string(),
"11_hello".to_string(),
],
);
register_task_used(&inventory, "11_hello", cx);
assert_eq!(
list_task_names(&inventory, None, false, cx),
&expected_updated_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
vec![
"11_hello".to_string(),
"3_task".to_string(),
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
"10_hello".to_string(),
],
);
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct TestTask {
id: TaskId,
name: String,
}
impl Task for TestTask {
fn id(&self) -> &TaskId {
&self.id
}
fn name(&self) -> &str {
&self.name
}
fn cwd(&self) -> Option<&Path> {
None
}
fn exec(&self, _cwd: Option<PathBuf>) -> Option<task::SpawnInTerminal> {
None
}
}
struct TestSource {
tasks: Vec<TestTask>,
}
impl TestSource {
fn new(
task_names: impl IntoIterator<Item = String>,
cx: &mut AppContext,
) -> Model<Box<dyn TaskSource>> {
cx.new_model(|_| {
Box::new(Self {
tasks: task_names
.into_iter()
.enumerate()
.map(|(i, name)| TestTask {
id: TaskId(format!("task_{i}_{name}")),
name,
})
.collect(),
}) as Box<dyn TaskSource>
})
}
}
impl TaskSource for TestSource {
fn tasks_for_path(
&mut self,
_path: Option<&Path>,
_cx: &mut ModelContext<Box<dyn TaskSource>>,
) -> Vec<Arc<dyn Task>> {
self.tasks
.clone()
.into_iter()
.map(|task| Arc::new(task) as Arc<dyn Task>)
.collect()
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
}
fn list_task_names(
inventory: &Model<Inventory>,
path: Option<&Path>,
lru: bool,
cx: &mut TestAppContext,
) -> Vec<String> {
inventory.update(cx, |inventory, cx| {
inventory
.list_tasks(path, lru, cx)
.into_iter()
.map(|task| task.name().to_string())
.collect()
})
}
fn register_task_used(inventory: &Model<Inventory>, task_name: &str, cx: &mut TestAppContext) {
inventory.update(cx, |inventory, cx| {
let task = inventory
.list_tasks(None, false, cx)
.into_iter()
.find(|task| task.name() == task_name)
.unwrap_or_else(|| panic!("Failed to find task with name {task_name}"));
inventory.task_scheduled(task.id().clone());
});
}
}

View File

@@ -64,6 +64,7 @@ pub enum GitGutterSetting {
#[serde(rename_all = "snake_case")]
pub struct LspSettings {
pub initialization_options: Option<serde_json::Value>,
pub settings: Option<serde_json::Value>,
}
impl Settings for ProjectSettings {

View File

@@ -68,6 +68,11 @@ use util::{
ResultExt,
};
#[cfg(feature = "test-support")]
pub const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
#[cfg(not(feature = "test-support"))]
const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub struct WorktreeId(usize);
@@ -652,7 +657,7 @@ fn start_background_scan_tasks(
let abs_path = abs_path.to_path_buf();
let background = cx.background_executor().clone();
async move {
let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
let events = fs.watch(&abs_path, FS_WATCH_LATENCY).await;
let case_sensitive = fs.is_case_sensitive().await.unwrap_or_else(|e| {
log::error!(
"Failed to determine whether filesystem is case sensitive (falling back to true) due to error: {e:#}"
@@ -4340,7 +4345,7 @@ impl BackgroundScanner {
return self.executor.simulate_random_delay().await;
}
smol::Timer::after(Duration::from_millis(100)).await;
smol::Timer::after(FS_WATCH_LATENCY).await;
}
}

View File

@@ -26,7 +26,7 @@ serde_json.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true
unicase = "2.6"
unicase.workspace = true
util.workspace = true
client.workspace = true
workspace.workspace = true

View File

@@ -27,7 +27,7 @@ use std::{cmp::Ordering, ffi::OsStr, ops::Range, path::Path, sync::Arc};
use theme::ThemeSettings;
use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem};
use unicase::UniCase;
use util::{maybe, ResultExt, TryFutureExt};
use util::{maybe, NumericPrefixWithSuffix, ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
notifications::DetachAndPromptErr,
@@ -338,7 +338,7 @@ impl ProjectPanel {
let panel = ProjectPanel::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width;
panel.width = serialized_panel.width.map(|px| px.round());
cx.notify();
});
}
@@ -1182,11 +1182,15 @@ impl ProjectPanel {
let num_and_remainder_a = Path::new(component_a.as_os_str())
.file_stem()
.and_then(|s| s.to_str())
.and_then(NumericPrefixWithSuffix::from_str)?;
.and_then(
NumericPrefixWithSuffix::from_numeric_prefixed_str,
)?;
let num_and_remainder_b = Path::new(component_b.as_os_str())
.file_stem()
.and_then(|s| s.to_str())
.and_then(NumericPrefixWithSuffix::from_str)?;
.and_then(
NumericPrefixWithSuffix::from_numeric_prefixed_str,
)?;
num_and_remainder_a.partial_cmp(&num_and_remainder_b)
});
@@ -1498,35 +1502,6 @@ impl ProjectPanel {
}
}
#[derive(Debug, PartialEq)]
struct NumericPrefixWithSuffix<'a>(i32, &'a str);
impl<'a> NumericPrefixWithSuffix<'a> {
fn from_str(str: &'a str) -> Option<Self> {
let mut chars = str.chars();
let prefix: String = chars.by_ref().take_while(|c| c.is_digit(10)).collect();
let remainder = chars.as_str();
match prefix.parse::<i32>() {
Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
Err(_) => None,
}
}
}
impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let NumericPrefixWithSuffix(num_a, remainder_a) = self;
let NumericPrefixWithSuffix(num_b, remainder_b) = other;
Some(
num_a
.cmp(&num_b)
.then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b))),
)
}
}
impl Render for ProjectPanel {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
let has_worktree = self.visible_entries.len() != 0;

View File

@@ -15,7 +15,15 @@ gpui.workspace = true
menu.workspace = true
ordered-float.workspace = true
picker.workspace = true
serde.workspace = true
smol.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
project = { workspace = true, features = ["test-support"] }
serde_json.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View File

@@ -8,12 +8,23 @@ use gpui::{
use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use serde::Deserialize;
use std::sync::Arc;
use ui::{prelude::*, tooltip_container, HighlightedLabel, ListItem, ListItemSpacing, Tooltip};
use util::paths::PathExt;
use workspace::{ModalView, Workspace, WorkspaceId, WorkspaceLocation, WORKSPACE_DB};
gpui::actions!(projects, [OpenRecent]);
#[derive(PartialEq, Clone, Deserialize, Default)]
pub struct OpenRecent {
#[serde(default = "default_create_new_window")]
pub create_new_window: bool,
}
fn default_create_new_window() -> bool {
true
}
gpui::impl_actions!(projects, [OpenRecent]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(RecentProjects::register).detach();
@@ -63,9 +74,9 @@ impl RecentProjects {
}
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, _: &OpenRecent, cx| {
workspace.register_action(|workspace, open_recent: &OpenRecent, cx| {
let Some(recent_projects) = workspace.active_modal::<Self>(cx) else {
if let Some(handler) = Self::open(workspace, cx) {
if let Some(handler) = Self::open(workspace, open_recent.create_new_window, cx) {
handler.detach_and_log_err(cx);
}
return;
@@ -79,12 +90,17 @@ impl RecentProjects {
});
}
fn open(_: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<Task<Result<()>>> {
fn open(
_: &mut Workspace,
create_new_window: bool,
cx: &mut ViewContext<Workspace>,
) -> Option<Task<Result<()>>> {
Some(cx.spawn(|workspace, mut cx| async move {
workspace.update(&mut cx, |workspace, cx| {
let weak_workspace = cx.view().downgrade();
workspace.toggle_modal(cx, |cx| {
let delegate = RecentProjectsDelegate::new(weak_workspace, true);
let delegate =
RecentProjectsDelegate::new(weak_workspace, create_new_window, true);
let modal = Self::new(delegate, 34., cx);
modal
@@ -95,7 +111,13 @@ impl RecentProjects {
}
pub fn open_popover(workspace: WeakView<Workspace>, cx: &mut WindowContext<'_>) -> View<Self> {
cx.new_view(|cx| Self::new(RecentProjectsDelegate::new(workspace, false), 20., cx))
cx.new_view(|cx| {
Self::new(
RecentProjectsDelegate::new(workspace, false, false),
20.,
cx,
)
})
}
}
@@ -126,17 +148,19 @@ pub struct RecentProjectsDelegate {
selected_match_index: usize,
matches: Vec<StringMatch>,
render_paths: bool,
create_new_window: bool,
// Flag to reset index when there is a new query vs not reset index when user delete an item
reset_selected_match_index: bool,
}
impl RecentProjectsDelegate {
fn new(workspace: WeakView<Workspace>, render_paths: bool) -> Self {
fn new(workspace: WeakView<Workspace>, create_new_window: bool, render_paths: bool) -> Self {
Self {
workspace,
workspaces: vec![],
selected_match_index: 0,
matches: Default::default(),
create_new_window,
render_paths,
reset_selected_match_index: true,
}
@@ -147,10 +171,19 @@ impl PickerDelegate for RecentProjectsDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, cx: &mut WindowContext) -> Arc<str> {
let (create_window, reuse_window) = if self.create_new_window {
(
cx.keystroke_text_for(&menu::Confirm),
cx.keystroke_text_for(&menu::SecondaryConfirm),
)
} else {
(
cx.keystroke_text_for(&menu::SecondaryConfirm),
cx.keystroke_text_for(&menu::Confirm),
)
};
Arc::from(format!(
"{} reuses the window, {} opens a new one",
cx.keystroke_text_for(&menu::Confirm),
cx.keystroke_text_for(&menu::SecondaryConfirm),
"{reuse_window} reuses the window, {create_window} opens a new one",
))
}
@@ -219,15 +252,39 @@ impl PickerDelegate for RecentProjectsDelegate {
{
let (candidate_workspace_id, candidate_workspace_location) =
&self.workspaces[selected_match.candidate_id];
let replace_current_window = !secondary;
let replace_current_window = if self.create_new_window {
secondary
} else {
!secondary
};
workspace
.update(cx, |workspace, cx| {
if workspace.database_id() != *candidate_workspace_id {
workspace.open_workspace_for_paths(
replace_current_window,
candidate_workspace_location.paths().as_ref().clone(),
cx,
)
let candidate_paths = candidate_workspace_location.paths().as_ref().clone();
if replace_current_window {
cx.spawn(move |workspace, mut cx| async move {
let continue_replacing = workspace
.update(&mut cx, |workspace, cx| {
workspace.prepare_to_close(true, cx)
})?
.await?;
if continue_replacing {
workspace
.update(&mut cx, |workspace, cx| {
workspace.open_workspace_for_paths(
true,
candidate_paths,
cx,
)
})?
.await
} else {
Ok(())
}
})
} else {
workspace.open_workspace_for_paths(false, candidate_paths, cx)
}
} else {
Task::ready(Ok(()))
}
@@ -360,3 +417,141 @@ impl Render for MatchTooltip {
})
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use editor::Editor;
use gpui::{TestAppContext, WindowHandle};
use project::Project;
use serde_json::json;
use workspace::{open_paths, AppState};
use super::*;
#[gpui::test]
async fn test_prompts_on_dirty_before_submit(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/dir",
json!({
"main.ts": "a"
}),
)
.await;
cx.update(|cx| open_paths(&[PathBuf::from("/dir/main.ts")], &app_state, None, cx))
.await
.unwrap();
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
let workspace = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
workspace
.update(cx, |workspace, _| assert!(!workspace.is_edited()))
.unwrap();
let editor = workspace
.read_with(cx, |workspace, cx| {
workspace
.active_item(cx)
.unwrap()
.downcast::<Editor>()
.unwrap()
})
.unwrap();
workspace
.update(cx, |_, cx| {
editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
})
.unwrap();
workspace
.update(cx, |workspace, _| assert!(workspace.is_edited(), "After inserting more text into the editor without saving, we should have a dirty project"))
.unwrap();
let recent_projects_picker = open_recent_projects(&workspace, cx);
workspace
.update(cx, |_, cx| {
recent_projects_picker.update(cx, |picker, cx| {
assert_eq!(picker.query(cx), "");
let delegate = &mut picker.delegate;
delegate.matches = vec![StringMatch {
candidate_id: 0,
score: 1.0,
positions: Vec::new(),
string: "fake candidate".to_string(),
}];
delegate.workspaces = vec![(0, WorkspaceLocation::new(vec!["/test/path/"]))];
});
})
.unwrap();
assert!(
!cx.has_pending_prompt(),
"Should have no pending prompt on dirty project before opening the new recent project"
);
cx.dispatch_action((*workspace).into(), menu::Confirm);
workspace
.update(cx, |workspace, cx| {
assert!(
workspace.active_modal::<RecentProjects>(cx).is_none(),
"Should remove the modal after selecting new recent project"
)
})
.unwrap();
assert!(
cx.has_pending_prompt(),
"Dirty workspace should prompt before opening the new recent project"
);
// Cancel
cx.simulate_prompt_answer(0);
assert!(
!cx.has_pending_prompt(),
"Should have no pending prompt after cancelling"
);
workspace
.update(cx, |workspace, _| {
assert!(
workspace.is_edited(),
"Should be in the same dirty project after cancelling"
)
})
.unwrap();
}
fn open_recent_projects(
workspace: &WindowHandle<Workspace>,
cx: &mut TestAppContext,
) -> View<Picker<RecentProjectsDelegate>> {
cx.dispatch_action(
(*workspace).into(),
OpenRecent {
create_new_window: false,
},
);
workspace
.update(cx, |workspace, cx| {
workspace
.active_modal::<RecentProjects>(cx)
.unwrap()
.read(cx)
.picker
.clone()
})
.unwrap()
}
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
cx.update(|cx| {
let state = AppState::test(cx);
language::init(cx);
crate::init(cx);
editor::init(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
state
})
}
}

View File

@@ -315,10 +315,13 @@ impl Peer {
"incoming response: requester resumed"
);
} else {
let message_type = proto::build_typed_envelope(connection_id, incoming)
.map(|p| p.payload_type_name());
tracing::warn!(
%connection_id,
message_id,
responding_to,
message_type,
"incoming response: unknown request"
);
}

View File

@@ -56,13 +56,13 @@ pub trait Task {
///
/// Implementations of this trait could be e.g. [`StaticSource`] that parses tasks from a .json files and provides process templates to be spawned;
/// another one could be a language server providing lenses with tests or build server listing all targets for a given project.
pub trait Source: Any {
pub trait TaskSource: Any {
/// A way to erase the type of the source, processing and storing them generically.
fn as_any(&mut self) -> &mut dyn Any;
/// Collects all tasks available for scheduling, for the path given.
fn tasks_for_path(
&mut self,
path: Option<&Path>,
cx: &mut ModelContext<Box<dyn Source>>,
cx: &mut ModelContext<Box<dyn TaskSource>>,
) -> Vec<Arc<dyn Task>>;
}

View File

@@ -2,7 +2,7 @@
use std::sync::Arc;
use crate::{Source, SpawnInTerminal, Task, TaskId};
use crate::{SpawnInTerminal, Task, TaskId, TaskSource};
use gpui::{AppContext, Context, Model};
/// A storage and source of tasks generated out of user command prompt inputs.
@@ -54,8 +54,8 @@ impl Task for OneshotTask {
impl OneshotSource {
/// Initializes the oneshot source, preparing to store user prompts.
pub fn new(cx: &mut AppContext) -> Model<Box<dyn Source>> {
cx.new_model(|_| Box::new(Self { tasks: Vec::new() }) as Box<dyn Source>)
pub fn new(cx: &mut AppContext) -> Model<Box<dyn TaskSource>> {
cx.new_model(|_| Box::new(Self { tasks: Vec::new() }) as Box<dyn TaskSource>)
}
/// Spawns a certain task based on the user prompt.
@@ -66,7 +66,7 @@ impl OneshotSource {
}
}
impl Source for OneshotSource {
impl TaskSource for OneshotSource {
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
@@ -74,7 +74,7 @@ impl Source for OneshotSource {
fn tasks_for_path(
&mut self,
_path: Option<&std::path::Path>,
_cx: &mut gpui::ModelContext<Box<dyn Source>>,
_cx: &mut gpui::ModelContext<Box<dyn TaskSource>>,
) -> Vec<Arc<dyn Task>> {
self.tasks.clone()
}

View File

@@ -12,7 +12,7 @@ use schemars::{gen::SchemaSettings, JsonSchema};
use serde::{Deserialize, Serialize};
use util::ResultExt;
use crate::{Source, SpawnInTerminal, Task, TaskId};
use crate::{SpawnInTerminal, Task, TaskId, TaskSource};
use futures::channel::mpsc::UnboundedReceiver;
/// A single config file entry with the deserialized task definition.
@@ -152,12 +152,12 @@ impl StaticSource {
pub fn new(
tasks_file_tracker: UnboundedReceiver<String>,
cx: &mut AppContext,
) -> Model<Box<dyn Source>> {
) -> Model<Box<dyn TaskSource>> {
let definitions = TrackedFile::new(DefinitionProvider::default(), tasks_file_tracker, cx);
cx.new_model(|cx| {
let _subscription = cx.observe(
&definitions,
|source: &mut Box<(dyn Source + 'static)>, new_definitions, cx| {
|source: &mut Box<(dyn TaskSource + 'static)>, new_definitions, cx| {
if let Some(static_source) = source.as_any().downcast_mut::<Self>() {
static_source.tasks = new_definitions
.read(cx)
@@ -181,11 +181,11 @@ impl StaticSource {
}
}
impl Source for StaticSource {
impl TaskSource for StaticSource {
fn tasks_for_path(
&mut self,
_: Option<&Path>,
_: &mut ModelContext<Box<dyn Source>>,
_: &mut ModelContext<Box<dyn TaskSource>>,
) -> Vec<Arc<dyn Task>> {
self.tasks
.clone()

View File

@@ -41,7 +41,7 @@ fn schedule_task(workspace: &Workspace, task: &dyn Task, cx: &mut ViewContext<'_
if let Some(spawn_in_terminal) = spawn_in_terminal {
workspace.project().update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, _| {
inventory.last_scheduled_task = Some(task.id().clone());
inventory.task_scheduled(task.id().clone());
})
});
cx.emit(workspace::Event::SpawnTask(spawn_in_terminal));

View File

@@ -24,7 +24,7 @@ pub(crate) struct TasksModalDelegate {
matches: Vec<StringMatch>,
selected_index: usize,
workspace: WeakView<Workspace>,
last_prompt: String,
prompt: String,
}
impl TasksModalDelegate {
@@ -35,20 +35,21 @@ impl TasksModalDelegate {
candidates: Vec::new(),
matches: Vec::new(),
selected_index: 0,
last_prompt: String::default(),
prompt: String::default(),
}
}
fn spawn_oneshot(&mut self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
let oneshot_source = self
.inventory
.update(cx, |this, _| this.source::<OneshotSource>())?;
oneshot_source.update(cx, |this, _| {
let Some(this) = this.as_any().downcast_mut::<OneshotSource>() else {
return None;
};
Some(this.spawn(self.last_prompt.clone()))
})
self.inventory
.update(cx, |inventory, _| inventory.source::<OneshotSource>())?
.update(cx, |oneshot_source, _| {
Some(
oneshot_source
.as_any()
.downcast_mut::<OneshotSource>()?
.spawn(self.prompt.clone()),
)
})
}
}
@@ -132,12 +133,7 @@ impl PickerDelegate for TasksModalDelegate {
picker.delegate.candidates = picker
.delegate
.inventory
.update(cx, |inventory, cx| inventory.list_tasks(None, cx));
picker
.delegate
.candidates
.sort_by(|a, b| a.name().cmp(&b.name()));
.update(cx, |inventory, cx| inventory.list_tasks(None, true, cx));
picker
.delegate
.candidates
@@ -167,7 +163,7 @@ impl PickerDelegate for TasksModalDelegate {
.update(&mut cx, |picker, _| {
let delegate = &mut picker.delegate;
delegate.matches = matches;
delegate.last_prompt = query;
delegate.prompt = query;
if delegate.matches.is_empty() {
delegate.selected_index = 0;
@@ -184,7 +180,7 @@ impl PickerDelegate for TasksModalDelegate {
let current_match_index = self.selected_index();
let task = if secondary {
if !self.last_prompt.trim().is_empty() {
if !self.prompt.trim().is_empty() {
self.spawn_oneshot(cx)
} else {
None

View File

@@ -489,15 +489,12 @@ impl TerminalElement {
}
});
let interactive_text_bounds = InteractiveBounds {
bounds,
stacking_order: cx.stacking_order().clone(),
};
if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
if bounds.contains(&cx.mouse_position()) {
let stacking_order = cx.stacking_order().clone();
if self.can_navigate_to_selected_word && last_hovered_word.is_some() {
cx.set_cursor_style(gpui::CursorStyle::PointingHand)
cx.set_cursor_style(gpui::CursorStyle::PointingHand, stacking_order);
} else {
cx.set_cursor_style(gpui::CursorStyle::IBeam)
cx.set_cursor_style(gpui::CursorStyle::IBeam, stacking_order);
}
}

View File

@@ -192,8 +192,8 @@ impl TerminalPanel {
let items = if let Some(serialized_panel) = serialized_panel.as_ref() {
panel.update(cx, |panel, cx| {
cx.notify();
panel.height = serialized_panel.height;
panel.width = serialized_panel.width;
panel.height = serialized_panel.height.map(|h| h.round());
panel.width = serialized_panel.width.map(|w| w.round());
panel.pane.update(cx, |_, cx| {
serialized_panel
.items

View File

@@ -31,6 +31,7 @@ serde_json.workspace = true
smol.workspace = true
take-until = "0.2.0"
tempfile = { workspace = true, optional = true }
unicase.workspace = true
url.workspace = true
[target.'cfg(windows)'.dependencies]

View File

@@ -3,6 +3,7 @@ use anyhow::{anyhow, bail, Context, Result};
use futures::AsyncReadExt;
use serde::Deserialize;
use std::sync::Arc;
use url::Url;
pub struct GitHubLspBinaryVersion {
pub name: String,
@@ -74,3 +75,70 @@ pub async fn latest_github_release(
.find(|release| release.pre_release == pre_release)
.ok_or(anyhow!("Failed to find a release"))
}
pub async fn github_release_with_tag(
repo_name_with_owner: &str,
tag: &str,
http: Arc<dyn HttpClient>,
) -> Result<GithubRelease, anyhow::Error> {
let url = build_tagged_release_url(repo_name_with_owner, tag)?;
let mut response = http
.get(&url, Default::default(), true)
.await
.context("error fetching latest release")?;
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading latest release")?;
if response.status().is_client_error() {
let text = String::from_utf8_lossy(body.as_slice());
bail!(
"status error {}, response: {text:?}",
response.status().as_u16()
);
}
match serde_json::from_slice::<GithubRelease>(body.as_slice()) {
Ok(release) => Ok(release),
Err(err) => {
log::error!("Error deserializing: {:?}", err);
log::error!(
"GitHub API response text: {:?}",
String::from_utf8_lossy(body.as_slice())
);
Err(anyhow!("error deserializing latest release"))
}
}
}
fn build_tagged_release_url(repo_name_with_owner: &str, tag: &str) -> Result<String> {
let mut url = Url::parse(&format!(
"https://api.github.com/repos/{repo_name_with_owner}/releases/tags"
))?;
// We're pushing this here, because tags may contain `/` and other characters
// that need to be escaped.
url.path_segments_mut()
.map_err(|_| anyhow!("cannot modify url path segments"))?
.push(tag);
Ok(url.to_string())
}
#[cfg(test)]
mod tests {
use super::build_tagged_release_url;
#[test]
fn test_build_tagged_release_url() {
let tag = "release/2.2.20-Insider";
let repo_name_with_owner = "microsoft/vscode-eslint";
let have = build_tagged_release_url(repo_name_with_owner, tag).unwrap();
assert_eq!(have, "https://api.github.com/repos/microsoft/vscode-eslint/releases/tags/release%2F2.2.20-Insider");
}
}

View File

@@ -22,6 +22,7 @@ use std::{
task::{Context, Poll},
time::Instant,
};
use unicase::UniCase;
pub use take_until::*;
@@ -487,6 +488,43 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
}
}
/// A way to sort strings with starting numbers numerically first, falling back to alphanumeric one,
/// case-insensitive.
///
/// This is useful for turning regular alphanumerically sorted sequences as `1-abc, 10, 11-def, .., 2, 21-abc`
/// into `1-abc, 2, 10, 11-def, .., 21-abc`
#[derive(Debug, PartialEq, Eq)]
pub struct NumericPrefixWithSuffix<'a>(i32, &'a str);
impl<'a> NumericPrefixWithSuffix<'a> {
pub fn from_numeric_prefixed_str(str: &'a str) -> Option<Self> {
let mut chars = str.chars();
let prefix: String = chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
let remainder = chars.as_str();
match prefix.parse::<i32>() {
Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
Err(_) => None,
}
}
}
impl Ord for NumericPrefixWithSuffix<'_> {
fn cmp(&self, other: &Self) -> Ordering {
let NumericPrefixWithSuffix(num_a, remainder_a) = self;
let NumericPrefixWithSuffix(num_b, remainder_b) = other;
num_a
.cmp(num_b)
.then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b)))
}
}
impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -526,4 +564,23 @@ mod tests {
assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
}
#[test]
fn test_numeric_prefix_with_suffix() {
let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
sorted.sort_by_key(|s| {
NumericPrefixWithSuffix::from_numeric_prefixed_str(s).unwrap_or_else(|| {
panic!("Cannot convert string `{s}` into NumericPrefixWithSuffix")
})
});
assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
assert_eq!(
NumericPrefixWithSuffix::from_numeric_prefixed_str(numeric_prefix_less),
None,
"String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
)
}
}
}

View File

@@ -522,7 +522,7 @@ impl Dock {
pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round());
entry.panel.set_size(size, cx);
cx.notify();
}

View File

@@ -11,6 +11,7 @@ use client::{
proto::{self, PeerId},
Client,
};
use futures::{channel::mpsc, StreamExt};
use gpui::{
AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
@@ -27,14 +28,13 @@ use std::{
ops::Range,
path::PathBuf,
rc::Rc,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
sync::Arc,
time::Duration,
};
use theme::Theme;
pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
#[derive(Deserialize)]
pub struct ItemSettings {
pub git_status: bool,
@@ -405,7 +405,7 @@ impl<T: Item> ItemHandle for View<T> {
followed_item.is_project_item(cx),
proto::update_followers::Variant::CreateView(proto::View {
id: followed_item
.remote_id(&workspace.app_state.client, cx)
.remote_id(&workspace.client(), cx)
.map(|id| id.to_proto()),
variant: Some(message),
leader_id: workspace.leader_for_pane(&pane),
@@ -421,8 +421,46 @@ impl<T: Item> ItemHandle for View<T> {
.is_none()
{
let mut pending_autosave = DelayedDebouncedEditAction::new();
let (pending_update_tx, mut pending_update_rx) = mpsc::unbounded();
let pending_update = Rc::new(RefCell::new(None));
let pending_update_scheduled = Arc::new(AtomicBool::new(false));
let mut send_follower_updates = None;
if let Some(item) = self.to_followable_item_handle(cx) {
let is_project_item = item.is_project_item(cx);
let item = item.downgrade();
send_follower_updates = Some(cx.spawn({
let pending_update = pending_update.clone();
|workspace, mut cx| async move {
while let Some(mut leader_id) = pending_update_rx.next().await {
while let Ok(Some(id)) = pending_update_rx.try_next() {
leader_id = id;
}
workspace.update(&mut cx, |workspace, cx| {
let item = item.upgrade().expect(
"item to be alive, otherwise task would have been dropped",
);
workspace.update_followers(
is_project_item,
proto::update_followers::Variant::UpdateView(
proto::UpdateView {
id: item
.remote_id(workspace.client(), cx)
.map(|id| id.to_proto()),
variant: pending_update.borrow_mut().take(),
leader_id,
},
),
cx,
);
})?;
cx.background_executor().timer(LEADER_UPDATE_THROTTLE).await;
}
anyhow::Ok(())
}
}));
}
let mut event_subscription =
Some(cx.subscribe(self, move |workspace, item, event, cx| {
@@ -438,9 +476,7 @@ impl<T: Item> ItemHandle for View<T> {
};
if let Some(item) = item.to_followable_item_handle(cx) {
let is_project_item = item.is_project_item(cx);
let leader_id = workspace.leader_for_pane(&pane);
let follow_event = item.to_follow_event(event);
if leader_id.is_some()
&& matches!(follow_event, Some(FollowEvent::Unfollow))
@@ -448,35 +484,13 @@ impl<T: Item> ItemHandle for View<T> {
workspace.unfollow(&pane, cx);
}
if item.focus_handle(cx).contains_focused(cx)
&& item.add_event_to_update_proto(
if item.focus_handle(cx).contains_focused(cx) {
item.add_event_to_update_proto(
event,
&mut *pending_update.borrow_mut(),
cx,
)
&& !pending_update_scheduled.load(Ordering::SeqCst)
{
pending_update_scheduled.store(true, Ordering::SeqCst);
cx.defer({
let pending_update = pending_update.clone();
let pending_update_scheduled = pending_update_scheduled.clone();
move |this, cx| {
pending_update_scheduled.store(false, Ordering::SeqCst);
this.update_followers(
is_project_item,
proto::update_followers::Variant::UpdateView(
proto::UpdateView {
id: item
.remote_id(&this.app_state.client, cx)
.map(|id| id.to_proto()),
variant: pending_update.borrow_mut().take(),
leader_id,
},
),
cx,
);
}
});
);
pending_update_tx.unbounded_send(leader_id).ok();
}
}
@@ -525,6 +539,7 @@ impl<T: Item> ItemHandle for View<T> {
cx.observe_release(self, move |workspace, _, _| {
workspace.panes_by_item.remove(&item_id);
event_subscription.take();
send_follower_updates.take();
})
.detach();
}
@@ -700,6 +715,7 @@ pub trait FollowableItem: Item {
pub trait FollowableItemHandle: ItemHandle {
fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId>;
fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle>;
fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
fn add_event_to_update_proto(
@@ -728,6 +744,10 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
})
}
fn downgrade(&self) -> Box<dyn WeakFollowableItemHandle> {
Box::new(self.downgrade())
}
fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
}
@@ -767,6 +787,16 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
}
}
pub trait WeakFollowableItemHandle: Send + Sync {
fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>>;
}
impl<T: FollowableItem> WeakFollowableItemHandle for WeakView<T> {
fn upgrade(&self) -> Option<Box<dyn FollowableItemHandle>> {
Some(Box::new(self.upgrade()?))
}
}
#[cfg(any(test, feature = "test-support"))]
pub mod test {
use super::{Item, ItemEvent};

View File

@@ -588,9 +588,9 @@ mod element {
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
use gpui::{
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
Size, Style, WeakView, WindowContext,
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
WeakView, WindowContext,
};
use parking_lot::Mutex;
use settings::Settings;
@@ -754,15 +754,13 @@ mod element {
};
cx.with_z_index(3, |cx| {
let interactive_handle_bounds = InteractiveBounds {
bounds: handle_bounds,
stacking_order: cx.stacking_order().clone(),
};
if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
cx.set_cursor_style(match axis {
if handle_bounds.contains(&cx.mouse_position()) {
let stacking_order = cx.stacking_order().clone();
let cursor_style = match axis {
Axis::Vertical => CursorStyle::ResizeUpDown,
Axis::Horizontal => CursorStyle::ResizeLeftRight,
})
};
cx.set_cursor_style(cursor_style, stacking_order);
}
cx.add_opaque_layer(handle_bounds);
@@ -885,7 +883,8 @@ mod element {
let child_size = bounds
.size
.apply_along(self.axis, |_| space_per_flex * child_flex);
.apply_along(self.axis, |_| space_per_flex * child_flex)
.map(|d| d.round());
let child_bounds = Bounds {
origin,

View File

@@ -22,6 +22,16 @@ impl WorkspaceLocation {
pub fn paths(&self) -> Arc<Vec<PathBuf>> {
self.0.clone()
}
#[cfg(any(test, feature = "test-support"))]
pub fn new<P: AsRef<Path>>(paths: Vec<P>) -> Self {
Self(Arc::new(
paths
.into_iter()
.map(|p| p.as_ref().to_path_buf())
.collect(),
))
}
}
impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {

View File

@@ -121,7 +121,6 @@ actions!(
ToggleRightDock,
ToggleBottomDock,
CloseAllDocks,
ToggleGraphicsProfiler,
]
);
@@ -1110,7 +1109,7 @@ impl Workspace {
)
}
pub fn client(&self) -> &Client {
pub fn client(&self) -> &Arc<Client> {
&self.app_state.client
}
@@ -3572,7 +3571,6 @@ impl Workspace {
workspace.reopen_closed_item(cx).detach();
}),
)
.on_action(|_: &ToggleGraphicsProfiler, cx| cx.toggle_graphics_profiler())
}
#[cfg(any(test, feature = "test-support"))]

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.125.0"
version = "0.125.4"
publish = false
license = "GPL-3.0-or-later"

View File

@@ -1 +1 @@
dev
stable

View File

@@ -22,5 +22,3 @@
<string>An application in Zed wants to use speech recognition.</string>
<key>NSRemindersUsageDescription</key>
<string>An application in Zed wants to use your reminders.</string>
<key>MetalHudEnabled</key>
<true />

View File

@@ -37,7 +37,12 @@ pub fn app_menus() -> Vec<Menu<'static>> {
MenuItem::action("New Window", workspace::NewWindow),
MenuItem::separator(),
MenuItem::action("Open…", workspace::Open),
MenuItem::action("Open Recent...", recent_projects::OpenRecent),
MenuItem::action(
"Open Recent...",
recent_projects::OpenRecent {
create_new_window: true,
},
),
MenuItem::separator(),
MenuItem::action("Add Folder to Project…", workspace::AddFolderToProject),
MenuItem::action("Save", workspace::Save { save_intent: None }),
@@ -156,10 +161,6 @@ pub fn app_menus() -> Vec<Menu<'static>> {
MenuItem::action("View Telemetry", crate::OpenTelemetryLog),
MenuItem::action("View Dependency Licenses", crate::OpenLicenses),
MenuItem::action("Show Welcome", workspace::Welcome),
MenuItem::action(
"Toggle Graphics Profiler",
workspace::ToggleGraphicsProfiler,
),
MenuItem::separator(),
MenuItem::action(
"Documentation",

View File

@@ -807,7 +807,7 @@ async fn upload_previous_crashes(
.unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
let mut uploaded = last_uploaded.clone();
let crash_report_url = http.build_url("/api/crash");
let crash_report_url = http.build_zed_api_url("/telemetry/crashes");
for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {
let mut children = smol::fs::read_dir(&dir).await?;

View File

@@ -2751,18 +2751,20 @@ mod tests {
}
#[gpui::test]
fn test_bundled_languages(cx: &mut AppContext) {
let settings = SettingsStore::test(cx);
async fn test_bundled_languages(cx: &mut TestAppContext) {
let settings = cx.update(|cx| SettingsStore::test(cx));
cx.set_global(settings);
let mut languages = LanguageRegistry::test();
languages.set_executor(cx.background_executor().clone());
languages.set_executor(cx.executor().clone());
let languages = Arc::new(languages);
let node_runtime = node_runtime::FakeNodeRuntime::new();
languages::init(languages.clone(), node_runtime, cx);
cx.update(|cx| {
languages::init(languages.clone(), node_runtime, cx);
});
for name in languages.language_names() {
languages.language_for_name(&name);
languages.language_for_name(&name).await.unwrap();
}
cx.background_executor().run_until_parked();
cx.run_until_parked();
}
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {