Compare commits

..

64 Commits

Author SHA1 Message Date
Piotr Osiewicz
bfd06df221 Update 2024-06-28 13:17:53 +02:00
Piotr Osiewicz
ba098e3047 Merge branch 'main' into yarn-pwnage 2024-06-28 11:45:35 +02:00
Aleksei Gusev
6e1b99b039 Fix PageUp for context menu (#13593)
The PageUp key was not working for the context menu. Instead of
selecting one of the previous items in the context menu, `MovePageUp`
closed the menu and scrolled the editor. `MovePageDown` was working
correctly because it has the same fix.



Release Notes:

- Fixed `pageup` key, when bound to `editor::MovePageUp`, not moving context menus as other keys
2024-06-28 12:03:30 +03:00
Kirill Bulatov
00d1561156 Use better names for prompts duplicated (#13630)
Repeats project panel duplicated file name logic for prompts:
* add a ` copy` suffix
* if conflicts still, add a ` i` digit suffix, where `i` is the first
number available starting from 1

Release Notes:

- N/A
2024-06-28 10:26:45 +03:00
Aleksei Gusev
d5fbf75ccf Add keyboard shortcuts for scrolling in terminal (#13508)
Fixes #4917, #12231

Release Notes:
-  Added keyboard shortcuts for scrolling in terminal ([4917](https://github.com/zed-industries/zed/issues/4917), [12231](https://github.com/zed-industries/zed/issues/12231))
2024-06-28 10:26:14 +03:00
Nathan Sobo
61bbb3539a Fix a stupid bug that was dropping system prompts for Claude (#13626)
Release Notes:

- Fixed a bug that was causing system prompts to be dropped for
Anthropic models.

@JosephTLyons @notpeter We probably need to hot-fix this as I'm pretty
sure this affects the regular anthropic provider in addition to just the
feature-flagged cloud stuff. Wouldn't mind confirming that first so we
can communicate around it. 😬
2024-06-27 22:08:47 -06:00
Conrad Irwin
c560a24e7d Default theme to match system (#13621)
Release Notes:

- Default to a light theme during the day (#9627)
2024-06-27 22:06:22 -06:00
Conrad Irwin
da03610555 vim: Fix issues with r/R (#13623)
Release Notes:

- vim: Fix undo after repeated insert/replace mode (#13573)
- vim: Fix 'r' repeating too much (#13566)
2024-06-27 21:54:34 -06:00
Conrad Irwin
363ac6bc96 vim: Empty pane improvements (#13624)
Release Notes:

- vim: Fixed `:` in empty diagnostics view
- vim: Fixed `g/` outside of an editor
2024-06-27 21:54:03 -06:00
Max Brunsfeld
97159bd88d Associate logs from log_err with the calling crate (#13617)
Now, when you selectively enable logs from particular crates with
`RUST_LOG=call,worktree`, logs created via `log_err` calls in those
crates get correctly enabled. Previously, they were all attributed to
the `util` crate, because they used the normal logging macros, which
implicitly insert the current crate name.

This relies on the regularity of our directory naming. Rust's
`track_caller` feature allows you to obtain the file system path of the
caller, but not its rust module path, so I'm inferring the crate name
from the file system path (which I believe is always valid, in our
codebase).

Release Notes:

- N/A
2024-06-27 17:03:47 -07:00
Nate Butler
0b57df5deb Extract title_bar crate (#13597)
This PR extracts a singular title bar (`title_bar::TitleBar`) from
`ui::TitleBar` and
`collab_ui::collab_titlebar_item::CollabTitlebarItem`.

This is a first step towards organizing title bar things into one place,
and standardizing platform titlebar/window control implementations.

Release Notes:

- N/A
2024-06-27 19:14:13 -04:00
Conrad Irwin
7652a8ae23 Fix font selection on macOS (#13615)
Release Notes:

- N/A
2024-06-27 17:02:45 -06:00
Conrad Irwin
1d193585b0 Fix multi-keystroke shortcuts better (#13612)
Release Notes:

- N/A
2024-06-27 16:39:05 -06:00
Bennet Bo Fenner
af5efcea1f Fix typo in README (#13610)
Fixes a typo in the README which (I believe) was accidentally committed
in #13604

Release Notes:

- N/A
2024-06-27 23:58:23 +02:00
Yongkang Chen
228202a469 Store starts open state of outline panel (#13601)
- Fixed issue where outline panel remains open despite being closed
before window close.

Before the release of Outline Panel feature, everything works fine. But
after that, the outline panel keeps open. It's very annoy that I only
want to edit a simple file. Event I close it before I close the window.
The active state of this panel didn't stored.

### Description:
Before the introduction of the Outline Panel feature, the application
behaved as expected. However, with the addition of the Outline Panel, an
issue arose where the panel would persistently remain open. This
behavior was observed even when manually closing the panel before
closing the application window. The problem stemmed from the inactive
state of the panel not being stored properly. This fix addresses the
issue by ensuring that the panel's active state is correctly stored and
retrieved, thereby improving user experience and preventing unnecessary
persistence of the panel's visibility.

### Screen Records

#### Before Release of Outline Panel


https://github.com/zed-industries/zed/assets/704762/2a222c70-c6d7-4472-9f27-7868d1786a5f


#### After Release of Outline Panel


https://github.com/zed-industries/zed/assets/704762/69c16a5d-beed-4d4a-8341-83c53f6a6713


#### After Fixing This Issue


https://github.com/zed-industries/zed/assets/704762/f51c5df7-54e3-4a62-ac54-b5d12cfe69d1

### Release Notes:

- Persist outline panel open state to avoid opening it on Zed startup
2024-06-27 23:51:42 +03:00
Conrad Irwin
e1fbef0dfd Fix multi-key shortcuts (#13606)
Broken by the shift shift support PR

Release Notes:

- Fix multi-key shortcuts (preview only)
2024-06-27 14:44:18 -06:00
Conrad Irwin
7d7fd7d25d Move from Zed fonts to IBM Plex (#13596)
Release Notes:

- Changed the default fonts to IBM Plex Sans and IBM Plex Mono. If you'd
like to go back to using Zed Sans/Zed mono you need to
[download](https://github.com/zed-industries/zed-fonts/releases/tag/1.2.0)
them and install them.
2024-06-27 14:36:44 -06:00
Conrad Irwin
6a1b257d39 Fix X11 window activation better (#13604)
Release Notes:

- N/A
2024-06-27 14:36:29 -06:00
Mikayla Maki
a695322f83 Fix incorrect point types in scroll calculations (#13600)
fixes https://github.com/zed-industries/zed/issues/13559

Release Notes:

- Fixed incorrect scroll behavior when using different
`scroll_beyond_last_line` settings
([#13559](https://github.com/zed-industries/zed/issues/13559)) (preview
only)
2024-06-27 11:19:27 -07:00
Evan Liu
cb2d05b78f editor: Fix scroll_beyond_last_line off for short files (#13571)
Release Notes:

- Fixed bug with `scroll_beyond_last_line: off` for short files
([#13559](https://github.com/zed-industries/zed/issues/13559)).
2024-06-27 10:10:30 -07:00
Piotr Osiewicz
a29b9eea34 Yay, this works ig 2024-06-27 18:07:20 +02:00
Aditya Kumar
45d4de75b3 Add jq as a dependency for Linux script for Arch Linux (#13569)
## Error
```sh
./script/install-linux 

+ [[ 0 -gt 0 ]]
+ export ZED_CHANNEL=dev
+ ZED_CHANNEL=dev
++ pwd
+ export 'ZED_UPDATE_EXPLANATION=You need to fetch and rebuild zed in /home/adityakrcodes/repos/zed'
+ ZED_UPDATE_EXPLANATION='You need to fetch and rebuild zed in /home/adityakrcodes/repos/zed'
+ script/bundle-linux
+ getopts h flag
+ export ZED_BUNDLE=true
+ ZED_BUNDLE=true
+ channel=dev
++ script/get-crate-version zed
script/get-crate-version: line 16: jq: command not found <-- ERROR
+ version=
```
The script fails to install zed on Arch Linux due to a missing
dependency, `jq`.
## My machine info
OS: Arch Linux
Kernel: 6.6.34-1-lts 
WM: dwm 
Terminal: kitty
CPU: Ryzen 5 5500U with Radeon Graphics
GPU: AMD ATI 04:00.0 Lucienne 
Memory: 16 GB

## Error Description
The error occurs when running the script in `script/install-linux`,
which is unable to find the `jq` package, which is not installed by
default on the machine.
## Solution
To resolve this issue, you can install `jq` independently by running
`sudo pacman -S jq`. Alternatively, I have updated script `script/linux`
to include `jq` as a dependency ([link to the
commit](2349ad111f)),
ensuring it is installed automatically when running the initial script
for system libraries.

Release Notes:

- N/A
2024-06-27 09:58:53 -06:00
Peter Tripp
20c1f8245a Improve PageUp/PageDown keybinds for Linux/Mac (#13510)
This makes pageup/pagedown move the cursor too.  Fixes: #13389

Co-authored-by: llogick <16590917+llogick@users.noreply.github.com>
2024-06-27 09:26:53 -04:00
Piotr Osiewicz
7fc5c98dfc Introduce EntryData to represent files with known contents 2024-06-27 15:14:27 +02:00
Kirill Bulatov
b16075d00c Enable "duplicate prompt" button (#13588)
Release Notes:

- N/A
2024-06-27 16:11:43 +03:00
Piotr Osiewicz
b4d3e7c2d3 Checkpoint 2024-06-27 14:26:56 +02:00
Piotr Osiewicz
75d50052f8 Merge branch 'main' into yarn-pwnage 2024-06-27 13:06:55 +02:00
Piotr Osiewicz
325602aa26 Containerisation 2024-06-27 12:31:44 +02:00
Piotr Osiewicz
da22e0dd0b Revert "vue: Release 0.0.4" (#13584)
Reverts zed-industries/zed#13580 as it turned out that the issue lied in
incorrect user settings.

Release notes:
- N/A
2024-06-27 11:36:17 +02:00
Tim Havlicek
fb3ef0d140 Add separate JSONC language (#12655)
Resolves https://github.com/zed-industries/extensions/issues/860 and
https://github.com/zed-industries/zed/issues/10921, also
https://github.com/biomejs/biome-zed/issues/11.

### Problem:
When opening .json files, zed allows comments by default in the JSON
language, which can cause some problems.
For example, language-servers also get "json" as the language, which may
show errors for those comments.

<img width="935" alt="image"
src="https://github.com/zed-industries/zed/assets/10381895/fed3d83d-abc0-44b5-9982-eb249bb04c3b">

### Solution:

This PR adds a JSONC language. 

<img width="816" alt="image"
src="https://github.com/zed-industries/zed/assets/10381895/8b40e671-d4f0-4e8d-80cb-82ee7c0ec490">

This allows for more specific configuration for language servers. 
Also any json file can be set explicitly to be JSONC using the
file_types setting:

```jsonc
{
  "file_types": {
    // set all .json files to be seen as JSONC
    "JSONC": ["*.json"]
  }
}
```


Release Notes:

- N/A
2024-06-27 11:12:02 +02:00
Piotr Osiewicz
e71b642f44 vue: Release 0.0.4 (#13580)
Respect user settings in initialization_options.


Release Notes:

- Fixed Vue extension not picking up user-provided initialization
options.
2024-06-27 11:11:22 +02:00
Piotr Osiewicz
ef0eab54f8 Remove fs_zip, start adjusting worktree 2024-06-27 10:49:24 +02:00
Jason Lee
6cedfa0ce7 example: Fix Input example mistake (#13574)
![CleanShot 2024-06-27 at 15 52
48](https://github.com/zed-industries/zed/assets/5518/71b25759-0cd5-40ed-b7c2-2f1045f81683)

Release Notes:

- N/A
2024-06-27 11:28:44 +03:00
Gilles Peiffer
209b1d1931 Code maintenance in the editor crate (#13565)
Release Notes:

- N/A
2024-06-27 09:40:48 +03:00
Gilles Peiffer
6986ac4c27 Use iterators instead of loops in clock.rs (#13561)
This should be slightly faster and makes the code easier to read.

Release Notes:

- N/A
2024-06-27 09:30:21 +03:00
Peter Tripp
d50d1611b9 Release notes upload fix (#13560)
- Action for release notes upload (softprops/action-gh-release) configured with incorrect key. 
- Valid keys here: https://github.com/softprops/action-gh-release?tab=readme-ov-file#-customizing
2024-06-26 17:24:59 -04:00
Gilles Peiffer
1260c616ba Simplify font feature tag validation (#13548)
Simplifies the logic for the changes of #13542.

Release Notes:

- N/A
2024-06-26 17:11:57 -04:00
Joseph T. Lyons
89951f7e66 Add shift shift to open command palette (#13556)
I've add `shift shift` as a default keybinding to open command palette,
when using JetBrains keymap, along with the already existing
`cmd-shift-a`. This isn't quite right, as in JetBrains, `cmd-shift-a`
opens the actions modal, which would be our command palette, and `shift
shift` actually opens up a view for searching everything, commands,
actions, settings, etc - we do not have a unified modal for these
things, so I think this is the best thing we can do. Some users might
want to change this to be our file picker, but I think adding it as the
default at least puts it on their radar that they can use this type of
binding; they can change it if they want.

Release Notes:

- Added `shift shift` as a default binding to open the command palette
in the JetBrains keymap.
2024-06-26 16:44:40 -04:00
Conrad Irwin
cd81dad2fa fix panics (#13554)
Release Notes:

- Fixed a panic when editing HTML near the end of a file
- Fixed a panic when editing settings.json from inside the .zed
directory
2024-06-26 14:32:16 -06:00
Piotr Osiewicz
3a08d7ab43 json: Fix package-version-server referencing the wrong path to the binary (#13555)
We were trying to access the binary at
package-version-server-{VERSION}/package-version-server, whereas the
binary itself is placed at package-version-server-{VERSION}

Release Notes:

- Fixed package.json language server failing to start.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-06-26 16:17:55 -04:00
Josef Zoller
49dc63812a Stop relying on binary location to be in libexec on Linux (#13374)
This fixes #13360 by adding fallback directories that are searched by
the CLI if the main executable cannot be found in the `libexec`
directory.

Release Notes:

- Added the fallback directories `lib/zed` and `lib/zed-editor` for the
main executable search in the CLI
([#13360](https://github.com/zed-industries/zed/issues/13360)).

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-06-26 13:00:52 -06:00
Tristan Hume
c0a3642f77 Improve prompt for Claude models (#13531)
This inline assistant prompt is one I designed that in my experience
works much better with Claude 3.5 Sonnet than the default prompt.

Mainly because it takes advantage of a weird property of our finetuning
which is that when you use XML tags it knows that it's doing a
machine-read tasks and stops trying to elide things for brevity. The
default prompt will often remove comments and otherwise add elisions for
brevity when doing large rewrites.

It also avoids giving the entire file content twice when the rewrite
region is large relative to the non-rewritten region.

Not necessarily meant to be merged as-is since it may mess up OAI
models. This is mainly meant for your reference. But everyone should be
using 3.5 Sonnet for coding use cases now anyhow 😛

Release Notes:

- N/A
2024-06-26 20:41:40 +02:00
Nate Butler
4d5441c09d Add UI setting components (#13550)
Adds some of the UI components to allow us to visually render settings.

These are UI only and are not functional yet (@maxdeviant will be
working on these when he is back.)

You can see some examples by running `script/storybook setting`.

![CleanShot 2024-06-26 at 12 38
37@2x](https://github.com/zed-industries/zed/assets/1714999/b5e6434d-3bc5-4fcd-9c0a-d280950cbef2)

Release Notes:

- N/A
2024-06-26 13:02:58 -04:00
Peter Tripp
2dc840132b v0.143.x dev 2024-06-26 12:20:15 -04:00
Fernando Tagawa
5d766f61fa linux: Fix some panics related to xkb compose (#13529)
Release Notes:

- N/A

Fixed #13463 Fixed crash when the locale was non UTF-8 and fixed the
fallback locale.
Fixed #13010 Fixed crash when `compose.keysym()` was `XKB_KEY_NoSymbol`

I also extracted the `xkb_compose_state` to a single place
2024-06-26 09:34:39 -06:00
张小白
18b4573064 Fix font feature tag validation (#13542)
The previous implementation that I implemented had two issues:
1. It did not throw an error when the user input some invalid values
such as "panic".
2. The feature tag for OpenType fonts should be a combination of letters
and digits. We only checked if the input was an ASCII character, which
could lead to undefined behavior.

Closes #13517 

Release Notes:

- N/A
2024-06-26 11:01:48 -04:00
Toshimaru
d044dc8485 Update Docker Compose configuration (#13530)
- Fix Docker Compose obsolete setting

## Remove `version`

Fix the following error:

```
WARN[0000] /docker-compose.yml: `version` is obsolete
```

see also.
https://github.com/compose-spec/compose-spec/blob/master/spec.md#version-top-level-element-obsolete

## Rename: docker-compose.yml -> compose.yml

The preferred file name is now `compose.yml`.

> The default path for a Compose file is compose.yaml (preferred)

ref.
https://docs.docker.com/compose/compose-application-model/#the-compose-file

Release Notes:

- N/A
2024-06-26 08:05:23 -04:00
Piotr Osiewicz
474da34946 Add fs_zip crate 2024-06-26 12:23:33 +02:00
Alexander Mankuta
f00bea5d0f docs: Fix Decrease buffer font size key binding (#13453)
Release Notes:

- N/A
2024-06-26 10:48:00 +03:00
Conrad Irwin
b43df6048b Add an input example to gpui (#13534)
Add a single-line text input example to gpui

(I'm hoping to be able to debug keyboard issues without rebuilding the
whole
app every time)

Release Notes:

- N/A
2024-06-25 22:06:50 -06:00
Conrad Irwin
eb914682b3 Fix multi-cursor copy/paste on linux (#13523)
The clipboard library we use for X11 doesn't yet support multiple
formats on the clipboard, so for now we just store this in memory for
the current zed process, as we do for Wayland.

Fixes: #11971

Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-06-25 14:54:52 -06:00
Joseph T. Lyons
5b7e31c075 Add metrics_id to editor_events (#13525)
Release Notes:

- N/A
2024-06-25 16:47:55 -04:00
ᴀᴍᴛᴏᴀᴇʀ
922fcaf5a6 Add the ability to customize available models for OpenAI-compatible services (#13276)
Closes #11984, closes #11075.

Release Notes:

- Added the ability to customize available models for OpenAI-compatible
services ([#11984](https://github.com/zed-industries/zed/issues/11984))
([#11075](https://github.com/zed-industries/zed/issues/11075)).


![image](https://github.com/zed-industries/zed/assets/32017007/01057e7b-1f21-49ad-a3ad-abc5282ffaf0)
2024-06-25 16:37:02 -04:00
Nate Butler
9f88460870 Move token count in prompt editor (#13524)
Moves the token count back up to the editor header.

Release Notes:

- N/A
2024-06-25 16:10:05 -04:00
Mikayla Maki
e5d1cf84cf Fix 9263 (#13521)
Fix #9263

Release Notes:

- N/A
2024-06-25 11:35:50 -07:00
Mikayla Maki
41d2c52638 Adjust keybindings for deletion in the project panel (#13326)
- Improve compatibility keybindings (Atom, JetBrains, TextMate)
- Revert MacOS cmd+backspace regression. Should trash without prompting (like MacOS)

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-06-25 14:21:44 -04:00
张小白
d1a55d64a8 Change window_min_size from Size<Pixels> to Option<Size<Pixels>> (#13501)
Now we can set `window_min_size` to `None` instead of `Size::default()`.
I think this makes more sense.

Release Notes:

- N/A
2024-06-25 12:09:08 -06:00
Shubham Kanodia
db06244972 typescript: Pass hostInfo to tsserver (#12055)
- Added `hostInfo` property to zed's typescript plugin. This can be
useful for telemetry (for e.g. identifying the usage of editors based on
typescript usage) when building typescript plugins.

- VSCode / IntelliJ based editors already set this property
([see](aa31bfc9fd/extensions/typescript-language-features/src/typescriptServiceClient.ts (L574)))

The config option as available —
https://github.com/typescript-language-server/typescript-language-server/blob/master/docs/configuration.md#initializationoptions

Release Notes:

- N/A
2024-06-25 13:51:30 -04:00
Marshall Bowers
597469bbbd Remove blank line (#13519)
This PR removes an extra blank line that was missed in #13518.

Release Notes:

- N/A
2024-06-25 13:11:25 -04:00
Marshall Bowers
e0c192d831 Clean up json! literal for vtsls configuration (#13518)
This PR cleans up the formatting of the `json!` literal used to provided
`vtsls` configuration.

Release Notes:

- N/A
2024-06-25 13:04:31 -04:00
Mikayla Maki
b2a0a7fa3c Fix a bug introduced by #13479 (#13516)
Fixes a bug introduced by
https://github.com/zed-industries/zed/pull/13479 where dot files might
not be processed in the correct order.

Release Notes:

- N/A
2024-06-25 10:03:29 -07:00
Piotr Osiewicz
ef20c801c6 WIP 2024-06-25 18:56:07 +02:00
Dov Alperin
0b1a589183 keymap: Allow modifiers as keys (#12047)
It is sometimes desirable to allow modifers to serve as keys themselves
for the purposes of keybinds. For example, the popular keybind in
jetbrains IDEs `shift shift` which opens the file finder.

This change treats modifers in the keymaps as keys themselves if they
are not accompanied by a key they are modifying.

Further this change wires up they key dispatcher to treat modifer change
events as key presses which are considered for matching against
keybinds.


Release Notes:

- Fixes #6460

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-06-25 10:17:23 -06:00
ᴀᴍᴛᴏᴀᴇʀ
7e694d1bcf Fix an issue where provider settings were lost when switching between Ollama models (#13402)
Closes #13399.

Release Notes:

- Fixed an issue where provider settings were lost when switching
between Ollama models
([#13399](https://github.com/zed-industries/zed/issues/13399)).
2024-06-25 11:58:13 -04:00
149 changed files with 4036 additions and 1555 deletions

View File

@@ -254,7 +254,7 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
body_file: target/release-notes.md
body_path: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

48
Cargo.lock generated
View File

@@ -2551,18 +2551,13 @@ name = "collab_ui"
version = "0.1.0"
dependencies = [
"anyhow",
"auto_update",
"call",
"channel",
"client",
"collections",
"command_palette",
"db",
"dev_server_projects",
"editor",
"emojis",
"extensions_ui",
"feedback",
"futures 0.3.28",
"fuzzy",
"gpui",
@@ -2575,7 +2570,6 @@ dependencies = [
"picker",
"pretty_assertions",
"project",
"recent_projects",
"release_channel",
"rich_text",
"rpc",
@@ -2587,15 +2581,14 @@ dependencies = [
"smallvec",
"story",
"theme",
"theme_selector",
"time",
"time_format",
"title_bar",
"tree-sitter-markdown",
"ui",
"util",
"vcs_menu",
"workspace",
"zed_actions",
]
[[package]]
@@ -4919,6 +4912,7 @@ dependencies = [
"taffy",
"thiserror",
"time",
"unicode-segmentation",
"usvg",
"util",
"uuid",
@@ -11045,6 +11039,41 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "title_bar"
version = "0.1.0"
dependencies = [
"auto_update",
"call",
"client",
"collections",
"command_palette",
"dev_server_projects",
"editor",
"extensions_ui",
"feedback",
"gpui",
"http 0.1.0",
"notifications",
"pretty_assertions",
"project",
"recent_projects",
"rpc",
"serde",
"settings",
"smallvec",
"story",
"theme",
"theme_selector",
"tree-sitter-markdown",
"ui",
"util",
"vcs_menu",
"windows 0.57.0",
"workspace",
"zed_actions",
]
[[package]]
name = "tokio"
version = "1.37.0"
@@ -13287,6 +13316,7 @@ name = "worktree"
version = "0.1.0"
dependencies = [
"anyhow",
"async_zip",
"clock",
"collections",
"env_logger",
@@ -13549,7 +13579,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.142.0"
version = "0.143.0"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -21,6 +21,7 @@ members = [
"crates/command_palette_hooks",
"crates/copilot",
"crates/db",
"crates/dev_server_projects",
"crates/diagnostics",
"crates/editor",
"crates/extension",
@@ -77,14 +78,11 @@ members = [
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/dev_server_projects",
"crates/repl",
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/rustdoc",
"crates/task",
"crates/tasks_ui",
"crates/search",
"crates/semantic_index",
"crates/semantic_version",
@@ -95,17 +93,20 @@ members = [
"crates/story",
"crates/storybook",
"crates/sum_tree",
"crates/tab_switcher",
"crates/supermaven",
"crates/supermaven_api",
"crates/tab_switcher",
"crates/task",
"crates/tasks_ui",
"crates/telemetry_events",
"crates/terminal",
"crates/terminal_view",
"crates/text",
"crates/theme",
"crates/theme_importer",
"crates/theme_selector",
"crates/telemetry_events",
"crates/time_format",
"crates/title_bar",
"crates/ui",
"crates/ui_text_field",
"crates/util",
@@ -175,6 +176,7 @@ command_palette_hooks = { path = "crates/command_palette_hooks" }
copilot = { path = "crates/copilot" }
dashmap = "5.5.3"
db = { path = "crates/db" }
dev_server_projects = { path = "crates/dev_server_projects" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
@@ -195,9 +197,9 @@ gpui_macros = { path = "crates/gpui_macros" }
headless = { path = "crates/headless" }
html_to_markdown = { path = "crates/html_to_markdown" }
http = { path = "crates/http" }
install_cli = { path = "crates/install_cli" }
image_viewer = { path = "crates/image_viewer" }
inline_completion_button = { path = "crates/inline_completion_button" }
install_cli = { path = "crates/install_cli" }
journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_selector = { path = "crates/language_selector" }
@@ -223,21 +225,17 @@ plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
prettier = { path = "crates/prettier" }
project = { path = "crates/project" }
proto = { path = "crates/proto" }
worktree = { path = "crates/worktree" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
proto = { path = "crates/proto" }
quick_action_bar = { path = "crates/quick_action_bar" }
recent_projects = { path = "crates/recent_projects" }
release_channel = { path = "crates/release_channel" }
dev_server_projects = { path = "crates/dev_server_projects" }
repl = { path = "crates/repl" }
rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
rustdoc = { path = "crates/rustdoc" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" }
@@ -245,20 +243,23 @@ settings = { path = "crates/settings" }
snippet = { path = "crates/snippet" }
sqlez = { path = "crates/sqlez" }
sqlez_macros = { path = "crates/sqlez_macros" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
story = { path = "crates/story" }
storybook = { path = "crates/storybook" }
sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
telemetry_events = { path = "crates/telemetry_events" }
terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
theme = { path = "crates/theme" }
theme_importer = { path = "crates/theme_importer" }
theme_selector = { path = "crates/theme_selector" }
telemetry_events = { path = "crates/telemetry_events" }
time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
ui = { path = "crates/ui" }
ui_text_field = { path = "crates/ui_text_field" }
util = { path = "crates/util" }
@@ -266,6 +267,7 @@ vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
welcome = { path = "crates/welcome" }
workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,92 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,92 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

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-chevrons-up-down"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>

After

Width:  |  Height:  |  Size: 276 B

1
assets/icons/font.svg Normal file
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-type"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" x2="15" y1="20" y2="20"/><line x1="12" x2="12" y1="4" y2="20"/></svg>

After

Width:  |  Height:  |  Size: 329 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-a-large-small"><path d="M21 14h-5"/><path d="M16 16v-3.5a2.5 2.5 0 0 1 5 0V16"/><path d="M4.5 13h6"/><path d="m3 16 4.5-9 4.5 9"/></svg>

After

Width:  |  Height:  |  Size: 339 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-bold"><path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"/></svg>

After

Width:  |  Height:  |  Size: 296 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="M4 13.6667H12" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 2.33333H12" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 11L8 5L11 11" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 9H10" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 539 B

1
assets/icons/visible.svg Normal file
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-eye"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>

After

Width:  |  Height:  |  Size: 301 B

View File

@@ -70,6 +70,14 @@
{
"context": "ProjectPanel",
"bindings": {
"a": "project_panel::NewFile",
"shift-a": "project_panel::NewDirectory",
"f2": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"shift-d": "project_panel::Duplicate",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
"ctrl-[": "project_panel::CollapseSelectedEntry",
"ctrl-b": "project_panel::CollapseSelectedEntry",
"alt-b": "project_panel::CollapseSelectedEntry",

View File

@@ -55,11 +55,13 @@
"up": "editor::MoveUp",
"ctrl-up": "editor::LineUp",
"ctrl-down": "editor::LineDown",
"pageup": "editor::PageUp",
"pageup": "editor::MovePageUp",
"alt-pageup": "editor::PageUp",
"shift-pageup": "editor::SelectPageUp",
"home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown",
"pagedown": "editor::PageDown",
"pagedown": "editor::MovePageDown",
"alt-pagedown": "editor::PageDown",
"shift-pagedown": "editor::SelectPageDown",
"end": "editor::MoveToEndOfLine",
"left": "editor::MoveLeft",
@@ -587,8 +589,9 @@
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
"backspace": "project_panel::Trash",
"delete": "project_panel::Trash",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFinder",
@@ -656,7 +659,13 @@
"pagedown": ["terminal::SendKeystroke", "pagedown"],
"escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown",
"shift-up": "terminal::ScrollLineUp",
"shift-down": "terminal::ScrollLineDown",
"shift-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom"
}
}
]

View File

@@ -61,13 +61,17 @@
"cmd-shift-z": "editor::Redo",
"up": "editor::MoveUp",
"ctrl-up": "editor::MoveToStartOfParagraph",
"pageup": "editor::PageUp",
"shift-pageup": "editor::MovePageUp",
"pageup": "editor::MovePageUp",
"shift-pageup": "editor::SelectPageUp",
"cmd-pageup": "editor::PageUp",
"ctrl-pageup": "editor::LineUp",
"home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown",
"ctrl-down": "editor::MoveToEndOfParagraph",
"pagedown": "editor::PageDown",
"shift-pagedown": "editor::MovePageDown",
"pagedown": "editor::MovePageDown",
"shift-pagedown": "editor::SelectPageDown",
"cmd-pagedown": "editor::PageDown",
"ctrl-pagedown": "editor::LineDown",
"end": "editor::MoveToEndOfLine",
"left": "editor::MoveLeft",
"right": "editor::MoveRight",
@@ -605,6 +609,7 @@
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"cmd-n": "project_panel::NewFile",
"cmd-d": "project_panel::Duplicate",
"alt-cmd-n": "project_panel::NewDirectory",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
@@ -614,8 +619,9 @@
"enter": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"cmd-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
@@ -697,7 +703,13 @@
"pagedown": ["terminal::SendKeystroke", "pagedown"],
"escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown",
"shift-up": "terminal::ScrollLineUp",
"shift-down": "terminal::ScrollLineDown",
"shift-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom"
}
}
]

View File

@@ -78,6 +78,7 @@
"bindings": {
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle",
"cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy"
@@ -94,6 +95,10 @@
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
}

View File

@@ -87,7 +87,15 @@
},
{
"context": "ProjectPanel",
"bindings": {}
"bindings": {
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-d": "project_panel::Duplicate",
"cmd-n": "project_panel::NewFolder",
"return": "project_panel::Rename",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
"cmd-alt-c": "project_panel::CopyPath"
}
},
{
"context": "Dock",

View File

@@ -47,19 +47,16 @@
"{": "vim::StartOfParagraph",
"}": "vim::EndOfParagraph",
"|": "vim::GoToColumn",
// Word motions
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",
"b": "vim::PreviousWordStart",
"g e": "vim::PreviousWordEnd",
// Subword motions
// "w": "vim::NextSubwordStart",
// "b": "vim::PreviousSubwordStart",
// "e": "vim::NextSubwordEnd",
// "g e": "vim::PreviousSubwordEnd",
"shift-w": [
"vim::NextWordStart",
{
@@ -78,8 +75,12 @@
"ignorePunctuation": true
}
],
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
"g shift-e": [
"vim::PreviousWordEnd",
{
"ignorePunctuation": true
}
],
"/": "vim::Search",
"g /": "pane::DeploySearch",
"?": [
@@ -125,16 +126,39 @@
}
}
],
"m": ["vim::PushOperator", "Mark"],
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
"m": [
"vim::PushOperator",
"Mark"
],
"'": [
"vim::PushOperator",
{
"Jump": {
"line": true
}
}
],
"`": [
"vim::PushOperator",
{
"Jump": {
"line": false
}
}
],
";": "vim::RepeatFind",
",": "vim::RepeatFindReversed",
"ctrl-o": "pane::GoBack",
"ctrl-i": "pane::GoForward",
"ctrl-]": "editor::GoToDefinition",
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"],
"escape": [
"vim::SwitchMode",
"Normal"
],
"ctrl-[": [
"vim::SwitchMode",
"Normal"
],
"v": "vim::ToggleVisual",
"shift-v": "vim::ToggleVisualLine",
"ctrl-v": "vim::ToggleVisualBlock",
@@ -260,7 +284,10 @@
// z commands
"z t": "editor::ScrollCursorTop",
"z z": "editor::ScrollCursorCenter",
"z .": ["workspace::SendKeystrokes", "z z ^"],
"z .": [
"workspace::SendKeystrokes",
"z z ^"
],
"z b": "editor::ScrollCursorBottom",
"z c": "editor::Fold",
"z o": "editor::UnfoldLines",
@@ -278,36 +305,123 @@
}
],
// Count support
"1": ["vim::Number", 1],
"2": ["vim::Number", 2],
"3": ["vim::Number", 3],
"4": ["vim::Number", 4],
"5": ["vim::Number", 5],
"6": ["vim::Number", 6],
"7": ["vim::Number", 7],
"8": ["vim::Number", 8],
"9": ["vim::Number", 9],
"1": [
"vim::Number",
1
],
"2": [
"vim::Number",
2
],
"3": [
"vim::Number",
3
],
"4": [
"vim::Number",
4
],
"5": [
"vim::Number",
5
],
"6": [
"vim::Number",
6
],
"7": [
"vim::Number",
7
],
"8": [
"vim::Number",
8
],
"9": [
"vim::Number",
9
],
// window related commands (ctrl-w X)
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w left": [
"workspace::ActivatePaneInDirection",
"Left"
],
"ctrl-w right": [
"workspace::ActivatePaneInDirection",
"Right"
],
"ctrl-w up": [
"workspace::ActivatePaneInDirection",
"Up"
],
"ctrl-w down": [
"workspace::ActivatePaneInDirection",
"Down"
],
"ctrl-w h": [
"workspace::ActivatePaneInDirection",
"Left"
],
"ctrl-w l": [
"workspace::ActivatePaneInDirection",
"Right"
],
"ctrl-w k": [
"workspace::ActivatePaneInDirection",
"Up"
],
"ctrl-w j": [
"workspace::ActivatePaneInDirection",
"Down"
],
"ctrl-w ctrl-h": [
"workspace::ActivatePaneInDirection",
"Left"
],
"ctrl-w ctrl-l": [
"workspace::ActivatePaneInDirection",
"Right"
],
"ctrl-w ctrl-k": [
"workspace::ActivatePaneInDirection",
"Up"
],
"ctrl-w ctrl-j": [
"workspace::ActivatePaneInDirection",
"Down"
],
"ctrl-w shift-left": [
"workspace::SwapPaneInDirection",
"Left"
],
"ctrl-w shift-right": [
"workspace::SwapPaneInDirection",
"Right"
],
"ctrl-w shift-up": [
"workspace::SwapPaneInDirection",
"Up"
],
"ctrl-w shift-down": [
"workspace::SwapPaneInDirection",
"Down"
],
"ctrl-w shift-h": [
"workspace::SwapPaneInDirection",
"Left"
],
"ctrl-w shift-l": [
"workspace::SwapPaneInDirection",
"Right"
],
"ctrl-w shift-k": [
"workspace::SwapPaneInDirection",
"Up"
],
"ctrl-w shift-j": [
"workspace::SwapPaneInDirection",
"Down"
],
"ctrl-w g t": "pane::ActivateNextItem",
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
"ctrl-w g shift-t": "pane::ActivatePrevItem",
@@ -329,9 +443,14 @@
"ctrl-w ctrl-q": "pane::CloseAllItems",
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w n": [
"workspace::NewFileInDirection",
"Up"
],
"ctrl-w ctrl-n": [
"workspace::NewFileInDirection",
"Up"
],
"ctrl-w d": "editor::GoToDefinitionSplit",
"ctrl-w g d": "editor::GoToDefinitionSplit",
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
@@ -353,12 +472,21 @@
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
"bindings": {
".": "vim::Repeat",
"c": ["vim::PushOperator", "Change"],
"c": [
"vim::PushOperator",
"Change"
],
"shift-c": "vim::ChangeToEndOfLine",
"d": ["vim::PushOperator", "Delete"],
"d": [
"vim::PushOperator",
"Delete"
],
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines",
"y": ["vim::PushOperator", "Yank"],
"y": [
"vim::PushOperator",
"Yank"
],
"shift-y": "vim::YankLine",
"i": "vim::InsertBefore",
"shift-i": "vim::InsertFirstNonWhitespace",
@@ -380,15 +508,36 @@
],
"u": "editor::Undo",
"ctrl-r": "editor::Redo",
"r": ["vim::PushOperator", "Replace"],
"r": [
"vim::PushOperator",
"Replace"
],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
">": ["vim::PushOperator", "Indent"],
"<": ["vim::PushOperator", "Outdent"],
"g u": ["vim::PushOperator", "Lowercase"],
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
"\"": ["vim::PushOperator", "Register"],
">": [
"vim::PushOperator",
"Indent"
],
"<": [
"vim::PushOperator",
"Outdent"
],
"g u": [
"vim::PushOperator",
"Lowercase"
],
"g shift-u": [
"vim::PushOperator",
"Uppercase"
],
"g ~": [
"vim::PushOperator",
"OppositeCase"
],
"\"": [
"vim::PushOperator",
"Register"
],
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
// tree-sitter related commands
@@ -403,7 +552,10 @@
{
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
"bindings": {
"\"": ["vim::PushOperator", "Register"],
"\"": [
"vim::PushOperator",
"Register"
],
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
@@ -412,7 +564,10 @@
{
"context": "Editor && VimCount && vim_mode != insert",
"bindings": {
"0": ["vim::Number", 0]
"0": [
"vim::Number",
0
]
}
},
{
@@ -463,7 +618,10 @@
{
"context": "Editor && vim_mode == normal && vim_operator == d",
"bindings": {
"s": ["vim::PushOperator", "DeleteSurrounds"]
"s": [
"vim::PushOperator",
"DeleteSurrounds"
]
}
},
{
@@ -585,10 +743,22 @@
"shift-i": "vim::InsertBefore",
"shift-a": "vim::InsertAfter",
"shift-j": "vim::JoinLines",
"r": ["vim::PushOperator", "Replace"],
"ctrl-c": ["vim::SwitchMode", "Normal"],
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"],
"r": [
"vim::PushOperator",
"Replace"
],
"ctrl-c": [
"vim::SwitchMode",
"Normal"
],
"escape": [
"vim::SwitchMode",
"Normal"
],
"ctrl-[": [
"vim::SwitchMode",
"Normal"
],
">": "vim::Indent",
"<": "vim::Outdent",
"i": [
@@ -636,7 +806,10 @@
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent",
"ctrl-r": ["vim::PushOperator", "Register"]
"ctrl-r": [
"vim::PushOperator",
"Register"
]
}
},
{
@@ -655,8 +828,14 @@
"bindings": {
"tab": "vim::Tab",
"enter": "vim::Enter",
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"]
"escape": [
"vim::SwitchMode",
"Normal"
],
"ctrl-[": [
"vim::SwitchMode",
"Normal"
]
}
},
{
@@ -676,7 +855,8 @@
{
"context": "EmptyPane || SharedScreen",
"bindings": {
":": "command_palette::Toggle"
":": "command_palette::Toggle",
"g /": "pane::DeploySearch"
}
},
{

View File

@@ -1,19 +1,15 @@
{
// The name of the Zed theme to use for the UI.
//
// The theme can also be set to follow system preferences:
//
// "theme": {
// "mode": "system",
// "light": "One Light",
// "dark": "One Dark"
// }
//
// Where `mode` is one of:
// `mode` is one of:
// - "system": Use the theme that corresponds to the system's appearance
// - "light": Use the theme indicated by the "light" field
// - "dark": Use the theme indicated by the "dark" field
"theme": "One Dark",
"theme": {
"mode": "system",
"light": "One Light",
"dark": "One Dark",
},
// The name of a base set of key bindings to use.
// This setting can take four values, each named after another
// text editor:
@@ -29,7 +25,7 @@
"inline_completion_provider": "copilot"
},
// The name of a font to use for rendering text in the editor
"buffer_font_family": "Zed Mono",
"buffer_font_family": "Zed Plex Mono",
// The OpenType features to enable for text in the editor.
"buffer_font_features": {
// Disable ligatures:
@@ -51,7 +47,8 @@
// },
"buffer_line_height": "comfortable",
// The name of a font to use for rendering text in the UI
"ui_font_family": ".SystemUIFont",
// (On macOS) You can set this to ".SysmtemUIFont" to use the system font
"ui_font_family": "Zed Plex Sans",
// The OpenType features to enable for text in the UI
"ui_font_features": {
// Disable ligatures:
@@ -409,7 +406,9 @@
// The list of language servers to use (or disable) for all languages.
//
// This is typically customized on a per-language basis.
"language_servers": ["..."],
"language_servers": [
"..."
],
// When to automatically save edited buffers. This setting can
// take four values.
//
@@ -544,7 +543,9 @@
},
"inline_completions": {
// A list of globs representing files that inline completions should be disabled for.
"disabled_globs": [".env"]
"disabled_globs": [
".env"
]
},
// Settings specific to journaling
"journal": {
@@ -655,7 +656,12 @@
// Default directories to search for virtual environments, relative
// to the current working directory. We recommend overriding this
// in your project's settings, rather than globally.
"directories": [".env", "env", ".venv", "venv"],
"directories": [
".env",
"env",
".venv",
"venv"
],
// Can also be `csh`, `fish`, and `nushell`
"activate_script": "default"
}
@@ -669,7 +675,7 @@
// "font_size": 15,
// Set the terminal's font family. If this option is not included,
// the terminal will default to matching the buffer's font family.
// "font_family": "Zed Mono",
// "font_family": "Zed Plex Mono",
// Sets the maximum number of lines in the terminal's scrollback buffer.
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
// Existing terminals will not pick up this change until they are recreated.
@@ -688,7 +694,12 @@
// "TOML": ["Embargo.lock"]
// }
//
"file_types": {},
"file_types": {
"JSONC": [
"**/.zed/**/*.json",
"**/zed/**/*.json"
]
},
// The extensions that Zed should automatically install on startup.
//
// If you don't want any of these extensions, add this field to your settings
@@ -701,7 +712,9 @@
"Astro": {
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-astro"]
"plugins": [
"prettier-plugin-astro"
]
}
},
"Blade": {
@@ -721,7 +734,12 @@
}
},
"Elixir": {
"language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."]
"language_servers": [
"elixir-ls",
"!next-ls",
"!lexical",
"..."
]
},
"Go": {
"code_actions_on_format": {
@@ -734,7 +752,12 @@
}
},
"HEEX": {
"language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."]
"language_servers": [
"elixir-ls",
"!next-ls",
"!lexical",
"..."
]
},
"HTML": {
"prettier": {
@@ -744,11 +767,17 @@
"Java": {
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-java"]
"plugins": [
"prettier-plugin-java"
]
}
},
"JavaScript": {
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"language_servers": [
"!typescript-language-server",
"vtsls",
"..."
],
"prettier": {
"allowed": true
}
@@ -767,11 +796,17 @@
"PHP": {
"prettier": {
"allowed": true,
"plugins": ["@prettier/plugin-php"]
"plugins": [
"@prettier/plugin-php"
]
}
},
"Ruby": {
"language_servers": ["solargraph", "!ruby-lsp", "..."]
"language_servers": [
"solargraph",
"!ruby-lsp",
"..."
]
},
"SCSS": {
"prettier": {
@@ -781,17 +816,25 @@
"SQL": {
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-sql"]
"plugins": [
"prettier-plugin-sql"
]
}
},
"Svelte": {
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-svelte"]
"plugins": [
"prettier-plugin-svelte"
]
}
},
"TSX": {
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"language_servers": [
"!typescript-language-server",
"vtsls",
"..."
],
"prettier": {
"allowed": true
}
@@ -802,7 +845,11 @@
}
},
"TypeScript": {
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"language_servers": [
"!typescript-language-server",
"vtsls",
"..."
],
"prettier": {
"allowed": true
}
@@ -815,7 +862,9 @@
"XML": {
"prettier": {
"allowed": true,
"plugins": ["@prettier/plugin-xml"]
"plugins": [
"@prettier/plugin-xml"
]
}
},
"YAML": {

View File

@@ -8,5 +8,10 @@
// from the command palette or from `Zed` application menu.
{
"ui_font_size": 16,
"buffer_font_size": 16
"buffer_font_size": 16,
"theme": {
"mode": "system",
"light": "One Light",
"dark": "One Dark",
},
}

View File

@@ -1,5 +1,3 @@
version: "3.7"
services:
postgres:
image: postgres:15

View File

@@ -52,4 +52,13 @@ impl Assets {
cx.text_system().add_fonts(embedded_fonts)
}
pub fn load_test_fonts(&self, cx: &AppContext) {
cx.text_system()
.add_fonts(vec![self
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
.unwrap()
.unwrap()])
.unwrap()
}
}

View File

@@ -169,6 +169,7 @@ pub enum AssistantProvider {
model: OpenAiModel,
api_url: String,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Vec<OpenAiModel>,
},
Anthropic {
model: AnthropicModel,
@@ -188,6 +189,7 @@ impl Default for AssistantProvider {
model: OpenAiModel::default(),
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
}
}
@@ -202,6 +204,7 @@ pub enum AssistantProviderContent {
default_model: Option<OpenAiModel>,
api_url: Option<String>,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Option<Vec<OpenAiModel>>,
},
#[serde(rename = "anthropic")]
Anthropic {
@@ -272,6 +275,7 @@ impl AssistantSettingsContent {
default_model: settings.default_open_ai_model.clone(),
api_url: Some(open_ai_api_url.clone()),
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
})
} else {
settings.default_open_ai_model.clone().map(|open_ai_model| {
@@ -279,6 +283,7 @@ impl AssistantSettingsContent {
default_model: Some(open_ai_model),
api_url: None,
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
}
})
},
@@ -326,6 +331,14 @@ impl AssistantSettingsContent {
*model = Some(new_model);
}
}
Some(AssistantProviderContent::Ollama {
default_model: model,
..
}) => {
if let LanguageModel::Ollama(new_model) = new_model {
*model = Some(new_model);
}
}
provider => match new_model {
LanguageModel::Cloud(model) => {
*provider = Some(AssistantProviderContent::ZedDotDev {
@@ -337,6 +350,7 @@ impl AssistantSettingsContent {
default_model: Some(model),
api_url: None,
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
})
}
LanguageModel::Anthropic(model) => {
@@ -481,15 +495,18 @@ impl Settings for AssistantSettings {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
AssistantProviderContent::OpenAi {
default_model: model_override,
api_url: api_url_override,
low_speed_timeout_in_seconds: low_speed_timeout_in_seconds_override,
available_models: available_models_override,
},
) => {
merge(model, model_override);
merge(api_url, api_url_override);
merge(available_models, available_models_override);
if let Some(low_speed_timeout_in_seconds_override) =
low_speed_timeout_in_seconds_override
{
@@ -550,10 +567,12 @@ impl Settings for AssistantSettings {
default_model: model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => AssistantProvider::OpenAi {
model: model.unwrap_or_default(),
api_url: api_url.unwrap_or_else(|| open_ai::OPEN_AI_API_URL.into()),
low_speed_timeout_in_seconds,
available_models: available_models.unwrap_or_default(),
},
AssistantProviderContent::Anthropic {
default_model: model,
@@ -610,6 +629,7 @@ mod tests {
model: OpenAiModel::FourOmni,
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);
@@ -632,6 +652,7 @@ mod tests {
model: OpenAiModel::FourOmni,
api_url: "test-url".into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);
SettingsStore::update_global(cx, |store, cx| {
@@ -652,6 +673,7 @@ mod tests {
model: OpenAiModel::Four,
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);

View File

@@ -24,6 +24,20 @@ use settings::{Settings, SettingsStore};
use std::sync::Arc;
use std::time::Duration;
/// Choose which model to use for openai provider.
/// If the model is not available, try to use the first available model, or fallback to the original model.
fn choose_openai_model(
model: &::open_ai::Model,
available_models: &[::open_ai::Model],
) -> ::open_ai::Model {
available_models
.iter()
.find(|&m| m == model)
.or_else(|| available_models.first())
.unwrap_or_else(|| model)
.clone()
}
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
let mut settings_version = 0;
let provider = match &AssistantSettings::get_global(cx).provider {
@@ -34,8 +48,9 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
model.clone(),
choose_openai_model(model, available_models),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
@@ -77,10 +92,11 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
provider.update(
model.clone(),
choose_openai_model(model, available_models),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
@@ -136,10 +152,11 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
*provider = CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
model.clone(),
choose_openai_model(model, available_models),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
@@ -201,10 +218,10 @@ impl CompletionProvider {
cx.global::<Self>()
}
pub fn available_models(&self) -> Vec<LanguageModel> {
pub fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
match self {
CompletionProvider::OpenAi(provider) => provider
.available_models()
.available_models(cx)
.map(LanguageModel::OpenAi)
.collect(),
CompletionProvider::Anthropic(provider) => provider

View File

@@ -236,7 +236,7 @@ pub fn preprocess_anthropic_request(request: &mut LanguageModelRequest) {
}
if !system_message.is_empty() {
request.messages.insert(
new_messages.insert(
0,
LanguageModelRequestMessage {
role: Role::System,

View File

@@ -1,4 +1,5 @@
use crate::assistant_settings::CloudModel;
use crate::assistant_settings::{AssistantProvider, AssistantSettings};
use crate::{
assistant_settings::OpenAiModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
};
@@ -56,8 +57,26 @@ impl OpenAiCompletionProvider {
self.settings_version = settings_version;
}
pub fn available_models(&self) -> impl Iterator<Item = OpenAiModel> {
OpenAiModel::iter()
pub fn available_models(&self, cx: &AppContext) -> impl Iterator<Item = OpenAiModel> {
if let AssistantProvider::OpenAi {
available_models, ..
} = &AssistantSettings::get_global(cx).provider
{
if !available_models.is_empty() {
// available_models is set, just return it
return available_models.clone().into_iter();
}
}
let available_models = if matches!(self.model, OpenAiModel::Custom { .. }) {
// available_models is not set but the default model is set to custom, only show custom
vec![self.model.clone()]
} else {
// default case, use all models except custom
OpenAiModel::iter()
.filter(|model| !matches!(model, OpenAiModel::Custom { .. }))
.collect()
};
available_models.into_iter()
}
pub fn settings_version(&self) -> usize {
@@ -213,7 +232,8 @@ pub fn count_open_ai_tokens(
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Opus)
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
| LanguageModel::Cloud(CloudModel::Claude3Haiku)
| LanguageModel::OpenAi(OpenAiModel::Custom { .. }) => {
// Tiktoken doesn't yet support these models, so we manually use the
// same tokenizer as GPT-4.
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)

View File

@@ -1298,7 +1298,8 @@ impl Render for PromptEditor {
PopoverMenu::new("model-switcher")
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::global(cx).available_models() {
for model in CompletionProvider::global(cx).available_models(cx)
{
menu = menu.custom_entry(
{
let model = model.clone();

View File

@@ -23,7 +23,7 @@ impl RenderOnce for ModelSelector {
.with_handle(self.handle)
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::global(cx).available_models() {
for model in CompletionProvider::global(cx).available_models(cx) {
menu = menu.custom_entry(
{
let model = model.clone();

View File

@@ -5,7 +5,7 @@ use crate::{
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandRegistry;
use chrono::{DateTime, Utc};
use collections::HashMap;
use collections::{HashMap, HashSet};
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
use futures::{
future::{self, BoxFuture, Shared},
@@ -34,7 +34,7 @@ use std::{
use theme::ThemeSettings;
use ui::{
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
SharedString, Styled, Tooltip, ViewContext, VisualContext,
};
use util::{ResultExt, TryFutureExt};
use uuid::Uuid;
@@ -42,7 +42,12 @@ use workspace::Workspace;
actions!(
prompt_library,
[NewPrompt, DeletePrompt, ToggleDefaultPrompt]
[
NewPrompt,
DeletePrompt,
DuplicatePrompt,
ToggleDefaultPrompt
]
);
/// Init starts loading the PromptStore in the background and assigns
@@ -411,6 +416,12 @@ impl PromptLibrary {
}
}
pub fn duplicate_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.duplicate_prompt(active_prompt_id, cx);
}
}
pub fn toggle_default_for_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.toggle_default_for_prompt(active_prompt_id, cx);
@@ -564,6 +575,47 @@ impl PromptLibrary {
}
}
pub fn duplicate_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(prompt) = self.prompt_editors.get(&prompt_id) {
const DUPLICATE_SUFFIX: &str = " copy";
let title_to_duplicate = prompt.title_editor.read(cx).text(cx);
let existing_titles = self
.prompt_editors
.iter()
.filter(|&(&id, _)| id != prompt_id)
.map(|(_, prompt_editor)| prompt_editor.title_editor.read(cx).text(cx))
.filter(|title| title.starts_with(&title_to_duplicate))
.collect::<HashSet<_>>();
let title = if existing_titles.is_empty() {
title_to_duplicate + DUPLICATE_SUFFIX
} else {
let mut i = 1;
loop {
let new_title = format!("{title_to_duplicate}{DUPLICATE_SUFFIX} {i}");
if !existing_titles.contains(&new_title) {
break new_title;
}
i += 1;
}
};
let new_id = PromptId::new();
let body = prompt.body_editor.read(cx).text(cx);
let save = self
.store
.save(new_id, Some(title.into()), false, body.into());
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.spawn(|this, mut cx| async move {
save.await?;
this.update(&mut cx, |prompt_library, cx| {
prompt_library.load_prompt(new_id, true, cx)
})
})
.detach_and_log_err(cx);
}
}
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
if let Some(active_prompt) = self.active_prompt_id {
self.prompt_editors[&active_prompt]
@@ -721,7 +773,7 @@ impl PromptLibrary {
.child(
h_flex()
.p(Spacing::Small.rems(cx))
.h(TitleBar::height(cx))
.h_9()
.w_full()
.flex_none()
.justify_end()
@@ -841,6 +893,42 @@ impl PromptLibrary {
h_flex()
.h_full()
.gap(Spacing::XXLarge.rems(cx))
.children(prompt_editor.token_count.map(
|token_count| {
let token_count: SharedString =
token_count.to_string().into();
let label_token_count: SharedString =
token_count.to_string().into();
h_flex()
.id("token_count")
.tooltip(move |cx| {
let token_count =
token_count.clone();
Tooltip::with_meta(
format!(
"{} tokens",
token_count.clone()
),
None,
format!(
"Model: {}",
current_model
.display_name()
),
cx,
)
})
.child(
Label::new(format!(
"{} tokens",
label_token_count.clone()
))
.color(Color::Muted),
)
},
))
.child(
IconButton::new(
"delete-prompt",
@@ -861,24 +949,28 @@ impl PromptLibrary {
cx.dispatch_action(Box::new(DeletePrompt));
}),
)
// .child(
// IconButton::new(
// "duplicate-prompt",
// IconName::BookCopy,
// )
// .size(ButtonSize::Large)
// .style(ButtonStyle::Transparent)
// .shape(IconButtonShape::Square)
// .size(ButtonSize::Large)
// .tooltip(move |cx| {
// Tooltip::for_action(
// "Duplicate Prompt",
// &gpui::NoAction,
// cx,
// )
// })
// .disabled(true),
// )
.child(
IconButton::new(
"duplicate-prompt",
IconName::BookCopy,
)
.size(ButtonSize::Large)
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
Tooltip::for_action(
"Duplicate Prompt",
&DuplicatePrompt,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
DuplicatePrompt,
));
}),
)
.child(
IconButton::new(
"toggle-default-prompt",
@@ -920,38 +1012,7 @@ impl PromptLibrary {
.on_action(cx.listener(Self::move_up_from_body))
.flex_grow()
.h_full()
.child(prompt_editor.body_editor.clone())
.children(prompt_editor.token_count.map(|token_count| {
let token_count: SharedString = token_count.to_string().into();
let label_token_count: SharedString =
token_count.to_string().into();
h_flex()
.id("token_count")
.absolute()
.bottom_1()
.right_4()
.flex_initial()
.px_2()
.py_1()
.tooltip(move |cx| {
let token_count = token_count.clone();
Tooltip::with_meta(
format!("{} tokens", token_count.clone()),
None,
format!("Model: {}", current_model.display_name()),
cx,
)
})
.child(
Label::new(format!(
"{} tokens",
label_token_count.clone()
))
.color(Color::Muted),
)
})),
.child(prompt_editor.body_editor.clone()),
),
)
}))
@@ -968,6 +1029,7 @@ impl Render for PromptLibrary {
.key_context("PromptLibrary")
.on_action(cx.listener(|this, &NewPrompt, cx| this.new_prompt(cx)))
.on_action(cx.listener(|this, &DeletePrompt, cx| this.delete_active_prompt(cx)))
.on_action(cx.listener(|this, &DuplicatePrompt, cx| this.duplicate_active_prompt(cx)))
.on_action(cx.listener(|this, &ToggleDefaultPrompt, cx| {
this.toggle_default_for_active_prompt(cx)
}))

View File

@@ -6,118 +6,106 @@ pub fn generate_content_prompt(
language_name: Option<&str>,
buffer: BufferSnapshot,
range: Range<usize>,
project_name: Option<String>,
_project_name: Option<String>,
) -> anyhow::Result<String> {
let mut prompt = String::new();
let content_type = match language_name {
None | Some("Markdown" | "Plain Text") => {
writeln!(prompt, "You are an expert engineer.")?;
"Text"
}
Some(language_name) => {
writeln!(prompt, "You are an expert {language_name} engineer.")?;
writeln!(
prompt,
"Your answer MUST always and only be valid {}.",
language_name
"Here's a file of text that I'm going to ask you to make an edit to."
)?;
"Code"
"text"
}
Some(language_name) => {
writeln!(
prompt,
"Here's a file of {language_name} that I'm going to ask you to make an edit to."
)?;
"code"
}
};
if let Some(project_name) = project_name {
writeln!(
prompt,
"You are currently working inside the '{project_name}' project in code editor Zed."
)?;
}
writeln!(
prompt,
"The user has the following file open in the editor:"
)?;
const MAX_CTX: usize = 50000;
let mut is_truncated = false;
if range.is_empty() {
write!(prompt, "```")?;
if let Some(language_name) = language_name {
write!(prompt, "{language_name}")?;
}
for chunk in buffer.as_rope().chunks_in_range(0..range.start) {
prompt.push_str(chunk);
}
prompt.push_str("<|CURSOR|>");
for chunk in buffer.as_rope().chunks_in_range(range.start..buffer.len()) {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"Assume the cursor is located where the `<|CURSOR|>` span is."
)
.unwrap();
writeln!(
prompt,
"{content_type} can't be replaced, so assume your answer will be inserted at the cursor.",
)
.unwrap();
writeln!(
prompt,
"Generate {content_type} based on the users prompt: {user_prompt}",
)
.unwrap();
prompt.push_str("The point you'll need to insert at is marked with <insert_here></insert_here>.\n\n<document>");
} else {
write!(prompt, "```")?;
for chunk in buffer.as_rope().chunks() {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"In particular, the following piece of text is selected:"
)?;
write!(prompt, "```")?;
if let Some(language_name) = language_name {
write!(prompt, "{language_name}")?;
}
prompt.push('\n');
prompt.push_str("The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.\n\n<document>");
}
// Include file content.
let before_range = 0..range.start;
let truncated_before = if before_range.len() > MAX_CTX {
is_truncated = true;
range.start - MAX_CTX..range.start
} else {
before_range
};
let mut non_rewrite_len = truncated_before.len();
for chunk in buffer.text_for_range(truncated_before) {
prompt.push_str(chunk);
}
if !range.is_empty() {
prompt.push_str("<rewrite_this>\n");
for chunk in buffer.text_for_range(range.clone()) {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"Modify the user's selected {content_type} based upon the users prompt: {user_prompt}"
)
.unwrap();
writeln!(
prompt,
"You must reply with only the adjusted {content_type}, not the entire file."
)
.unwrap();
prompt.push_str("\n<rewrite_this>");
} else {
prompt.push_str("<insert_here></insert_here>");
}
let after_range = range.end..buffer.len();
let truncated_after = if after_range.len() > MAX_CTX {
is_truncated = true;
range.end..range.end + MAX_CTX
} else {
after_range
};
non_rewrite_len += truncated_after.len();
for chunk in buffer.text_for_range(truncated_after) {
prompt.push_str(chunk);
}
writeln!(prompt, "Never make remarks about the output.").unwrap();
writeln!(
prompt,
"Do not return anything else, except the generated {content_type}."
)
.unwrap();
write!(prompt, "</document>\n\n").unwrap();
if is_truncated {
writeln!(prompt, "The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.\n")?;
}
if range.is_empty() {
writeln!(
prompt,
"You can't replace {content_type}, your answer will be inserted in place of the `<insert_here></insert_here>` tags. Don't include the insert_here tags in your output.",
)
.unwrap();
writeln!(
prompt,
"Generate {content_type} based on the following prompt:\n\n<prompt>\n{user_prompt}\n</prompt>",
)
.unwrap();
writeln!(prompt, "Match the indentation in the original file in the inserted {content_type}, don't include any indentation on blank lines.\n").unwrap();
prompt.push_str("Immediately start with the following format with no remarks:\n\n```\n{{INSERTED_CODE}}\n```");
} else {
writeln!(prompt, "Edit the section of {content_type} in <rewrite_this></rewrite_this> tags based on the following prompt:'").unwrap();
writeln!(prompt, "\n<prompt>\n{user_prompt}\n</prompt>\n").unwrap();
let rewrite_len = range.end - range.start;
if rewrite_len < 20000 && rewrite_len * 2 < non_rewrite_len {
writeln!(prompt, "And here's the section to rewrite based on that prompt again for reference:\n\n<rewrite_this>\n").unwrap();
for chunk in buffer.text_for_range(range.clone()) {
prompt.push_str(chunk);
}
writeln!(prompt, "\n</rewrite_this>\n").unwrap();
}
writeln!(prompt, "Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {content_type} will be preserved.\n").unwrap();
write!(
prompt,
"Start at the indentation level in the original file in the rewritten {content_type}. "
)
.unwrap();
prompt.push_str("Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.");
prompt.push_str("\n\nImmediately start with the following format with no remarks:\n\n```\n{{REWRITTEN_CODE}}\n```");
}
Ok(prompt)
}

View File

@@ -221,11 +221,11 @@ fn collect_files(
.unwrap_or_default()
.to_string();
if entry.is_dir() {
if entry.is_container() {
// Auto-fold directories that contain no files
let mut child_entries = snapshot.child_entries(&entry.path);
if let Some(child) = child_entries.next() {
if child_entries.next().is_none() && child.kind.is_dir() {
if child_entries.next().is_none() && child.kind.is_container() {
if is_top_level_directory {
is_top_level_directory = false;
folded_directory_names_stack.push(

View File

@@ -196,23 +196,24 @@ mod linux {
impl Detect {
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
let path = if let Some(path) = path {
path.to_path_buf().canonicalize()
path.to_path_buf().canonicalize()?
} else {
let cli = env::current_exe()?;
let dir = cli
.parent()
.and_then(Path::parent)
.ok_or_else(|| anyhow!("no parent path for cli"))?;
match dir.join("libexec").join("zed-editor").canonicalize() {
Ok(path) => Ok(path),
// In development cli and zed are in the ./target/ directory together
Err(e) => match cli.parent().unwrap().join("zed").canonicalize() {
Ok(path) if path != cli => Ok(path),
_ => Err(e),
},
}
}?;
// libexec is the standard, lib/zed is for Arch (and other non-libexec distros),
// ./zed is for the target directory in development builds.
let possible_locations =
["../libexec/zed-editor", "../lib/zed/zed-editor", "./zed"];
possible_locations
.iter()
.find_map(|p| dir.join(p).canonicalize().ok().filter(|path| path != &cli))
.ok_or_else(|| {
anyhow!("could not find any of: {}", possible_locations.join(", "))
})?
};
Ok(App(path))
}

View File

@@ -611,6 +611,7 @@ impl Telemetry {
let request_body = EventRequestBody {
installation_id: state.installation_id.as_deref().map(Into::into),
metrics_id: state.metrics_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
is_staff: state.is_staff,
app_version: state.app_version.clone(),

View File

@@ -87,51 +87,27 @@ impl Global {
}
pub fn observed_any(&self, other: &Self) -> bool {
let mut lhs = self.0.iter();
let mut rhs = other.0.iter();
loop {
if let Some(left) = lhs.next() {
if let Some(right) = rhs.next() {
if *right > 0 && left >= right {
return true;
}
} else {
return false;
}
} else {
return false;
}
}
self.0
.iter()
.zip(other.0.iter())
.any(|(left, right)| *right > 0 && left >= right)
}
pub fn observed_all(&self, other: &Self) -> bool {
let mut lhs = self.0.iter();
let mut rhs = other.0.iter();
loop {
if let Some(left) = lhs.next() {
if let Some(right) = rhs.next() {
if left < right {
return false;
}
} else {
return true;
}
} else {
return rhs.next().is_none();
}
}
self.0.iter().all(|left| match rhs.next() {
Some(right) => left >= right,
None => true,
}) && rhs.next().is_none()
}
pub fn changed_since(&self, other: &Self) -> bool {
if self.0.len() > other.0.len() {
return true;
}
for (left, right) in self.0.iter().zip(other.0.iter()) {
if left > right {
return true;
}
}
false
self.0.len() > other.0.len()
|| self
.0
.iter()
.zip(other.0.iter())
.any(|(left, right)| left > right)
}
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {

View File

@@ -664,6 +664,7 @@ where
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
installation_id: String,
metrics_id: String,
operation: String,
app_version: String,
file_extension: String,
@@ -713,6 +714,7 @@ impl EditorEventRow {
os_version: body.os_version.clone().unwrap_or_default(),
architecture: body.architecture.clone(),
installation_id: body.installation_id.clone().unwrap_or_default(),
metrics_id: body.metrics_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),

View File

@@ -306,7 +306,9 @@ impl RandomizedTest for ProjectCollaborationTest {
let worktree = worktree.read(cx);
worktree.is_visible()
&& worktree.entries(false, 0).any(|e| e.is_file())
&& worktree.root_entry().map_or(false, |e| e.is_dir())
&& worktree
.root_entry()
.map_or(false, |e| e.is_container())
})
.choose(rng)
});

View File

@@ -30,17 +30,13 @@ test-support = [
[dependencies]
anyhow.workspace = true
auto_update.workspace = true
call.workspace = true
channel.workspace = true
client.workspace = true
collections.workspace = true
command_palette.workspace = true
db.workspace = true
editor.workspace = true
emojis.workspace = true
extensions_ui.workspace = true
feedback.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
@@ -51,8 +47,6 @@ notifications.workspace = true
parking_lot.workspace = true
picker.workspace = true
project.workspace = true
recent_projects.workspace = true
dev_server_projects.workspace = true
release_channel.workspace = true
rich_text.workspace = true
rpc.workspace = true
@@ -64,14 +58,13 @@ settings.workspace = true
smallvec.workspace = true
story = { workspace = true, optional = true }
theme.workspace = true
theme_selector.workspace = true
time_format.workspace = true
time.workspace = true
title_bar.workspace = true
ui.workspace = true
util.workspace = true
vcs_menu.workspace = true
workspace.workspace = true
zed_actions.workspace = true
[dev-dependencies]
call = { workspace = true, features = ["test-support"] }

View File

@@ -2,10 +2,7 @@ mod channel_modal;
mod contact_finder;
use self::channel_modal::ChannelModal;
use crate::{
channel_view::ChannelView, chat_panel::ChatPanel, face_pile::FacePile,
CollaborationPanelSettings,
};
use crate::{channel_view::ChannelView, chat_panel::ChatPanel, CollaborationPanelSettings};
use call::ActiveCall;
use channel::{Channel, ChannelEvent, ChannelStore};
use client::{ChannelId, Client, Contact, ProjectId, User, UserStore};
@@ -34,7 +31,8 @@ use std::{mem, sync::Arc};
use theme::{ActiveTheme, ThemeSettings};
use ui::{
prelude::*, tooltip_container, Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu,
Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem, Tooltip,
Facepile, Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem,
Tooltip,
};
use util::{maybe, ResultExt, TryFutureExt};
use workspace::{
@@ -2542,7 +2540,7 @@ impl CollabPanel {
None
} else {
let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
let result = FacePile::new(
let result = Facepile::new(
participants
.iter()
.map(|user| Avatar::new(user.avatar_uri.clone()).into_any_element())

View File

@@ -1,20 +1,16 @@
pub mod channel_view;
pub mod chat_panel;
pub mod collab_panel;
mod collab_titlebar_item;
mod face_pile;
pub mod notification_panel;
pub mod notifications;
mod panel_settings;
use std::{rc::Rc, sync::Arc};
use call::{report_call_event_for_room, ActiveCall};
pub use collab_panel::CollabPanel;
pub use collab_titlebar_item::CollabTitlebarItem;
use gpui::{
actions, point, AppContext, Pixels, PlatformDisplay, Size, Task, WindowBackgroundAppearance,
WindowBounds, WindowContext, WindowKind, WindowOptions,
point, AppContext, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds,
WindowKind, WindowOptions,
};
use panel_settings::MessageEditorSettings;
pub use panel_settings::{
@@ -23,12 +19,7 @@ pub use panel_settings::{
use release_channel::ReleaseChannel;
use settings::Settings;
use ui::px;
use workspace::{notifications::DetachAndPromptErr, AppState};
actions!(
collab,
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
);
use workspace::AppState;
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
CollaborationPanelSettings::register(cx);
@@ -36,63 +27,13 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
NotificationPanelSettings::register(cx);
MessageEditorSettings::register(cx);
vcs_menu::init(cx);
collab_titlebar_item::init(cx);
collab_panel::init(cx);
channel_view::init(cx);
chat_panel::init(cx);
collab_panel::init(cx);
notification_panel::init(cx);
notifications::init(&app_state, cx);
}
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut WindowContext) {
let call = ActiveCall::global(cx).read(cx);
if let Some(room) = call.room().cloned() {
let client = call.client();
let toggle_screen_sharing = room.update(cx, |room, cx| {
if room.is_screen_sharing() {
report_call_event_for_room(
"disable screen share",
room.id(),
room.channel_id(),
&client,
);
Task::ready(room.unshare_screen(cx))
} else {
report_call_event_for_room(
"enable screen share",
room.id(),
room.channel_id(),
&client,
);
room.share_screen(cx)
}
});
toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", cx, |e, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e)));
}
}
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
let call = ActiveCall::global(cx).read(cx);
if let Some(room) = call.room().cloned() {
let client = call.client();
room.update(cx, |room, cx| {
let operation = if room.is_muted() {
"enable microphone"
} else {
"disable microphone"
};
report_call_event_for_room(operation, room.id(), room.channel_id(), &client);
room.toggle_mute(cx)
});
}
}
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
room.update(cx, |room, cx| room.toggle_deafen(cx));
}
title_bar::init(cx);
vcs_menu::init(cx);
}
fn notification_window_options(
@@ -124,6 +65,6 @@ fn notification_window_options(
display_id: Some(screen.id()),
window_background: WindowBackgroundAppearance::default(),
app_id: Some(app_id.to_owned()),
window_min_size: Size::default(),
window_min_size: None,
}
}

View File

@@ -102,6 +102,9 @@ impl Render for ProjectDiagnosticsEditor {
div()
.track_focus(&self.focus_handle)
.when(self.path_states.is_empty(), |el| {
el.key_context("EmptyPane")
})
.size_full()
.on_action(cx.listener(Self::toggle_warnings))
.child(child)

View File

@@ -720,8 +720,7 @@ impl DisplaySnapshot {
if let Some(severity) = chunk.diagnostic_severity {
// Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
let diagnostic_color =
super::diagnostic_style(severity, true, &editor_style.status);
let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
diagnostic_highlight.underline = Some(UnderlineStyle {
color: Some(diagnostic_color),
thickness: 1.0.into(),
@@ -957,16 +956,18 @@ impl DisplaySnapshot {
return false;
}
for next_row in (buffer_row.0 + 1)..=max_row.0 {
let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
if next_line_indent.raw_len() > line_indent.raw_len() {
return true;
} else if !next_line_indent.is_line_blank() {
break;
}
}
false
(buffer_row.0 + 1..=max_row.0)
.find_map(|next_row| {
let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
if next_line_indent.raw_len() > line_indent.raw_len() {
Some(true)
} else if !next_line_indent.is_line_blank() {
Some(false)
} else {
None
}
})
.unwrap_or(false)
}
pub fn foldable_range(

View File

@@ -1221,7 +1221,7 @@ mod tests {
use super::*;
use crate::display_map::inlay_map::InlayMap;
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
use gpui::{div, font, px, AssetSource, Element};
use gpui::{div, font, px, Element};
use multi_buffer::MultiBuffer;
use rand::prelude::*;
use settings::SettingsStore;
@@ -2014,12 +2014,7 @@ mod tests {
let settings = SettingsStore::test(cx);
cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx);
cx.text_system()
.add_fonts(vec![assets::Assets
.load("fonts/zed-mono/zed-mono-extended.ttf")
.unwrap()
.unwrap()])
.unwrap();
assets::Assets.load_test_fonts(cx);
}
impl TransformBlock {

View File

@@ -2131,7 +2131,7 @@ impl Editor {
self.refresh_inline_completion(false, cx);
}
pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&str> {
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
self.placeholder_text.as_deref()
}
@@ -2914,6 +2914,9 @@ impl Editor {
let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
let end_offset = start_offset + end_difference;
let start_offset = start_offset + start_difference;
if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
continue;
}
let start = buffer_snapshot.anchor_after(start_offset);
let end = buffer_snapshot.anchor_after(end_offset);
linked_edits
@@ -6809,6 +6812,16 @@ impl Editor {
return;
}
if self
.context_menu
.write()
.as_mut()
.map(|menu| menu.select_first(self.project.as_ref(), cx))
.unwrap_or(false)
{
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
cx.propagate();
return;
@@ -8813,13 +8826,7 @@ impl Editor {
let display_point = initial_point.to_display_point(snapshot);
let mut hunks = hunks
.map(|hunk| diff_hunk_to_display(&hunk, &snapshot))
.filter(|hunk| {
if is_wrapped {
true
} else {
!hunk.contains_display_row(display_point.row())
}
})
.filter(|hunk| is_wrapped || !hunk.contains_display_row(display_point.row()))
.dedup();
if let Some(hunk) = hunks.next() {
@@ -12390,6 +12397,7 @@ impl ViewInputHandler for Editor {
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx
.text_system()
.typographic_bounds(font_id, font_size, 'm')
@@ -12517,7 +12525,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
let group_id: SharedString = cx.block_id.to_string().into();
let mut text_style = cx.text_style().clone();
text_style.color = diagnostic_style(diagnostic.severity, true, cx.theme().status());
text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
let theme_settings = ThemeSettings::get_global(cx);
text_style.font_family = theme_settings.buffer_font.family.clone();
text_style.font_style = theme_settings.buffer_font.style;
@@ -12613,25 +12621,19 @@ pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, V
prev_offset = ix + 1;
if in_code_block {
code_ranges.push(prev_len..text_without_backticks.len());
in_code_block = false;
} else {
in_code_block = true;
}
in_code_block = !in_code_block;
}
(text_without_backticks.into(), code_ranges)
}
fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
match (severity, valid) {
(DiagnosticSeverity::ERROR, true) => colors.error,
(DiagnosticSeverity::ERROR, false) => colors.error,
(DiagnosticSeverity::WARNING, true) => colors.warning,
(DiagnosticSeverity::WARNING, false) => colors.warning,
(DiagnosticSeverity::INFORMATION, true) => colors.info,
(DiagnosticSeverity::INFORMATION, false) => colors.info,
(DiagnosticSeverity::HINT, true) => colors.info,
(DiagnosticSeverity::HINT, false) => colors.info,
fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
match severity {
DiagnosticSeverity::ERROR => colors.error,
DiagnosticSeverity::WARNING => colors.warning,
DiagnosticSeverity::INFORMATION => colors.info,
DiagnosticSeverity::HINT => colors.info,
_ => colors.ignored,
}
}

View File

@@ -10,8 +10,8 @@ use crate::{
};
use futures::StreamExt;
use gpui::{
div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
WindowBounds, WindowOptions,
div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
WindowOptions,
};
use indoc::indoc;
use language::{
@@ -12489,12 +12489,7 @@ pub(crate) fn update_test_project_settings(
pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
_ = cx.update(|cx| {
cx.text_system()
.add_fonts(vec![assets::Assets
.load("fonts/zed-mono/zed-mono-extended.ttf")
.unwrap()
.unwrap()])
.unwrap();
assets::Assets.load_test_fonts(cx);
let store = SettingsStore::test(cx);
cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx);

View File

@@ -1119,11 +1119,12 @@ impl EditorElement {
ScrollBeyondLastLine::Off => 1.0,
ScrollBeyondLastLine::VerticalScrollMargin => 1.0 + settings.vertical_scroll_margin,
};
let total_rows = snapshot.max_point().row().as_f32() + scroll_beyond_last_line;
let total_rows =
(snapshot.max_point().row().as_f32() + scroll_beyond_last_line).max(rows_per_page);
let height = bounds.size.height;
let px_per_row = height / total_rows;
let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.0);
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.);
Some(ScrollbarLayout {
hitbox: cx.insert_hitbox(track_bounds, false),
@@ -4676,17 +4677,17 @@ impl Element for EditorElement {
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
let height_in_lines = bounds.size.height / line_height;
let max_row = snapshot.max_point().row().as_f32();
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
(snapshot.max_point().row().as_f32() - height_in_lines + 1.).max(0.)
(max_row - height_in_lines + 1.).max(0.)
} else {
let settings = EditorSettings::get_global(cx);
let max_row = snapshot.max_point().row().as_f32();
match settings.scroll_beyond_last_line {
ScrollBeyondLastLine::OnePage => max_row,
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.0).max(0.0),
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.).max(0.),
ScrollBeyondLastLine::VerticalScrollMargin => {
(max_row - height_in_lines + 1.0 + settings.vertical_scroll_margin)
.max(0.0)
(max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
.max(0.)
}
}
};

View File

@@ -203,13 +203,24 @@ impl ScrollManager {
let scroll_top = scroll_position.y;
let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
ScrollBeyondLastLine::OnePage => scroll_top,
ScrollBeyondLastLine::Off => scroll_top
.min((map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap() + 1.0),
ScrollBeyondLastLine::VerticalScrollMargin => scroll_top.min(
(map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap()
+ 1.0
+ self.vertical_scroll_margin,
),
ScrollBeyondLastLine::Off => {
if let Some(height_in_lines) = self.visible_line_count {
let max_row = map.max_point().row().0 as f32;
scroll_top.min(max_row - height_in_lines + 1.).max(0.)
} else {
scroll_top
}
}
ScrollBeyondLastLine::VerticalScrollMargin => {
if let Some(height_in_lines) = self.visible_line_count {
let max_row = map.max_point().row().0 as f32;
scroll_top
.min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
.max(0.)
} else {
scroll_top
}
}
};
let scroll_top_buffer_point =

View File

@@ -25,7 +25,7 @@ pub fn marked_display_snapshot(
let (unmarked_text, markers) = marked_text_offsets(text);
let font = Font {
family: "Zed Mono".into(),
family: "Zed Plex Mono".into(),
features: FontFeatures::default(),
weight: FontWeight::default(),
style: FontStyle::default(),

View File

@@ -10,7 +10,7 @@ use serde_json::json;
use crate::{Editor, ToPoint};
use collections::HashSet;
use futures::Future;
use gpui::{AssetSource, View, ViewContext, VisualTestContext};
use gpui::{View, ViewContext, VisualTestContext};
use indoc::indoc;
use language::{
point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,
@@ -39,12 +39,7 @@ impl EditorLspTestContext {
let app_state = cx.update(AppState::test);
cx.update(|cx| {
cx.text_system()
.add_fonts(vec![assets::Assets
.load("fonts/zed-mono/zed-mono-extended.ttf")
.unwrap()
.unwrap()])
.unwrap();
assets::Assets.load_test_fonts(cx);
language::init(cx);
crate::init(cx);
workspace::init(app_state.clone(), cx);

View File

@@ -46,7 +46,7 @@ impl Match {
}
fn is_dir(&self, project: &Project, cx: &WindowContext) -> bool {
self.entry(project, cx).is_some_and(|e| e.is_dir())
self.entry(project, cx).is_some_and(|e| e.is_container())
|| self.suffix.as_ref().is_some_and(|s| s.ends_with('/'))
}
@@ -121,7 +121,7 @@ impl Match {
text.push_str(suffix);
let entry = self.entry(project, cx);
let color = if let Some(entry) = entry {
if entry.is_dir() {
if entry.is_container() {
Color::Accent
} else {
Color::Conflict
@@ -134,7 +134,7 @@ impl Match {
HighlightStyle::color(color.color(cx)),
));
offset += suffix.as_bytes().len();
if entry.is_some_and(|e| e.is_dir()) {
if entry.is_some_and(|e| e.is_container()) {
text.push(separator);
offset += separator.len_utf8();

View File

@@ -67,7 +67,10 @@ pub trait Fs: Send + Sync {
self.remove_file(path, options).await
}
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
async fn load(&self, path: &Path) -> Result<String>;
async fn load(&self, path: &Path) -> Result<String> {
Ok(String::from_utf8(self.load_bytes(path).await?)?)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>>;
async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
@@ -319,6 +322,12 @@ impl Fs for RealFs {
Ok(text)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
let path = path.to_path_buf();
let bytes = smol::unblock(|| std::fs::read(path)).await?;
Ok(bytes)
}
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
smol::unblock(move || {
let mut tmp_file = if cfg!(target_os = "linux") {
@@ -1433,6 +1442,10 @@ impl Fs for FakeFs {
Ok(String::from_utf8(content.clone())?)
}
async fn load_bytes(&self, path: &Path) -> Result<String> {
self.load_internal(path).await
}
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
self.simulate_random_delay().await;
let path = normalize_path(path.as_path());

View File

@@ -80,6 +80,7 @@ backtrace = "0.3"
collections = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }
http = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
[build-dependencies]
embed-resource = "2.4"
@@ -157,3 +158,7 @@ path = "examples/image/image.rs"
[[example]]
name = "set_menus"
path = "examples/set_menus.rs"
[[example]]
name = "input"
path = "examples/input.rs"

View File

@@ -0,0 +1,489 @@
use std::ops::Range;
use gpui::*;
use unicode_segmentation::*;
actions!(
text_input,
[
Backspace,
Delete,
Left,
Right,
SelectLeft,
SelectRight,
SelectAll,
Home,
End,
ShowCharacterPalette
]
);
struct TextInput {
focus_handle: FocusHandle,
content: SharedString,
selected_range: Range<usize>,
selection_reversed: bool,
marked_range: Option<Range<usize>>,
last_layout: Option<ShapedLine>,
}
impl TextInput {
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
} else {
self.move_to(self.selected_range.start, cx)
}
}
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.move_to(self.next_boundary(self.selected_range.end), cx);
} else {
self.move_to(self.selected_range.end, cx)
}
}
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
self.select_to(self.previous_boundary(self.cursor_offset()), cx);
}
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
self.select_to(self.next_boundary(self.cursor_offset()), cx);
}
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
self.move_to(0, cx);
self.select_to(self.content.len(), cx)
}
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
self.move_to(0, cx);
}
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
self.move_to(self.content.len(), cx);
}
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
}
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.select_to(self.next_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
}
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
cx.show_character_palette();
}
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
self.selected_range = offset..offset;
cx.notify()
}
fn cursor_offset(&self) -> usize {
if self.selection_reversed {
self.selected_range.start
} else {
self.selected_range.end
}
}
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
if self.selection_reversed {
self.selected_range.start = offset
} else {
self.selected_range.end = offset
};
if self.selected_range.end < self.selected_range.start {
self.selection_reversed = !self.selection_reversed;
self.selected_range = self.selected_range.end..self.selected_range.start;
}
cx.notify()
}
fn offset_from_utf16(&self, offset: usize) -> usize {
let mut utf8_offset = 0;
let mut utf16_count = 0;
for ch in self.content.chars() {
if utf16_count >= offset {
break;
}
utf16_count += ch.len_utf16();
utf8_offset += ch.len_utf8();
}
utf8_offset
}
fn offset_to_utf16(&self, offset: usize) -> usize {
let mut utf16_offset = 0;
let mut utf8_count = 0;
for ch in self.content.chars() {
if utf8_count >= offset {
break;
}
utf8_count += ch.len_utf8();
utf16_offset += ch.len_utf16();
}
utf16_offset
}
fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
}
fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
}
fn previous_boundary(&self, offset: usize) -> usize {
self.content
.grapheme_indices(true)
.rev()
.find_map(|(idx, _)| (idx < offset).then_some(idx))
.unwrap_or(0)
}
fn next_boundary(&self, offset: usize) -> usize {
self.content
.grapheme_indices(true)
.find_map(|(idx, _)| (idx > offset).then_some(idx))
.unwrap_or(self.content.len())
}
}
impl ViewInputHandler for TextInput {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
_cx: &mut ViewContext<Self>,
) -> Option<String> {
let range = self.range_from_utf16(&range_utf16);
Some(self.content[range].to_string())
}
fn selected_text_range(&mut self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
Some(self.range_to_utf16(&self.selected_range))
}
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
self.marked_range
.as_ref()
.map(|range| self.range_to_utf16(range))
}
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
self.marked_range = None;
}
fn replace_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
cx: &mut ViewContext<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
self.content =
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
.into();
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
self.marked_range.take();
cx.notify();
}
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range_utf16: Option<Range<usize>>,
cx: &mut ViewContext<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
self.content =
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
.into();
self.marked_range = Some(range.start..range.start + new_text.len());
self.selected_range = new_selected_range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.map(|new_range| new_range.start + range.start..new_range.end + range.end)
.unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
cx.notify();
}
fn bounds_for_range(
&mut self,
range_utf16: Range<usize>,
bounds: Bounds<Pixels>,
_cx: &mut ViewContext<Self>,
) -> Option<Bounds<Pixels>> {
let Some(last_layout) = self.last_layout.as_ref() else {
return None;
};
let range = self.range_from_utf16(&range_utf16);
Some(Bounds::from_corners(
point(
bounds.left() + last_layout.x_for_index(range.start),
bounds.top(),
),
point(
bounds.left() + last_layout.x_for_index(range.end),
bounds.bottom(),
),
))
}
}
struct TextElement {
input: View<TextInput>,
}
struct PrepaintState {
line: Option<ShapedLine>,
cursor: Option<PaintQuad>,
selection: Option<PaintQuad>,
}
impl IntoElement for TextElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for TextElement {
type RequestLayoutState = ();
type PrepaintState = PrepaintState;
fn id(&self) -> Option<ElementId> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = cx.line_height().into();
(cx.request_layout(style, []), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Self::PrepaintState {
let input = self.input.read(cx);
let content = input.content.clone();
let selected_range = input.selected_range.clone();
let cursor = input.cursor_offset();
let style = cx.text_style();
let run = TextRun {
len: input.content.len(),
font: style.font(),
color: style.color,
background_color: None,
underline: None,
strikethrough: None,
};
let runs = if let Some(marked_range) = input.marked_range.as_ref() {
vec![
TextRun {
len: marked_range.start,
..run.clone()
},
TextRun {
len: marked_range.end - marked_range.start,
underline: Some(UnderlineStyle {
color: Some(run.color),
thickness: px(1.0),
wavy: false,
}),
..run.clone()
},
TextRun {
len: input.content.len() - marked_range.end,
..run.clone()
},
]
.into_iter()
.filter(|run| run.len > 0)
.collect()
} else {
vec![run]
};
let font_size = style.font_size.to_pixels(cx.rem_size());
let line = cx
.text_system()
.shape_line(content, font_size, &runs)
.unwrap();
let cursor_pos = line.x_for_index(cursor);
let (selection, cursor) = if selected_range.is_empty() {
(
None,
Some(fill(
Bounds::new(
point(bounds.left() + cursor_pos, bounds.top()),
size(px(2.), bounds.bottom() - bounds.top()),
),
gpui::blue(),
)),
)
} else {
(
Some(fill(
Bounds::from_corners(
point(
bounds.left() + line.x_for_index(selected_range.start),
bounds.top(),
),
point(
bounds.left() + line.x_for_index(selected_range.end),
bounds.bottom(),
),
),
rgba(0x3311FF30),
)),
None,
)
};
PrepaintState {
line: Some(line),
cursor,
selection,
}
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut WindowContext,
) {
let focus_handle = self.input.read(cx).focus_handle.clone();
cx.handle_input(
&focus_handle,
ElementInputHandler::new(bounds, self.input.clone()),
);
if let Some(selection) = prepaint.selection.take() {
cx.paint_quad(selection)
}
let line = prepaint.line.take().unwrap();
line.paint(bounds.origin, cx.line_height(), cx).unwrap();
if let Some(cursor) = prepaint.cursor.take() {
cx.paint_quad(cursor);
}
self.input.update(cx, |input, _cx| {
input.last_layout = Some(line);
});
}
}
impl Render for TextInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.key_context("TextInput")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::backspace))
.on_action(cx.listener(Self::delete))
.on_action(cx.listener(Self::left))
.on_action(cx.listener(Self::right))
.on_action(cx.listener(Self::select_left))
.on_action(cx.listener(Self::select_right))
.on_action(cx.listener(Self::select_all))
.on_action(cx.listener(Self::home))
.on_action(cx.listener(Self::end))
.on_action(cx.listener(Self::show_character_palette))
.bg(rgb(0xeeeeee))
.size_full()
.line_height(px(30.))
.text_size(px(24.))
.child(
div()
.h(px(30. + 4. * 2.))
.w_full()
.p(px(4.))
.bg(white())
.child(TextElement {
input: cx.view().clone(),
}),
)
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.bind_keys([
KeyBinding::new("backspace", Backspace, None),
KeyBinding::new("delete", Delete, None),
KeyBinding::new("left", Left, None),
KeyBinding::new("right", Right, None),
KeyBinding::new("shift-left", SelectLeft, None),
KeyBinding::new("shift-right", SelectRight, None),
KeyBinding::new("cmd-a", SelectAll, None),
KeyBinding::new("home", Home, None),
KeyBinding::new("end", End, None),
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
]);
let window = cx
.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|cx| {
cx.new_view(|cx| TextInput {
focus_handle: cx.focus_handle(),
content: "".into(),
selected_range: 0..0,
selection_reversed: false,
marked_range: None,
last_layout: None,
})
},
)
.unwrap();
window
.update(cx, |view, cx| {
view.focus_handle.focus(cx);
cx.activate(true)
})
.unwrap();
});
}

View File

@@ -51,7 +51,7 @@ fn main() {
kind: WindowKind::PopUp,
is_movable: false,
app_id: None,
window_min_size: Size::default(),
window_min_size: None,
}
};

View File

@@ -569,7 +569,7 @@ pub struct WindowOptions {
pub app_id: Option<String>,
/// Window minimum size
pub window_min_size: Size<Pixels>,
pub window_min_size: Option<Size<Pixels>>,
}
/// The variables that can be configured when creating a new window
@@ -599,7 +599,7 @@ pub(crate) struct WindowParams {
pub window_background: WindowBackgroundAppearance,
#[cfg_attr(target_os = "linux", allow(dead_code))]
pub window_min_size: Size<Pixels>,
pub window_min_size: Option<Size<Pixels>>,
}
/// Represents the status of how a window should be opened.
@@ -648,7 +648,7 @@ impl Default for WindowOptions {
display_id: None,
window_background: WindowBackgroundAppearance::default(),
app_id: None,
window_min_size: Size::default(),
window_min_size: None,
}
}
}

View File

@@ -205,7 +205,7 @@ impl CosmicTextSystemState {
) -> Result<SmallVec<[FontId; 4]>> {
// TODO: Determine the proper system UI font.
let name = if name == ".SystemUIFont" {
"Zed Sans"
"Zed Plex Sans"
} else {
name
};

View File

@@ -94,6 +94,27 @@ impl Keystroke {
}
}
//Allow for the user to specify a keystroke modifier as the key itself
//This sets the `key` to the modifier, and disables the modifier
if key.is_none() {
if shift {
key = Some("shift".to_string());
shift = false;
} else if control {
key = Some("control".to_string());
control = false;
} else if alt {
key = Some("alt".to_string());
alt = false;
} else if platform {
key = Some("platform".to_string());
platform = false;
} else if function {
key = Some("function".to_string());
function = false;
}
}
let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
Ok(Keystroke {
@@ -186,6 +207,10 @@ impl std::fmt::Display for Keystroke {
"right" => '→',
"tab" => '⇥',
"escape" => '⎋',
"shift" => '⇧',
"control" => '⌃',
"alt" => '⌥',
"platform" => '⌘',
key => {
if key.len() == 1 {
key.chars().next().unwrap().to_ascii_uppercase()
@@ -241,6 +266,15 @@ impl Modifiers {
}
}
/// How many modifier keys are pressed
pub fn number_of_modifiers(&self) -> u8 {
self.control as u8
+ self.alt as u8
+ self.shift as u8
+ self.platform as u8
+ self.function as u8
}
/// helper method for Modifiers with no modifiers
pub fn none() -> Modifiers {
Default::default()

View File

@@ -3,6 +3,7 @@
use std::any::{type_name, Any};
use std::cell::{self, RefCell};
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
@@ -508,6 +509,27 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
}
pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::State> {
let mut locales = Vec::default();
if let Some(locale) = std::env::var_os("LC_CTYPE") {
locales.push(locale);
}
locales.push(OsString::from("C"));
let mut state: Option<xkb::compose::State> = None;
for locale in locales {
if let Ok(table) =
xkb::compose::Table::new_from_locale(&cx, &locale, xkb::compose::COMPILE_NO_FLAGS)
{
state = Some(xkb::compose::State::new(
&table,
xkb::compose::STATE_NO_FLAGS,
));
break;
}
}
state
}
pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result<String> {
let mut file = File::from_raw_fd(fd.as_raw_fd());

View File

@@ -1,5 +1,4 @@
use std::cell::{RefCell, RefMut};
use std::ffi::OsString;
use std::hash::Hash;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::path::PathBuf;
@@ -65,7 +64,6 @@ use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL};
use super::display::WaylandDisplay;
use super::window::{ImeInput, WaylandWindowStatePtr};
use crate::platform::linux::is_within_click_distance;
use crate::platform::linux::wayland::clipboard::{
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
};
@@ -74,6 +72,7 @@ use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
use crate::platform::linux::wayland::window::WaylandWindow;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::LinuxClient;
use crate::platform::linux::{get_xkb_compose_state, is_within_click_distance};
use crate::platform::PlatformWindow;
use crate::{
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
@@ -671,7 +670,7 @@ impl LinuxClient for WaylandClient {
return;
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set_primary(item.text);
state.clipboard.set_primary(item);
let serial = state.serial_tracker.get(SerialKind::KeyPress);
let data_source = primary_selection_manager.create_source(&state.globals.qh, ());
data_source.offer(state.clipboard.self_mime());
@@ -689,7 +688,7 @@ impl LinuxClient for WaylandClient {
return;
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set(item.text);
state.clipboard.set(item);
let serial = state.serial_tracker.get(SerialKind::KeyPress);
let data_source = data_device_manager.create_data_source(&state.globals.qh, ());
data_source.offer(state.clipboard.self_mime());
@@ -699,25 +698,11 @@ impl LinuxClient for WaylandClient {
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.clipboard
.read_primary()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
self.0.borrow_mut().clipboard.read_primary()
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.clipboard
.read()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
self.0.borrow_mut().clipboard.read()
}
fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -1068,21 +1053,8 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
.flatten()
.expect("Failed to create keymap")
};
let table = {
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
xkb::compose::Table::new_from_locale(
&xkb_context,
&locale,
xkb::compose::COMPILE_NO_FLAGS,
)
.log_err()
.unwrap()
};
state.keymap_state = Some(xkb::State::new(&keymap));
state.compose_state = Some(xkb::compose::State::new(
&table,
xkb::compose::STATE_NO_FLAGS,
));
state.compose_state = get_xkb_compose_state(&xkb_context);
}
wl_keyboard::Event::Enter {
serial, surface, ..
@@ -1162,6 +1134,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
compose.feed(keysym);
match compose.status() {
xkb::Status::Composing => {
keystroke.ime_key = None;
state.pre_edit_text =
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
let pre_edit =
@@ -1174,7 +1147,9 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
xkb::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = compose.utf8();
keystroke.key = xkb::keysym_get_name(compose.keysym().unwrap());
if let Some(keysym) = compose.keysym() {
keystroke.key = xkb::keysym_get_name(keysym);
}
}
xkb::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();

View File

@@ -9,7 +9,7 @@ use filedescriptor::Pipe;
use wayland_client::{protocol::wl_data_offer::WlDataOffer, Connection};
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
use crate::{platform::linux::platform::read_fd, WaylandClientStatePtr};
use crate::{platform::linux::platform::read_fd, ClipboardItem, WaylandClientStatePtr};
pub(crate) const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8";
pub(crate) const FILE_LIST_MIME_TYPE: &str = "text/uri-list";
@@ -23,13 +23,13 @@ pub(crate) struct Clipboard {
self_mime: String,
// Internal clipboard
contents: Option<String>,
primary_contents: Option<String>,
contents: Option<ClipboardItem>,
primary_contents: Option<ClipboardItem>,
// External clipboard
cached_read: Option<String>,
cached_read: Option<ClipboardItem>,
current_offer: Option<DataOffer<WlDataOffer>>,
cached_primary_read: Option<String>,
cached_primary_read: Option<ClipboardItem>,
current_primary_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
}
@@ -89,12 +89,12 @@ impl Clipboard {
}
}
pub fn set(&mut self, text: String) {
self.contents = Some(text);
pub fn set(&mut self, item: ClipboardItem) {
self.contents = Some(item);
}
pub fn set_primary(&mut self, text: String) {
self.primary_contents = Some(text);
pub fn set_primary(&mut self, item: ClipboardItem) {
self.primary_contents = Some(item);
}
pub fn set_offer(&mut self, data_offer: Option<DataOffer<WlDataOffer>>) {
@@ -113,17 +113,17 @@ impl Clipboard {
pub fn send(&self, _mime_type: String, fd: OwnedFd) {
if let Some(contents) = &self.contents {
self.send_internal(fd, contents.as_bytes().to_owned());
self.send_internal(fd, contents.text.as_bytes().to_owned());
}
}
pub fn send_primary(&self, _mime_type: String, fd: OwnedFd) {
if let Some(primary_contents) = &self.primary_contents {
self.send_internal(fd, primary_contents.as_bytes().to_owned());
self.send_internal(fd, primary_contents.text.as_bytes().to_owned());
}
}
pub fn read(&mut self) -> Option<String> {
pub fn read(&mut self) -> Option<ClipboardItem> {
let offer = self.current_offer.clone()?;
if let Some(cached) = self.cached_read.clone() {
return Some(cached);
@@ -145,8 +145,8 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_read = Some(v.clone());
Some(v)
self.cached_read = Some(ClipboardItem::new(v));
self.cached_read.clone()
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");
@@ -155,7 +155,7 @@ impl Clipboard {
}
}
pub fn read_primary(&mut self) -> Option<String> {
pub fn read_primary(&mut self) -> Option<ClipboardItem> {
let offer = self.current_primary_offer.clone()?;
if let Some(cached) = self.cached_primary_read.clone() {
return Some(cached);
@@ -177,8 +177,8 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_primary_read = Some(v.clone());
Some(v)
self.cached_primary_read = Some(ClipboardItem::new(v.clone()));
self.cached_primary_read.clone()
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");

View File

@@ -1,6 +1,5 @@
use std::cell::RefCell;
use std::collections::HashSet;
use std::ffi::OsString;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use std::time::{Duration, Instant};
@@ -29,13 +28,13 @@ use xkbcommon::xkb as xkbc;
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput,
Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{
super::{open_uri_internal, SCROLL_LINES},
super::{get_xkb_compose_state, open_uri_internal, SCROLL_LINES},
X11Display, X11WindowStatePtr, XcbAtoms,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
@@ -116,7 +115,7 @@ pub struct X11ClientState {
pub(crate) xim_handler: Option<XimHandler>,
pub modifiers: Modifiers,
pub(crate) compose_state: xkbc::compose::State,
pub(crate) compose_state: Option<xkbc::compose::State>,
pub(crate) pre_edit_text: Option<String>,
pub(crate) composing: bool,
pub(crate) cursor_handle: cursor::Handle,
@@ -129,6 +128,7 @@ pub struct X11ClientState {
pub(crate) common: LinuxCommon,
pub(crate) clipboard: x11_clipboard::Clipboard,
pub(crate) clipboard_item: Option<ClipboardItem>,
}
#[derive(Clone)]
@@ -249,18 +249,7 @@ impl X11Client {
);
xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
};
let compose_state = {
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
let table = xkbc::compose::Table::new_from_locale(
&xkb_context,
&locale,
xkbc::compose::COMPILE_NO_FLAGS,
)
.log_err()
.unwrap();
xkbc::compose::State::new(&table, xkbc::compose::STATE_NO_FLAGS)
};
let compose_state = get_xkb_compose_state(&xkb_context);
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
let scale_factor = resource_database
@@ -400,7 +389,7 @@ impl X11Client {
ximc,
xim_handler,
compose_state: compose_state,
compose_state,
pre_edit_text: None,
composing: false,
@@ -413,6 +402,7 @@ impl X11Client {
scroll_y: None,
clipboard,
clipboard_item: None,
})))
}
@@ -524,7 +514,9 @@ impl X11Client {
window.set_focused(false);
let mut state = self.0.borrow_mut();
state.focused_window = None;
state.compose_state.reset();
if let Some(compose_state) = state.compose_state.as_mut() {
compose_state.reset();
}
state.pre_edit_text.take();
drop(state);
self.disable_ime();
@@ -570,37 +562,42 @@ impl X11Client {
if keysym.is_modifier_key() {
return Some(());
}
state.compose_state.feed(keysym);
match state.compose_state.status() {
xkbc::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = state.compose_state.utf8();
keystroke.key =
xkbc::keysym_get_name(state.compose_state.keysym().unwrap());
}
xkbc::Status::Composing => {
state.pre_edit_text = state
.compose_state
.utf8()
.or(crate::Keystroke::underlying_dead_key(keysym));
let pre_edit = state.pre_edit_text.clone().unwrap_or(String::default());
drop(state);
window.handle_ime_preedit(pre_edit);
state = self.0.borrow_mut();
}
xkbc::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();
drop(state);
if let Some(pre_edit) = pre_edit {
window.handle_ime_commit(pre_edit);
if let Some(mut compose_state) = state.compose_state.take() {
compose_state.feed(keysym);
match compose_state.status() {
xkbc::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = compose_state.utf8();
if let Some(keysym) = compose_state.keysym() {
keystroke.key = xkbc::keysym_get_name(keysym);
}
}
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
window.handle_ime_preedit(current_key);
xkbc::Status::Composing => {
keystroke.ime_key = None;
state.pre_edit_text = compose_state
.utf8()
.or(crate::Keystroke::underlying_dead_key(keysym));
let pre_edit =
state.pre_edit_text.clone().unwrap_or(String::default());
drop(state);
window.handle_ime_preedit(pre_edit);
state = self.0.borrow_mut();
}
state = self.0.borrow_mut();
state.compose_state.feed(keysym);
xkbc::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();
drop(state);
if let Some(pre_edit) = pre_edit {
window.handle_ime_commit(pre_edit);
}
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
window.handle_ime_preedit(current_key);
}
state = self.0.borrow_mut();
compose_state.feed(keysym);
}
_ => {}
}
_ => {}
state.compose_state = Some(compose_state);
}
keystroke
};
@@ -649,7 +646,9 @@ impl X11Client {
window.handle_ime_unmark();
state = self.0.borrow_mut();
} else if let Some(text) = state.pre_edit_text.take() {
state.compose_state.reset();
if let Some(compose_state) = state.compose_state.as_mut() {
compose_state.reset();
}
drop(state);
window.handle_ime_commit(text);
state = self.0.borrow_mut();
@@ -1097,7 +1096,7 @@ impl LinuxClient for X11Client {
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
let state = self.0.borrow_mut();
let mut state = self.0.borrow_mut();
state
.clipboard
.store(
@@ -1106,6 +1105,7 @@ impl LinuxClient for X11Client {
item.text().as_bytes(),
)
.ok();
state.clipboard_item.replace(item);
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
@@ -1127,6 +1127,20 @@ impl LinuxClient for X11Client {
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
let state = self.0.borrow_mut();
// if the last copy was from this app, return our cached item
// which has metadata attached.
if state
.clipboard
.setter
.connection
.get_selection_owner(state.clipboard.setter.atoms.clipboard)
.ok()
.and_then(|r| r.reply().ok())
.map(|reply| reply.owner == state.clipboard.setter.window)
.unwrap_or(false)
{
return state.clipboard_item.clone();
}
state
.clipboard
.load(
@@ -1167,6 +1181,10 @@ impl LinuxClient for X11Client {
// Adatpted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
return Duration::from_millis(16);
}
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros);

View File

@@ -34,7 +34,6 @@ use std::{
};
use super::{X11Display, XINPUT_MASTER_DEVICE};
x11rb::atom_manager! {
pub XcbAtoms: AtomsCookie {
UTF8_STRING,
@@ -48,6 +47,7 @@ x11rb::atom_manager! {
_NET_WM_STATE_FULLSCREEN,
_NET_WM_STATE_HIDDEN,
_NET_WM_STATE_FOCUSED,
_NET_ACTIVE_WINDOW,
_NET_WM_MOVERESIZE,
_NET_WM_WINDOW_TYPE,
_NET_WM_WINDOW_TYPE_NOTIFICATION,
@@ -802,10 +802,21 @@ impl PlatformWindow for X11Window {
}
fn activate(&self) {
let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
let message = xproto::ClientMessageEvent::new(
32,
self.0.x_window,
self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
data,
);
self.0
.xcb_connection
.configure_window(self.0.x_window, &win_aux)
.send_event(
false,
self.0.state.borrow().x_root_window,
xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
message,
)
.log_err();
self.0
.xcb_connection

View File

@@ -647,10 +647,12 @@ impl MacWindow {
native_window.setMovable_(is_movable as BOOL);
native_window.setContentMinSize_(NSSize {
width: window_min_size.width.to_f64(),
height: window_min_size.height.to_f64(),
});
if let Some(window_min_size) = window_min_size {
native_window.setContentMinSize_(NSSize {
width: window_min_size.width.to_f64(),
height: window_min_size.height.to_f64(),
});
}
if titlebar.map_or(true, |titlebar| titlebar.appears_transparent) {
native_window.setTitlebarAppearsTransparent_(YES);
@@ -1670,9 +1672,13 @@ extern "C" fn first_rect_for_character_range(
range: NSRange,
_: id,
) -> NSRect {
let frame = unsafe {
let window = get_window_state(this).lock().native_window;
NSView::frame(window)
let frame: NSRect = unsafe {
let state = get_window_state(this);
let lock = state.lock();
let mut frame = NSWindow::frame(lock.native_window);
let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
frame.origin.y -= frame.size.height - content_layout_rect.size.height;
frame
};
with_input_handler(this, |input_handler| {
input_handler.bounds_for_range(range.to_range()?)

View File

@@ -64,7 +64,7 @@ impl TextSystem {
fallback_font_stack: smallvec![
// TODO: This is currently Zed-specific.
// We should allow GPUI users to provide their own fallback font stack.
font("Zed Mono"),
font("Zed Plex Mono"),
font("Helvetica"),
font("Cantarell"), // Gnome
font("Ubuntu"), // Gnome (Ubuntu)

View File

@@ -58,7 +58,7 @@ impl<'de> serde::Deserialize<'de> for FontFeatures {
while let Some((key, value)) =
access.next_entry::<String, Option<FeatureValue>>()?
{
if key.len() != 4 && !key.is_ascii() {
if !is_valid_feature_tag(&key) {
log::error!("Incorrect font feature tag: {}", key);
continue;
}
@@ -142,3 +142,7 @@ impl schemars::JsonSchema for FontFeatures {
schema.into()
}
}
fn is_valid_feature_tag(tag: &str) -> bool {
tag.len() == 4 && tag.chars().all(|c| c.is_ascii_alphanumeric())
}

View File

@@ -154,12 +154,12 @@ mod tests {
let cx = TestAppContext::new(dispatcher, None);
cx.text_system()
.add_fonts(vec![std::fs::read(
"../../assets/fonts/zed-mono/zed-mono-extended.ttf",
"../../assets/fonts/plex-mono/ZedPlexMono-Regular.ttf",
)
.unwrap()
.into()])
.unwrap();
let id = cx.text_system().font_id(&font("Zed Mono")).unwrap();
let id = cx.text_system().font_id(&font("Zed Plex Mono")).unwrap();
cx.update(|cx| {
let text_system = cx.text_system().clone();

View File

@@ -549,10 +549,17 @@ pub struct Window {
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
pending_input: Option<PendingInput>,
pending_modifier: ModifierState,
pending_input_observers: SubscriberSet<(), AnyObserver>,
prompt: Option<RenderablePromptHandle>,
}
#[derive(Clone, Debug, Default)]
struct ModifierState {
modifiers: Modifiers,
saw_keystroke: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum DrawPhase {
None,
@@ -754,11 +761,6 @@ impl Window {
handle
.update(&mut cx, |_, cx| {
cx.window.active.set(active);
// If the window is occluded we may not render it again
// until
if !active {
cx.window.rendered_frame.window_active = false;
}
cx.window
.activation_observers
.clone()
@@ -828,6 +830,7 @@ impl Window {
focus: None,
focus_enabled: true,
pending_input: None,
pending_modifier: ModifierState::default(),
pending_input_observers: SubscriberSet::new(),
prompt: None,
})
@@ -3166,70 +3169,135 @@ impl<'a> WindowContext<'a> {
.dispatch_tree
.dispatch_path(node_id);
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
let KeymatchResult { bindings, pending } = self
let mut bindings: SmallVec<[KeyBinding; 1]> = SmallVec::new();
let mut pending = false;
let mut keystroke: Option<Keystroke> = None;
if let Some(event) = event.downcast_ref::<ModifiersChangedEvent>() {
if event.modifiers.number_of_modifiers() == 0
&& self.window.pending_modifier.modifiers.number_of_modifiers() == 1
&& !self.window.pending_modifier.saw_keystroke
{
if event.modifiers.number_of_modifiers() == 0 {
let key = match self.window.pending_modifier.modifiers {
modifiers if modifiers.shift => Some("shift"),
modifiers if modifiers.control => Some("control"),
modifiers if modifiers.alt => Some("alt"),
modifiers if modifiers.platform => Some("platform"),
modifiers if modifiers.function => Some("function"),
_ => None,
};
if let Some(key) = key {
let key = Keystroke {
key: key.to_string(),
ime_key: None,
modifiers: Modifiers::default(),
};
let KeymatchResult {
bindings: modifier_bindings,
pending: pending_bindings,
} = self
.window
.rendered_frame
.dispatch_tree
.dispatch_key(&key, &dispatch_path);
keystroke = Some(key);
bindings = modifier_bindings;
pending = pending_bindings;
}
}
}
if self.window.pending_modifier.modifiers.number_of_modifiers() == 0
&& event.modifiers.number_of_modifiers() == 1
{
self.window.pending_modifier.saw_keystroke = false
}
self.window.pending_modifier.modifiers = event.modifiers
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
self.window.pending_modifier.saw_keystroke = true;
let KeymatchResult {
bindings: key_down_bindings,
pending: key_down_pending,
} = self
.window
.rendered_frame
.dispatch_tree
.dispatch_key(&key_down_event.keystroke, &dispatch_path);
if pending {
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus
{
currently_pending = PendingInput::default();
}
currently_pending.focus = self.window.focus;
currently_pending
.keystrokes
.push(key_down_event.keystroke.clone());
for binding in bindings {
currently_pending.bindings.push(binding);
}
keystroke = Some(key_down_event.keystroke.clone());
currently_pending.timer = Some(self.spawn(|mut cx| async move {
cx.background_executor.timer(Duration::from_secs(1)).await;
cx.update(move |cx| {
cx.clear_pending_keystrokes();
let Some(currently_pending) = cx.window.pending_input.take() else {
return;
};
cx.pending_input_changed();
cx.replay_pending_input(currently_pending);
})
.log_err();
}));
bindings = key_down_bindings;
pending = key_down_pending;
}
self.window.pending_input = Some(currently_pending);
self.pending_input_changed();
if keystroke.is_none() {
self.finish_dispatch_key_event(event, dispatch_path);
return;
}
self.propagate_event = false;
return;
} else if let Some(currently_pending) = self.window.pending_input.take() {
self.pending_input_changed();
if bindings
.iter()
.all(|binding| !currently_pending.used_by_binding(binding))
{
self.replay_pending_input(currently_pending)
}
if pending {
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus {
currently_pending = PendingInput::default();
}
if !bindings.is_empty() {
self.clear_pending_keystrokes();
currently_pending.focus = self.window.focus;
if let Some(keystroke) = keystroke {
currently_pending.keystrokes.push(keystroke.clone());
}
self.propagate_event = true;
for binding in bindings {
self.dispatch_action_on_node(node_id, binding.action.as_ref());
if !self.propagate_event {
self.dispatch_keystroke_observers(event, Some(binding.action));
return;
}
currently_pending.bindings.push(binding);
}
currently_pending.timer = Some(self.spawn(|mut cx| async move {
cx.background_executor.timer(Duration::from_secs(1)).await;
cx.update(move |cx| {
cx.clear_pending_keystrokes();
let Some(currently_pending) = cx.window.pending_input.take() else {
return;
};
cx.replay_pending_input(currently_pending);
cx.pending_input_changed();
})
.log_err();
}));
self.window.pending_input = Some(currently_pending);
self.pending_input_changed();
self.propagate_event = false;
return;
} else if let Some(currently_pending) = self.window.pending_input.take() {
self.pending_input_changed();
if bindings
.iter()
.all(|binding| !currently_pending.used_by_binding(binding))
{
self.replay_pending_input(currently_pending)
}
}
if !bindings.is_empty() {
self.clear_pending_keystrokes();
}
self.propagate_event = true;
for binding in bindings {
self.dispatch_action_on_node(node_id, binding.action.as_ref());
if !self.propagate_event {
self.dispatch_keystroke_observers(event, Some(binding.action));
return;
}
}
self.finish_dispatch_key_event(event, dispatch_path)
}
fn finish_dispatch_key_event(
&mut self,
event: &dyn Any,
dispatch_path: SmallVec<[DispatchNodeId; 32]>,
) {
self.dispatch_key_down_up_event(event, &dispatch_path);
if !self.propagate_event {
return;

View File

@@ -511,7 +511,7 @@ impl LanguageRegistry {
) -> impl Future<Output = Result<Arc<Language>>> {
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name();
let path_suffixes = [extension, filename];
let path_suffixes = [extension, filename, path.to_str()];
let empty = GlobSet::empty();
let rx = self.get_or_load_language(move |language_name, config| {

View File

@@ -662,6 +662,17 @@ impl settings::Settings for AllLanguageSettings {
.ok_or_else(Self::missing_default)?;
let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default();
for (language, suffixes) in &default_value.file_types {
let mut builder = GlobSetBuilder::new();
for suffix in suffixes {
builder.add(Glob::new(suffix)?);
}
file_types.insert(language.clone(), builder.build()?);
}
for user_settings in sources.customizations() {
if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
copilot_enabled = Some(copilot);
@@ -701,6 +712,15 @@ impl settings::Settings for AllLanguageSettings {
for (language, suffixes) in &user_settings.file_types {
let mut builder = GlobSetBuilder::new();
let default_value = default_value.file_types.get(&language.clone());
// Merge the default value with the user's value.
if let Some(suffixes) = default_value {
for suffix in suffixes {
builder.add(Glob::new(suffix)?);
}
}
for suffix in suffixes {
builder.add(Glob::new(suffix)?);
}

View File

@@ -213,7 +213,12 @@ impl LspAdapter for JsonLspAdapter {
}
fn language_ids(&self) -> HashMap<String, String> {
[("JSON".into(), "jsonc".into())].into_iter().collect()
[
("JSON".into(), "json".into()),
("JSONC".into(), "jsonc".into()),
]
.into_iter()
.collect()
}
}
@@ -348,7 +353,7 @@ impl LspAdapter for NodeVersionAdapter {
}
Ok(LanguageServerBinary {
path: destination_path.join("package-version-server"),
path: destination_path,
env: None,
arguments: Default::default(),
})

View File

@@ -0,0 +1,3 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)

View File

@@ -0,0 +1,12 @@
name = "JSONC"
grammar = "jsonc"
path_suffixes = ["jsonc"]
line_comments = ["// "]
autoclose_before = ",]}"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
]
tab_size = 2
prettier_parser_name = "jsonc"

View File

@@ -0,0 +1,14 @@
; Only produce one embedding for the entire file.
(document) @item
; Collapse arrays, except for the first object.
(array
"[" @keep
.
(object)? @keep
"]" @keep) @collapse
; Collapse string values (but not keys).
(pair value: (string
"\"" @keep
"\"" @keep) @collapse)

View File

@@ -0,0 +1,21 @@
(comment) @comment
(string) @string
(pair
key: (string) @property.json_key)
(number) @number
[
(true)
(false)
(null)
] @constant
[
"{"
"}"
"["
"]"
] @punctuation.bracket

View File

@@ -0,0 +1,2 @@
(array "]" @end) @indent
(object "}" @end) @indent

View File

@@ -0,0 +1,2 @@
(pair
key: (string (string_content) @name)) @item

View File

@@ -0,0 +1 @@
(string) @string

View File

@@ -0,0 +1,4 @@
(pair value: (number) @redact)
(pair value: (string) @redact)
(array (number) @redact)
(array (string) @redact)

View File

@@ -45,6 +45,7 @@ pub fn init(
("gowork", tree_sitter_gowork::language()),
("jsdoc", tree_sitter_jsdoc::language()),
("json", tree_sitter_json::language()),
("jsonc", tree_sitter_json::language()),
("markdown", tree_sitter_markdown::language()),
("proto", tree_sitter_proto::language()),
("python", tree_sitter_python::language()),
@@ -126,6 +127,14 @@ pub fn init(
],
json_task_context()
);
language!(
"jsonc",
vec![Arc::new(json::JsonLspAdapter::new(
node_runtime.clone(),
languages.clone(),
))],
json_task_context()
);
language!("markdown");
language!(
"python",

View File

@@ -200,8 +200,9 @@ impl LspAdapter for TypeScriptLspAdapter {
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({
"provideFormatter": true,
"hostInfo": "zed",
"tsserver": {
"path": "node_modules/typescript/lib",
"path": "/Users/hiro/Projects/repros/yarn-test/.yarn/sdks/typescript/lib",
},
"preferences": {
"includeInlayParameterNameHints": "all",

View File

@@ -164,45 +164,44 @@ impl LspAdapter for VtslsLspAdapter {
Ok(Some(json!({
"typescript":
{
"tsdk": "node_modules/typescript/lib",
"tsdk": "/Users/hiro/Projects/repros/yarn-test/.yarn/sdks/typescript/lib",
"format": {
"enable": true
},
"inlayHints":{
"parameterNames":
{
"inlayHints": {
"parameterNames": {
"enabled": "all",
"suppressWhenArgumentMatchesName": false,
},
"parameterTypes":
{
"parameterTypes": {
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": false,
},
"propertyDeclarationTypes":{
"propertyDeclarationTypes": {
"enabled": true,
},
"functionLikeReturnTypes": {
"enabled": true,
},
"enumMemberValues":{
"enumMemberValues": {
"enabled": true,
}
}
},
"vtsls":
{"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
"vtsls": {
"typescript": {
"globalTsdk": "/Users/hiro/Projects/repros/yarn-test/.yarn/sdks/typescript/lib"
},
"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
}
}
}
})))
}
@@ -216,44 +215,43 @@ impl LspAdapter for VtslsLspAdapter {
"suggest": {
"completeFunctionCalls": true
},
"tsdk": "node_modules/typescript/lib",
"tsdk": "/Users/hiro/Projects/repros/yarn-test/.yarn/sdks/typescript/lib",
"format": {
"enable": true
},
"inlayHints":{
"parameterNames":
{
"inlayHints": {
"parameterNames": {
"enabled": "all",
"suppressWhenArgumentMatchesName": false,
},
"parameterTypes":
{
"parameterTypes": {
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": false,
},
"propertyDeclarationTypes":{
"propertyDeclarationTypes": {
"enabled": true,
},
"functionLikeReturnTypes": {
"enabled": true,
},
"enumMemberValues":{
"enumMemberValues": {
"enabled": true,
}
}
},
"vtsls":
{"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
}
},
"vtsls": {
"typescript": {
"globalTsdk": "/Users/hiro/Projects/repros/yarn-test/.yarn/sdks/typescript/lib"
},
"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
}
}
}))
}

View File

@@ -109,13 +109,13 @@ pub fn main() {
MARKDOWN_EXAMPLE.to_string(),
MarkdownStyle {
code_block: gpui::TextStyleRefinement {
font_family: Some("Zed Mono".into()),
font_family: Some("Zed Plex Mono".into()),
color: Some(cx.theme().colors().editor_foreground),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
},
inline_code: gpui::TextStyleRefinement {
font_family: Some("Zed Mono".into()),
font_family: Some("Zed Plex Mono".into()),
// @nate: Could we add inline-code specific styles to the theme?
color: Some(cx.theme().colors().editor_foreground),
background_color: Some(cx.theme().colors().editor_background),

View File

@@ -55,6 +55,8 @@ pub enum Model {
#[serde(rename = "gpt-4o", alias = "gpt-4o-2024-05-13")]
#[default]
FourOmni,
#[serde(rename = "custom")]
Custom { name: String, max_tokens: usize },
}
impl Model {
@@ -74,15 +76,17 @@ impl Model {
Self::Four => "gpt-4",
Self::FourTurbo => "gpt-4-turbo-preview",
Self::FourOmni => "gpt-4o",
Self::Custom { .. } => "custom",
}
}
pub fn display_name(&self) -> &'static str {
pub fn display_name(&self) -> &str {
match self {
Self::ThreePointFiveTurbo => "gpt-3.5-turbo",
Self::Four => "gpt-4",
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::Custom { name, .. } => name,
}
}
@@ -92,12 +96,24 @@ impl Model {
Model::Four => 8192,
Model::FourTurbo => 128000,
Model::FourOmni => 128000,
Model::Custom { max_tokens, .. } => *max_tokens,
}
}
}
fn serialize_model<S>(model: &Model, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match model {
Model::Custom { name, .. } => serializer.serialize_str(name),
_ => serializer.serialize_str(model.id()),
}
}
#[derive(Debug, Serialize)]
pub struct Request {
#[serde(serialize_with = "serialize_model")]
pub model: Model,
pub messages: Vec<RequestMessage>,
pub stream: bool,

View File

@@ -272,6 +272,7 @@ pub enum Event {
#[derive(Serialize, Deserialize)]
struct SerializedOutlinePanel {
width: Option<Pixels>,
active: Option<bool>,
}
pub fn init_settings(cx: &mut AppContext) {
@@ -312,6 +313,7 @@ impl OutlinePanel {
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|px| px.round());
panel.active = serialized_panel.active.unwrap_or(false);
cx.notify();
});
}
@@ -407,12 +409,13 @@ impl OutlinePanel {
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
let width = self.width;
let active = Some(self.active);
self.pending_serialization = cx.background_executor().spawn(
async move {
KEY_VALUE_STORE
.write_kvp(
OUTLINE_PANEL_KEY.into(),
serde_json::to_string(&SerializedOutlinePanel { width })?,
serde_json::to_string(&SerializedOutlinePanel { width, active })?,
)
.await?;
anyhow::Ok(())
@@ -493,7 +496,7 @@ impl OutlinePanel {
}
for child_dir in worktree
.child_entries(&entry.path)
.filter(|entry| entry.is_dir())
.filter(|entry| entry.is_container())
{
let removed = unfolded_dirs.remove(&child_dir.id);
if !removed {
@@ -1197,7 +1200,7 @@ impl OutlinePanel {
);
let mut current_entry = buffer_entry;
loop {
if current_entry.is_dir() {
if current_entry.is_container() {
if self
.collapsed_entries
.remove(&CollapsedEntry::Dir(worktree_id, current_entry.id))
@@ -1694,7 +1697,7 @@ impl OutlinePanel {
.insert(entry.id, (buffer_id, excerpts));
let mut current_entry = entry;
loop {
if current_entry.is_dir() {
if current_entry.is_container() {
let is_root =
worktree.root_entry().map(|entry| entry.id)
== Some(current_entry.id);
@@ -1787,7 +1790,7 @@ impl OutlinePanel {
.or_default()
.entry(parent.to_path_buf())
.or_default();
if entry.is_dir() {
if entry.is_container() {
children.dirs += 1;
} else {
children.files += 1;
@@ -1795,7 +1798,7 @@ impl OutlinePanel {
}
}
if entry.is_dir() {
if entry.is_container() {
Some(FsEntry::Directory(worktree_id, entry))
} else {
let (buffer_id, excerpts) = worktree_excerpts
@@ -2522,7 +2525,7 @@ impl Panel for OutlinePanel {
}
fn starts_open(&self, _: &WindowContext) -> bool {
self.active_item.is_some()
self.active
}
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
@@ -2551,6 +2554,7 @@ impl Panel for OutlinePanel {
}
}
}
self.serialize(cx);
}
}

View File

@@ -2141,12 +2141,14 @@ impl Project {
/// LanguageServerName is owned, because it is inserted into a map
pub fn open_local_buffer_via_lsp(
&mut self,
abs_path: lsp::Url,
mut abs_path: lsp::Url,
language_server_id: LanguageServerId,
language_server_name: LanguageServerName,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Buffer>>> {
cx.spawn(move |this, mut cx| async move {
// Escape percent-encoded string.
let _ = abs_path.set_scheme("file");
let abs_path = abs_path
.to_file_path()
.map_err(|_| anyhow!("can't convert URI to path"))?;
@@ -2167,7 +2169,8 @@ impl Project {
);
})
.ok();
(worktree, PathBuf::new())
let worktree_root = worktree.update(&mut cx, |this, _| this.abs_path())?;
(worktree, abs_path.strip_prefix(worktree_root)?.into())
};
let project_path = ProjectPath {
@@ -7901,7 +7904,7 @@ impl Project {
File {
is_local: true,
entry_id: Some(entry.id),
mtime: entry.mtime,
mtime: entry.data.disk_entry().map(|entry| entry.1),
path: entry.path.clone(),
worktree: worktree_handle.clone(),
is_deleted: false,
@@ -7911,7 +7914,7 @@ impl Project {
File {
is_local: true,
entry_id: Some(entry.id),
mtime: entry.mtime,
mtime: entry.data.disk_entry().map(|entry| entry.1),
path: entry.path.clone(),
worktree: worktree_handle.clone(),
is_deleted: false,
@@ -8192,7 +8195,7 @@ impl Project {
}
};
if abs_path.ends_with(local_settings_file_relative_path()) {
if path.ends_with(local_settings_file_relative_path()) {
let settings_dir = Arc::from(
path.ancestors()
.nth(local_settings_file_relative_path().components().count())
@@ -8209,7 +8212,7 @@ impl Project {
},
)
});
} else if abs_path.ends_with(local_tasks_file_relative_path()) {
} else if path.ends_with(local_tasks_file_relative_path()) {
self.task_inventory().update(cx, |task_inventory, cx| {
if removed {
task_inventory.remove_local_static_source(&abs_path);
@@ -8229,7 +8232,7 @@ impl Project {
);
}
})
} else if abs_path.ends_with(local_vscode_tasks_file_relative_path()) {
} else if path.ends_with(local_vscode_tasks_file_relative_path()) {
self.task_inventory().update(cx, |task_inventory, cx| {
if removed {
task_inventory.remove_local_static_source(&abs_path);
@@ -10954,7 +10957,7 @@ impl Project {
let worktree = worktree.read(cx);
worktree.is_visible()
&& worktree.is_local()
&& worktree.root_entry().map_or(false, |e| e.is_dir())
&& worktree.root_entry().map_or(false, |e| e.is_container())
})
.collect::<Vec<_>>();
let cwd = match available_worktrees.len() {
@@ -11411,7 +11414,7 @@ impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
fn next(&mut self) -> Option<Self::Item> {
self.traversal.next().map(|entry| match entry.kind {
EntryKind::Dir => fuzzy::PathMatchCandidate {
EntryKind::Container => fuzzy::PathMatchCandidate {
path: &entry.path,
char_bag: CharBag::from_iter(entry.path.to_string_lossy().to_lowercase().chars()),
},
@@ -11419,7 +11422,7 @@ impl<'a> Iterator for PathMatchCandidateSetIter<'a> {
path: &entry.path,
char_bag,
},
EntryKind::UnloadedDir | EntryKind::PendingDir => unreachable!(),
EntryKind::UnloadedContainer | EntryKind::PendingContainer => unreachable!(),
})
}
}

View File

@@ -45,7 +45,7 @@ use workspace::{
notifications::{DetachAndPromptErr, NotifyTaskExt},
DraggedSelection, OpenInTerminal, SelectedEntry, Workspace,
};
use worktree::CreatedEntry;
use worktree::{CreatedEntry, EntryData};
const PROJECT_PANEL_KEY: &str = "ProjectPanel";
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
@@ -456,7 +456,7 @@ impl ProjectPanel {
if let Some((worktree, entry)) = self.selected_entry(cx) {
let auto_fold_dirs = ProjectPanelSettings::get_global(cx).auto_fold_dirs;
let is_root = Some(entry) == worktree.root_entry();
let is_dir = entry.is_dir();
let is_dir = entry.is_container();
let is_foldable = auto_fold_dirs && self.is_foldable(entry, worktree);
let is_unfoldable = auto_fold_dirs && self.is_unfoldable(entry, worktree);
let worktree_id = worktree.id();
@@ -559,7 +559,7 @@ impl ProjectPanel {
}
fn is_unfoldable(&self, entry: &Entry, worktree: &Worktree) -> bool {
if !entry.is_dir() || self.unfolded_dir_ids.contains(&entry.id) {
if !entry.is_container() || self.unfolded_dir_ids.contains(&entry.id) {
return false;
}
@@ -568,7 +568,7 @@ impl ProjectPanel {
let mut child_entries = snapshot.child_entries(&parent_path);
if let Some(child) = child_entries.next() {
if child_entries.next().is_none() {
return child.kind.is_dir();
return child.kind.is_container();
}
}
};
@@ -576,13 +576,13 @@ impl ProjectPanel {
}
fn is_foldable(&self, entry: &Entry, worktree: &Worktree) -> bool {
if entry.is_dir() {
if entry.is_container() {
let snapshot = worktree.snapshot();
let mut child_entries = snapshot.child_entries(&entry.path);
if let Some(child) = child_entries.next() {
if child_entries.next().is_none() {
return child.kind.is_dir();
return child.kind.is_container();
}
}
}
@@ -591,7 +591,7 @@ impl ProjectPanel {
fn expand_selected_entry(&mut self, _: &ExpandSelectedEntry, cx: &mut ViewContext<Self>) {
if let Some((worktree, entry)) = self.selected_entry(cx) {
if entry.is_dir() {
if entry.is_container() {
let worktree_id = worktree.id();
let entry_id = entry.id;
let expanded_dir_ids =
@@ -911,7 +911,7 @@ impl ProjectPanel {
let worktree = worktree.read(cx);
if let Some(mut entry) = worktree.entry_for_id(entry_id) {
loop {
if entry.is_dir() {
if entry.is_container() {
if let Err(ix) = expanded_dir_ids.binary_search(&entry.id) {
expanded_dir_ids.insert(ix, entry.id);
}
@@ -963,7 +963,7 @@ impl ProjectPanel {
worktree_id,
entry_id,
is_new_entry: false,
is_dir: entry.is_dir(),
is_dir: entry.is_container(),
processing_filename: None,
});
let file_name = entry
@@ -1117,7 +1117,7 @@ impl ProjectPanel {
loop {
let mut child_entries_iter = snapshot.child_entries(path);
if let Some(child) = child_entries_iter.next() {
if child_entries_iter.next().is_none() && child.is_dir() {
if child_entries_iter.next().is_none() && child.is_container() {
self.unfolded_dir_ids.remove(&child.id);
path = &*child.path;
} else {
@@ -1259,7 +1259,9 @@ impl ProjectPanel {
) -> Option<PathBuf> {
let mut new_path = target_entry.path.to_path_buf();
// If we're pasting into a file, or a directory into itself, go up one level.
if target_entry.is_file() || (target_entry.is_dir() && target_entry.id == source.entry_id) {
if target_entry.is_file()
|| (target_entry.is_container() && target_entry.id == source.entry_id)
{
new_path.pop();
}
let clipboard_entry_file_name = self
@@ -1362,7 +1364,7 @@ impl ProjectPanel {
fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
if let Some((worktree, entry)) = self.selected_entry(cx) {
let abs_path = worktree.abs_path().join(&entry.path);
let working_directory = if entry.is_dir() {
let working_directory = if entry.is_container() {
Some(abs_path)
} else {
if entry.is_symlink {
@@ -1384,7 +1386,7 @@ impl ProjectPanel {
cx: &mut ViewContext<Self>,
) {
if let Some((worktree, entry)) = self.selected_entry(cx) {
if entry.is_dir() {
if entry.is_container() {
let include_root = self.project.read(cx).visible_worktrees(cx).count() > 1;
let dir_path = if include_root {
let mut full_path = PathBuf::from(worktree.root_name());
@@ -1540,7 +1542,7 @@ impl ProjectPanel {
let Some(entry) = worktree.entry_for_path(path) else {
continue;
};
if entry.is_dir() {
if entry.is_container() {
if let Err(idx) = expanded_dir_ids.binary_search(&entry.id) {
expanded_dir_ids.insert(idx, entry.id);
}
@@ -1583,12 +1585,12 @@ impl ProjectPanel {
};
let mut new_entry_parent_id = None;
let mut new_entry_kind = EntryKind::Dir;
let mut new_entry_kind = EntryKind::Container;
if let Some(edit_state) = &self.edit_state {
if edit_state.worktree_id == worktree_id && edit_state.is_new_entry {
new_entry_parent_id = Some(edit_state.entry_id);
new_entry_kind = if edit_state.is_dir {
EntryKind::Dir
EntryKind::Container
} else {
EntryKind::File(Default::default())
};
@@ -1599,7 +1601,7 @@ impl ProjectPanel {
let mut entry_iter = snapshot.entries(true, 0);
while let Some(entry) = entry_iter.entry() {
if auto_collapse_dirs
&& entry.kind.is_dir()
&& entry.kind.is_container()
&& !self.unfolded_dir_ids.contains(&entry.id)
{
if let Some(root_path) = snapshot.root_entry() {
@@ -1607,7 +1609,7 @@ impl ProjectPanel {
if let Some(child) = child_entries.next() {
if entry.path != root_path.path
&& child_entries.next().is_none()
&& child.kind.is_dir()
&& child.kind.is_container()
{
entry_iter.advance();
continue;
@@ -1618,12 +1620,16 @@ impl ProjectPanel {
visible_worktree_entries.push(entry.clone());
if Some(entry.id) == new_entry_parent_id {
let mtime = if let EntryData::DiskEntry { mtime, .. } = entry.data {
mtime
} else {
None
};
visible_worktree_entries.push(Entry {
id: NEW_ENTRY_ID,
kind: new_entry_kind,
path: entry.path.join("\0").into(),
inode: 0,
mtime: entry.mtime,
data: EntryData::DiskEntry { inode: 0, mtime },
is_ignored: entry.is_ignored,
is_external: false,
is_private: false,
@@ -2169,7 +2175,7 @@ impl ProjectPanel {
if !this.marked_entries.insert(selection) {
this.marked_entries.remove(&selection);
}
} else if kind.is_dir() {
} else if kind.is_container() {
this.toggle_expanded(entry_id, cx);
} else {
let click_count = event.up.click_count;
@@ -2596,7 +2602,7 @@ impl Panel for ProjectPanel {
|| project.visible_worktrees(cx).any(|tree| {
tree.read(cx)
.root_entry()
.map_or(false, |entry| entry.is_dir())
.map_or(false, |entry| entry.is_container())
})
}
}
@@ -5016,7 +5022,7 @@ mod tests {
}
let indent = " ".repeat(details.depth);
let icon = if details.kind.is_dir() {
let icon = if details.kind.is_container() {
if details.is_expanded {
"v "
} else {

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