Compare commits

...

18 Commits

Author SHA1 Message Date
Marshall Bowers
c822188a53 Replace license with symlink 2024-09-14 15:53:02 -04:00
Marshall Bowers
32fe23a182 Remove blank line 2024-09-14 15:52:22 -04:00
Marshall Bowers
b337f419d3 Merge branch 'main' into buffer-extensions 2024-09-14 15:51:33 -04:00
Marshall Bowers
4d8c3855c2 theme: Clamp font sizes between 6px and 100px (#17829)
This PR clamps the `ui_font_size` and `buffer_font_size` settings
between 6px and 100px.

Release Notes:

- Changed `ui_font_size` and `buffer_font_size` to require values to be
between 6px and 100px (inclusive).
2024-09-14 15:44:54 -04:00
Nate Butler
e8a2dd92c8 Derive icon paths (#17816)
This PR improves adding and working with icons by using the new
`DerivePathStr` to derive icon paths.

This means paths no longer need to be manually specified, and the
`IconName` and file name will always be consistent between icons.

This PR does not do any work to standardize icons visually, remove
unused icons, or any other such cleanup.

Release Notes:

- N/A
2024-09-13 21:12:29 -04:00
Nate Butler
ce848375fe add ui::Vector and separate images from icons (#17815)
This PR pulls non-icon assets out of `ui::components::icon` in
preparation for icon standardization.

In the future icons will have standard names and sizes, and these image
assets won't conform to those constraints.

We can also add a `ui::components::image::Image` wrapper around the
`gpui::img` element in the future for any Zed-specific image styling we
want to enforce.

Of note:

```rust
#[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize, DerivePathStr)]
#[strum(serialize_all = "snake_case")]
#[path_str(prefix = "images", suffix = ".svg")]
pub enum VectorName {
    ZedLogo,
    ZedXCopilot,
}
```

You can see in the above code we no longer need to manually specify
paths for image/icon enums like we currently do in
`ui::components::icon`.

The icon component will get this same treatment in the future, once we:

- do the design work needed to standardize the icons
- remove unused icons
- update icon names

Release Notes:

- N/A
2024-09-13 17:44:16 -04:00
Nate Butler
fac9ee5f86 Add ui_macros crate & DerivePathStr derive macro (#17811)
This PR adds the `ui_macros` crate to allow building supporting macros
for the `ui` crate.

Additionally, it implements the `DerivePathStr` derive macro and the
`path_str` attribute macro. These macros work together to generate a
`path` method for enum variants, which is useful for creating
standardized string representations of enum variants.

The `DerivePathStr` macro provides the following functionality:
- Generates a `path` method for each enum variant.
- Allows specifying a prefix (required) and suffix (optional) for all
paths.
- Supports `strum` attributes for case conversion (e.g., snake_case,
lowercase).

Usage example:

```rust
#[derive(DerivePathStr)]
#[path_str(prefix = "my_prefix", suffix = ".txt")]
#[strum(serialize_all = "snake_case")]
enum MyEnum {
    VariantOne,
    VariantTwo,
}

// Generated paths:
// MyEnum::VariantOne.path() -> "my_prefix/variant_one.txt"
// MyEnum::VariantTwo.path() -> "my_prefix/variant_two.txt"
```

In a later PR this will be used to automate the creation of icon & image
paths in the `ui` crate.

This gives the following benefits:

1. Ensures standard naming of assets as paths are not manually
specified.
2. Makes adding new enum variants less tedious and error-prone.
3. Quickly catches missing or incorrect paths during compilation.
3. Adds a building block towards being able to lint for unused assets in
the future.

Release Notes:

- N/A
2024-09-13 16:45:16 -04:00
Peter Tripp
d245f5e75c OpenAI o1-preview and o1-mini support (#17796)
Release Notes:

- Added support for OpenAI o1-mini and o1-preview models.

---------

Co-authored-by: Jason Mancuso <7891333+jvmncs@users.noreply.github.com>
Co-authored-by: Bennet <bennet@zed.dev>
2024-09-13 16:23:55 -04:00
Danilo Leal
e145c13f73 Add stray UI polish to the SSH flow (#17798)
Some super subtle refinement opportunities I spotted while playing
around with this flow. There are mostly copywriting tweaks and some UI
tweaks here and there (including editing the modal horizontal padding).

--- 

Release Notes:

- N/A
2024-09-13 17:05:34 -03:00
Conrad Irwin
548878d8a0 Add support for extensions that edit buffers
Co-Authored-by: Zach <zachary.tc.@gmail.com>
Co-Authored-by: Marshall <marshall@zed.dev>
2024-09-13 15:55:57 -04:00
jvmncs
c71f052276 Add ability to use o1-preview and o1-mini as custom models (#17804)
This is a barebones modification of the OpenAI provider code to
accommodate non-streaming completions. This is specifically for the o1
models, which do not support streaming. Tested that this is working by
running a `/workflow` with the following (arbitrarily chosen) settings:

```json
{
  "language_models": {
    "openai": {
      "version": "1",
      "available_models": [
        {
          "name": "o1-preview",
          "display_name": "o1-preview",
          "max_tokens": 128000,
          "max_completion_tokens": 30000
        },
        {
          "name": "o1-mini",
          "display_name": "o1-mini",
          "max_tokens": 128000,
          "max_completion_tokens": 20000
        }
      ]
    }
  },
}
```

Release Notes:

- Changed  `low_speed_timeout_in_seconds` option to `600` for OpenAI
provider to accommodate recent o1 model release.

---------

Co-authored-by: Peter <peter@zed.dev>
Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-13 15:42:15 -04:00
tepek2
1b36c62188 Add keybinding to swap pane items (#15583)
- Rearrange tabs (left: `ctrl-shift-pageup`, right: `ctrl-shift-pagedown`) like Chrome

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-09-13 15:17:01 -04:00
Thorsten Ball
adbe973f02 editor: In OpenFile check if file with path_suffix exists (#17805)
Demo:


https://github.com/user-attachments/assets/6acb6c1e-bb15-4205-9dcb-2aa4bb99dcf9



Release Notes:

- When using `OpenFile` (`gf` in Vim mode) and the word under the cursor
is not an existing file path, we now fall back and additionally check
whether a file called
`<word-under-cursor>.<language-specific-path-suffixes>` exists. That's
similar to Vim's `suffixesadd` option.

---------

Co-authored-by: Abdelhakim Qbaich <abdelhakim@qbaich.com>
Co-authored-by: Pete LeVasseur <plevasseur@gmail.com>
2024-09-13 15:11:10 -04:00
Barry Penner
8f833ea029 Fix missing on-mouseup when dragging the window on Linux (#17801)
Zed Hackathon entry :D

Release Notes:

- Fixed a bug where Zed would initiate a window move and then refuse to
release the mouse.

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-09-13 11:51:14 -07:00
Marshall Bowers
de344c833b zed_extension_api: Use v0.2.0 WIT types (#17802)
This PR makes `zed_extension_api` use the WIT types from v0.2.0 of
extension API.

A follow-up from #17795, since I had forgotten to do it there.

Release Notes:

- N/A
2024-09-13 14:49:50 -04:00
Conrad Irwin
cdb88f52b7 temp 2024-09-13 13:44:58 -04:00
Richard Feldman
91ffa02e2c /auto (#16696)
Add `/auto` behind a feature flag that's disabled for now, even for
staff.

We've decided on a different design for context inference, but there are
parts of /auto that will be useful for that, so we want them in the code
base even if they're unused for now.

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-13 13:17:49 -04:00
Conrad Irwin
14b4dc9a3a it compiles 2024-09-13 13:15:32 -04:00
145 changed files with 4323 additions and 1541 deletions

48
Cargo.lock generated
View File

@@ -304,6 +304,9 @@ name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
dependencies = [
"serde",
]
[[package]]
name = "as-raw-xcb-connection"
@@ -1709,6 +1712,19 @@ dependencies = [
"profiling",
]
[[package]]
name = "blake3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
]
[[package]]
name = "block"
version = "0.1.6"
@@ -2752,6 +2768,12 @@ dependencies = [
"tiny-keccak",
]
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]]
name = "context_servers"
version = "0.1.0"
@@ -4038,6 +4060,7 @@ dependencies = [
"client",
"collections",
"ctor",
"editor",
"env_logger",
"fs",
"futures 0.3.30",
@@ -4048,6 +4071,7 @@ dependencies = [
"language",
"log",
"lsp",
"multi_buffer",
"node_runtime",
"parking_lot",
"paths",
@@ -4187,6 +4211,7 @@ dependencies = [
name = "feature_flags"
version = "0.1.0"
dependencies = [
"futures 0.3.30",
"gpui",
]
@@ -9814,10 +9839,13 @@ name = "semantic_index"
version = "0.1.0"
dependencies = [
"anyhow",
"arrayvec",
"blake3",
"client",
"clock",
"collections",
"env_logger",
"feature_flags",
"fs",
"futures 0.3.30",
"futures-batch",
@@ -9825,6 +9853,7 @@ dependencies = [
"heed",
"http_client",
"language",
"language_model",
"languages",
"log",
"open_ai",
@@ -12246,6 +12275,7 @@ dependencies = [
"story",
"strum 0.25.0",
"theme",
"ui_macros",
"windows 0.58.0",
]
@@ -12260,6 +12290,16 @@ dependencies = [
"ui",
]
[[package]]
name = "ui_macros"
version = "0.1.0"
dependencies = [
"convert_case 0.6.0",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "unicase"
version = "2.7.0"
@@ -14517,6 +14557,14 @@ dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_uppercase"
version = "0.0.5"
dependencies = [
"serde_json",
"zed_extension_api 0.2.0",
]
[[package]]
name = "zed_vue"
version = "0.1.0"

View File

@@ -118,6 +118,7 @@ members = [
"crates/title_bar",
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
"crates/util",
"crates/vcs_menu",
"crates/vim",
@@ -158,6 +159,7 @@ members = [
"extensions/terraform",
"extensions/test-extension",
"extensions/toml",
"extensions/uppercase",
"extensions/uiua",
"extensions/vue",
"extensions/zig",
@@ -292,6 +294,7 @@ time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
util = { path = "crates/util" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
@@ -309,6 +312,7 @@ aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91d034ff8b53867143c005acfaa14609147c9a2c" }
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
ashpd = "0.9.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
@@ -325,12 +329,14 @@ bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blake3 = "1.5.3"
cargo_metadata = "0.18"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
clickhouse = "0.11.6"
cocoa = "0.26"
convert_case = "0.6.0"
core-foundation = "0.9.3"
core-foundation-sys = "0.8.6"
ctor = "0.2.6"

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-volume-off"><path d="M16 9a5 5 0 0 1 .95 2.293"/><path d="M19.364 5.636a9 9 0 0 1 1.889 9.96"/><path d="m2 2 20 20"/><path d="m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11"/><path d="M9.828 4.172A.686.686 0 0 1 11 4.657v.686"/></svg>

After

Width:  |  Height:  |  Size: 527 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-volume-2"><path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/><path d="M16 9a5 5 0 0 1 0 6"/><path d="M19.364 18.364a9 9 0 0 0 0-12.728"/></svg>

After

Width:  |  Height:  |  Size: 475 B

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 238 B

After

Width:  |  Height:  |  Size: 238 B

View File

Before

Width:  |  Height:  |  Size: 610 B

After

Width:  |  Height:  |  Size: 610 B

View File

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 345 B

View File

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 443 B

View File

@@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 11V11.8374C13 11.9431 12.9665 12.046 12.9044 12.1315L12.1498 13.1691C12.0557 13.2985 11.9054 13.375 11.7454 13.375H4.25461C4.09464 13.375 3.94433 13.2985 3.85024 13.1691L3.09563 12.1315C3.03348 12.046 3 11.9431 3 11.8374V3" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M3 13V11L8 12H13V13H3Z" fill="black"/>
<path d="M6.63246 3.04418C7.44914 3.31641 8 4.08069 8 4.94155V11.7306C8 12.0924 7.62757 12.3345 7.29693 12.1875L3.79693 10.632C3.61637 10.5518 3.5 10.3727 3.5 10.1751V2.69374C3.5 2.35246 3.83435 2.11148 4.15811 2.2194L6.63246 3.04418Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 3C8.67157 3 8 3.67157 8 4.5V13C8 12.1954 11.2366 12.0382 12.5017 12.0075C12.7778 12.0008 13 11.7761 13 11.5V3.5C13 3.22386 12.7761 3 12.5 3H9.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1009 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 5H11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M3 8H13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M3 11H9" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 13C6.10457 13 7 12.1046 7 11C7 9.89543 6.10457 9 5 9C3.89543 9 3 9.89543 3 11C3 12.1046 3.89543 13 5 13Z" stroke="black" stroke-width="1.5"/>
<path d="M11 7C12.1046 7 13 6.10457 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5C9 6.10457 9.89543 7 11 7Z" fill="black" stroke="black" stroke-width="1.5"/>
<path d="M4.625 3.625V8.375" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M11 7C11 9.20914 9.20914 11 7 11" stroke="black" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 591 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 6.5C3.25 5.80964 3.80964 5.25 4.5 5.25H11.5C12.1904 5.25 12.75 5.80964 12.75 6.5V12.5C12.75 13.1904 12.1904 13.75 11.5 13.75H4.5C3.80964 13.75 3.25 13.1904 3.25 12.5V6.5ZM8.75 9.66146C8.90559 9.48517 9 9.25361 9 9C9 8.44772 8.55228 8 8 8C7.44772 8 7 8.44772 7 9C7 9.25361 7.09441 9.48517 7.25 9.66146V11C7.25 11.4142 7.58579 11.75 8 11.75C8.41421 11.75 8.75 11.4142 8.75 11V9.66146Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 667 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.27935 10.9821C5.32063 10.4038 4.9204 9.89049 4.35998 9.80276L3.60081 9.68387C3.37979 9.64945 3.20167 9.48001 3.15225 9.25614L3.01378 8.63511C2.96382 8.41235 3.05233 8.1807 3.23696 8.05125L3.8631 7.61242C4.33337 7.28297 4.47456 6.6369 4.18621 6.13364L3.79467 5.45092C3.68118 5.25261 3.69801 5.00374 3.83757 4.82321L4.22314 4.32436C4.3627 4.14438 4.59621 4.06994 4.81071 4.13772L5.57531 4.37769C6.11944 4.54879 6.70048 4.26159 6.90683 3.71886L7.1811 2.99782C7.26255 2.78395 7.46345 2.64285 7.68772 2.6423L8.31007 2.64063C8.53434 2.64007 8.73579 2.78006 8.81834 2.99337L9.09965 3.72275C9.30821 4.26214 9.88655 4.54712 10.429 4.37714L11.1632 4.14716C11.3772 4.07994 11.6096 4.15382 11.7492 4.3327L12.1374 4.83099" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.76988 11.5933C8.76988 11.6595 8.8236 11.7133 8.88988 11.7133H8.97588C9.32602 11.7133 9.60988 11.9971 9.60988 12.3472C9.60988 12.6974 9.32602 12.9812 8.97588 12.9812H7.05587C6.70573 12.9812 6.42188 12.6974 6.42188 12.3472C6.42188 11.9971 6.70573 11.7133 7.05587 11.7133H7.14188C7.20815 11.7133 7.26188 11.6595 7.26188 11.5933V7.66925C7.26188 7.60298 7.20815 7.54925 7.14188 7.54925H7.05588C6.70573 7.54925 6.42188 7.2654 6.42188 6.91525C6.42188 6.5651 6.70573 6.28125 7.05588 6.28125H9.89988C11.0518 6.28125 12.8619 6.71487 12.8619 8.15185C12.8619 8.67078 12.7284 9.10362 12.4642 9.45348C12.1981 9.79765 11.8458 10.0564 11.4056 10.2293C11.3782 10.2401 11.3673 10.273 11.3829 10.298L12.2163 11.6342C12.247 11.6834 12.3008 11.7133 12.3588 11.7133H12.7319C13.082 11.7133 13.3659 11.9971 13.3659 12.3472C13.3659 12.6974 13.082 12.9812 12.7319 12.9812H11.5637C11.4955 12.9812 11.432 12.9465 11.3952 12.889L9.96523 10.6541C9.92847 10.5966 9.86495 10.5618 9.79675 10.5618H8.96988C8.85942 10.5618 8.76988 10.6514 8.76988 10.7619V11.5933ZM9.61188 7.54925C10.0296 7.54925 11.125 7.54925 11.2339 8.18785C11.2975 8.56123 11.1181 8.86557 10.8812 9.07715C10.6423 9.29046 10.2053 9.38985 9.58788 9.38985H8.86988C8.81465 9.38985 8.76988 9.34508 8.76988 9.28985V7.64925C8.76988 7.59402 8.81465 7.54925 8.86988 7.54925H9.61188Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 6H10" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M8 6V11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M5 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H5M11 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 472 B

View File

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 462 B

3
assets/icons/folder.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.26046 3.97337C8.3527 4.17617 8.4795 4.47151 8.57375 4.69341C8.65258 4.87898 8.83437 4.99999 9.03599 4.99999H12.5C12.7761 4.99999 13 5.22385 13 5.49999V12.125C13 12.4011 12.7761 12.625 12.5 12.625H3.5C3.22386 12.625 3 12.4011 3 12.125V3.86932C3 3.59318 3.22386 3.36932 3.5 3.36932H7.34219C7.74141 3.36932 8.09483 3.60924 8.26046 3.97337Z" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 512 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.42782 7.2487C4.43495 6.97194 4.65009 6.75 4.91441 6.75H13.5293C13.7935 6.75 14.007 6.97194 13.9998 7.2487C13.9628 8.6885 13.7533 12.75 12.5721 12.75H3.375C4.55631 12.75 4.3907 8.6885 4.42782 7.2487Z" fill="black" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
<path d="M5.19598 12.625H3.66515C3.42618 12.625 3.22289 12.4453 3.18626 12.2017L1.94333 3.93602C1.89776 3.63295 2.12496 3.35938 2.42223 3.35938H5.78585C6.11241 3.35938 6.41702 3.52903 6.59618 3.81071L6.94517 4.35938H9.92811C10.4007 4.35938 10.8044 4.71102 10.8836 5.1917L11.1251 6.65624" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 759 B

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 677 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.16089 10.2476L3.99598 10.3784C4.61244 10.4749 5.05269 11.0395 5.00728 11.6755L4.94576 12.5377C4.92784 12.789 5.06165 13.0255 5.28326 13.1348L5.90091 13.4391C6.12253 13.5485 6.38717 13.5075 6.56817 13.3371L7.1888 12.7505C7.64641 12.3178 8.35245 12.3178 8.81059 12.7505L9.43121 13.3371C9.61222 13.5081 9.87629 13.5485 10.0985 13.4391L10.7173 13.1341C10.9384 13.0255 11.0716 12.7895 11.0537 12.539L10.9921 11.6755C10.9467 11.0395 11.3869 10.4749 12.0033 10.3784L12.8385 10.2476C13.0817 10.2097 13.2776 10.0233 13.3325 9.77768L13.4848 9.09455C13.5398 8.8489 13.4425 8.59408 13.2393 8.45229L12.5422 7.96404C12.0279 7.60355 11.8708 6.89963 12.1814 6.34659L12.6025 5.59745C12.7249 5.3793 12.7047 5.10616 12.5511 4.9094L12.1241 4.36128C11.9706 4.16451 11.7149 4.08325 11.4795 4.15719L10.6719 4.41016C10.0752 4.59714 9.43903 4.28367 9.20962 3.69035L8.90017 2.88803C8.80937 2.65339 8.58777 2.4994 8.34108 2.5L7.65649 2.50184C7.40979 2.50244 7.1888 2.65766 7.09921 2.89291L6.79751 3.68607C6.57053 4.28307 5.93138 4.59898 5.33284 4.41077L4.49178 4.1468C4.25583 4.07225 3.99897 4.15413 3.84545 4.35212L3.42133 4.90084C3.26781 5.09943 3.2493 5.37319 3.37414 5.59133L3.80483 6.34232C4.12201 6.89591 3.96671 7.60659 3.44941 7.96897L2.76065 8.45169C2.55756 8.59408 2.4602 8.84891 2.51516 9.09393L2.66747 9.77708C2.72184 10.0233 2.91777 10.2097 3.16089 10.2476Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.41432 6.83576C8.63332 6.05481 7.36676 6.05476 6.58575 6.83571C5.8048 7.61672 5.80476 8.88327 6.58571 9.66427C7.36671 10.4452 8.63326 10.4452 9.41426 9.66432C10.1952 8.88332 10.1952 7.61676 9.41432 6.83576Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 450 B

View File

@@ -1,8 +0,0 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.72361 1.05279C7.893 1.13749 8 1.31062 8 1.5V13.5C8 13.6894 7.893 13.8625 7.72361 13.9472C7.55421 14.0319 7.35151 14.0136 7.2 13.9L3.33333 11H1.5C0.671573 11 0 10.3284 0 9.5V5.5C0 4.67158 0.671573 4 1.5 4H3.33333L7.2 1.1C7.35151 0.986371 7.55421 0.968093 7.72361 1.05279ZM7 2.5L3.8 4.9C3.71345 4.96491 3.60819 5 3.5 5H1.5C1.22386 5 1 5.22386 1 5.5V9.5C1 9.77614 1.22386 10 1.5 10H3.5C3.60819 10 3.71345 10.0351 3.8 10.1L7 12.5V2.5ZM14.8536 5.14645C15.0488 5.34171 15.0488 5.65829 14.8536 5.85355L13.2071 7.5L14.8536 9.14645C15.0488 9.34171 15.0488 9.65829 14.8536 9.85355C14.6583 10.0488 14.3417 10.0488 14.1464 9.85355L12.5 8.20711L10.8536 9.85355C10.6583 10.0488 10.3417 10.0488 10.1464 9.85355C9.95118 9.65829 9.95118 9.34171 10.1464 9.14645L11.7929 7.5L10.1464 5.85355C9.95118 5.65829 9.95118 5.34171 10.1464 5.14645C10.3417 4.95118 10.6583 4.95118 10.8536 5.14645L12.5 6.79289L14.1464 5.14645C14.3417 4.95118 14.6583 4.95118 14.8536 5.14645Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 947 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 785 B

View File

@@ -0,0 +1,10 @@
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1957_1318)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 6C7.34315 6 6 7.34315 6 9V75H0V9C0 4.02944 4.02944 0 9 0H89.3787C93.3878 0 95.3955 4.84715 92.5607 7.68198L43.0551 57.1875H57V51H63V58.6875C63 61.1728 60.9853 63.1875 58.5 63.1875H37.0551L26.7426 73.5H73.5V36H79.5V73.5C79.5 76.8137 76.8137 79.5 73.5 79.5H20.7426L10.2426 90H87C88.6569 90 90 88.6569 90 87V21H96V87C96 91.9706 91.9706 96 87 96H6.62132C2.61224 96 0.604504 91.1529 3.43934 88.318L52.7574 39H39V45H33V37.5C33 35.0147 35.0147 33 37.5 33H58.7574L69.2574 22.5H22.5V60H16.5V22.5C16.5 19.1863 19.1863 16.5 22.5 16.5H75.2574L85.7574 6H9Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_1957_1318">
<rect width="96" height="96" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 861 B

View File

@@ -0,0 +1,14 @@
<svg width="93" height="32" viewBox="0 0 93 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.03994 7.04962C8.00934 7.67635 7.30394 8.63219 7.30394 10.0149C7.30394 11.6908 7.72423 12.5893 8.20468 13.0744C8.68379 13.5581 9.40524 13.8149 10.4054 13.8149C11.815 13.8149 13.0291 13.5336 13.8802 12.9464C14.6756 12.3977 15.2708 11.5042 15.3438 9.96182C15.3991 8.79382 15.3678 8.01341 15.0568 7.45711C14.8094 7.01449 14.2326 6.47436 12.4901 6.27416C11.4684 6.15678 10.1114 6.39804 9.03994 7.04962ZM7.8731 5.13084C9.39145 4.2075 11.2531 3.87155 12.7464 4.04312C14.8843 4.28874 16.2844 5.05049 17.0171 6.36142C17.6863 7.55867 17.6384 8.98348 17.587 10.068C17.484 12.2439 16.5804 13.8118 15.1554 14.7949C13.7861 15.7396 12.0582 16.0606 10.4054 16.0606C9.04199 16.0606 7.65126 15.7069 6.60911 14.6547C5.5683 13.6038 5.05823 12.0408 5.05823 10.0149C5.05823 7.6958 6.31388 6.07903 7.8731 5.13084Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.983 18.2811C14.6595 18.2811 15.2079 18.8295 15.2079 19.506V22.16C15.2079 22.8365 14.6595 23.385 13.983 23.385C13.3065 23.385 12.758 22.8365 12.758 22.16V19.506C12.758 18.8295 13.3065 18.2811 13.983 18.2811Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25567 14.8903C5.48362 14.039 5.99219 12.9059 7.20735 12.5361L7.92069 14.8798L7.92376 14.8785C7.92375 14.8785 7.92323 14.8778 7.92069 14.8798C7.91365 14.8852 7.89134 14.9052 7.85879 14.951C7.78817 15.0505 7.70058 15.2311 7.62217 15.524C7.46355 16.1164 7.40615 16.9168 7.40617 17.7813V22.1675C7.43457 22.3571 7.45083 22.438 7.48149 22.5189C7.50698 22.5862 7.55955 22.695 7.72277 22.8617C8.0938 23.2406 8.94056 23.8264 10.9275 24.7081C12.5399 25.4236 13.2749 25.7456 13.9586 25.9166C14.6053 26.0784 15.2382 26.1115 16.6666 26.1115V28.5613C15.2536 28.5613 14.3395 28.5372 13.3641 28.2932C12.452 28.0651 11.5174 27.6502 10.0614 27.004C10.0193 26.9853 9.9768 26.9664 9.93383 26.9474C7.89761 26.0438 6.6867 25.3053 5.97233 24.5757C5.59285 24.1882 5.34579 23.7967 5.19056 23.387C5.05419 23.0271 5.00551 22.6875 4.9751 22.4754C4.97301 22.4608 4.971 22.4468 4.96906 22.4335L4.9563 22.3458V17.7814C4.95628 16.8606 5.01201 15.8003 5.25567 14.8903Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.98306 15.3589C4.62844 14.6695 5.39976 13.9884 5.99652 13.6348L7.24552 15.7424C6.93706 15.9252 6.34938 16.4159 5.77155 17.0332C5.21994 17.6224 4.85695 18.1377 4.73071 18.4254C4.70277 18.6674 4.71195 19.116 4.78977 19.648C4.87834 20.2536 5.01731 20.7033 5.10492 20.8628L5.19261 21.0224L5.23006 21.2007C5.28661 21.4698 5.55952 21.8651 6.30146 22.3907C6.88143 22.8015 7.56502 23.1703 8.29605 23.5648C8.45794 23.6521 8.62215 23.7407 8.78809 23.8313C9.5952 24.2156 10.848 24.7773 12.0191 25.2425C12.613 25.4784 13.1698 25.6831 13.6288 25.8268C13.8584 25.8987 14.0505 25.9512 14.2021 25.9847C14.3503 26.0175 14.4185 26.0227 14.4277 26.0234C14.4288 26.0234 14.4281 26.0234 14.4277 26.0234L14.4287 28.4733C13.9646 28.4733 13.3889 28.3188 12.8968 28.1648C12.3562 27.9955 11.7367 27.7664 11.1147 27.5193C9.86871 27.0244 8.54832 26.4314 7.70298 26.028L7.67249 26.0135L7.64284 25.9973C7.49779 25.918 7.34502 25.8357 7.18702 25.7506C6.4505 25.354 5.60004 24.896 4.88539 24.3898C4.08363 23.8219 3.18153 23.0135 2.87437 21.8785C2.61828 21.3365 2.4557 20.618 2.3657 20.0026C2.26537 19.3167 2.2169 18.4951 2.33888 17.8727L2.35434 17.7938L2.37996 17.7176C2.6522 16.9085 3.36219 16.0221 3.98306 15.3589Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.48529 21.264V17.1402H5.93516V21.264H3.48529Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.2932 7.04962C25.3238 7.67635 26.0292 8.63219 26.0292 10.0149C26.0292 11.6908 25.609 12.5893 25.1285 13.0744C24.6494 13.5581 23.9279 13.8149 22.9278 13.8149C21.5182 13.8149 20.3041 13.5336 19.453 12.9464C18.6576 12.3977 18.0624 11.5042 17.9894 9.96182C17.9341 8.79382 17.9654 8.01341 18.2764 7.45711C18.5238 7.01449 19.1006 6.47436 20.8431 6.27416C21.8648 6.15678 23.2218 6.39804 24.2932 7.04962ZM25.4601 5.13084C23.9417 4.2075 22.0801 3.87155 20.5868 4.04312C18.4489 4.28874 17.0488 5.05049 16.3161 6.36142C15.6469 7.55867 15.6948 8.98348 15.7462 10.068C15.8492 12.2439 16.7528 13.8118 18.1778 14.7949C19.5471 15.7396 21.275 16.0606 22.9278 16.0606C24.2912 16.0606 25.6819 15.7069 26.7241 14.6547C27.7649 13.6038 28.275 12.0408 28.275 10.0149C28.275 7.6958 27.0193 6.07903 25.4601 5.13084Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3502 18.2811C18.6737 18.2811 18.1253 18.8295 18.1253 19.506V22.16C18.1253 22.8365 18.6737 23.385 19.3502 23.385C20.0267 23.385 20.5752 22.8365 20.5752 22.16V19.506C20.5752 18.8295 20.0267 18.2811 19.3502 18.2811Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.0775 14.8903C27.8496 14.039 27.341 12.9059 26.1259 12.5361L25.4125 14.8798L25.4095 14.8785C25.4095 14.8785 25.41 14.8778 25.4125 14.8798C25.4196 14.8852 25.4419 14.9052 25.4744 14.951C25.545 15.0505 25.6326 15.2311 25.711 15.524C25.8697 16.1164 25.9271 16.9168 25.927 17.7813V22.1675C25.8986 22.3571 25.8824 22.438 25.8517 22.5189C25.8262 22.5862 25.7737 22.695 25.6104 22.8617C25.2394 23.2406 24.3927 23.8264 22.4057 24.7081C20.7933 25.4236 20.0583 25.7456 19.3746 25.9166C18.7279 26.0784 18.0949 26.1115 16.6666 26.1115V28.5613C18.0796 28.5613 18.9937 28.5372 19.9691 28.2932C20.8812 28.0651 21.8158 27.6502 23.2718 27.004C23.3139 26.9853 23.3564 26.9664 23.3994 26.9474C25.4356 26.0438 26.6465 25.3053 27.3609 24.5757C27.7404 24.1882 27.9874 23.7967 28.1427 23.387C28.279 23.0271 28.3277 22.6875 28.3581 22.4754C28.3602 22.4608 28.3622 22.4468 28.3642 22.4335L28.3769 22.3458V17.7814C28.3769 16.8606 28.3212 15.8003 28.0775 14.8903Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.3501 15.3589C28.7048 14.6695 27.9334 13.9884 27.3367 13.6348L26.0877 15.7424C26.3961 15.9252 26.9838 16.4159 27.5616 17.0332C28.1133 17.6224 28.4763 18.1377 28.6025 18.4254C28.6304 18.6674 28.6213 19.116 28.5434 19.648C28.4549 20.2536 28.3159 20.7033 28.2283 20.8628L28.1406 21.0224L28.1031 21.2007C28.0466 21.4698 27.7737 21.8651 27.0317 22.3907C26.4518 22.8015 25.7682 23.1703 25.0372 23.5648C24.8753 23.6521 24.711 23.7407 24.5451 23.8313C23.738 24.2156 22.4852 24.7773 21.3141 25.2425C20.7202 25.4784 20.1634 25.6831 19.7044 25.8268C19.4748 25.8987 19.2827 25.9512 19.1311 25.9847C18.9829 26.0175 18.9147 26.0227 18.9055 26.0234C18.9051 26.0234 18.9044 26.0234 18.9055 26.0234L18.9045 28.4733C19.3686 28.4733 19.9443 28.3188 20.4364 28.1648C20.977 27.9955 21.5965 27.7664 22.2185 27.5193C23.4645 27.0244 24.7849 26.4314 25.6302 26.028L25.6607 26.0135L25.6904 25.9973C25.8354 25.918 25.9882 25.8357 26.1462 25.7506C26.8827 25.354 27.7332 24.896 28.4478 24.3898C29.2496 23.8219 30.1517 23.0135 30.4588 21.8785C30.7149 21.3365 30.8775 20.618 30.9675 20.0026C31.0678 19.3167 31.1163 18.4951 30.9943 17.8727L30.9789 17.7938L30.9532 17.7176C30.681 16.9085 29.971 16.0221 29.3501 15.3589Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.8479 21.264V17.1402H27.398V21.264H29.8479Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.6666 11C49.2189 11 49.6666 11.4477 49.6666 12V15H52.6666C53.2189 15 53.6666 15.4477 53.6666 16C53.6666 16.5523 53.2189 17 52.6666 17H49.6666V20C49.6666 20.5523 49.2189 21 48.6666 21C48.1143 21 47.6666 20.5523 47.6666 20V17H44.6666C44.1143 17 43.6666 16.5523 43.6666 16C43.6666 15.4477 44.1143 15 44.6666 15H47.6666V12C47.6666 11.4477 48.1143 11 48.6666 11Z" fill="black" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.1666 4.33329C66.7064 4.33329 66.3333 4.70639 66.3333 5.16663V23.5H64.6666V5.16663C64.6666 3.78591 65.7859 2.66663 67.1666 2.66663H89.494C90.6077 2.66663 91.1654 4.01306 90.3779 4.80051L76.6264 18.552H80.5V16.8333H82.1666V18.9687C82.1666 19.6591 81.607 20.2187 80.9166 20.2187H74.9597L72.0951 23.0833H85.0833V12.6666H86.75V23.0833C86.75 24.0038 86.0038 24.75 85.0833 24.75H70.4285L67.5118 27.6666H88.8333C89.2935 27.6666 89.6666 27.2935 89.6666 26.8333V8.49996H91.3333V26.8333C91.3333 28.214 90.214 29.3333 88.8333 29.3333H66.5059C65.3922 29.3333 64.8345 27.9869 65.622 27.1994L79.3214 13.5H75.5V15.1666H73.8333V13.0833C73.8333 12.3929 74.3929 11.8333 75.0833 11.8333H80.9881L83.9048 8.91663H70.9166V19.3333H69.25V8.91663C69.25 7.99615 69.9962 7.24996 70.9166 7.24996H85.5714L88.4881 4.33329H67.1666Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -245,6 +245,8 @@
"bindings": {
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"ctrl-w": "pane::CloseActiveItem",
"ctrl-f4": "pane::CloseActiveItem",
"alt-ctrl-t": "pane::CloseInactiveItems",

View File

@@ -285,6 +285,8 @@
"cmd-}": "pane::ActivateNextItem",
"alt-cmd-left": "pane::ActivatePrevItem",
"alt-cmd-right": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-t": "pane::CloseInactiveItems",
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",

View File

@@ -916,7 +916,8 @@
},
"openai": {
"version": "1",
"api_url": "https://api.openai.com/v1"
"api_url": "https://api.openai.com/v1",
"low_speed_timeout_in_seconds": 600
}
},
// Zed's Prettier integration settings.

View File

@@ -262,7 +262,7 @@ impl ActivityIndicator {
if !failed.is_empty() {
return Some(Content {
icon: Some(
Icon::new(IconName::ExclamationTriangle)
Icon::new(IconName::Warning)
.size(IconSize::Small)
.into_any_element(),
),
@@ -280,7 +280,7 @@ impl ActivityIndicator {
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
return Some(Content {
icon: Some(
Icon::new(IconName::ExclamationTriangle)
Icon::new(IconName::Warning)
.size(IconSize::Small)
.into_any_element(),
),
@@ -333,7 +333,7 @@ impl ActivityIndicator {
}),
AutoUpdateStatus::Errored => Some(Content {
icon: Some(
Icon::new(IconName::ExclamationTriangle)
Icon::new(IconName::Warning)
.size(IconSize::Small)
.into_any_element(),
),

View File

@@ -8,6 +8,7 @@ use rust_embed::RustEmbed;
#[folder = "../../assets"]
#[include = "fonts/**/*"]
#[include = "icons/**/*"]
#[include = "images/**/*"]
#[include = "themes/**/*"]
#[exclude = "themes/src/*"]
#[include = "sounds/**/*"]

View File

@@ -37,13 +37,13 @@ use language_model::{
pub(crate) use model_selector::*;
pub use prompts::PromptBuilder;
use prompts::PromptLoadingParams;
use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use slash_command::{
context_server_command, default_command, diagnostics_command, docs_command, fetch_command,
file_command, now_command, project_command, prompt_command, search_command, symbols_command,
tab_command, terminal_command, workflow_command,
auto_command, context_server_command, default_command, diagnostics_command, docs_command,
fetch_command, file_command, now_command, project_command, prompt_command, search_command,
symbols_command, tab_command, terminal_command, workflow_command,
};
use std::path::PathBuf;
use std::sync::Arc;
@@ -210,12 +210,13 @@ pub fn init(
let client = client.clone();
async move {
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
let semantic_index = SemanticIndex::new(
let semantic_index = SemanticDb::new(
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
Arc::new(embedding_provider),
&mut cx,
)
.await?;
cx.update(|cx| cx.set_global(semantic_index))
}
})
@@ -364,6 +365,7 @@ fn update_active_language_model_from_settings(cx: &mut AppContext) {
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut AppContext) {
let slash_command_registry = SlashCommandRegistry::global(cx);
slash_command_registry.register_command(file_command::FileSlashCommand, true);
slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true);
slash_command_registry.register_command(tab_command::TabSlashCommand, true);
@@ -382,6 +384,17 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
}
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
cx.observe_flag::<auto_command::AutoSlashCommandFeatureFlag, _>({
let slash_command_registry = slash_command_registry.clone();
move |is_enabled, _cx| {
if is_enabled {
// [#auto-staff-ship] TODO remove this when /auto is no longer staff-shipped
slash_command_registry.register_command(auto_command::AutoCommand, true);
}
}
})
.detach();
update_slash_commands_from_settings(cx);
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
.detach();

View File

@@ -4110,7 +4110,7 @@ impl ContextEditor {
h_flex()
.gap_3()
.child(
Icon::new(IconName::ExclamationTriangle)
Icon::new(IconName::Warning)
.size(IconSize::Small)
.color(Color::Warning),
)
@@ -4723,6 +4723,20 @@ impl Render for ContextEditorToolbarItem {
let weak_self = cx.view().downgrade();
let right_side = h_flex()
.gap_2()
// TODO display this in a nicer way, once we have a design for it.
// .children({
// let project = self
// .workspace
// .upgrade()
// .map(|workspace| workspace.read(cx).project().downgrade());
//
// let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
// project.and_then(|project| db.remaining_summaries(&project, cx))
// });
// scan_items_remaining
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
// })
.child(
ModelSelector::new(
self.fs.clone(),
@@ -5221,7 +5235,7 @@ fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) ->
ButtonLike::new(fold_id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::TextSelect))
.child(Icon::new(IconName::CursorIBeam))
.child(Label::new(title.clone()).single_line())
.on_click(move |_, cx| {
editor
@@ -5325,7 +5339,7 @@ fn render_docs_slash_command_trailer(
div()
.id(("latest-error", row.0))
.child(
Icon::new(IconName::ExclamationTriangle)
Icon::new(IconName::Warning)
.size(IconSize::Small)
.color(Color::Warning),
)

View File

@@ -163,11 +163,13 @@ impl AssistantSettingsContent {
display_name,
max_tokens,
max_output_tokens,
max_completion_tokens: None,
} => Some(open_ai::AvailableModel {
name,
display_name,
max_tokens,
max_output_tokens,
max_completion_tokens: None,
}),
_ => None,
})
@@ -519,6 +521,7 @@ impl Settings for AssistantSettings {
&mut settings.default_model,
value.default_model.map(Into::into),
);
// merge(&mut settings.infer_context, value.infer_context); TODO re-enable this once we ship context inference
}
Ok(settings)

View File

@@ -1478,7 +1478,7 @@ impl Render for PromptEditor {
.child(
ModelSelector::new(
self.fs.clone(),
IconButton::new("context", IconName::SlidersAlt)
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
@@ -2407,7 +2407,7 @@ impl Codegen {
Ok(LanguageModelRequest {
messages,
tools: Vec::new(),
stop: vec!["|END|>".to_string()],
stop: Vec::new(),
temperature: 1.,
})
}

View File

@@ -19,6 +19,7 @@ use std::{
use ui::ActiveTheme;
use workspace::Workspace;
pub mod auto_command;
pub mod context_server_command;
pub mod default_command;
pub mod diagnostics_command;

View File

@@ -0,0 +1,360 @@
use super::create_label_for_command;
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::ArgumentCompletion;
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
use language_model::{
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role,
};
use semantic_index::{FileSummary, SemanticDb};
use smol::channel;
use std::sync::{atomic::AtomicBool, Arc};
use ui::{BorrowAppContext, WindowContext};
use util::ResultExt;
use workspace::Workspace;
pub struct AutoSlashCommandFeatureFlag;
impl FeatureFlag for AutoSlashCommandFeatureFlag {
const NAME: &'static str = "auto-slash-command";
}
pub(crate) struct AutoCommand;
impl SlashCommand for AutoCommand {
fn name(&self) -> String {
"auto".into()
}
fn description(&self) -> String {
"Automatically infer what context to add, based on your prompt".into()
}
fn menu_text(&self) -> String {
"Automatically Infer Context".into()
}
fn label(&self, cx: &AppContext) -> CodeLabel {
create_label_for_command("auto", &["--prompt"], cx)
}
fn complete_argument(
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
// There's no autocomplete for a prompt, since it's arbitrary text.
// However, we can use this opportunity to kick off a drain of the backlog.
// That way, it can hopefully be done resummarizing by the time we've actually
// typed out our prompt. This re-runs on every keystroke during autocomplete,
// but in the future, we could instead do it only once, when /auto is first entered.
let Some(workspace) = workspace.and_then(|ws| ws.upgrade()) else {
log::warn!("workspace was dropped or unavailable during /auto autocomplete");
return Task::ready(Ok(Vec::new()));
};
let project = workspace.read(cx).project().clone();
let Some(project_index) =
cx.update_global(|index: &mut SemanticDb, cx| index.project_index(project, cx))
else {
return Task::ready(Err(anyhow!("No project indexer, cannot use /auto")));
};
let cx: &mut AppContext = cx;
cx.spawn(|cx: gpui::AsyncAppContext| async move {
let task = project_index.read_with(&cx, |project_index, cx| {
project_index.flush_summary_backlogs(cx)
})?;
cx.background_executor().spawn(task).await;
anyhow::Ok(Vec::new())
})
}
fn requires_argument(&self) -> bool {
true
}
fn run(
self: Arc<Self>,
arguments: &[String],
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
};
if arguments.is_empty() {
return Task::ready(Err(anyhow!("missing prompt")));
};
let argument = arguments.join(" ");
let original_prompt = argument.to_string();
let project = workspace.read(cx).project().clone();
let Some(project_index) =
cx.update_global(|index: &mut SemanticDb, cx| index.project_index(project, cx))
else {
return Task::ready(Err(anyhow!("no project indexer")));
};
let task = cx.spawn(|cx: gpui::AsyncWindowContext| async move {
let summaries = project_index
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
.await?;
commands_for_summaries(&summaries, &original_prompt, &cx).await
});
// As a convenience, append /auto's argument to the end of the prompt
// so you don't have to write it again.
let original_prompt = argument.to_string();
cx.background_executor().spawn(async move {
let commands = task.await?;
let mut prompt = String::new();
log::info!(
"Translating this response into slash-commands: {:?}",
commands
);
for command in commands {
prompt.push('/');
prompt.push_str(&command.name);
prompt.push(' ');
prompt.push_str(&command.arg);
prompt.push('\n');
}
prompt.push('\n');
prompt.push_str(&original_prompt);
Ok(SlashCommandOutput {
text: prompt,
sections: Vec::new(),
run_commands_in_text: true,
})
})
}
}
const PROMPT_INSTRUCTIONS_BEFORE_SUMMARY: &str = include_str!("prompt_before_summary.txt");
const PROMPT_INSTRUCTIONS_AFTER_SUMMARY: &str = include_str!("prompt_after_summary.txt");
fn summaries_prompt(summaries: &[FileSummary], original_prompt: &str) -> String {
let json_summaries = serde_json::to_string(summaries).unwrap();
format!("{PROMPT_INSTRUCTIONS_BEFORE_SUMMARY}\n{json_summaries}\n{PROMPT_INSTRUCTIONS_AFTER_SUMMARY}\n{original_prompt}")
}
/// The slash commands that the model is told about, and which we look for in the inference response.
const SUPPORTED_SLASH_COMMANDS: &[&str] = &["search", "file"];
#[derive(Debug, Clone)]
struct CommandToRun {
name: String,
arg: String,
}
/// Given the pre-indexed file summaries for this project, as well as the original prompt
/// string passed to `/auto`, get a list of slash commands to run, along with their arguments.
///
/// The prompt's output does not include the slashes (to reduce the chance that it makes a mistake),
/// so taking one of these returned Strings and turning it into a real slash-command-with-argument
/// involves prepending a slash to it.
///
/// This function will validate that each of the returned lines begins with one of SUPPORTED_SLASH_COMMANDS.
/// Any other lines it encounters will be discarded, with a warning logged.
async fn commands_for_summaries(
summaries: &[FileSummary],
original_prompt: &str,
cx: &AsyncAppContext,
) -> Result<Vec<CommandToRun>> {
if summaries.is_empty() {
log::warn!("Inferring no context because there were no summaries available.");
return Ok(Vec::new());
}
// Use the globally configured model to translate the summaries into slash-commands,
// because Qwen2-7B-Instruct has not done a good job at that task.
let Some(model) = cx.update(|cx| LanguageModelRegistry::read_global(cx).active_model())? else {
log::warn!("Can't infer context because there's no active model.");
return Ok(Vec::new());
};
// Only go up to 90% of the actual max token count, to reduce chances of
// exceeding the token count due to inaccuracies in the token counting heuristic.
let max_token_count = (model.max_token_count() * 9) / 10;
// Rather than recursing (which would require this async function use a pinned box),
// we use an explicit stack of arguments and answers for when we need to "recurse."
let mut stack = vec![summaries];
let mut final_response = Vec::new();
let mut prompts = Vec::new();
// TODO We only need to create multiple Requests because we currently
// don't have the ability to tell if a CompletionProvider::complete response
// was a "too many tokens in this request" error. If we had that, then
// we could try the request once, instead of having to make separate requests
// to check the token count and then afterwards to run the actual prompt.
let make_request = |prompt: String| LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
role: Role::User,
content: vec![prompt.into()],
// Nothing in here will benefit from caching
cache: false,
}],
tools: Vec::new(),
stop: Vec::new(),
temperature: 1.0,
};
while let Some(current_summaries) = stack.pop() {
// The split can result in one slice being empty and the other having one element.
// Whenever that happens, skip the empty one.
if current_summaries.is_empty() {
continue;
}
log::info!(
"Inferring prompt context using {} file summaries",
current_summaries.len()
);
let prompt = summaries_prompt(&current_summaries, original_prompt);
let start = std::time::Instant::now();
// Per OpenAI, 1 token ~= 4 chars in English (we go with 4.5 to overestimate a bit, because failed API requests cost a lot of perf)
// Verifying this against an actual model.count_tokens() confirms that it's usually within ~5% of the correct answer, whereas
// getting the correct answer from tiktoken takes hundreds of milliseconds (compared to this arithmetic being ~free).
// source: https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
let token_estimate = prompt.len() * 2 / 9;
let duration = start.elapsed();
log::info!(
"Time taken to count tokens for prompt of length {:?}B: {:?}",
prompt.len(),
duration
);
if token_estimate < max_token_count {
prompts.push(prompt);
} else if current_summaries.len() == 1 {
log::warn!("Inferring context for a single file's summary failed because the prompt's token length exceeded the model's token limit.");
} else {
log::info!(
"Context inference using file summaries resulted in a prompt containing {token_estimate} tokens, which exceeded the model's max of {max_token_count}. Retrying as two separate prompts, each including half the number of summaries.",
);
let (left, right) = current_summaries.split_at(current_summaries.len() / 2);
stack.push(right);
stack.push(left);
}
}
let all_start = std::time::Instant::now();
let (tx, rx) = channel::bounded(1024);
let completion_streams = prompts
.into_iter()
.map(|prompt| {
let request = make_request(prompt.clone());
let model = model.clone();
let tx = tx.clone();
let stream = model.stream_completion(request, &cx);
(stream, tx)
})
.collect::<Vec<_>>();
cx.background_executor()
.spawn(async move {
let futures = completion_streams
.into_iter()
.enumerate()
.map(|(ix, (stream, tx))| async move {
let start = std::time::Instant::now();
let events = stream.await?;
log::info!("Time taken for awaiting /await chunk stream #{ix}: {:?}", start.elapsed());
let completion: String = events
.filter_map(|event| async {
if let Ok(LanguageModelCompletionEvent::Text(text)) = event {
Some(text)
} else {
None
}
})
.collect()
.await;
log::info!("Time taken for all /auto chunks to come back for #{ix}: {:?}", start.elapsed());
for line in completion.split('\n') {
if let Some(first_space) = line.find(' ') {
let command = &line[..first_space].trim();
let arg = &line[first_space..].trim();
tx.send(CommandToRun {
name: command.to_string(),
arg: arg.to_string(),
})
.await?;
} else if !line.trim().is_empty() {
// All slash-commands currently supported in context inference need a space for the argument.
log::warn!(
"Context inference returned a non-blank line that contained no spaces (meaning no argument for the slash command): {:?}",
line
);
}
}
anyhow::Ok(())
})
.collect::<Vec<_>>();
let _ = futures::future::try_join_all(futures).await.log_err();
let duration = all_start.elapsed();
eprintln!("All futures completed in {:?}", duration);
})
.await;
drop(tx); // Close the channel so that rx.collect() won't hang. This is safe because all futures have completed.
let results = rx.collect::<Vec<_>>().await;
eprintln!(
"Finished collecting from the channel with {} results",
results.len()
);
for command in results {
// Don't return empty or duplicate commands
if !command.name.is_empty()
&& !final_response
.iter()
.any(|cmd: &CommandToRun| cmd.name == command.name && cmd.arg == command.arg)
{
if SUPPORTED_SLASH_COMMANDS
.iter()
.any(|supported| &command.name == supported)
{
final_response.push(command);
} else {
log::warn!(
"Context inference returned an unrecognized slash command: {:?}",
command
);
}
}
}
// Sort the commands by name (reversed just so that /search appears before /file)
final_response.sort_by(|cmd1, cmd2| cmd1.name.cmp(&cmd2.name).reverse());
Ok(final_response)
}

View File

@@ -193,11 +193,11 @@ impl SlashCommand for DiagnosticsSlashCommand {
.map(|(range, placeholder_type)| SlashCommandOutputSection {
range,
icon: match placeholder_type {
PlaceholderType::Root(_, _) => IconName::ExclamationTriangle,
PlaceholderType::Root(_, _) => IconName::Warning,
PlaceholderType::File(_) => IconName::File,
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => IconName::XCircle,
PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
IconName::ExclamationTriangle
IconName::Warning
}
},
label: match placeholder_type {

View File

@@ -0,0 +1,24 @@
Actions have a cost, so only include actions that you think
will be helpful to you in doing a great job answering the
prompt in the future.
You must respond ONLY with a list of actions you would like to
perform. Each action should be on its own line, and followed by a space and then its parameter.
Actions can be performed more than once with different parameters.
Here is an example valid response:
```
file path/to/my/file.txt
file path/to/another/file.txt
search something to search for
search something else to search for
```
Once again, do not forget: you must respond ONLY in the format of
one action per line, and the action name should be followed by
its parameter. Your response must not include anything other
than a list of actions, with one action per line, in this format.
It is extremely important that you do not deviate from this format even slightly!
This is the end of my instructions for how to respond. The rest is the prompt:

View File

@@ -0,0 +1,31 @@
I'm going to give you a prompt. I don't want you to respond
to the prompt itself. I want you to figure out which of the following
actions on my project, if any, would help you answer the prompt.
Here are the actions:
## file
This action's parameter is a file path to one of the files
in the project. If you ask for this action, I will tell you
the full contents of the file, so you can learn all the
details of the file.
## search
This action's parameter is a string to do a semantic search for
across the files in the project. (You will have a JSON summary
of all the files in the project.) It will tell you which files this string
(or similar strings; it is a semantic search) appear in,
as well as some context of the lines surrounding each result.
It's very important that you only use this action when you think
that searching across the specific files in this project for the query
in question will be useful. For example, don't use this command to search
for queries you might put into a general Web search engine, because those
will be too general to give useful results in this project-specific search.
---
That was the end of the list of actions.
Here is a JSON summary of each of the files in my project:

View File

@@ -8,7 +8,7 @@ use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
use semantic_index::SemanticIndex;
use semantic_index::SemanticDb;
use std::{
fmt::Write,
path::PathBuf,
@@ -92,8 +92,11 @@ impl SlashCommand for SearchSlashCommand {
let project = workspace.read(cx).project().clone();
let fs = project.read(cx).fs().clone();
let project_index =
cx.update_global(|index: &mut SemanticIndex, cx| index.project_index(project, cx));
let Some(project_index) =
cx.update_global(|index: &mut SemanticDb, cx| index.project_index(project, cx))
else {
return Task::ready(Err(anyhow::anyhow!("no project indexer")));
};
cx.spawn(|cx| async move {
let results = project_index

View File

@@ -585,7 +585,7 @@ impl Render for PromptEditor {
.gap_2()
.child(ModelSelector::new(
self.fs.clone(),
IconButton::new("context", IconName::SlidersAlt)
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)

View File

@@ -149,16 +149,16 @@ spec:
secretKeyRef:
name: google-ai
key: api_key
- name: QWEN2_7B_API_KEY
- name: RUNPOD_API_KEY
valueFrom:
secretKeyRef:
name: hugging-face
name: runpod
key: api_key
- name: QWEN2_7B_API_URL
- name: RUNPOD_API_SUMMARY_URL
valueFrom:
secretKeyRef:
name: hugging-face
key: qwen2_api_url
name: runpod
key: summary
- name: BLOB_STORE_ACCESS_KEY
valueFrom:
secretKeyRef:

View File

@@ -728,6 +728,11 @@ impl Database {
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
// This is only used in the summarization backlog, so if it's None,
// that just means we won't be able to detect when to resummarize
// based on total number of backlogged bytes - instead, we'd go
// on number of files only. That shouldn't be a huge deal in practice.
size: None,
is_fifo: db_entry.is_fifo,
});
}

View File

@@ -663,6 +663,11 @@ impl Database {
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
// This is only used in the summarization backlog, so if it's None,
// that just means we won't be able to detect when to resummarize
// based on total number of backlogged bytes - instead, we'd go
// on number of files only. That shouldn't be a huge deal in practice.
size: None,
is_fifo: db_entry.is_fifo,
});
}

View File

@@ -170,8 +170,8 @@ pub struct Config {
pub anthropic_api_key: Option<Arc<str>>,
pub anthropic_staff_api_key: Option<Arc<str>>,
pub llm_closed_beta_model_name: Option<Arc<str>>,
pub qwen2_7b_api_key: Option<Arc<str>>,
pub qwen2_7b_api_url: Option<Arc<str>>,
pub runpod_api_key: Option<Arc<str>>,
pub runpod_api_summary_url: Option<Arc<str>>,
pub zed_client_checksum_seed: Option<String>,
pub slack_panics_webhook: Option<String>,
pub auto_join_channel_id: Option<ChannelId>,
@@ -235,8 +235,8 @@ impl Config {
stripe_api_key: None,
stripe_price_id: None,
supermaven_admin_api_key: None,
qwen2_7b_api_key: None,
qwen2_7b_api_url: None,
runpod_api_key: None,
runpod_api_summary_url: None,
user_backfiller_github_access_token: None,
}
}

View File

@@ -402,12 +402,12 @@ async fn perform_completion(
LanguageModelProvider::Zed => {
let api_key = state
.config
.qwen2_7b_api_key
.runpod_api_key
.as_ref()
.context("no Qwen2-7B API key configured on the server")?;
let api_url = state
.config
.qwen2_7b_api_url
.runpod_api_summary_url
.as_ref()
.context("no Qwen2-7B URL configured on the server")?;
let chunks = open_ai::stream_completion(

View File

@@ -1,5 +1,5 @@
use super::*;
use sea_orm::QueryOrder;
use sea_orm::{sea_query::OnConflict, QueryOrder};
use std::str::FromStr;
use strum::IntoEnumIterator as _;
@@ -99,6 +99,17 @@ impl LlmDatabase {
..Default::default()
}
}))
.on_conflict(
OnConflict::columns([model::Column::ProviderId, model::Column::Name])
.update_columns([
model::Column::MaxRequestsPerMinute,
model::Column::MaxTokensPerMinute,
model::Column::MaxTokensPerDay,
model::Column::PricePerMillionInputTokens,
model::Column::PricePerMillionOutputTokens,
])
.to_owned(),
)
.exec_without_returning(&*tx)
.await?;
Ok(())

View File

@@ -40,6 +40,15 @@ pub async fn seed_database(_config: &Config, db: &mut LlmDatabase, _force: bool)
price_per_million_input_tokens: 25, // $0.25/MTok
price_per_million_output_tokens: 125, // $1.25/MTok
},
ModelParams {
provider: LanguageModelProvider::Zed,
name: "Qwen/Qwen2-7B-Instruct".into(),
max_requests_per_minute: 5,
max_tokens_per_minute: 25_000, // These are arbitrary limits we've set to cap costs; we control this number
max_tokens_per_day: 300_000,
price_per_million_input_tokens: 25,
price_per_million_output_tokens: 125,
},
])
.await
}

View File

@@ -679,8 +679,8 @@ impl TestServer {
stripe_api_key: None,
stripe_price_id: None,
supermaven_admin_api_key: None,
qwen2_7b_api_key: None,
qwen2_7b_api_url: None,
runpod_api_key: None,
runpod_api_summary_url: None,
user_backfiller_github_access_token: None,
},
})

View File

@@ -2831,7 +2831,7 @@ impl Panel for CollabPanel {
fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::IconName> {
CollaborationPanelSettings::get_global(cx)
.button
.then_some(ui::IconName::Collab)
.then_some(ui::IconName::UserGroup)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {

View File

@@ -210,10 +210,9 @@ impl CommandPaletteDelegate {
positions,
}) = intercept_result
{
if let Some(idx) = matches
.iter()
.position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
{
if let Some(idx) = matches.iter().position(|m| {
commands[m.candidate_id].action.action_type_id() == action.action_type_id()
}) {
matches.remove(idx);
}
commands.push(Command {

View File

@@ -2,11 +2,9 @@
#![deny(missing_docs)]
use std::any::TypeId;
use collections::HashSet;
use derive_more::{Deref, DerefMut};
use gpui::{Action, AppContext, BorrowAppContext, Global};
use gpui::{Action, ActionTypeId, AppContext, BorrowAppContext, Global};
/// Initializes the command palette hooks.
pub fn init(cx: &mut AppContext) {
@@ -18,7 +16,7 @@ pub fn init(cx: &mut AppContext) {
#[derive(Default)]
pub struct CommandPaletteFilter {
hidden_namespaces: HashSet<&'static str>,
hidden_action_types: HashSet<TypeId>,
hidden_action_types: HashSet<ActionTypeId>,
}
#[derive(Deref, DerefMut, Default)]
@@ -52,7 +50,7 @@ impl CommandPaletteFilter {
let namespace = name.split("::").next().unwrap_or("malformed action name");
self.hidden_namespaces.contains(namespace)
|| self.hidden_action_types.contains(&action.type_id())
|| self.hidden_action_types.contains(&action.action_type_id())
}
/// Hides all actions in the given namespace.
@@ -66,12 +64,13 @@ impl CommandPaletteFilter {
}
/// Hides all actions with the given types.
pub fn hide_action_types(&mut self, action_types: &[TypeId]) {
self.hidden_action_types.extend(action_types);
pub fn hide_action_types(&mut self, action_types: &[ActionTypeId]) {
self.hidden_action_types
.extend(action_types.iter().cloned());
}
/// Shows all actions with the given types.
pub fn show_action_types<'a>(&mut self, action_types: impl Iterator<Item = &'a TypeId>) {
pub fn show_action_types<'a>(&mut self, action_types: impl Iterator<Item = &'a ActionTypeId>) {
for action_type in action_types {
self.hidden_action_types.remove(action_type);
}

View File

@@ -11,8 +11,8 @@ use collections::{HashMap, HashSet};
use command_palette_hooks::CommandPaletteFilter;
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
use gpui::{
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
ModelContext, Task, WeakModel,
actions, ActionTypeId, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter,
Global, Model, ModelContext, Task, WeakModel,
};
use http_client::github::latest_github_release;
use http_client::HttpClient;
@@ -69,13 +69,13 @@ pub fn init(
Copilot::set_global(copilot.clone(), cx);
cx.observe(&copilot, |handle, cx| {
let copilot_action_types = [
TypeId::of::<Suggest>(),
TypeId::of::<NextSuggestion>(),
TypeId::of::<PreviousSuggestion>(),
TypeId::of::<Reinstall>(),
ActionTypeId::of::<Suggest>(),
ActionTypeId::of::<NextSuggestion>(),
ActionTypeId::of::<PreviousSuggestion>(),
ActionTypeId::of::<Reinstall>(),
];
let copilot_auth_action_types = [TypeId::of::<SignOut>()];
let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
let copilot_auth_action_types = [ActionTypeId::of::<SignOut>()];
let copilot_no_auth_action_types = [ActionTypeId::of::<SignIn>()];
let status = handle.read(cx).status();
let filter = CommandPaletteFilter::global_mut(cx);

View File

@@ -1,10 +1,10 @@
use crate::{request::PromptUserDeviceFlow, Copilot, Status};
use gpui::{
div, svg, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle,
div, AppContext, ClipboardItem, DismissEvent, Element, EventEmitter, FocusHandle,
FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, ParentElement, Render,
Styled, Subscription, ViewContext,
};
use ui::{prelude::*, Button, IconName, Label};
use ui::{prelude::*, Button, Label, Vector, VectorName};
use workspace::ModalView;
const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
@@ -198,12 +198,8 @@ impl Render for CopilotCodeVerification {
cx.focus(&this.focus_handle);
}))
.child(
svg()
.w_32()
.h_16()
.flex_none()
.path(IconName::ZedXCopilot.path())
.text_color(cx.theme().colors().icon),
Vector::new(VectorName::ZedXCopilot, rems(8.), rems(4.))
.color(Color::Custom(cx.theme().colors().icon)),
)
.child(prompt)
}

View File

@@ -667,7 +667,7 @@ impl Item for ProjectDiagnosticsEditor {
then.child(
h_flex()
.gap_1()
.child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
.child(Icon::new(IconName::Warning).color(Color::Warning))
.child(
Label::new(self.summary.warning_count.to_string())
.color(params.text_color()),
@@ -804,7 +804,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
icon.path(IconName::XCircle.path())
.text_color(Color::Error.color(cx))
} else {
icon.path(IconName::ExclamationTriangle.path())
icon.path(IconName::Warning.path())
.text_color(Color::Warning.color(cx))
}
}),

View File

@@ -30,7 +30,7 @@ impl Render for DiagnosticIndicator {
(0, warning_count) => h_flex()
.gap_1()
.child(
Icon::new(IconName::ExclamationTriangle)
Icon::new(IconName::Warning)
.size(IconSize::Small)
.color(Color::Warning),
)
@@ -52,7 +52,7 @@ impl Render for DiagnosticIndicator {
)
.child(Label::new(error_count.to_string()).size(LabelSize::Small))
.child(
Icon::new(IconName::ExclamationTriangle)
Icon::new(IconName::Warning)
.size(IconSize::Small)
.color(Color::Warning),
)

View File

@@ -50,7 +50,7 @@ impl Render for ToolbarControls {
)
})
.child(
IconButton::new("toggle-warnings", IconName::ExclamationTriangle)
IconButton::new("toggle-warnings", IconName::Warning)
.tooltip(move |cx| Tooltip::text(tooltip, cx))
.on_click(cx.listener(|this, _, cx| {
if let Some(editor) = this.editor() {

View File

@@ -35,7 +35,7 @@ chrono.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
convert_case = "0.6.0"
convert_case.workspace = true
db.workspace = true
emojis.workspace = true
file_icons.workspace = true

View File

@@ -72,14 +72,14 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use git::blame::GitBlame;
use git::diff_hunk_to_display;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler,
VisualContext, WeakFocusHandle, WeakView, WindowContext,
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, ActionTypeId,
AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds,
ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter,
FocusHandle, FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla,
InteractiveText, KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement,
Pixels, Render, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task,
TextStyle, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext,
ViewInputHandler, VisualContext, WeakFocusHandle, WeakView, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -128,6 +128,7 @@ use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
use smallvec::SmallVec;
use snippet::Snippet;
use std::any::Any;
use std::{
any::TypeId,
borrow::Cow,
@@ -12051,10 +12052,12 @@ impl Editor {
cx.notify();
}
pub fn register_action<A: Action>(
pub fn register_runtime_action(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
action_type_id: ActionTypeId,
listener: impl Fn(&dyn Any, &mut WindowContext) + 'static,
) -> Subscription {
dbg!("register runtime action", action_type_id.clone());
let id = self.next_editor_action_id.post_inc();
let listener = Arc::new(listener);
self.editor_actions.borrow_mut().insert(
@@ -12062,8 +12065,8 @@ impl Editor {
Box::new(move |cx| {
let cx = cx.window_context();
let listener = listener.clone();
cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
cx.on_action(action_type_id.clone(), move |action, phase, cx| {
eprintln!("Action fired; phase is {phase:?}");
if phase == DispatchPhase::Bubble {
listener(action, cx)
}
@@ -12077,6 +12080,16 @@ impl Editor {
})
}
pub fn register_action<A: Action>(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
) -> Subscription {
self.register_runtime_action(ActionTypeId::of::<A>(), move |action, cx| {
let action = action.downcast_ref().unwrap();
listener(action, cx)
})
}
pub fn file_header_size(&self) -> u32 {
self.file_header_size
}

View File

@@ -29,7 +29,6 @@ use crate::{
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap};
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
use gpui::Subscription;
use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
@@ -40,6 +39,7 @@ use gpui::{
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
ViewContext, WeakView, WindowContext,
};
use gpui::{ActionTypeId, Subscription};
use itertools::Itertools;
use language::{
language_settings::{
@@ -6258,7 +6258,7 @@ pub fn register_action<T: Action>(
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
) {
let view = view.clone();
cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
cx.on_action(ActionTypeId::of::<T>(), move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
view.update(cx, |editor, cx| {

View File

@@ -713,17 +713,42 @@ pub(crate) async fn find_file(
cx: &mut AsyncWindowContext,
) -> Option<(Range<text::Anchor>, ResolvedPath)> {
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()).ok()?;
let scope = snapshot.language_scope_at(position);
let (range, candidate_file_path) = surrounding_filename(snapshot, position)?;
let existing_path = project
.update(cx, |project, cx| {
project.resolve_existing_file_path(&candidate_file_path, buffer, cx)
})
.ok()?
.await?;
async fn check_path(
candidate_file_path: &str,
project: &Model<Project>,
buffer: &Model<language::Buffer>,
cx: &mut AsyncWindowContext,
) -> Option<ResolvedPath> {
project
.update(cx, |project, cx| {
project.resolve_existing_file_path(&candidate_file_path, buffer, cx)
})
.ok()?
.await
}
Some((range, existing_path))
if let Some(existing_path) = check_path(&candidate_file_path, &project, buffer, cx).await {
return Some((range, existing_path));
}
if let Some(scope) = scope {
for suffix in scope.path_suffixes() {
if candidate_file_path.ends_with(format!(".{suffix}").as_str()) {
continue;
}
let suffixed_candidate = format!("{candidate_file_path}.{suffix}");
if let Some(existing_path) = check_path(&suffixed_candidate, &project, buffer, cx).await
{
return Some((range, existing_path));
}
}
}
None
}
fn surrounding_filename(
@@ -1490,7 +1515,8 @@ mod tests {
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.ˇ
Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file.ˇ
"});
// File does not exist
@@ -1499,6 +1525,7 @@ mod tests {
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
// No highlight
@@ -1517,6 +1544,7 @@ mod tests {
Go to fˇile2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
@@ -1525,6 +1553,7 @@ mod tests {
Go to «file2.rsˇ» if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file.
"});
// Moving the mouse over a relative path that does exist should highlight it
@@ -1533,6 +1562,7 @@ mod tests {
Go to file2.rs if you want.
Or go to ../dir/fˇile2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
@@ -1541,6 +1571,7 @@ mod tests {
Go to file2.rs if you want.
Or go to «../dir/file2.rsˇ» if you want.
Or go to /root/dir/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file.
"});
// Moving the mouse over an absolute path that does exist should highlight it
@@ -1549,6 +1580,7 @@ mod tests {
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/diˇr/file2.rs if project is local.
Or go to /root/dir/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
@@ -1557,6 +1589,25 @@ mod tests {
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to «/root/dir/file2.rsˇ» if project is local.
Or go to /root/dir/file2 if this is a Rust file.
"});
// Moving the mouse over a path that exists, if we add the language-specific suffix, it should highlight it
let screen_coord = cx.pixel_position(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
Or go to /root/diˇr/file2 if this is a Rust file.
"});
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
You can't go to a file that does_not_exist.txt.
Go to file2.rs if you want.
Or go to ../dir/file2.rs if you want.
Or go to /root/dir/file2.rs if project is local.
Or go to «/root/dir/file2ˇ» if this is a Rust file.
"});
cx.simulate_click(screen_coord, Modifiers::secondary_key());

View File

@@ -24,6 +24,7 @@ async-trait.workspace = true
client.workspace = true
collections.workspace = true
fs.workspace = true
editor.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
@@ -32,7 +33,9 @@ isahc.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
multi_buffer.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
paths.workspace = true
project.workspace = true
release_channel.workspace = true

View File

@@ -80,6 +80,8 @@ pub struct ExtensionManifest {
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
#[serde(default)]
pub snippets: Option<PathBuf>,
#[serde(default)]
pub editor_actions: BTreeMap<Arc<str>, EditorActionManifestEntry>,
}
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -116,6 +118,9 @@ pub struct LanguageServerManifestEntry {
pub code_action_kinds: Option<Vec<lsp::CodeActionKind>>,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct ActionManifestEntry {}
impl LanguageServerManifestEntry {
/// Returns the list of languages for the language server.
///
@@ -140,6 +145,11 @@ pub struct SlashCommandManifestEntry {
pub requires_argument: bool,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct EditorActionManifestEntry {
pub name: String, // <extension_id>:: "Name"
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct IndexedDocsProviderEntry {}
@@ -187,6 +197,7 @@ fn manifest_from_old_manifest(
authors: manifest_json.authors,
schema_version: SchemaVersion::ZERO,
lib: Default::default(),
editor_actions: Default::default(),
themes: {
let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
themes.sort();

View File

@@ -18,7 +18,8 @@ use assistant_slash_command::SlashCommandRegistry;
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
use collections::{btree_map, BTreeMap, HashSet};
use collections::{btree_map, BTreeMap, HashMap, HashSet};
use editor::Editor;
use extension_builder::{CompileExtensionOptions, ExtensionBuilder};
use fs::{Fs, RemoveOptions};
use futures::{
@@ -30,8 +31,9 @@ use futures::{
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
};
use gpui::{
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
WeakModel,
actions, Action, ActionTypeId, AnyWindowHandle, AppContext, AsyncAppContext,
BackgroundExecutor, Context, EventEmitter, Global, Model, ModelContext, Subscription, Task,
WeakModel, WeakView, WindowHandle,
};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use indexed_docs::{IndexedDocsRegistry, ProviderId};
@@ -48,6 +50,7 @@ use settings::Settings;
use snippet_provider::SnippetRegistry;
use std::ops::RangeInclusive;
use std::str::FromStr;
use std::sync::LazyLock;
use std::{
cmp::Ordering,
path::{self, Path, PathBuf},
@@ -55,6 +58,7 @@ use std::{
time::{Duration, Instant},
};
use theme::{ThemeRegistry, ThemeSettings};
use ui::SharedString;
use url::Url;
use util::{maybe, ResultExt};
use wasm_host::{
@@ -102,6 +106,143 @@ pub fn is_version_compatible(
true
}
#[derive(Clone, PartialEq, Eq)]
pub struct ExtensionAction {
name: Arc<str>,
action_type_id: ActionTypeId,
}
static NAME_TO_TYPE_ID: LazyLock<Arc<parking_lot::Mutex<HashMap<String, ActionTypeId>>>> =
LazyLock::new(|| Default::default());
impl gpui::Action for ExtensionAction {
fn boxed_clone(&self) -> Box<dyn gpui::Action> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn std::any::Any {
self as _
}
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
let Some(other) = action.as_any().downcast_ref::<ExtensionAction>() else {
return false;
};
other == self
}
fn name(&self) -> &str {
&self.name
}
fn debug_name() -> &'static str
where
Self: Sized,
{
"extension::ActionDebugName"
}
fn action_type_id(&self) -> gpui::ActionTypeId {
self.action_type_id.clone()
}
fn build(name: &str, value: serde_json::Value) -> Result<Box<dyn gpui::Action>>
where
Self: Sized,
{
let Some(action_type_id) = NAME_TO_TYPE_ID.lock().get(name).cloned() else {
anyhow::bail!("lol")
};
Ok(Box::new(Self {
name: name.into(),
action_type_id,
}))
}
}
pub struct EditorActionRegistry {
executor: BackgroundExecutor,
wasm_host: Arc<WasmHost>,
editors: Vec<(AnyWindowHandle, WeakView<Editor>)>,
actions: Vec<String>,
}
impl EditorActionRegistry {
fn new(wasm_host: Arc<WasmHost>, cx: &mut AppContext) -> Self {
EditorActionRegistry {
executor: cx.background_executor().clone(),
wasm_host: wasm_host.clone(),
editors: Default::default(),
actions: Default::default(),
}
}
fn add_editor(
&mut self,
editor: &mut Editor,
handle: WeakView<Editor>,
window: AnyWindowHandle,
cx: &mut AppContext,
) {
self.editors.push((window, handle));
dbg!(&self.actions);
for action in self.actions.iter() {
let Some(type_id) = NAME_TO_TYPE_ID.lock().get(action).cloned() else {
panic!("yikes");
};
dbg!("Registering runtime action");
editor
.register_runtime_action(type_id.clone(), |any, cx| {
dbg!("oh wow!");
})
.detach();
}
}
fn add_actions(&mut self, actions: Vec<String>) {
self.actions.extend(actions.clone());
for action in actions.into_iter() {
let editors = self.editors.clone();
let task = self.wasm_host.on_main_thread(|cx| {
{
async move {
cx.update(|cx| {
let type_id = NAME_TO_TYPE_ID
.lock()
.entry(action.clone())
.or_insert_with(|| {
cx.register_action_type(action.into(), ExtensionAction::build)
})
.clone();
dbg!(&type_id, &editors.len());
for (window, editor) in editors {
window
.update(cx, |_, cx| {
editor.update(cx, |editor, cx| {
editor
.register_runtime_action(
type_id.clone(),
|any, cx| {
dbg!("oh wow!");
},
)
.detach()
})
})
.log_err();
}
})
.log_err()
}
}
.boxed_local()
});
self.executor.spawn(task).detach();
}
}
}
pub struct ExtensionStore {
builder: Arc<ExtensionBuilder>,
extension_index: ExtensionIndex,
@@ -114,6 +255,7 @@ pub struct ExtensionStore {
outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
index_path: PathBuf,
language_registry: Arc<LanguageRegistry>,
editor_actions_registry: EditorActionRegistry,
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
@@ -239,11 +381,37 @@ impl ExtensionStore {
let installed_dir = extensions_dir.join("installed");
let index_path = extensions_dir.join("index.json");
let handle = cx.handle();
cx.observe_new_views(move |editor: &mut Editor, cx| {
let editor_handle = cx.view().downgrade();
let window = cx.window_handle();
handle.update(cx, |extension_store, cx| {
dbg!("registering!");
extension_store.editor_actions_registry.add_editor(
editor,
editor_handle,
window,
cx,
)
})
})
.detach();
let wasm_host = WasmHost::new(
fs.clone(),
http_client.clone(),
node_runtime,
language_registry.clone(),
work_dir,
cx,
);
let (reload_tx, mut reload_rx) = unbounded();
let mut this = Self {
extension_index: Default::default(),
installed_dir,
index_path,
editor_actions_registry: EditorActionRegistry::new(wasm_host.clone(), cx),
builder: Arc::new(ExtensionBuilder::new(
// Construct a real HTTP client for the extension builder, as we
// don't want to use a fake one in the tests.
@@ -253,15 +421,8 @@ impl ExtensionStore {
outstanding_operations: Default::default(),
modified_extensions: Default::default(),
reload_complete_senders: Vec::new(),
wasm_host: WasmHost::new(
fs.clone(),
http_client.clone(),
node_runtime,
language_registry.clone(),
work_dir,
cx,
),
wasm_extensions: Vec::new(),
wasm_host,
fs,
http_client,
telemetry,
@@ -939,7 +1100,7 @@ impl ExtensionStore {
///
/// First, this unloads any themes, languages, or grammars that are
/// no longer in the manifest, or whose files have changed on disk.
/// Then it loads any themes, languages, or grammars that are newly
/// Then it loads any themes, languages, edits, or grammars that are newly
/// added to the manifest, or whose files have changed on disk.
fn extensions_updated(
&mut self,
@@ -1064,6 +1225,7 @@ impl ExtensionStore {
let mut grammars_to_add = Vec::new();
let mut themes_to_add = Vec::new();
let mut snippets_to_add = Vec::new();
let mut actions_to_add = Vec::new();
for extension_id in &extensions_to_load {
let Some(extension) = new_index.extensions.get(extension_id) else {
continue;
@@ -1086,8 +1248,25 @@ impl ExtensionStore {
path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
path
}));
actions_to_add.extend(
extension
.manifest
.editor_actions
.iter()
.map(|(k, v)| dbg!(format!("{}::{}", extension_id, v.name))),
);
actions_to_add.extend(
extension
.manifest
.editor_actions
.iter()
.map(|(k, v)| dbg!(format!("editor::{}", v.name))),
);
}
dbg!("hello!", &actions_to_add);
self.editor_actions_registry.add_actions(actions_to_add);
self.language_registry
.register_wasm_grammars(grammars_to_add);

View File

@@ -145,6 +145,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
id: "zed-ruby".into(),
name: "Zed Ruby".into(),
version: "1.0.0".into(),
editor_actions: Default::default(),
schema_version: SchemaVersion::ZERO,
description: None,
authors: Vec::new(),
@@ -170,6 +171,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
"zed-monokai".into(),
ExtensionIndexEntry {
manifest: Arc::new(ExtensionManifest {
editor_actions: Default::default(),
id: "zed-monokai".into(),
name: "Zed Monokai".into(),
version: "2.0.0".into(),
@@ -336,6 +338,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
"zed-gruvbox".into(),
ExtensionIndexEntry {
manifest: Arc::new(ExtensionManifest {
editor_actions: Default::default(),
id: "zed-gruvbox".into(),
name: "Zed Gruvbox".into(),
version: "1.0.0".into(),

View File

@@ -104,6 +104,25 @@ impl WasmHost {
})
}
pub fn on_main_thread<T, Fn>(&self, f: Fn) -> impl 'static + Future<Output = T>
where
T: 'static + Send,
Fn: 'static + Send + for<'a> FnOnce(&'a mut AsyncAppContext) -> LocalBoxFuture<'a, T>,
{
let (return_tx, return_rx) = oneshot::channel();
self.main_thread_message_tx
.clone()
.unbounded_send(Box::new(move |cx| {
async {
let result = f(cx).await;
return_tx.send(result).ok();
}
.boxed_local()
}))
.expect("main thread message channel should not be closed yet");
async move { return_rx.await.expect("main thread message channel") }
}
pub fn load_extension(
self: &Arc<Self>,
wasm_bytes: Vec<u8>,
@@ -270,19 +289,7 @@ impl WasmState {
T: 'static + Send,
Fn: 'static + Send + for<'a> FnOnce(&'a mut AsyncAppContext) -> LocalBoxFuture<'a, T>,
{
let (return_tx, return_rx) = oneshot::channel();
self.host
.main_thread_message_tx
.clone()
.unbounded_send(Box::new(move |cx| {
async {
let result = f(cx).await;
return_tx.send(result).ok();
}
.boxed_local()
}))
.expect("main thread message channel should not be closed yet");
async move { return_rx.await.expect("main thread message channel") }
self.host.on_main_thread(f)
}
fn work_dir(&self) -> PathBuf {

View File

@@ -5,7 +5,7 @@ mod since_v0_1_0;
mod since_v0_2_0;
use indexed_docs::IndexedDocsDatabase;
use release_channel::ReleaseChannel;
use since_v0_2_0 as latest;
use since_v0_2_0::{self as latest, ExtensionBuffer};
use super::{wasm_engine, WasmState};
use anyhow::{anyhow, Context, Result};
@@ -139,6 +139,21 @@ impl Extension {
}
}
pub async fn call_run_editor_action(
&self,
store: &mut Store<WasmState>,
action_name: &str,
resource: Resource<ExtensionBuffer>,
) -> Result<()> {
match self {
Extension::V020(ext) => {
ext.call_run_editor_action(store, action_name, resource)
.await
}
_ => anyhow::bail!("not implemented"),
}
}
pub async fn call_language_server_command(
&self,
store: &mut Store<WasmState>,

View File

@@ -5,6 +5,7 @@ use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use editor::MultiBufferSnapshot;
use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt};
use indexed_docs::IndexedDocsDatabase;
@@ -32,17 +33,47 @@ wasmtime::component::bindgen!({
path: "../extension_api/wit/since_v0.2.0",
with: {
"worktree": ExtensionWorktree,
"buffer": ExtensionBuffer,
"char-iterator": ExtensionCharIterator,
"key-value-store": ExtensionKeyValueStore,
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
},
});
pub struct BoopCharIterator {
snapshot: multi_buffer::MultiBufferSnapshot,
offset: usize,
}
impl Iterator for BoopCharIterator {
type Item = (char, u64);
fn next(&mut self) -> Option<Self::Item> {
let offset = self.offset;
let char = self.snapshot.chars_at(self.offset).next()?;
self.offset = offset + char.len_utf8();
Some((char, offset as u64))
}
}
pub use self::zed::extension::*;
mod settings {
include!(concat!(env!("OUT_DIR"), "/since_v0.2.0/settings.rs"));
}
pub struct ExtensionBuffer {
snapshot: MultiBufferSnapshot,
selections: Vec<Selection>,
}
impl ExtensionBuffer {
fn selections(&self) -> Vec<Selection> {
self.selections.clone()
}
}
pub type ExtensionCharIterator = Arc<Mutex<BoopCharIterator>>;
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
@@ -549,3 +580,53 @@ impl ExtensionImports for WasmState {
Ok(Ok(()))
}
}
#[async_trait]
impl HostBuffer for WasmState {
async fn selections(&mut self, buffer: Resource<Buffer>) -> wasmtime::Result<Vec<Selection>> {
let buffer = self.table.get(&buffer)?;
// Assuming Buffer has a selections() method that returns Vec<Selection>
Ok(buffer.selections())
}
async fn chars_at(
&mut self,
buffer: Resource<Buffer>,
offset: u64,
) -> wasmtime::Result<Resource<CharIterator>> {
todo!("Implement chars_at")
}
async fn chars_before(
&mut self,
buffer: Resource<Buffer>,
offset: u64,
) -> wasmtime::Result<Resource<CharIterator>> {
todo!("Implement chars_before")
}
async fn edits(&mut self, buffer: Resource<Buffer>) -> wasmtime::Result<Vec<Edit>> {
todo!("Implement edits")
}
fn drop(&mut self, buffer: Resource<Buffer>) -> wasmtime::Result<()> {
todo!("Implement drop for Buffer")
}
}
#[async_trait]
impl HostCharIterator for WasmState {
async fn next(
&mut self,
iterator: Resource<CharIterator>,
) -> wasmtime::Result<Option<(char, u64)>> {
let char_iterator = self.table.get(&iterator)?;
let mut state = char_iterator.lock().await;
Ok(state.next())
}
fn drop(&mut self, _: Resource<CharIterator>) -> wasmtime::Result<()> {
Ok(())
}
}

View File

@@ -27,7 +27,7 @@ pub use wit::{
zed::extension::slash_command::{
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
},
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
Buffer, CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
};
@@ -57,6 +57,9 @@ pub fn set_language_server_installation_status(
wit::set_language_server_installation_status(&language_server_id.0, status)
}
// regsister editor action
// handle action (editor)
/// A Zed extension.
pub trait Extension: Send + Sync {
/// Returns a new instance of the extension.
@@ -147,6 +150,9 @@ pub trait Extension: Send + Sync {
) -> Result<(), String> {
Err("`index_docs` not implemented".to_string())
}
/// Returns the output from running the provided slash command.
fn run_editor_action(&self, _name: String, buffer: Buffer) {}
}
/// Registers the provided type as a Zed extension.
@@ -186,7 +192,7 @@ mod wit {
wit_bindgen::generate!({
skip: ["init-extension"],
path: "./wit/since_v0.1.0",
path: "./wit/since_v0.2.0",
});
}
@@ -281,6 +287,10 @@ impl wit::Guest for Component {
) -> Result<(), String> {
extension().index_docs(provider, package, database)
}
fn run_editor_action(
name: String,buffer: Buffe extension().run_editor_action(name, buffer)
}
}
/// The ID of a language server.

View File

@@ -83,12 +83,36 @@ world extension {
shell-env: func() -> env-vars;
}
resource buffer {
selections: func() -> list<selection>;
chars-at: func(offset: u64) -> char-iterator;
chars-before: func(offset: u64) -> char-iterator;
edits: func() -> list<edit>;
}
record edit {
range: range,
text: string,
}
resource char-iterator {
next: func() -> option<tuple<char, u64>>;
}
record selection {
range: range,
reversed: bool,
}
/// A key-value store.
resource key-value-store {
/// Inserts an entry under the specified key.
insert: func(key: string, value: string) -> result<_, string>;
}
/// Runs the editor action
export run-editor-action: func(name: string, buffer: buffer);
/// Returns the command used to start up the language server.
export language-server-command: func(language-server-id: string, worktree: borrow<worktree>) -> result<command, string>;

View File

@@ -13,3 +13,4 @@ path = "src/feature_flags.rs"
[dependencies]
gpui.workspace = true
futures.workspace = true

View File

@@ -1,4 +1,10 @@
use futures::{channel::oneshot, FutureExt as _};
use gpui::{AppContext, Global, Subscription, ViewContext};
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
#[derive(Default)]
struct FeatureFlags {
@@ -53,6 +59,15 @@ impl FeatureFlag for ZedPro {
const NAME: &'static str = "zed-pro";
}
pub struct AutoCommand {}
impl FeatureFlag for AutoCommand {
const NAME: &'static str = "auto-command";
fn enabled_for_staff() -> bool {
false
}
}
pub trait FeatureFlagViewExt<V: 'static> {
fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
where
@@ -75,6 +90,7 @@ where
}
pub trait FeatureFlagAppExt {
fn wait_for_flag<T: FeatureFlag>(&mut self) -> WaitForFlag;
fn update_flags(&mut self, staff: bool, flags: Vec<String>);
fn set_staff(&mut self, staff: bool);
fn has_flag<T: FeatureFlag>(&self) -> bool;
@@ -82,7 +98,7 @@ pub trait FeatureFlagAppExt {
fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
where
F: Fn(bool, &mut AppContext) + 'static;
F: FnMut(bool, &mut AppContext) + 'static;
}
impl FeatureFlagAppExt for AppContext {
@@ -109,13 +125,49 @@ impl FeatureFlagAppExt for AppContext {
.unwrap_or(false)
}
fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
fn observe_flag<T: FeatureFlag, F>(&mut self, mut callback: F) -> Subscription
where
F: Fn(bool, &mut AppContext) + 'static,
F: FnMut(bool, &mut AppContext) + 'static,
{
self.observe_global::<FeatureFlags>(move |cx| {
let feature_flags = cx.global::<FeatureFlags>();
callback(feature_flags.has_flag::<T>(), cx);
})
}
fn wait_for_flag<T: FeatureFlag>(&mut self) -> WaitForFlag {
let (tx, rx) = oneshot::channel::<bool>();
let mut tx = Some(tx);
let subscription: Option<Subscription>;
match self.try_global::<FeatureFlags>() {
Some(feature_flags) => {
subscription = None;
tx.take().unwrap().send(feature_flags.has_flag::<T>()).ok();
}
None => {
subscription = Some(self.observe_global::<FeatureFlags>(move |cx| {
let feature_flags = cx.global::<FeatureFlags>();
if let Some(tx) = tx.take() {
tx.send(feature_flags.has_flag::<T>()).ok();
}
}));
}
}
WaitForFlag(rx, subscription)
}
}
pub struct WaitForFlag(oneshot::Receiver<bool>, Option<Subscription>);
impl Future for WaitForFlag {
type Output = bool;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.0.poll_unpin(cx).map(|result| {
self.1.take();
result.unwrap_or(false)
})
}
}

View File

@@ -171,6 +171,7 @@ pub struct Metadata {
pub mtime: SystemTime,
pub is_symlink: bool,
pub is_dir: bool,
pub len: u64,
pub is_fifo: bool,
}
@@ -497,6 +498,7 @@ impl Fs for RealFs {
Ok(Some(Metadata {
inode,
mtime: metadata.modified().unwrap(),
len: metadata.len(),
is_symlink,
is_dir: metadata.file_type().is_dir(),
is_fifo,
@@ -800,11 +802,13 @@ enum FakeFsEntry {
File {
inode: u64,
mtime: SystemTime,
len: u64,
content: Vec<u8>,
},
Dir {
inode: u64,
mtime: SystemTime,
len: u64,
entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
git_repo_state: Option<Arc<Mutex<git::repository::FakeGitRepositoryState>>>,
},
@@ -935,6 +939,7 @@ impl FakeFs {
root: Arc::new(Mutex::new(FakeFsEntry::Dir {
inode: 0,
mtime: SystemTime::UNIX_EPOCH,
len: 0,
entries: Default::default(),
git_repo_state: None,
})),
@@ -969,6 +974,7 @@ impl FakeFs {
inode: new_inode,
mtime: new_mtime,
content: Vec::new(),
len: 0,
})));
}
btree_map::Entry::Occupied(mut e) => match &mut *e.get_mut().lock() {
@@ -1016,6 +1022,7 @@ impl FakeFs {
let file = Arc::new(Mutex::new(FakeFsEntry::File {
inode,
mtime,
len: content.len() as u64,
content,
}));
let mut kind = None;
@@ -1369,6 +1376,7 @@ impl Fs for FakeFs {
Arc::new(Mutex::new(FakeFsEntry::Dir {
inode,
mtime,
len: 0,
entries: Default::default(),
git_repo_state: None,
}))
@@ -1391,6 +1399,7 @@ impl Fs for FakeFs {
let file = Arc::new(Mutex::new(FakeFsEntry::File {
inode,
mtime,
len: 0,
content: Vec::new(),
}));
let mut kind = Some(PathEventKind::Created);
@@ -1539,6 +1548,7 @@ impl Fs for FakeFs {
e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
inode,
mtime,
len: content.len() as u64,
content: Vec::new(),
})))
.clone(),
@@ -1694,16 +1704,22 @@ impl Fs for FakeFs {
let entry = entry.lock();
Ok(Some(match &*entry {
FakeFsEntry::File { inode, mtime, .. } => Metadata {
FakeFsEntry::File {
inode, mtime, len, ..
} => Metadata {
inode: *inode,
mtime: *mtime,
len: *len,
is_dir: false,
is_symlink,
is_fifo: false,
},
FakeFsEntry::Dir { inode, mtime, .. } => Metadata {
FakeFsEntry::Dir {
inode, mtime, len, ..
} => Metadata {
inode: *inode,
mtime: *mtime,
len: *len,
is_dir: true,
is_symlink,
is_fifo: false,

View File

@@ -57,7 +57,6 @@ impl GitStatus {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(anyhow!("git status process failed: {}", stderr));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut entries = stdout
.split('\0')

View File

@@ -3,7 +3,10 @@ use anyhow::{anyhow, Context, Result};
use collections::HashMap;
pub use no_action::NoAction;
use serde_json::json;
use std::any::{Any, TypeId};
use std::{
any::{Any, TypeId},
sync::atomic::AtomicUsize,
};
/// Actions are used to implement keyboard-driven UI.
/// When you declare an action, you can bind keys to the action in the keymap and
@@ -52,6 +55,9 @@ pub trait Action: 'static + Send {
/// Get the name of this action, for displaying in UI
fn name(&self) -> &str;
/// Get the type_id of this action (to uniquely identify it)
fn action_type_id(&self) -> ActionTypeId;
/// Get the name of this action for debugging
fn debug_name() -> &'static str
where
@@ -59,7 +65,7 @@ pub trait Action: 'static + Send {
/// Build this action from a JSON value. This is used to construct actions from the keymap.
/// A value of `{}` will be passed for actions that don't have any parameters.
fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
fn build(name: &str, value: serde_json::Value) -> Result<Box<dyn Action>>
where
Self: Sized;
}
@@ -72,19 +78,33 @@ impl std::fmt::Debug for dyn Action {
}
}
impl dyn Action {
/// Get the type id of this action
pub fn type_id(&self) -> TypeId {
self.as_any().type_id()
}
}
type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
/// ActionBuilder is a constructor
pub type ActionBuilder = fn(name: &str, json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
pub(crate) struct ActionRegistry {
builders_by_name: HashMap<SharedString, ActionBuilder>,
names_by_type_id: HashMap<TypeId, SharedString>,
names_by_type_id: HashMap<ActionTypeId, SharedString>,
all_names: Vec<SharedString>, // So we can return a static slice.
next_action_type_id: AtomicUsize,
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
/// ActionTypeId uniquely identifies an action
pub enum ActionTypeId {
/// TypeId is for staticly defined ones
TypeId(TypeId),
/// Runtime is for runtime defined ones
Runtime(usize),
}
impl ActionTypeId {
/// of returns the static type id for an action type
pub fn of<T>() -> Self
where
T: 'static,
{
ActionTypeId::TypeId(std::any::TypeId::of::<T>())
}
}
impl Default for ActionRegistry {
@@ -93,6 +113,7 @@ impl Default for ActionRegistry {
builders_by_name: Default::default(),
names_by_type_id: Default::default(),
all_names: Default::default(),
next_action_type_id: Default::default(),
};
this.load_actions();
@@ -111,7 +132,7 @@ pub type MacroActionBuilder = fn() -> ActionData;
#[doc(hidden)]
pub struct ActionData {
pub name: &'static str,
pub type_id: TypeId,
pub type_id: ActionTypeId,
pub build: ActionBuilder,
}
@@ -122,6 +143,13 @@ pub struct ActionData {
pub static __GPUI_ACTIONS: [MacroActionBuilder];
impl ActionRegistry {
pub fn next_action_type_id(&self) -> ActionTypeId {
let action_id = self
.next_action_type_id
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
ActionTypeId::Runtime(action_id)
}
/// Load all registered actions into the registry.
pub(crate) fn load_actions(&mut self) {
for builder in __GPUI_ACTIONS {
@@ -134,7 +162,7 @@ impl ActionRegistry {
pub(crate) fn load_action<A: Action>(&mut self) {
self.insert_action(ActionData {
name: A::debug_name(),
type_id: TypeId::of::<A>(),
type_id: ActionTypeId::TypeId(TypeId::of::<A>()),
build: A::build,
});
}
@@ -146,8 +174,19 @@ impl ActionRegistry {
self.all_names.push(name);
}
pub fn register_action_type(
&mut self,
name: SharedString,
build: ActionBuilder,
) -> ActionTypeId {
let type_id = self.next_action_type_id();
self.names_by_type_id.insert(type_id.clone(), name.clone());
self.builders_by_name.insert(name, build);
type_id
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
pub fn build_action_type(&self, type_id: &ActionTypeId) -> Result<Box<dyn Action>> {
let name = self
.names_by_type_id
.get(type_id)
@@ -167,12 +206,12 @@ impl ActionRegistry {
.builders_by_name
.get(name)
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
(build_action)(params.unwrap_or_else(|| json!({})))
(build_action)(name, params.unwrap_or_else(|| json!({})))
.with_context(|| format!("Attempting to build action {}", name))
}
pub fn all_action_names(&self) -> &[SharedString] {
self.all_names.as_slice()
pub fn all_action_names(&self) -> Vec<SharedString> {
self.all_names.clone()
}
}
@@ -190,7 +229,7 @@ macro_rules! actions {
pub struct $name;
gpui::__impl_action!($namespace, $name, $name,
fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
fn build(_: &str,_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self))
}
);
@@ -226,6 +265,7 @@ macro_rules! action_as {
$name,
$visual_name,
fn build(
_: &str,
_: gpui::private::serde_json::Value,
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self))
@@ -242,7 +282,7 @@ macro_rules! impl_actions {
($namespace:path, [ $($name:ident),* $(,)? ]) => {
$(
gpui::__impl_action!($namespace, $name, $name,
fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
fn build(_: &str, value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?))
}
);
@@ -262,6 +302,7 @@ macro_rules! impl_action_as {
$name,
$visual_name,
fn build(
_: &str,
value: gpui::private::serde_json::Value,
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(std::boxed::Box::new(
@@ -315,6 +356,10 @@ macro_rules! __impl_action {
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn action_type_id(&self) -> gpui::ActionTypeId {
gpui::ActionTypeId::TypeId(self.as_any().type_id())
}
}
};
}

View File

@@ -28,13 +28,14 @@ pub use test_context::*;
use util::ResultExt;
use crate::{
current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId,
Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext,
Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
current_platform, hash, init_app_menus, Action, ActionBuilder, ActionRegistry, ActionTypeId,
Any, AnyView, AnyWindowHandle, Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context,
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
Keystroke, LayoutId, MacroActionBuilder, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels,
Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
RenderablePromptHandle, Reservation, SharedString, SubscriberSet, Subscription, SvgRenderer,
Task, TextSystem, View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle,
WindowId,
};
mod async_context;
@@ -218,7 +219,7 @@ pub struct AppContext {
text_system: Arc<TextSystem>,
flushing_effects: bool,
pending_updates: usize,
pub(crate) actions: Rc<ActionRegistry>,
pub(crate) actions: Rc<RefCell<ActionRegistry>>,
pub(crate) active_drag: Option<AnyDrag>,
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
@@ -233,7 +234,7 @@ pub struct AppContext {
pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>,
pub(crate) keymap: Rc<RefCell<Keymap>>,
pub(crate) global_action_listeners:
FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
FxHashMap<ActionTypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
pending_effects: VecDeque<Effect>,
pub(crate) pending_notifications: FxHashSet<EntityId>,
pub(crate) pending_global_notifications: FxHashSet<TypeId>,
@@ -271,7 +272,7 @@ impl AppContext {
this: this.clone(),
platform: platform.clone(),
text_system,
actions: Rc::new(ActionRegistry::default()),
actions: Rc::new(RefCell::new(ActionRegistry::default())),
flushing_effects: false,
pending_updates: 0,
active_drag: None,
@@ -1078,7 +1079,7 @@ impl AppContext {
/// Register a global listener for actions invoked via the keyboard.
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
self.global_action_listeners
.entry(TypeId::of::<A>())
.entry(ActionTypeId::TypeId(TypeId::of::<A>()))
.or_default()
.push(Rc::new(move |action, phase, cx| {
if phase == DispatchPhase::Bubble {
@@ -1088,6 +1089,15 @@ impl AppContext {
}));
}
/// yeah
pub fn register_action_type(
&mut self,
name: SharedString,
build: ActionBuilder,
) -> ActionTypeId {
self.actions.borrow_mut().register_action_type(name, build)
}
/// Event handlers propagate events by default. Call this method to stop dispatching to
/// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
/// the opposite of [`Self::propagate`]. It's also possible to cancel a call to [`Self::propagate`] by
@@ -1110,15 +1120,15 @@ impl AppContext {
name: &str,
data: Option<serde_json::Value>,
) -> Result<Box<dyn Action>> {
self.actions.build_action(name, data)
self.actions.borrow().build_action(name, data)
}
/// Get a list of all action names that have been registered.
/// in the application. Note that registration only allows for
/// actions to be built dynamically, and is unrelated to binding
/// actions in the element tree.
pub fn all_action_names(&self) -> &[SharedString] {
self.actions.all_action_names()
pub fn all_action_names(&self) -> Vec<SharedString> {
self.actions.borrow().all_action_names()
}
/// Register a callback to be invoked when the application is about to quit.
@@ -1166,7 +1176,7 @@ impl AppContext {
action_available
|| self
.global_action_listeners
.contains_key(&action.as_any().type_id())
.contains_key(&action.action_type_id())
}
/// Sets the menu bar for this application. This will replace any existing menu bar.
@@ -1209,7 +1219,7 @@ impl AppContext {
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
{
for listener in &global_listeners {
listener(action.as_any(), DispatchPhase::Capture, self);
@@ -1220,18 +1230,18 @@ impl AppContext {
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
.insert(action.action_type_id(), global_listeners);
}
if self.propagate_event {
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
{
for listener in global_listeners.iter().rev() {
listener(action.as_any(), DispatchPhase::Bubble, self);
@@ -1242,12 +1252,12 @@ impl AppContext {
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
.insert(action.action_type_id(), global_listeners);
}
}
}

View File

@@ -16,9 +16,9 @@
//! constructed by combining these two systems into an all-in-one element.
use crate::{
point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, GlobalElementId, Hitbox,
HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
point, px, size, Action, ActionTypeId, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext,
Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, GlobalElementId,
Hitbox, HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext,
@@ -286,7 +286,7 @@ impl Interactivity {
listener: impl Fn(&A, &mut WindowContext) + 'static,
) {
self.action_listeners.push((
TypeId::of::<A>(),
ActionTypeId::TypeId(TypeId::of::<A>()),
Box::new(move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Capture {
@@ -304,7 +304,7 @@ impl Interactivity {
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
self.action_listeners.push((
TypeId::of::<A>(),
ActionTypeId::TypeId(TypeId::of::<A>()),
Box::new(move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
@@ -327,7 +327,7 @@ impl Interactivity {
) {
let action = action.boxed_clone();
self.action_listeners.push((
(*action).type_id(),
(*action).action_type_id(),
Box::new(move |_, phase, cx| {
if phase == DispatchPhase::Bubble {
(listener)(&*action, cx)
@@ -1268,7 +1268,7 @@ pub struct Interactivity {
pub(crate) key_down_listeners: Vec<KeyDownListener>,
pub(crate) key_up_listeners: Vec<KeyUpListener>,
pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
pub(crate) action_listeners: Vec<(ActionTypeId, ActionListener)>,
pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
pub(crate) can_drop_predicate: Option<CanDropPredicate>,
pub(crate) click_listeners: Vec<ClickListener>,

View File

@@ -50,18 +50,13 @@
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
///
use crate::{
Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
Keystroke, ModifiersChangedEvent, WindowContext,
Action, ActionRegistry, ActionTypeId, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext,
Keymap, Keystroke, ModifiersChangedEvent, WindowContext,
};
use collections::FxHashMap;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
cell::RefCell,
mem,
ops::Range,
rc::Rc,
};
use std::{any::Any, cell::RefCell, mem, ops::Range, rc::Rc};
use util::ResultExt;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub(crate) struct DispatchNodeId(usize);
@@ -74,7 +69,7 @@ pub(crate) struct DispatchTree {
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
keymap: Rc<RefCell<Keymap>>,
action_registry: Rc<ActionRegistry>,
action_registry: Rc<RefCell<ActionRegistry>>,
}
#[derive(Default)]
@@ -128,12 +123,12 @@ type ModifiersChangedListener = Rc<dyn Fn(&ModifiersChangedEvent, &mut WindowCon
#[derive(Clone)]
pub(crate) struct DispatchActionListener {
pub(crate) action_type: TypeId,
pub(crate) action_type: ActionTypeId,
pub(crate) listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
}
impl DispatchTree {
pub fn new(keymap: Rc<RefCell<Keymap>>, action_registry: Rc<ActionRegistry>) -> Self {
pub fn new(keymap: Rc<RefCell<Keymap>>, action_registry: Rc<RefCell<ActionRegistry>>) -> Self {
Self {
node_stack: Vec::new(),
context_stack: Vec::new(),
@@ -332,7 +327,7 @@ impl DispatchTree {
pub fn on_action(
&mut self,
action_type: TypeId,
action_type: ActionTypeId,
listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
) {
self.active_node()
@@ -364,12 +359,16 @@ impl DispatchTree {
let mut actions = Vec::<Box<dyn Action>>::new();
for node_id in self.dispatch_path(target) {
let node = &self.nodes[node_id.0];
dbg!(&node.context, &node.action_listeners.len());
for DispatchActionListener { action_type, .. } in &node.action_listeners {
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.as_any().type_id())
{
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.action_type_id()) {
// Intentionally silence these errors without logging.
// If an action cannot be built by default, it's not available.
let action = self.action_registry.build_action_type(action_type).ok();
let action = self
.action_registry
.borrow()
.build_action_type(action_type)
.log_err();
if let Some(action) = action {
actions.insert(ix, action);
}
@@ -385,7 +384,7 @@ impl DispatchTree {
if node
.action_listeners
.iter()
.any(|listener| listener.action_type == action.as_any().type_id())
.any(|listener| listener.action_type == action.action_type_id())
{
return true;
}
@@ -574,7 +573,9 @@ impl DispatchTree {
mod tests {
use std::{cell::RefCell, rc::Rc};
use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap};
use crate::{
Action, ActionRegistry, ActionTypeId, DispatchTree, KeyBinding, KeyContext, Keymap,
};
#[derive(PartialEq, Eq)]
struct TestAction;
@@ -606,7 +607,14 @@ mod tests {
self
}
fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
fn action_type_id(&self) -> crate::ActionTypeId {
ActionTypeId::TypeId(std::any::TypeId::of::<TestAction>())
}
fn build(
_type_id: ActionTypeId,
_value: serde_json::Value,
) -> anyhow::Result<Box<dyn Action>>
where
Self: Sized,
{

View File

@@ -18,7 +18,7 @@ pub struct KeymapVersion(usize);
#[derive(Default)]
pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
binding_indices_by_action_id: HashMap<crate::ActionTypeId, SmallVec<[usize; 3]>>,
version: KeymapVersion,
}
@@ -38,7 +38,7 @@ impl Keymap {
/// Add more bindings to the keymap.
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
for binding in bindings {
let action_id = binding.action().as_any().type_id();
let action_id = binding.action().action_type_id();
self.binding_indices_by_action_id
.entry(action_id)
.or_default()
@@ -66,7 +66,7 @@ impl Keymap {
&'a self,
action: &'a dyn Action,
) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
let action_id = action.type_id();
let action_id = action.action_type_id();
self.binding_indices_by_action_id
.get(&action_id)
.map_or(&[] as _, SmallVec::as_slice)
@@ -122,7 +122,7 @@ impl Keymap {
let bindings = bindings
.into_iter()
.map_while(|(binding, _)| {
if binding.action.as_any().type_id() == (NoAction {}).type_id() {
if binding.action.action_type_id() == (NoAction {}).action_type_id() {
None
} else {
Some(binding)

View File

@@ -1,7 +1,7 @@
use crate::{
point, prelude::*, px, size, transparent_black, Action, AnyDrag, AnyElement, AnyTooltip,
AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow,
Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
point, prelude::*, px, size, transparent_black, Action, ActionTypeId, AnyDrag, AnyElement,
AnyTooltip, AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds,
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, Flatten, FontId, GPUSpecs, Global, GlobalElementId, GlyphId, Hsla, InputHandler,
IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
@@ -3488,7 +3488,7 @@ impl<'a> WindowContext<'a> {
self.propagate_event = true;
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
{
for listener in &global_listeners {
listener(action.as_any(), DispatchPhase::Capture, self);
@@ -3499,12 +3499,12 @@ impl<'a> WindowContext<'a> {
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
.insert(action.action_type_id(), global_listeners);
}
if !self.propagate_event {
@@ -3519,9 +3519,8 @@ impl<'a> WindowContext<'a> {
listener,
} in node.action_listeners.clone()
{
let any_action = action.as_any();
if action_type == any_action.type_id() {
listener(any_action, DispatchPhase::Capture, self);
if action_type == action.action_type_id() {
listener(action.as_any(), DispatchPhase::Capture, self);
if !self.propagate_event {
return;
@@ -3538,10 +3537,9 @@ impl<'a> WindowContext<'a> {
listener,
} in node.action_listeners.clone()
{
let any_action = action.as_any();
if action_type == any_action.type_id() {
if action_type == action.action_type_id() {
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
listener(any_action, DispatchPhase::Bubble, self);
listener(action.as_any(), DispatchPhase::Bubble, self);
if !self.propagate_event {
return;
@@ -3553,7 +3551,7 @@ impl<'a> WindowContext<'a> {
// Bubble phase for global actions.
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
{
for listener in global_listeners.iter().rev() {
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
@@ -3566,12 +3564,12 @@ impl<'a> WindowContext<'a> {
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
.insert(action.action_type_id(), global_listeners);
}
}
@@ -3684,8 +3682,10 @@ impl<'a> WindowContext<'a> {
.dispatch_tree
.available_actions(node_id);
for action_type in self.global_action_listeners.keys() {
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.as_any().type_id()) {
let action = self.actions.build_action_type(action_type).ok();
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.action_type_id()) {
let action = RefCell::borrow(&self.actions)
.build_action_type(action_type)
.log_err();
if let Some(action) = action {
actions.insert(ix, action);
}
@@ -3765,7 +3765,7 @@ impl<'a> WindowContext<'a> {
/// a specific need to register a global listener.
pub fn on_action(
&mut self,
action_type: TypeId,
action_type: ActionTypeId,
listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
) {
self.window
@@ -4420,7 +4420,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// Register a callback to be invoked when the given Action type is dispatched to the window.
pub fn on_action(
&mut self,
action_type: TypeId,
action_type: ActionTypeId,
listener: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
) {
let handle = self.view().clone();

View File

@@ -46,5 +46,9 @@ fn test_action_macros() {
{
unimplemented!()
}
fn action_type_id(&self) -> gpui::ActionTypeId {
gpui::ActionTypeId::TypeId(std::any::TypeId::of::<Self>())
}
}
}

View File

@@ -0,0 +1,73 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Attribute, Data, DeriveInput, Lit, Meta, NestedMeta};
pub fn derive_path_static_str(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let prefix = get_attr_value(&input.attrs, "prefix").unwrap_or_else(|| "".to_string());
let suffix = get_attr_value(&input.attrs, "suffix").unwrap_or_else(|| "".to_string());
let delimiter = get_attr_value(&input.attrs, "delimiter").unwrap_or_else(|| "/".to_string());
let path_str_impl = impl_path_str(name, &input.data, &prefix, &suffix, &delimiter);
let expanded = quote! {
impl #name {
pub fn path_str(&self) -> &'static str {
#path_str_impl
}
}
};
TokenStream::from(expanded)
}
fn impl_path_str(
name: &syn::Ident,
data: &Data,
prefix: &str,
suffix: &str,
delimiter: &str,
) -> proc_macro2::TokenStream {
match *data {
Data::Enum(ref data) => {
let match_arms = data.variants.iter().map(|variant| {
let ident = &variant.ident;
let path = format!("{}{}{}{}{}", prefix, delimiter, ident, delimiter, suffix);
quote! {
#name::#ident => #path,
}
});
quote! {
match self {
#(#match_arms)*
}
}
}
_ => panic!("DerivePathStr only supports enums"),
}
}
fn get_attr_value(attrs: &[Attribute], key: &str) -> Option<String> {
attrs
.iter()
.filter(|attr| attr.path.is_ident("derive_path_static_str"))
.find_map(|attr| {
if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
meta_list.nested.iter().find_map(|nested_meta| {
if let NestedMeta::Meta(Meta::NameValue(name_value)) = nested_meta {
if name_value.path.is_ident(key) {
if let Lit::Str(lit_str) = &name_value.lit {
return Some(lit_str.value());
}
}
}
None
})
} else {
None
}
})
}

View File

@@ -1,4 +1,5 @@
mod derive_into_element;
mod derive_path_static_str;
mod derive_render;
mod register_action;
mod styles;
@@ -27,6 +28,12 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
derive_render::derive_render(input)
}
#[proc_macro_derive(PathStaticStr)]
#[doc(hidden)]
pub fn derive_path_static_str(input: TokenStream) -> TokenStream {
derive_path_static_str::derive_path_static_str(input)
}
/// Used by GPUI to generate the style helpers.
#[proc_macro]
#[doc(hidden)]

View File

@@ -32,7 +32,7 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
fn #action_builder_fn_name() -> gpui::ActionData {
gpui::ActionData {
name: <#type_name as gpui::Action>::debug_name(),
type_id: ::std::any::TypeId::of::<#type_name>(),
type_id: gpui::ActionTypeId::TypeId(::std::any::TypeId::of::<#type_name>()),
build: <#type_name as gpui::Action>::build,
}
}

View File

@@ -221,6 +221,10 @@ impl HttpClient for HttpClientWithUrl {
pub fn client(user_agent: Option<String>, proxy: Option<Uri>) -> Arc<dyn HttpClient> {
let mut builder = isahc::HttpClient::builder()
// Some requests to Qwen2 models on Runpod can take 32+ seconds,
// especially if there's a cold boot involved. We may need to have
// those requests use a different http client, because global timeouts
// of 50 and 60 seconds, respectively, would be very high!
.connect_timeout(Duration::from_secs(5))
.low_speed_timeout(100, Duration::from_secs(5))
.proxy(proxy.clone());

View File

@@ -141,6 +141,9 @@ pub trait ToLspPosition {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct LanguageServerName(pub Arc<str>);
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct ActionName(Arc<str>);
impl LanguageServerName {
pub fn from_proto(s: String) -> Self {
Self(Arc::from(s))
@@ -1410,6 +1413,10 @@ impl Language {
}
impl LanguageScope {
pub fn path_suffixes(&self) -> &[String] {
&self.language.path_suffixes()
}
pub fn language_name(&self) -> LanguageName {
self.language.config.name.clone()
}

View File

@@ -17,14 +17,14 @@ pub enum CloudModel {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, EnumIter)]
pub enum ZedModel {
#[serde(rename = "qwen2-7b-instruct")]
#[serde(rename = "Qwen/Qwen2-7B-Instruct")]
Qwen2_7bInstruct,
}
impl ZedModel {
pub fn id(&self) -> &str {
match self {
ZedModel::Qwen2_7bInstruct => "qwen2-7b-instruct",
ZedModel::Qwen2_7bInstruct => "Qwen/Qwen2-7B-Instruct",
}
}
@@ -102,6 +102,8 @@ impl CloudModel {
| open_ai::Model::FourTurbo
| open_ai::Model::FourOmni
| open_ai::Model::FourOmniMini
| open_ai::Model::O1Mini
| open_ai::Model::O1Preview
| open_ai::Model::Custom { .. } => {
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
}

View File

@@ -319,7 +319,7 @@ impl AnthropicModel {
};
async move {
let api_key = api_key.ok_or_else(|| anyhow!("missing api key"))?;
let api_key = api_key.ok_or_else(|| anyhow!("Missing Anthropic API Key"))?;
let request = anthropic::stream_completion(
http_client.as_ref(),
&api_url,

View File

@@ -78,6 +78,8 @@ pub struct AvailableModel {
pub max_tokens: usize,
/// The maximum number of output tokens allowed by the model.
pub max_output_tokens: Option<u32>,
/// The maximum number of completion tokens allowed by the model (o1-* only)
pub max_completion_tokens: Option<u32>,
/// Override this model with a different Anthropic model for tool calls.
pub tool_override: Option<String>,
/// Indicates whether this custom model supports caching.
@@ -257,6 +259,7 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
display_name: model.display_name.clone(),
max_tokens: model.max_tokens,
max_output_tokens: model.max_output_tokens,
max_completion_tokens: model.max_completion_tokens,
}),
AvailableProvider::Google => CloudModel::Google(google_ai::Model::Custom {
name: model.name.clone(),

View File

@@ -265,7 +265,7 @@ impl LanguageModel for GoogleLanguageModel {
let low_speed_timeout = settings.low_speed_timeout;
async move {
let api_key = api_key.ok_or_else(|| anyhow!("missing api key"))?;
let api_key = api_key.ok_or_else(|| anyhow!("Missing Google API key"))?;
let response = google_ai::count_tokens(
http_client.as_ref(),
&api_url,
@@ -304,7 +304,7 @@ impl LanguageModel for GoogleLanguageModel {
};
let future = self.rate_limiter.stream(async move {
let api_key = api_key.ok_or_else(|| anyhow!("missing api key"))?;
let api_key = api_key.ok_or_else(|| anyhow!("Missing Google API Key"))?;
let response = stream_generate_content(
http_client.as_ref(),
&api_url,

View File

@@ -43,6 +43,7 @@ pub struct AvailableModel {
pub display_name: Option<String>,
pub max_tokens: usize,
pub max_output_tokens: Option<u32>,
pub max_completion_tokens: Option<u32>,
}
pub struct OpenAiLanguageModelProvider {
@@ -175,6 +176,7 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
display_name: model.display_name.clone(),
max_tokens: model.max_tokens,
max_output_tokens: model.max_output_tokens,
max_completion_tokens: model.max_completion_tokens,
},
);
}
@@ -239,7 +241,7 @@ impl OpenAiLanguageModel {
};
let future = self.request_limiter.stream(async move {
let api_key = api_key.ok_or_else(|| anyhow!("missing api key"))?;
let api_key = api_key.ok_or_else(|| anyhow!("Missing OpenAI API Key"))?;
let request = stream_completion(
http_client.as_ref(),
&api_url,
@@ -370,10 +372,13 @@ pub fn count_open_ai_tokens(
})
.collect::<Vec<_>>();
if let open_ai::Model::Custom { .. } = model {
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)
} else {
tiktoken_rs::num_tokens_from_messages(model.id(), &messages)
match model {
open_ai::Model::Custom { .. }
| open_ai::Model::O1Mini
| open_ai::Model::O1Preview => {
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)
}
_ => tiktoken_rs::num_tokens_from_messages(model.id(), &messages),
}
})
.boxed()

View File

@@ -159,11 +159,13 @@ impl LanguageModelRegistry {
providers
}
pub fn available_models(&self, cx: &AppContext) -> Vec<Arc<dyn LanguageModel>> {
pub fn available_models<'a>(
&'a self,
cx: &'a AppContext,
) -> impl Iterator<Item = Arc<dyn LanguageModel>> + 'a {
self.providers
.values()
.flat_map(|provider| provider.provided_models(cx))
.collect()
}
pub fn provider(&self, id: &LanguageModelProviderId) -> Option<Arc<dyn LanguageModelProvider>> {

View File

@@ -241,6 +241,7 @@ pub struct LanguageModelRequest {
impl LanguageModelRequest {
pub fn into_open_ai(self, model: String, max_output_tokens: Option<u32>) -> open_ai::Request {
let stream = !model.starts_with("o1-");
open_ai::Request {
model,
messages: self
@@ -259,7 +260,7 @@ impl LanguageModelRequest {
},
})
.collect(),
stream: true,
stream,
stop: self.stop,
temperature: self.temperature,
max_tokens: max_output_tokens,

View File

@@ -178,11 +178,13 @@ impl OpenAiSettingsContent {
display_name,
max_tokens,
max_output_tokens,
max_completion_tokens,
} => Some(provider::open_ai::AvailableModel {
name,
max_tokens,
max_output_tokens,
display_name,
max_completion_tokens,
}),
_ => None,
})

View File

@@ -3,7 +3,7 @@ pub mod parser;
use crate::parser::CodeBlockKind;
use futures::FutureExt;
use gpui::{
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
actions, point, quad, ActionTypeId, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
@@ -761,7 +761,7 @@ impl Element for MarkdownElement {
context.add("Markdown");
cx.set_key_context(context);
let view = self.markdown.clone();
cx.on_action(std::any::TypeId::of::<crate::Copy>(), {
cx.on_action(ActionTypeId::of::<crate::Copy>(), {
let text = rendered_markdown.text.clone();
move |_, phase, cx| {
let text = text.clone();

View File

@@ -1,12 +1,21 @@
mod supported_countries;
use anyhow::{anyhow, Context, Result};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use futures::{
io::BufReader,
stream::{self, BoxStream},
AsyncBufReadExt, AsyncReadExt, Stream, StreamExt,
};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use isahc::config::Configurable;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{convert::TryFrom, future::Future, pin::Pin, time::Duration};
use std::{
convert::TryFrom,
future::{self, Future},
pin::Pin,
time::Duration,
};
use strum::EnumIter;
pub use supported_countries::*;
@@ -54,17 +63,22 @@ impl From<Role> for String {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
#[serde(rename = "gpt-3.5-turbo", alias = "gpt-3.5-turbo-0613")]
#[serde(rename = "gpt-3.5-turbo", alias = "gpt-3.5-turbo")]
ThreePointFiveTurbo,
#[serde(rename = "gpt-4", alias = "gpt-4-0613")]
#[serde(rename = "gpt-4", alias = "gpt-4")]
Four,
#[serde(rename = "gpt-4-turbo-preview", alias = "gpt-4-1106-preview")]
#[serde(rename = "gpt-4-turbo", alias = "gpt-4-turbo")]
FourTurbo,
#[serde(rename = "gpt-4o", alias = "gpt-4o-2024-05-13")]
#[serde(rename = "gpt-4o", alias = "gpt-4o")]
#[default]
FourOmni,
#[serde(rename = "gpt-4o-mini", alias = "gpt-4o-mini-2024-07-18")]
#[serde(rename = "gpt-4o-mini", alias = "gpt-4o-mini")]
FourOmniMini,
#[serde(rename = "o1-preview", alias = "o1-preview")]
O1Preview,
#[serde(rename = "o1-mini", alias = "o1-mini")]
O1Mini,
#[serde(rename = "custom")]
Custom {
name: String,
@@ -72,6 +86,7 @@ pub enum Model {
display_name: Option<String>,
max_tokens: usize,
max_output_tokens: Option<u32>,
max_completion_tokens: Option<u32>,
},
}
@@ -83,6 +98,8 @@ impl Model {
"gpt-4-turbo-preview" => Ok(Self::FourTurbo),
"gpt-4o" => Ok(Self::FourOmni),
"gpt-4o-mini" => Ok(Self::FourOmniMini),
"o1-preview" => Ok(Self::O1Preview),
"o1-mini" => Ok(Self::O1Mini),
_ => Err(anyhow!("invalid model id")),
}
}
@@ -91,9 +108,11 @@ impl Model {
match self {
Self::ThreePointFiveTurbo => "gpt-3.5-turbo",
Self::Four => "gpt-4",
Self::FourTurbo => "gpt-4-turbo-preview",
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::FourOmniMini => "gpt-4o-mini",
Self::O1Preview => "o1-preview",
Self::O1Mini => "o1-mini",
Self::Custom { name, .. } => name,
}
}
@@ -105,6 +124,8 @@ impl Model {
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::FourOmniMini => "gpt-4o-mini",
Self::O1Preview => "o1-preview",
Self::O1Mini => "o1-mini",
Self::Custom {
name, display_name, ..
} => display_name.as_ref().unwrap_or(name),
@@ -113,11 +134,13 @@ impl Model {
pub fn max_token_count(&self) -> usize {
match self {
Self::ThreePointFiveTurbo => 4096,
Self::ThreePointFiveTurbo => 16385,
Self::Four => 8192,
Self::FourTurbo => 128000,
Self::FourOmni => 128000,
Self::FourOmniMini => 128000,
Self::O1Preview => 128000,
Self::O1Mini => 128000,
Self::Custom { max_tokens, .. } => *max_tokens,
}
}
@@ -139,6 +162,7 @@ pub struct Request {
pub stream: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub stop: Vec<String>,
pub temperature: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -263,6 +287,111 @@ pub struct ResponseStreamEvent {
pub usage: Option<Usage>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Response {
pub id: String,
pub object: String,
pub created: u64,
pub model: String,
pub choices: Vec<Choice>,
pub usage: Usage,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Choice {
pub index: u32,
pub message: RequestMessage,
pub finish_reason: Option<String>,
}
pub async fn complete(
client: &dyn HttpClient,
api_url: &str,
api_key: &str,
request: Request,
low_speed_timeout: Option<Duration>,
) -> Result<Response> {
let uri = format!("{api_url}/chat/completions");
let mut request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key));
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
};
let mut request_body = request;
request_body.stream = false;
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request_body)?))?;
let mut response = client.send(request).await?;
if response.status().is_success() {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
let response: Response = serde_json::from_str(&body)?;
Ok(response)
} else {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
#[derive(Deserialize)]
struct OpenAiResponse {
error: OpenAiError,
}
#[derive(Deserialize)]
struct OpenAiError {
message: String,
}
match serde_json::from_str::<OpenAiResponse>(&body) {
Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
"Failed to connect to OpenAI API: {}",
response.error.message,
)),
_ => Err(anyhow!(
"Failed to connect to OpenAI API: {} {}",
response.status(),
body,
)),
}
}
}
fn adapt_response_to_stream(response: Response) -> ResponseStreamEvent {
ResponseStreamEvent {
created: response.created as u32,
model: response.model,
choices: response
.choices
.into_iter()
.map(|choice| ChoiceDelta {
index: choice.index,
delta: ResponseMessageDelta {
role: Some(match choice.message {
RequestMessage::Assistant { .. } => Role::Assistant,
RequestMessage::User { .. } => Role::User,
RequestMessage::System { .. } => Role::System,
RequestMessage::Tool { .. } => Role::Tool,
}),
content: match choice.message {
RequestMessage::Assistant { content, .. } => content,
RequestMessage::User { content } => Some(content),
RequestMessage::System { content } => Some(content),
RequestMessage::Tool { content, .. } => Some(content),
},
tool_calls: None,
},
finish_reason: choice.finish_reason,
})
.collect(),
usage: Some(response.usage),
}
}
pub async fn stream_completion(
client: &dyn HttpClient,
api_url: &str,
@@ -270,6 +399,12 @@ pub async fn stream_completion(
request: Request,
low_speed_timeout: Option<Duration>,
) -> Result<BoxStream<'static, Result<ResponseStreamEvent>>> {
if request.model == "o1-preview" || request.model == "o1-mini" {
let response = complete(client, api_url, api_key, request, low_speed_timeout).await;
let response_stream_event = response.map(adapt_response_to_stream);
return Ok(stream::once(future::ready(response_stream_event)).boxed());
}
let uri = format!("{api_url}/chat/completions");
let mut request_builder = HttpRequest::builder()
.method(Method::POST)

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