Compare commits

...

115 Commits

Author SHA1 Message Date
Piotr Osiewicz
e0a20690fb Add LanguageTaskBuilder and move languages to macro-based declarations 2024-02-28 16:46:10 +01:00
Piotr Osiewicz
e9613e552a Merge branch 'main' into dynamic-runnables 2024-02-28 12:45:19 +01:00
Piotr Osiewicz
a3174be565 chore: Move Location type to language (#8527)
Release Notes:

- N/A
2024-02-28 12:41:31 +01:00
Aryan Sjet
2f6b290084 linux: fix invalid cross-device link error (#8437)
This PR fix the "invalid cross-device link" error occurred in linux when
trying to write the settings file atomically, like when click the
"Enable vim mode" checkbox at first start.

```plain
[2024-02-26T22:59:25+08:00 ERROR util] .../zed/crates/settings/src/settings_file.rs:135: Failed to write settings to file "/home/$USER/.config/zed/settings.json"

Caused by:
0: failed to persist temporary file: Invalid cross-device link (os error 18)
1: Invalid cross-device link (os error 18)
```

Currently the `fs::RealFs::atomic_write()` method write to a temp file
created with `NamedTempFile::new()` and then call `persist()` method to
write to the config file path, which actually do a `rename` syscall
under the hood. As the
[issue](https://github.com/Stebalien/tempfile/issues/245) said

> `NamedTempFile::new()` will create a temporary file in your system's
temporary file directory. You need `NamedTempFile::new_in()`.

The temporary file directory in linux is in `/tmp`, which is mounted to
`tmpfs` filesystem, and in most case(all case I guess)
`$HOME/.config/zed` is mounted to a different filesystem. And the
`rename` syscall between different filesystems will return a `EXDEV`
errno, as described in the man page
[rename(2)](https://man7.org/linux/man-pages/man2/renameat2.2.html):

```plain
       EXDEV  oldpath and newpath are not on the same mounted
              filesystem.  (Linux permits a filesystem to be mounted at
              multiple points, but rename() does not work across
              different mount points, even if the same filesystem is
              mounted on both.)
```

And as the issue above said, use a different temp dir with
`NamedTempFile::new_in()` for linux platform might be a solution, since
the `rename` syscall provides atomicity.

Release Notes:
- Fix `settings.json` save failed with invalid cross-device link error
in linux
2024-02-27 21:49:28 -08:00
Conrad Irwin
9906b31691 fix vim repeat (#8513)
Release Notes:

- vim: Fixed `.` when multiple windows are open
([#7446](https://github.com/zed-industries/zed/issues/7446)).
- vim: Fixed switching to normal mode after `J`, `<` or `>` in visual
mode ([#4439](https://github.com/zed-industries/zed/issues/4439))
- vim: Added `ctrl-t` and `ctrl-d` for indent/outdent in insert mode.

- Fixed indent/outdent/join lines appearing to work in read-only buffers
([#8423](https://github.com/zed-industries/zed/issues/8423))
- Fixed indent with an empty selection when the cursor was in column 0
2024-02-27 21:36:12 -07:00
Rom Grk
9a7a267203 vim: f and t multiline option (#8448)
I'm not sure how compliant you're aiming to be with vim, but the `f`
behavior is more useful when it can search on multiple lines instead of
a single one, so I'd like to propose this change.

This change is quite frequent in vim/neovim as a plugin (e.g.
[clever-f](https://github.com/VSCodeVim/Vim),
[improved-ft](https://github.com/backdround/improved-ft.nvim), etc), and
in other vim emulations (e.g.
[vscode-vim](https://github.com/VSCodeVim/Vim)).
2024-02-27 19:34:19 -07:00
Sai Gokula Krishnan
bd8896a3dc Add icon support for files without extensions (#8453)
Release Notes:

- Added support for showing file icons for files without suffixes.

Before:

<img width="281" alt="image"
src="https://github.com/zed-industries/zed/assets/25414681/ab4c00ed-72c7-458f-8dda-61c68165590f">


After:

<img width="242" alt="Screenshot 2024-02-27 at 1 51 20 AM"
src="https://github.com/zed-industries/zed/assets/25414681/8f3082c4-9424-4bc3-9100-a527b9adc315">


This screenshot is to show if the file has extension, then the extension
takes precedence.

<img width="193" alt="image"
src="https://github.com/zed-industries/zed/assets/25414681/72fcebd1-361f-444b-8890-f59932963083">


<br>

- Added icons for
    - Docker - https://www.svgrepo.com/svg/473589/docker
    - License - https://www.svgrepo.com/svg/477704/license-1
    - Heroku - https://www.svgrepo.com/svg/341904/heroku
 
 - Updated tests
2024-02-27 20:36:38 -05:00
Conrad Irwin
d545fe9fe4 Add missing wait_for_anchors (#8509)
Release Notes:

- Fixed a panic when hovering in a collaboration session
2024-02-27 16:04:30 -07:00
Conrad Irwin
9765260567 Upgrade palette (#8506)
This fixes the compilation stck overflow here:
https://ogeon.github.io/2024/02/25/palette-0.7.5.html



Release Notes:

- N/A
2024-02-27 14:29:27 -07:00
Kirill Bulatov
9ae5366979 Ensure more Zed files are formatted uniformly (#8505)
Follow-up of
https://github.com/zed-industries/zed/pull/8500#issuecomment-1967522477

Now things are much better, but I still reformat the default.json
settings file:

```diff
diff --git a/assets/settings/default.json b/assets/settings/default.json
index c60c53026..67bf4505b 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -75,14 +75,7 @@
   // Hide the values of in variables from visual display in private files
   "redact_private_values": false,
   // Globs to match against file paths to determine if a file is private.
-  "private_files": [
-    "**/.env*",
-    "**/*.pem",
-    "**/*.key",
-    "**/*.cert",
-    "**/*.crt",
-    "**/secrets.yml"
-  ],
+  "private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
   // Whether to use additional LSP queries to format (and amend) the code after
   // every "trigger" symbol input, defined by LSP server capabilities.
   "use_on_type_format": true,
```

For me, Zed's doing that with the default prettier:
```
['/Users/someonetoignore/work/zed/zed/assets/settings/default.json' with options: {"printWidth":120,"tabWidth":2,"parser":"json","plugins":[],"path":"/Users/someonetoignore/work/zed/zed/assets/settings/default.json"}](stderr: Resolved config: {}, will format file '/Users/someonetoignore/work/zed/zed/assets/settings/default.json' with options: {"printWidth":120,"tabWidth":2,"parser":"json","plugins":[],"path":"/Users/someonetoignore/work/zed/zed/assets/settings/default.json"})
```

and `!/Library/Application Support/Zed/prettier/package-lock.json`
states that I have
```
    "node_modules/prettier": {
      "version": "3.2.5",
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
      "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
      "bin": {
        "prettier": "bin/prettier.cjs"
      },
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/prettier/prettier?sponsor=1"
      }
    },
```

Release Notes:

- N/A
2024-02-27 23:25:36 +02:00
Kirill Bulatov
e2bcb28286 Use proper buffer versions when [de]serializing hint proto requests (#8502)
During inlay hint<->proto conversions, uses proper buffer versions and
never carries them across the .await points, to fix the
```
thread 'main' panicked at 'invalid anchor Anchor { timestamp: Lamport {0: 8558}, offset: 54, bias: Right, buffer_id: Some(BufferId(8)) }. buffer id: 8, version: Global {0: 8546, 1: 8378}'
/Users/administrator/actions-runner-2/_work/zed/zed/crates/text/src/text.rs:1919
<backtrace::capture::Backtrace>::create
<backtrace::capture::Backtrace>::new
Zed::init_panic_hook::{closure#0}
std::panicking::rust_panic_with_hook
std::panicking::begin_panic_handler::{{closure}}
std::sys_common::backtrace::__rust_end_short_backtrace
_rust_begin_unwind
core::panicking::panic_fmt
<text::BufferSnapshot>::summary_for_anchor::<usize>
<multi_buffer::anchor::Anchor as multi_buffer::ToOffset>::to_offset
<editor::display_map::DisplayMap>::splice_inlays
<editor::Editor>::splice_inlay_hints
editor::inlay_hint_cache::fetch_and_update_hints::{closure#0}::{closure#0}
<async_task::raw::RawTask<<async_task::runnable::Builder<_>>::spawn_local::Checked<core::pin::Pin<alloc::boxed::Box<dyn core::future::future::Future<Output = core::result::Result<(), anyhow::Error>>>>>, core::result::Result<(), anyhow::Error>, <gpui::executor::ForegroundExecutor>::spawn::inner<core::result::Result<(), anyhow::Error>>::{closure#0}, ()>>::run
<gpui::platform::mac::platform::MacPlatform as gpui::platform::Platform>::run
Zed::main
std::sys_common::backtrace::__rust_begin_short_backtrace::<fn(), ()>
std::rt::lang_start::<()>::{closure#0}
std::rt::lang_start_internal
_main
```
class of panics.


Release Notes:

- Fixed occasional panics during collaborative editing with inlay hints
on both sides

Co-authored-by: Conrad <conrad@zed.dev>
2024-02-27 22:19:11 +02:00
Marshall Bowers
89caf06dbe Move note about screenshots up in the PR template (#8501)
This PR rearranges the PR template to move the line about including
screenshots or media up underneath the `Added/Fixed/Improved` section.

This makes it easier to delete one section or the other depending on
what kind of change you're making.

Release Notes:

- N/A
2024-02-27 14:19:18 -05:00
Marshall Bowers
f93272efe8 Format default settings.json with Prettier (#8500)
This PR formats the default `settings.json` file with Prettier.

This should help avoid unnecessary modifications in other PRs making
consequential changes to this file.

Release Notes:

- N/A
2024-02-27 14:14:42 -05:00
Dzmitry Malyshau
cb75c57fc0 Cleanup dependencies (part 4) (#8468)
Follow-up to #8425 . Final part - adds the CI check.

Release Notes:
- N/A
2024-02-27 20:41:49 +02:00
Thorsten Ball
ac31c824e4 Use numeric sorting if possible in project panel (#8486)
Previously, if you had the following files/folders in your project

    1-abc
    10
    11-def
    ...
    2
    21-abc

that's how we'd display them.

With this change, we now try to parse them as numbers, if possible, and
use that to sort. If we can't parse a component as a number, we fall
back to normal string comparison.

End result is this:

    1-abc
    2
    10
    11-def
    ...
    21-abc


Release Notes:

- Fixed filenames with numeric components (`1.txt`, `1/one.txt`, ...)
not being sorted as numbers, but as string.

Before:

![screenshot-2024-02-27-18 29
43@2x](https://github.com/zed-industries/zed/assets/1185253/2d223126-329f-4ae7-9a12-d33e2c3fe52f)


After:

![after](https://github.com/zed-industries/zed/assets/1185253/f4f98fa0-e66f-40aa-aa28-189143cbb75f)

---------

Co-authored-by: Marshall <marshall@zed.dev>
2024-02-27 18:42:15 +01:00
Piotr Osiewicz
91b011a2c6 Add new lsp capabilities 2024-02-27 18:36:41 +01:00
Thorsten Ball
3036c0cab7 Remove dead apply_code_actions_on_save code (#8488)
I think this was a left-over from when Conrad and I worked on this but
then it was changed to work with `format`.

Release Notes:

- N/A
2024-02-27 17:38:15 +01:00
Marshall Bowers
83bc24b480 Adjust Cargo caching in CI (#8494)
This PR adjusts the way we cache Cargo dependencies in CI.

We're trying out
[swatinem/rust-cache](https://github.com/swatinem/rust-cache) to see if
it can improve our caching strategy such that we're able to get more
cache hits on PRs.

We'll only write to the cache on `main` in the hopes that it will
mitigate the amount of thrashing of the cache.

Release Notes:

- N/A
2024-02-27 11:35:17 -05:00
Piotr Osiewicz
24677b11ed Merge branch 'main' into dynamic-runnables 2024-02-27 17:16:11 +01:00
Piotr Osiewicz
c70cb5a911 Progress on DocumentSymbol request 2024-02-27 17:15:58 +01:00
Thorsten Ball
7acd6879db Fix copying folders into themselves to create copies (#8484)
This fixes #7314 and #7778.

The problem was copying a folder into itself, which is actually quite a
common operation in macOS's `Finder.app`: you select a folder, hit
`cmd-c` and `cmd-v` and have a copy. That's also how it works in VS
Code.

The fix here is to detect when we're copying a folder into itself and
treating it like we're copying a file into itself: we don't want to copy
into the target, we want to copy into the folder one level higher up,
which will then automatically add a ` copy` to the end of the name.

Release Notes:

- Fixed ability to copy folders into themselves by selecting them in
project panel and hitting `copy` and `paste`. Instead of endless
recursion, a copy of the folder is now created.
([#7314](https://github.com/zed-industries/zed/issues/7314)).

Demo:



https://github.com/zed-industries/zed/assets/1185253/2141310a-991d-491d-8498-eb766275a1f5
2024-02-27 14:57:46 +01:00
Piotr Osiewicz
b884658e49 Merge branch 'main' into dynamic-runnables 2024-02-27 12:47:03 +01:00
Antonio Scandurra
7cbdea2ca0 Revert "Add support of auto folded directories" (#8476)
Reverts zed-industries/zed#7674

@ABckh: reverting this as it introduced a significant performance
slowdown, most likely caused by iterating through all the snapshot
entries to determine whether a directory is foldable/unfoldable/omitted.
It would be great if you could open a new PR that reverts this revert
and addresses the performance issues. Thank you!

/cc: @maxbrunsfeld 

Release notes:

- N/A
2024-02-27 11:26:18 +01:00
Thorsten Ball
ddca6a3fb7 Debounce refresh of inlay hints on buffer edits (#8282)
I think this makes it less chaotic to edit text when the inlay hints are
on.

It's for cases where you're editing to the right side of an inlay hint.
Example:

```rust
for name in names.iter().map(|item| item.len()) {
    println!("{:?}", name);
}
```

We display a `usize` inlay hint right next to `name`.

But as soon as you remove that `.` in `names.iter` your cursor jumps
around because the inlay hint has been removed.

With this change we now have a 700ms debounce before we update the inlay
hints.

VS Code seems to have an even longer debounce, I think somewhere around
~1s.

Release Notes:

- Added debouncing to make it easier to edit text when inlay hints are
enabled and to save rendering of inlay hints when scrolling. Both
debounce durations can be configured with `{"inlay_hints":
{"edit_debounce_ms": 700}}` (default) and `{"inlay_hints":
{"scroll_debounce_ms": 50}}`. Set a value to `0` to turn off the
debouncing.


### Before


https://github.com/zed-industries/zed/assets/1185253/3afbe548-dcfb-45a3-ab9f-cce14c04a148



### After



https://github.com/zed-industries/zed/assets/1185253/7ea90e42-bca6-4f6c-995e-83324669ab43

---------

Co-authored-by: Kirill <kirill@zed.dev>
2024-02-27 11:18:13 +01:00
Piotr Osiewicz
cbdc07dcd0 project: enable missing project_core/test-support feature when test-support is enabled (#8471)
This fixes collab's test build failure that @ConradIrwin spotted.


Release Notes:

- N/A
2024-02-27 10:07:40 +01:00
Dheeraj
af14bc7a27 Fall back to stdout if log file is inaccessible (#8415)
https://github.com/zed-industries/zed/assets/31967125/644f3524-e680-457c-bf4c-a7f11f3ec8db

Fixes #8209
Defaults to env logger in case of open/access failure.

Release Notes:

- Improved Zed behavior when no log file access is possible ([8209](https://github.com/zed-industries/zed/issues/8209))

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2024-02-27 09:48:19 +02:00
Conrad Irwin
8fc2431a2a vim: Keep multi-cursor on escape (#8464)
Release Notes:

- vim: Preserve multiple selections when returning to normal mode.

/cc @mrnugget
2024-02-26 22:54:02 -07:00
Hans
f3fa3b910a vim: Add HTML tag support for #4503 (#8175)
a simple code for html tag support, I've only done the basics, and if
it's okay, I'll optimize and organize the code, and adapt other parts
like `is_multiline`, `always_expands_both_ways`, `target_visual_mode`,
etc

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-02-26 22:48:19 -07:00
riccardofano
a42b987929 Add :tabonly and :only vim commands (#8337)
Release Notes:

- Added
[`:tabo[nly][!]`](https://neovim.io/doc/user/tabpage.html#%3Atabonly),
closes all the tabs except the active one but in the current pane only,
every other split pane remains unaffected.
The version with the `!` force closes the tabs while the one without
asks you to save or discard the changes.
- Added [`:on[ly][!]`](https://neovim.io/doc/user/windows.html#%3Aonly),
closes all the tabs *and* panes except the active one.
The version with the `!` force closes the tabs while the one without
asks you to save or discard the changes.
Since Zed does not have different splits per tab like in Neovim `:only`
works the same as it does in VscodeVim.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-02-26 22:15:50 -07:00
Conrad Irwin
c31626717f channel projects (#8456)
Add plumbing for hosted projects. This will currently show them if they
exist
but provides no UX to create/rename/delete them.

Also changed the `ChannelId` type to not auto-cast to u64; this avoids
type
confusion if you have multiple id types.


Release Notes:

- N/A
2024-02-26 22:15:11 -07:00
Conrad Irwin
8cf36ae603 vim: Fix some problems with visual mode testing (#8461)
Release Notes:

- N/A
2024-02-26 20:15:27 -07:00
Marshall Bowers
079c31fcac Update Cargo.lock (#8458)
This PR updates `Cargo.lock`, since it was missed in another PR.

Release Notes:

- N/A
2024-02-26 21:35:13 -05:00
Daniel Banck
bbc4ed9cab Add language server for Terraform (#7657)
* Depends on: https://github.com/zed-industries/zed/pull/7449
* Closes: https://github.com/zed-industries/zed/issues/5098

---

This PR adds support for downloading and running the Terraform language
server for `*.tf` and `*.tfvars` files. I've verified that the code
works for `aarch64` and `x86_64` macOS. Downloading new language server
versions on release also works as expected.

Furthermore this PR adds:
- A short docs page for Terraform
- An icon for `*.tf` and `*.tfvars` files

## UX

### File Icons

![CleanShot 2024-02-10 at 23 10
13@2x](https://github.com/zed-industries/zed/assets/45985/6f7cd4f0-e94c-4cfb-b3e9-64b0e33c8a43)

### Completion

![CleanShot 2024-02-13 at 20 54
15@2x](https://github.com/zed-industries/zed/assets/45985/18fafa3b-cb50-4f51-b071-ca9eee3521a6)

### Hover

![CleanShot 2024-02-13 at 20 53
40@2x](https://github.com/zed-industries/zed/assets/45985/4d215315-e019-4d3d-b23c-2691db1803e3)

### Go to definition

![2024-02-13 20 56
28](https://github.com/zed-industries/zed/assets/45985/c21d562f-eb0b-4df9-9175-c53b9923344e)

### Formatting

![2024-02-13 20 59
06](https://github.com/zed-industries/zed/assets/45985/0cdf4ec5-e231-4c8a-a257-cae30a8edc8b)

and more!

## Known issue(s)

@fdionisi discovered that sometimes completion results are inserted with
the wrong indentation. Or rather, if you look closely, they are inserted
with the correct indentation and then something shifts the closing `}`.
I don't think this is related to LSP support and can be addressed in a
separate PR.

![2024-02-13 20 58
16](https://github.com/zed-industries/zed/assets/45985/94a118dd-95f5-4e38-8f83-75fec7a0dddf)

Release Notes:

- Add language server support for Terraform
([#5098](https://github.com/zed-industries/zed/issues/5098)).

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2024-02-26 17:08:49 -08:00
Max Brunsfeld
8536ba54c3 Upgrade Tree-sitter and Wasmtime, compile Cranelift with optimizations in debug builds (#8452)
After upgrading to Wasmtime 18, we got crashes when running Zed in debug
mode. While bisecting the Wasmtime commits and trying to identify the
source of the crash, we noticed this Wasmtime PR, which increased the
stack size of background threads in an example. This alerted us to the
possibility that a stack overflow might be happening due to a lot of
stack usage by cranelift.

https://github.com/bytecodealliance/wasmtime/pull/7651

Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
2024-02-26 16:27:57 -08:00
Kirill Bulatov
a0c8debd35 Mention possible run options in the task modal placeholder (#8449)
Release Notes:

- Improved run task modal's placeholder

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-02-27 00:04:56 +02:00
Piotr Osiewicz
009384f948 Extract project_core out of project (#8438)
That's done to unblock work for dynamic tasks (`task` crate has to
access the worktree yet it is a dependency of a `project`).
Release Notes:

- N/A
2024-02-26 22:09:22 +01:00
Piotr Osiewicz
72009de309 chore: Fix warning from 1.77 rustc (#8265)
/cc @maxbrunsfeld , I didn't remove the field outright since I'm not
sure if the intent is to use it eventually in extensions work.
This is the warning we're getting on 1.77 (release date: 03.21.2024) :
```
warning: field `0` is never read
  --> crates/language/src/language_registry.rs:81:12
   |
81 |     Loaded(PathBuf, tree_sitter::Language),
   |     ------ ^^^^^^^
   |     |
   |     field in this variant
   |
   = note: `#[warn(dead_code)]` on by default
help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field
   |
81 |     Loaded((), tree_sitter::Language),
   |            ~~

warning: field `0` is never read
  --> crates/language/src/language_registry.rs:82:13
   |
82 |     Loading(PathBuf, Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
   |     ------- ^^^^^^^
   |     |
   |     field in this variant
   |
help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field
   |
82 |     Loading((), Vec<oneshot::Sender<Result<tree_sitter::Language>>>),
   |             ~~
```
Release Notes:

- N/A
2024-02-26 21:51:45 +01:00
Conrad Irwin
bdf59b8f06 fix migration (#8451)
Release Notes:

- N/A
2024-02-26 13:50:26 -07:00
Joseph T. Lyons
e01a606616 Update 2_crash_report.yml 2024-02-26 15:30:35 -05:00
Joseph T. Lyons
510f4328f2 Create 2_crash_report.yml 2024-02-26 15:28:57 -05:00
Joseph T. Lyons
4b7bd03db7 Update bug report issue template 2024-02-26 15:28:33 -05:00
Joseph T. Lyons
935938a27c Format feature request issue template 2024-02-26 15:28:24 -05:00
Julia
d4584a10b6 Tell Wayland compositor we can handle keyboard ver 4 for repeat info (#8446)
Fixes us not getting Wayland key repeat info from the compositor

Release Notes:

- N/A
2024-02-26 15:13:01 -05:00
Igal Tabachnik
3b2e315ead Open a new file on double-clicking the tab bar (#8431)
Fixes #6818

This is a convenience feature that exists in other editors, allowing
quick creation a new tab by double-clicking the empty space on the tab
bar:
![2024-02-26 14 26
16](https://github.com/zed-industries/zed/assets/601206/55d99a84-2e61-494d-b06c-6e5f15071655)

Release Notes:

- Added the ability to open a new buffer when double-clicking on the tab
bar ([#6818](https://github.com/zed-industries/zed/issues/6818)).
2024-02-26 14:07:04 -05:00
Bennet Bo Fenner
43163a0154 Support rendering strikethrough text in markdown (#8287)
Just noticed strikethrough text handling was not implemented for the
following:

Chat

![image](https://github.com/zed-industries/zed/assets/53836821/ddd98272-d4d4-4a94-bd79-77e967f3ca15)

Markdown Preview

![image](https://github.com/zed-industries/zed/assets/53836821/9087635c-5b89-40e6-8e4d-2785a43ef318)

Code Documentation

![image](https://github.com/zed-industries/zed/assets/53836821/5ed55c60-3e5e-4fc2-86c2-a81fac7de038)

It looks like there are three different markdown parsing/rendering
implementations, might be worth to investigate if any of these can be
combined into a single crate (looks like a lot of work though).

Release Notes:

- Added support for rendering strikethrough text in markdown elements
2024-02-26 21:04:48 +02:00
Marshall Bowers
cd8ede542b Fix bootstrap script (#8445)
This PR fixes the bootstrap script, as we had some unintentional changes
committed to it.

Closes #8370.

Release Notes:

- N/A
2024-02-26 13:20:31 -05:00
Ben Hamment
7d0c515be9 Improve Ruby grammar to recognize method parameters (#8284)
Release Notes:

- Improved Ruby Grammar to recognise various method parameters

![image](https://github.com/zed-industries/zed/assets/7274458/45d4ee2e-d174-4835-a461-22eee428a73b)


![image](https://github.com/zed-industries/zed/assets/7274458/c1bbf307-4f6b-4839-81dc-9d982c85bc58)
2024-02-26 10:09:52 -08:00
Yury Abykhodau
011ae8536c Add support of auto folded directories (#7674)
Added support of auto collapsed directories, for example when directory
has only one directory inside we should display it as dir1/dir2 (#6935
). Please feel free to propose better solutions, as I am new in Rust

Demo:
https://streamable.com/seo3n9

Release Notes:

- Added support for auto-collapsing directories.
2024-02-26 10:01:59 -08:00
Sai Gokula Krishnan
fcd0571ab4 Add file icons for Dart, Swift, Kotlin, Java, Fonts (#8404)
Added icons for
- Dart - https://www.svgrepo.com/svg/473578/dart
- Swift - https://www.svgrepo.com/svg/512939/swift-146
- Kotlin - https://www.svgrepo.com/svg/473692/kotlin
- Java - https://www.svgrepo.com/svg/449119/java-filled
- Fonts - https://www.svgrepo.com/svg/532172/font

Extended support for
- .plist as template

<img width="164" alt="Screenshot 2024-02-26 at 12 17 08 AM"
src="https://github.com/zed-industries/zed/assets/25414681/bd438028-af82-44cd-934f-21ab72ac9d0f">

Release Notes:

- Added icons for Dart, Swift, Kotlin, Java, and font files.
- Changed icon for `.plist` files.
2024-02-26 12:33:28 -05:00
Conrad Irwin
f27d59896f unique channel names (#8439)
Before this change duplicate channels were ordered arbitrarily, which
put the
collab channel in an inconsistent state.

Release Notes:

- Fixed duplicate channel names appearing in the collab sidebar.
2024-02-26 10:07:22 -07:00
Piotr Osiewicz
f0f49db969 WIP: TestSource 2024-02-26 16:01:47 +01:00
Dzmitry Malyshau
a44fc24445 Clean up many small dependencies (part 3) (#8425)
Follow-up to #8353

Release Notes:
- N/A
2024-02-26 11:08:57 +02:00
Joseph T. Lyons
f54bb32a7f Point requests for languages to extension repository 2024-02-26 01:04:54 -05:00
Joseph T. Lyons
d8902ca5d6 Rename bug report issue template 2024-02-26 01:04:39 -05:00
Joseph T. Lyons
b575fbb449 Delete 1_language_support.yml 2024-02-26 01:04:15 -05:00
Marshall Bowers
d8276b0f0d Hoist itertools dependency to workspace level (#8417)
This PR hoists the `itertools` dependency to the workspace level.

Release Notes:

- N/A
2024-02-25 20:37:52 -05:00
Marshall Bowers
841e010fa4 Hoist chrono dependency to workspace level (#8414)
This PR hoists the `chrono` dependency to the workspace level.

Release Notes:

- N/A
2024-02-25 18:52:59 -05:00
Marshall Bowers
ffdda588b4 Format JSON files in assets/ (#8405)
This PR formats the JSON files in the `assets/` directory with Prettier.

This should help avoid some of the changes in formatting when these
files are touched by contributors.

Release Notes:

 - N/A
2024-02-25 14:11:38 -05:00
Howins
6c5dfd1061 Add terminal icon for Nu file (#8399)
Nu is a shell language so it should has the `terminal` icon.

Release Notes:

- N/A

<img width="241" alt="Capture d’écran 2024-02-25 à 18 49 43"
src="https://github.com/zed-industries/zed/assets/24520681/0adcd8fd-f5b0-4688-b301-5c49c376c7a0">
2024-02-25 13:47:04 -05:00
Marshall Bowers
6ef32374d6 Add command_palette_hooks crate (#8398)
This PR introduces a new `command_palette_hooks` crate that contains the
types used to hook into the behavior of the command palette.

The `CommandPaletteFilter` was previously extracted to the `copilot`
crate in #7095, solely because that was the earliest ancestor of the
crates that depended on it.

The `CommandPaletteInterceptor` was still defined in `command_palette`
itself.

Both of these types were consumed by other crates wanting to influence
the behavior of the command palette, but required taking a dependency on
the entire `command_palette` crate in order to gain access to these
hooks.

By moving them out into their own crate, we can improve the compile
order and make crates like `vim` able to begin building sooner without
having to wait for `command_palette` to finish compiling.

Here's a comparison of the compilation graph before and after (ignore
the timings):

#### Before

<img width="332" alt="Screenshot 2024-02-25 at 12 42 29 PM"
src="https://github.com/zed-industries/zed/assets/1486634/a57c662e-fbc2-41ab-9e30-cca17afa6c73">

#### After

<img width="362" alt="Screenshot 2024-02-25 at 12 51 15 PM"
src="https://github.com/zed-industries/zed/assets/1486634/c1a6d29c-b607-4604-8f1b-e5d318bf8849">

Release Notes:

- N/A
2024-02-25 13:21:20 -05:00
Marshall Bowers
b29946130e Hoist languages crate's dependencies to the workspace level (#8394)
This PR hoists all of the dependencies of the `languages` crate to the
workspace level.

Release Notes:

- N/A
2024-02-25 12:02:59 -05:00
Marshall Bowers
368fec2822 Format scripts with Prettier (#8393)
This PR does a one-off format of some of our scripts using Prettier.

Release Notes:

- N/A
2024-02-25 11:03:33 -05:00
Bennet Bo Fenner
934af6ad45 recent projects: fix list flashing/empty (#8376)
If the list is large (size > overdraw + available height) the
`all_rendered` check was preventing the list from returning an inferred
size. Theoretically we can now report heights which are actually too
small (because not all items were affected during layout), this can be
manually adjusted using the overdraw parameter. In this case its fine
because the picker is inside a max_height which should never be more
then the overdraw we specify (1000 px), and the list will shrink down
either way when the request_measured_layout callback is called again.

Release Notes:

- Fixed flashing of recent projects list when there were a lot of
projects in the list
([#8364](https://github.com/zed-industries/zed/issues/8364#issuecomment-1962849393)).
2024-02-25 17:22:01 +02:00
Marshall Bowers
053b6cc715 Rework extension filtering to use a ToggleButton (#8387)
This PR reworks the extension filtering to use a `ToggleButton`, since
the filter states are mutually-exclusive.

<img width="1136" alt="Screenshot 2024-02-25 at 10 04 59 AM"
src="https://github.com/zed-industries/zed/assets/1486634/52c621da-201c-42b9-805d-62e3ab66f94b">

Release Notes:

- N/A
2024-02-25 10:17:50 -05:00
Hugo Urías
37a12a366f Fix double menu item separator in "Help" section of app menu (#8351)
I've modified the code at `crates/zed/src/app_menus.rs` as it looks like
there was a typo and instead of one
`MenuItem::separator()` there were two.

It would be something like this:

<img width="1512" alt="Captura de pantalla 2024-02-25 a las 16 12 10"
src="https://github.com/zed-industries/zed/assets/93369643/d35d5b57-247c-4a1f-b65a-a1b68cd302b9">
2024-02-25 10:17:10 -05:00
Joseph T. Lyons
633e31a47f Allow extensions to be filtered on installed and not installed (#8375)
Partially resolves: https://github.com/zed-industries/zed/issues/7785

Right now, we can engage `Only show installed`, but I've been wanting to
be able to filter down to just uninstalled extensions too, so I can
browse things I don't have. I changed this to have 2 checkboxes,
`Installed` and `Not installed` and both are on by default. You deselect
them to filter down.

<img width="1608" alt="SCR-20240225-etyg"
src="https://github.com/zed-industries/zed/assets/19867440/e2267651-ff86-437b-ba59-89f3d338ea02">

Release Notes:

- Allow extensions list to be filtered down to both installed and not
installed.
2024-02-25 05:07:13 -05:00
Kirill Bulatov
882cd6e52f Revert "Bump tree-sitter, wasmtime (#8306)" (#8373)
This reverts commit d4973846c0.

Fixes https://github.com/zed-industries/zed/issues/8360 and
https://github.com/zed-industries/zed/issues/8362


Release Notes:

- N/A
2024-02-25 10:35:19 +02:00
Dzmitry Malyshau
83f493b387 Clean up deps for file_finder, language_selector, task, rpc, storybook (#8353)
Following-up on #8330

Invocation
```bash
cargo-machete --with-metadata --skip-target-dir --fix
````

There is more stuff to fix, but it chokes on `async-lock`:
```
cargo-machete found the following unused dependencies in /x/Code/zed:
rpc -- /x/Code/zed/crates/rpc/Cargo.toml:
        async_lock
        prost_build
        serde_derive
Error: Dependency async_lock not found
```

Release Notes:
- N/A
2024-02-25 10:10:07 +02:00
rmu
dbe1f48f95 ocaml: Small query improvements and fix autoclose brackets (#7769)
Turns out auto-closing words was a bad idea. win**do**w, **struct**ure,
**sig**n and so on

They don't serve any purpose in `config.toml` nor `brackets.scm` at this
point, so I removed them>

Release Notes:
- N/A
2024-02-24 19:06:25 -05:00
Marshall Bowers
401798d9b8 Remove unused plugin crates (#8350)
This PR removes the unused crates for plugin support.

We're currently exploring Wasm-based extensions, and it's unlikely that
we'll be reusing any of this existing work.

Release Notes:

- N/A
2024-02-24 19:05:18 -05:00
Dzmitry Malyshau
35bec9803a Clean up dependencies of call,lsp,project,settings,vim,welcome, and workspace (#8330)
Based on the product of
[cargo-machete](https://blog.benj.me/2022/04/27/cargo-machete/):

[dependencies.txt](https://github.com/zed-industries/zed/files/14392213/dependencies.txt)

Release Notes:
- N/A
2024-02-25 00:41:28 +02:00
Max Brunsfeld
d4973846c0 Bump tree-sitter, wasmtime (#8306)
Fixes
https://github.com/zed-industries/zed/issues/8296#issuecomment-1961957369

Release Notes:

- Fixed a crash that would happen when loading an extension that added a
grammar that was generated using a very old version of Tree-sitter
([#8296](https://github.com/zed-industries/zed/issues/8296)).

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
2024-02-24 14:24:29 -08:00
Kirill Bulatov
16d826a3f4 Show keybindings instead of the action names in the recent project modal 2024-02-25 00:08:57 +02:00
Kirill Bulatov
c29ea9bdbc Allow using context in the placeholder_text method 2024-02-25 00:08:57 +02:00
Hugo Urías
cf3b875922 Add icon for R files (#8223)
Add an icon from https://www.svgrepo.com/svg/340612/logo-r-script for .r

<img width="1392" alt="307467014-63f68791-9d74-4bd1-8065-3698665f7c15"
src="https://github.com/zed-industries/zed/assets/93369643/ce24615c-6946-479a-8660-663bf83a7dde">

Credits For Image: @moshyfawn

Release Notes:

- Added R logo
2024-02-24 23:01:39 +02:00
Ngô Quốc Đạt
3ddf2f27d3 Add bun file icon (#8322)
Add bun file icon, source from https://bun.sh/press-kit

![Screenshot 2024-02-24 at 12 32
12](https://github.com/zed-industries/zed/assets/56961917/ebc731a6-5c78-481e-99da-f78f03574fad)

Release Notes:

- Added a bun file icon
2024-02-24 22:40:22 +02:00
Chase Bellisime
d1b58601a1 Add plus and dollar sign to terminal paths (#8321)
After fix (file path is now clickable):
<img width="1019" alt="Screenshot 2024-02-23 at 8 59 45 PM"
src="https://github.com/zed-industries/zed/assets/63214891/cbdcb0f5-cd58-436b-9980-f657436f2bc0">

Before fix:
<img width="936" alt="Screenshot 2024-02-23 at 9 15 25 PM"
src="https://github.com/zed-industries/zed/assets/63214891/72d27155-a708-4b8d-a0b6-85d1e9031627">

Release Notes:

- Fixed an issue with terminal paths not allowing links when the path
included `+` or `$` symbols.
([#8256](https://github.com/zed-industries/zed/issues/#8256)).
2024-02-24 21:39:34 +02:00
Dzmitry Malyshau
d895388809 linux: profile text system functions 2024-02-24 00:04:00 -08:00
Dzmitry Malyshau
76196694b7 Fix unused warning in time_format 2024-02-24 00:04:00 -08:00
Marshall Bowers
ba4e1699ae Rename ZedHttpClient for clarity (#8320)
This PR renames the `ZedHttpClient` to `HttpClientWithUrl` to make it
slightly clearer that it still is holding a `dyn HttpClient` as opposed
to being a concrete implementation.

Release Notes:

- N/A
2024-02-24 00:07:24 -05:00
Ivan Buryak
58463b2e97 Add icon for GraphQL files (#8213)
Add an icon from https://graphql.org/brand/ for `.graphql`


![Examples@2x](https://github.com/zed-industries/zed/assets/4057095/9751a509-0dca-4611-b98f-277307c4bfe7)
2024-02-23 22:03:02 -05:00
Marshall Bowers
03b0764df4 Replace time_format license with symlink 2024-02-23 21:54:39 -05:00
Marshall Bowers
c59aab5090 Adjust labels of buttons in extension list based on status (#8319)
This PR makes the labels of the buttons in the extension list adapt to
reflect the current status.

Release Notes:

- Changed the button labels in the extension list to reflect the current
status.
2024-02-23 21:53:14 -05:00
vultix
2e616f8388 Add new argument vim text object (#7791)
This PR adds a new `argument` vim text object, inspired by
[targets.vim](https://github.com/wellle/targets.vim).

As it's the first vim text object to use the syntax tree, it needed to
operate on the `Buffer` level, not the `MultiBuffer` level, then map the
buffer coordinates to `DisplayPoint` as necessary.

This required two main changes:
1. `innermost_enclosing_bracket_ranges` and `enclosing_bracket_ranges`
were moved into `Buffer`. The `MultiBuffer` implementations were updated
to map to/from these.
2. `MultiBuffer::excerpt_containing` was made public, returning a new
`MultiBufferExcerpt` type that contains a reference to the excerpt and
methods for mapping to/from `Buffer` and `MultiBuffer` offsets and
ranges.

Release Notes:
- Added new `argument` vim text object, inspired by
[targets.vim](https://github.com/wellle/targets.vim).
2024-02-23 19:37:13 -07:00
Bennet Bo Fenner
dc7e14f888 Respect user preferences when formatting timestamp (#7994)
This is a follow up to #7945. The current behaviour reads the locale and
infers from that which type of time format should be used (12 hour/24
hour).
However, in macOS you can override this behaviour, e.g. you can use
en_US locale but still use the 24 hour clock format (Can be customized
under Settings > General > Date & Format > 24-hour time). You can even
customize the date format.

This PR uses the macOS specific `CFDateFormatter` API, which outputs
time format strings, that respect those settings.

Partially fixes #7956 (as its not implemented for linux)

Release Notes:

- Added localization support for all macOS specific date and time
configurations in chat
2024-02-23 19:18:06 -07:00
Marshall Bowers
7599933f30 Fix uploads to edit_events table (#8318)
This PR fixes uploads the `edit_events` table.

We were trying to insert into a column that didn't exist:

```
HTTP error 500 Internal Server Error: failed to upload to table 'edit_events'

Caused by:
    bad response: Code: 16. DB::Exception: No such column os_name in table default.edit_events
```

Release Notes:

- N/A
2024-02-23 20:58:45 -05:00
Max
622cae19eb Fix alignment of traffic lights (#8128)
closes #7339
2024-02-23 20:09:14 -05:00
Marshall Bowers
e06ff5f507 Use SystemClock in EventCoalescer (#8317)
This PR updates the `EventCoalescer` to use the `SystemClock` trait to
abstract over the clock.

This allows us to test the advancement of time without relying on the
caller passing in the current time.

Release Notes:

- N/A
2024-02-23 20:07:13 -05:00
Paul Berg
9b44ba9382 linux/text: fix invalid span creation (#8286)
This fixes a crash when showing completions.

Release Notes:

- N/A
2024-02-23 17:03:00 -08:00
白山風露
aef299be3d CI: Enable clippy on Windows (#8240)
Release Notes:

- N/A
2024-02-23 16:23:42 -08:00
Dzmitry Malyshau
885ae2d863 linux/x11: prioritize input in the event loop (#8253)
With this change, interaction with Zed is actually real-time and usable
🚀 🎉

The gist of it is - trying to process all of the input events before
rendering anything.

Release Notes:
- N/A

**Note**: this can be further improved in a follow-up.
Currently, once the input and runnables are processed, we'd try to draw
+ render a frame.
Presentation starts with acquiring a new frame. We currently have FIFO
presentation method, so acquiring a frame is blocking on that swapchain
image to become available. As the result, presentation takes around 16
ms, most of which is just busy wait.
Ideally, we'd be able to process more input in this time frame, instead.

**Note2**: it's a bit laggy in Debug for me, but that's just because of
the extra-long `draw` times, which is unrelated to rendering (or
platform support, for the matter). I'm curious how come on MacOS the
`draw()` times in Debug are more modest.
2024-02-23 16:22:54 -08:00
Mikayla Maki
cab8b5a9a3 Switch LSP prompts to use a non-blocking toast (#8312)
This fixes a major degradation in usability that some users ran into.

Fixes https://github.com/zed-industries/zed/issues/8255 
Fixes https://github.com/zed-industries/zed/issues/8229

Release Notes:

- Switch from using platform prompts to toasts for LSP prompts.
([8255](https://github.com/zed-industries/zed/issues/8255),
[8229](https://github.com/zed-industries/zed/issues/8229))

<img width="583" alt="Screenshot 2024-02-23 at 2 40 05 PM"
src="https://github.com/zed-industries/zed/assets/2280405/1bfc027b-b7a8-4563-88b6-020e47869668">

Co-authored-by: Marshall <marshall@zed.dev>
2024-02-23 15:18:32 -08:00
Wes Morgan
d993dd3b2c Add .cljc, .edn, & .bb to Clojure filename extensions (#8285)
Release Notes:

- Added .cljc, .edn, & .bb to Clojure filename extensions
([#7845](https://github.com/zed-industries/zed/issues/7845)).
2024-02-23 23:46:27 +02:00
Conrad Irwin
351e6a5de2 Expose extensions API from api.zed.dev (#8307)
This avoids the need to pay for bandwidth

Co-Authored-By: Marshall <marshall@zed.dev>



Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
2024-02-23 16:08:14 -05:00
Conrad Irwin
41949d7b6c Log HTTP path in http logs (#8305)
Co-Authored-By: Marshall <marshall@zed.dev>


Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
2024-02-23 15:50:27 -05:00
Conrad Irwin
0fbd0d6649 collab: Log HTTP requests (#8297)
Co-Authored-By: Marshall <marshall@zed.dev>



Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-02-23 14:50:06 -05:00
Conrad Irwin
69c7d0e549 oops 2024-02-23 12:38:36 -07:00
Marshall Bowers
d3a38c69cd Only spawn the extensions reconciliation task in the collab service (#8301)
This PR makes it so the background task that reconciles the extensions
database with the blob store only runs on the `collab` service.

This avoids us having multiple of these jobs running at once.

Release Notes:

- N/A
2024-02-23 14:38:16 -05:00
Conrad Irwin
7c514d044f Fix collab (#8298)
Co-Authored-By: Marshall <marshall@zed.dev>

We broke it by deploying two servers simultaneously.

Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
2024-02-23 14:23:15 -05:00
ethan
a11ebe01ff Fix Flashing Hover Popover (#8238)
Release Notes:

- Use an inclusive range for local range containment check to match LSP
behavior & fix popover flashing while the cursor moves over the last
character of a symbol.


https://github.com/zed-industries/zed/assets/17223924/6c3ddc9c-04fb-4414-812f-025ede5ecaf7
2024-02-23 11:38:20 -07:00
Conrad Irwin
c5bb032224 Fix error logging (#8295)
and some more clickhouse type mismatches,

Co-Authored-By: Marshall <marshall@zed.dev>

Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
2024-02-23 13:36:20 -05:00
Conrad Irwin
58fd84308d Disable swift for now (#8291)
It causes segfaults on load

Release Notes:

- Fixed a segfault opening a Swift file with the Swift extension
installed.
2024-02-23 11:04:48 -07:00
Rom Grk
008d99d206 Wayland: implement key repeat (#8038)
Wayland requires the client to implement key repetition. This PR
implements the functionality as it's supposed to, but I don't see the
`repeat_info` event come in so the feature uses the default values (but
my system is configured for a much smaller `delay` and a much faster
`rate`). But this is good enough for now.

https://wayland-book.com/seat/keyboard.html#key-repeat


[Kooha-2024-02-20-20-42-12.webm](https://github.com/zed-industries/zed/assets/1423607/fb9fc327-efb7-43d1-9b53-1f8a3d9ba608)
2024-02-23 12:48:27 -05:00
Conrad Irwin
3bc7cd66b7 Allow typing space in workspace::SendKeystrokes (#8288)
Fixes #8222

Release Notes:

- N/A
2024-02-23 10:40:12 -07:00
Conrad Irwin
b0872b5b57 Deploy the ZED_CLIENT_CHECKSUM_SEED too (#8289)
Release Notes:

- N/A
2024-02-23 10:40:02 -07:00
Uladzislau Kaminski
602fd58929 Fix for toggles on the Welcome page (#8159)
Release Notes:

The issue is that when welcome page appears settings.json file is not
created yet. So the idea of this fix is to create the file in case it is
not there yet.

- Fixed the toggles on the welcome screen not working if no settings
file exists yet.
([#8153](https://github.com/zed-industries/zed/issues/8153)).

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
Co-authored-by: Marshall <marshall@zed.dev>
2024-02-23 18:24:04 +01:00
Thorsten Ball
ed3bb68206 Do not display inlay hints as bold (#8283)
I think bold is the least fitting font weight for inlay hints, which
should be subtle hints and not, well, bold.

If someone feels strongly about this, I can revert, but only if we add
the ability to change this per theme.

Until then: beautiful, thin, subtle inlay hints!

Release Notes:

- Improved styling of inlay hints by not making them bold in the editor.


![screenshot-2024-02-23-17 30
29@2x](https://github.com/zed-industries/zed/assets/1185253/89c2a162-76bb-45cd-8b45-2a5bdf8ca87b)
2024-02-23 18:17:13 +01:00
Marshall Bowers
522176d414 Adjust Kubernetes manifests for deploying API service (#8281)
This PR adjusts our Kubernetes manifests for deploying the new API
service.

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
2024-02-23 12:14:40 -05:00
Conrad Irwin
f19ab464c7 Add telemetry events backend for collab (#8220)
Send telemetry to collab not zed.dev

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-02-23 11:13:28 -05:00
Thorsten Ball
6d91224882 Debounce language server reinstall attempts (#8277)
I don't think there's value in retrying 4 times as fast as possible,
especially if we might hit the Github API every time to check for the
newest version.

That gets us in rate limit problems quickly.

Release Notes:

- N/A
2024-02-23 17:00:05 +01:00
Conrad Irwin
351c8c9a36 fix vim panics (#8245)
Release Notes:

- vim: Fixed a panic when using H/M/L when scrolled beyond the end of
the buffer
2024-02-23 08:31:41 -07:00
Bennet Bo Fenner
c90065e6ea chat: add copy text entry to message menu (#8271)
As we don't have selection inside the chat right now (which might be
complicated to implement, e.g. cross element selection and markdown
blocks), I think its viable to support copying the whole text of a
message using the message menu:

![image](https://github.com/zed-industries/zed/assets/53836821/6092dfed-0297-457e-9455-ba1e6190e288)

Release Notes:

- Added option to copy the text of a message within the chat
2024-02-23 08:00:20 -07:00
Piotr Osiewicz
0f584cb353 chore: Extract languages from zed crate (#8270)
- Moves languages module from `zed` into a separate crate. That way we
have less of a long pole at the end of compilation.
- Removes moot dependencies on editor/picker. This is totally harmless
and might help in the future if we decide to decouple picker from
editor.

Before:
```
Number of crates that depend on 'picker' but not on 'editor': 1
Total number of crates that depend on 'picker': 13
Total number of crates that depend on 'editor': 30
```
After:
```
Number of crates that depend on 'picker' but not on 'editor': 5
Total number of crates that depend on 'picker': 12
Total number of crates that depend on 'editor': 26
```
The more crates depend on just picker but not editor, the better in that
case.

Release Notes:

- N/A
2024-02-23 15:56:08 +01:00
Thorsten Ball
7cf0696c89 Pick up more home dir shell env when spawning (#8273)
Release Notes:

- Improved how Zed picks up shell environment when spawned.
2024-02-23 15:20:31 +01:00
Robin Pfäffle
576f8d3ef3 Fix svelte injections / outline (#8194)
This PR fixes the buffer symbol search to show `js` and `ts` buffer
symbols when using svelte components with `ts`.

Does also seem to improve `ts` capabilities (probably because there has
been a conflict of `js` and `ts` before).

Unfortunately when changing the script tag from no lang attribute to
`ts` one needs to update the file (input anyting) to get correct buffer
symbol search.

Before:

![SCR-20240222-mthf-2](https://github.com/zed-industries/zed/assets/67913738/980cf4bf-15d5-478d-a217-ed8f2b1f197d)

After:

![SCR-20240222-mvjr](https://github.com/zed-industries/zed/assets/67913738/41069e75-77ef-40fe-92d1-c19912b34770)

Release Notes:

- Fixed svelte outlines for `TS`.
2024-02-23 15:12:28 +01:00
569 changed files with 6935 additions and 6429 deletions

View File

@@ -2,23 +2,23 @@ name: Feature Request
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
labels: ["admin read", "triage", "enhancement"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the feature
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe the feature
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: |
If applicable, add mockups / screenshots to help present your vision of the feature
description: Drag images into the text input below
validations:
required: false
- type: textarea
attributes:
label: |
If applicable, add mockups / screenshots to help present your vision of the feature
description: Drag images into the text input below
validations:
required: false

40
.github/ISSUE_TEMPLATE/1_bug_report.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Bug Report
description: |
Use this template for **non-crash-related** bug reports.
Tip: open this issue template from within Zed with the `file bug report` command palette action.
labels: ["admin read", "triage", "defect"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
description: Drag issues into the text input below
validations:
required: false
- type: textarea
attributes:
label: |
If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
description: Drag Zed.log into the text input below
validations:
required: false

View File

@@ -1,47 +0,0 @@
name: Language Support
description: Request language support
title: "<name_of_language> support"
labels:
[
"admin read",
"triage",
"enhancement",
"language",
"unsupported language",
"potential extension",
]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: input
attributes:
label: Language
description: What language do you want support for?
placeholder: HTML
validations:
required: true
- type: input
attributes:
label: Tree Sitter parser link
description: If applicable, provide a link to the appropriate tree sitter parser. Look here first - https://tree-sitter.github.io/tree-sitter/#available-parsers
placeholder: https://github.com/tree-sitter/tree-sitter-html
validations:
required: false
- type: input
attributes:
label: Language server link
description: If applicable, provide a link to the appropriate language server. Look here first - https://microsoft.github.io/language-server-protocol/implementors/servers/
placeholder: https://github.com/Microsoft/vscode/tree/main/extensions/html-language-features/server
validations:
required: false
- type: textarea
attributes:
label: Misc notes
description: Provide any additional things the team should consider when adding support for this language
validations:
required: false

View File

@@ -1,38 +0,0 @@
name: Bug Report
description: "Tip: open this issue template from within Zed with the `file bug report` command palette action"
labels: ["admin read", "triage", "defect"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
description: Drag issues into the text input below
validations:
required: false
- type: textarea
attributes:
label: |
If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
description: Drag Zed.log into the text input below
validations:
required: false

View File

@@ -0,0 +1,39 @@
name: Crash Report
description: |
Use this template for crash reports.
labels: ["admin read", "triage", "defect", "panic / crash"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
description: Drag issues into the text input below
validations:
required: false
- type: textarea
attributes:
label: |
If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
description: Drag Zed.log into the text input below
validations:
required: false

View File

@@ -1,13 +1,16 @@
contact_links:
- name: Theme Request
url: https://github.com/zed-industries/extensions/issues/new/choose
about: Request a theme in the extensions repository
- name: Top-Ranking Issues
url: https://github.com/zed-industries/zed/issues/5393
about: See an overview of the most popular Zed issues
- name: Platform Support
url: https://github.com/zed-industries/zed/issues/5391
about: A quick note on platform support
- name: Positive Feedback
url: https://github.com/zed-industries/zed/discussions/5397
about: A central location for kind words about Zed
- name: Language Request
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E
about: Request a language in the extensions repository
- name: Theme Request
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=theme&projects=&template=0_theme_request.yml&title=%3Cname_of_theme%3E+theme
about: Request a theme in the extensions repository
- name: Top-Ranking Issues
url: https://github.com/zed-industries/zed/issues/5393
about: See an overview of the most popular Zed issues
- name: Platform Support
url: https://github.com/zed-industries/zed/issues/5391
about: A quick note on platform support
- name: Positive Feedback
url: https://github.com/zed-industries/zed/discussions/5397
about: A central location for kind words about Zed

View File

@@ -4,8 +4,8 @@ Release Notes:
- Added/Fixed/Improved ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/zed/issues/<public_issue_number_if_exists>)).
Optionally, include screenshots / media showcasing your addition that can be included in the release notes.
**or**
- N/A
Optionally, include screenshots / media showcasing your addition that can be included in the release notes.

View File

@@ -51,6 +51,9 @@ jobs:
- name: Run style checks
uses: ./.github/actions/check_style
- name: Check unused dependencies
uses: bnjbvr/cargo-machete@main
- name: Ensure fresh merge
shell: bash -euxo pipefail {0}
run: |
@@ -107,16 +110,10 @@ jobs:
clean: false
submodules: "recursive"
- name: Restore from cache
uses: actions/cache@v4
- name: Cache dependencies
uses: swatinem/rust-cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/rust-toolchain.toml') }}-${{ hashFiles('**/Cargo.lock') }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: configure linux
shell: bash -euxo pipefail {0}
@@ -140,20 +137,14 @@ jobs:
clean: false
submodules: "recursive"
- name: Restore from cache
uses: actions/cache@v4
- name: Cache dependencies
uses: swatinem/rust-cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/rust-toolchain.toml') }}-${{ hashFiles('**/Cargo.lock') }}
# todo!(windows): Actually run clippy
#- name: cargo clippy
# shell: bash -euxo pipefail {0}
# run: script/clippy
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: cargo clippy
shell: bash -euxo pipefail {0}
run: script/clippy
- name: Build Zed
run: cargo build -p zed

View File

@@ -120,6 +120,12 @@ jobs:
export ZED_DO_CERTIFICATE_ID=$(doctl compute certificate list --format ID --no-header)
export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
export ZED_SERVICE_NAME=collab
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/collab --watch
echo "deployed collab.template.yml to ${ZED_KUBE_NAMESPACE}"
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
export ZED_SERVICE_NAME=api
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"

View File

@@ -9,6 +9,11 @@
"format_on_save": "off"
},
"YAML": {
"tab_size": 2,
"formatter": "prettier"
},
"JSON": {
"tab_size": 2,
"formatter": "prettier"
}
},

1292
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@ members = [
"crates/collab_ui",
"crates/collections",
"crates/command_palette",
"crates/command_palette_hooks",
"crates/copilot",
"crates/copilot_ui",
"crates/db",
@@ -38,6 +39,7 @@ members = [
"crates/language",
"crates/language_selector",
"crates/language_tools",
"crates/languages",
"crates/live_kit_client",
"crates/live_kit_server",
"crates/lsp",
@@ -49,10 +51,9 @@ members = [
"crates/notifications",
"crates/outline",
"crates/picker",
"crates/plugin",
"crates/plugin_macros",
"crates/prettier",
"crates/project",
"crates/project_core",
"crates/project_panel",
"crates/project_symbols",
"crates/quick_action_bar",
@@ -80,6 +81,8 @@ members = [
"crates/theme",
"crates/theme_importer",
"crates/theme_selector",
"crates/telemetry_events",
"crates/time_format",
"crates/ui",
"crates/util",
"crates/vcs_menu",
@@ -110,6 +113,7 @@ collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
color = { path = "crates/color" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
copilot = { path = "crates/copilot" }
copilot_ui = { path = "crates/copilot_ui" }
db = { path = "crates/db" }
@@ -132,6 +136,7 @@ journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" }
languages = { path = "crates/languages" }
live_kit_client = { path = "crates/live_kit_client" }
live_kit_server = { path = "crates/live_kit_server" }
lsp = { path = "crates/lsp" }
@@ -147,6 +152,7 @@ plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
prettier = { path = "crates/prettier" }
project = { path = "crates/project" }
project_core = { path = "crates/project_core" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
quick_action_bar = { path = "crates/quick_action_bar" }
@@ -172,6 +178,8 @@ 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" }
ui = { path = "crates/ui" }
util = { path = "crates/util" }
vcs_menu = { path = "crates/vcs_menu" }
@@ -189,25 +197,32 @@ blade-graphics = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f394
blade-macros = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" }
blade-rwh = { package = "raw-window-handle", version = "0.5" }
chrono = { version = "0.4", features = ["serde"] }
clickhouse = { version = "0.11.6" }
ctor = "0.2.6"
core-foundation = { version = "0.9.3" }
core-foundation-sys = "0.8.6"
derive_more = "0.99.17"
env_logger = "0.9"
futures = "0.3"
git2 = { version = "0.15", default-features = false }
globset = "0.4"
hex = "0.4.3"
ignore = "0.4.22"
indoc = "1"
# We explicitly disable a http2 support in isahc.
# We explicitly disable http2 support in isahc.
isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
itertools = "0.11.0"
lazy_static = "1.4.0"
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.11.1"
profiling = "1"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = "1.3.0"
prost = "0.8"
pulldown-cmark = { version = "0.9.2", default-features = false }
pulldown-cmark = { version = "0.10.0", default-features = false }
rand = "0.8.5"
refineable = { path = "./crates/refineable" }
regex = "1.5"
@@ -220,6 +235,8 @@ serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
serde_json_lenient = { version = "0.1", features = ["preserve_order", "raw_value"] }
serde_repr = "0.1"
sha2 = "0.10"
shellexpand = "2.1.0"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
strum = { version = "0.25.0", features = ["derive"] }
@@ -229,6 +246,7 @@ thiserror = "1.0.29"
tiktoken-rs = "0.5.7"
time = { version = "0.3", features = ["serde", "serde-well-known", "formatting"] }
toml = "0.8"
tower-http = "0.4.4"
tree-sitter = { version = "0.20", features = ["wasm"] }
tree-sitter-astro = { git = "https://github.com/virchau13/tree-sitter-astro.git", rev = "e924787e12e8a03194f36a113290ac11d6dc10f3" }
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
@@ -278,13 +296,12 @@ tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "
unindent = "0.1.7"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4"] }
wasmtime = "16"
wasmtime = "18.0"
which = "6.0.0"
sys-locale = "0.3.1"
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1d8975319c2d5de1bf710e7e21db25b0eee4bc66" }
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "e4a23971ec3071a09c1e84816954c98f96e98e52" }
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "e4fcda0d5259d0acf902aee6de7d2501f2bd6629" }
@@ -297,10 +314,15 @@ debug = "limited"
split-debuginfo = "off"
debug = "full"
[profile.dev.package.taffy]
opt-level = 3
[profile.dev.package]
taffy = { opt-level = 3 }
cranelift-codegen = { opt-level = 3 }
wasmtime-cranelift = { opt-level = 3 }
[profile.release]
debug = "limited"
lto = "thin"
codegen-units = 1
[workspace.metadata.cargo-machete]
ignored = ["bindgen", "cbindgen", "prost_build", "serde"]

View File

@@ -1,3 +1,3 @@
collab: RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run --package=collab serve
collab: RUST_LOG=${RUST_LOG:-warn,tower_http=info,collab=info} cargo run --package=collab serve
livekit: livekit-server --dev
blob_store: MINIO_ROOT_USER=the-blob-store-access-key MINIO_ROOT_PASSWORD=the-blob-store-secret-key minio server .blob_store

View File

@@ -0,0 +1,3 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 9.203125 3.410156 C 8.792969 3 8.378906 2.597656 7.957031 2.207031 C 7.835938 2.078125 7.664062 1.996094 7.476562 1.996094 C 7.472656 1.996094 7.472656 1.996094 7.46875 1.996094 C 7.324219 2.011719 7.1875 2.042969 7.0625 2.089844 L 7.074219 2.085938 L 4.425781 3.40625 Z M 3.71875 3.71875 L 3.71875 9.078125 C 3.714844 9.109375 3.710938 9.144531 3.710938 9.179688 C 3.710938 9.40625 3.800781 9.613281 3.945312 9.765625 L 6.183594 12.003906 L 10.066406 12.003906 L 10.066406 10.066406 Z M 3.40625 3.40625 C 3.40625 3.40625 5.707031 2.257812 6.855469 1.683594 C 7.039062 1.59375 7.25 1.539062 7.476562 1.539062 C 7.496094 1.539062 7.515625 1.539062 7.53125 1.539062 C 7.824219 1.597656 8.082031 1.722656 8.296875 1.902344 L 8.292969 1.898438 L 12.460938 6.066406 L 12.460938 10.519531 L 10.519531 10.519531 L 10.519531 12.460938 L 5.996094 12.460938 L 1.898438 8.367188 C 1.683594 8.140625 1.546875 7.839844 1.542969 7.503906 C 1.558594 7.316406 1.609375 7.148438 1.6875 6.992188 L 1.683594 7 Z M 3.40625 3.40625 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 13.421875 5.847656 C 13.152344 5.671875 12.824219 5.566406 12.46875 5.566406 C 12.429688 5.566406 12.390625 5.566406 12.351562 5.570312 L 12.355469 5.570312 C 12.160156 5.570312 11.96875 5.585938 11.785156 5.621094 L 11.804688 5.617188 C 11.699219 5.023438 11.355469 4.523438 10.875 4.21875 L 10.867188 4.214844 L 10.679688 4.105469 L 10.554688 4.285156 C 10.410156 4.507812 10.292969 4.769531 10.226562 5.050781 L 10.222656 5.066406 C 10.183594 5.207031 10.160156 5.371094 10.160156 5.539062 C 10.160156 5.902344 10.265625 6.242188 10.445312 6.527344 L 10.441406 6.519531 C 10.164062 6.648438 9.839844 6.734375 9.496094 6.75 L 0.859375 6.75 C 0.632812 6.75 0.449219 6.929688 0.449219 7.15625 C 0.449219 7.179688 0.449219 7.210938 0.449219 7.238281 C 0.449219 8.003906 0.585938 8.738281 0.839844 9.417969 L 0.828125 9.375 C 1.070312 10.089844 1.53125 10.675781 2.128906 11.070312 L 2.144531 11.078125 C 2.90625 11.476562 3.808594 11.707031 4.765625 11.707031 C 4.855469 11.707031 4.945312 11.703125 5.035156 11.699219 L 5.023438 11.699219 C 5.03125 11.699219 5.039062 11.699219 5.050781 11.699219 C 5.605469 11.699219 6.148438 11.648438 6.675781 11.546875 L 6.621094 11.554688 C 7.40625 11.410156 8.109375 11.144531 8.742188 10.78125 L 8.710938 10.796875 C 9.261719 10.476562 9.730469 10.085938 10.128906 9.636719 L 10.136719 9.628906 C 10.714844 8.949219 11.1875 8.152344 11.511719 7.28125 L 11.527344 7.226562 L 11.648438 7.226562 C 11.671875 7.230469 11.703125 7.230469 11.730469 7.230469 C 12.265625 7.230469 12.753906 7.019531 13.113281 6.675781 C 13.277344 6.519531 13.410156 6.332031 13.496094 6.117188 L 13.5 6.105469 L 13.550781 5.949219 Z M 1.65625 6.496094 L 2.816406 6.496094 C 2.871094 6.496094 2.917969 6.449219 2.917969 6.394531 L 2.917969 5.363281 C 2.917969 5.308594 2.871094 5.265625 2.816406 5.261719 L 1.65625 5.261719 C 1.601562 5.265625 1.558594 5.308594 1.558594 5.363281 L 1.558594 6.394531 C 1.558594 6.453125 1.601562 6.496094 1.65625 6.496094 C 1.65625 6.496094 1.65625 6.496094 1.660156 6.496094 Z M 3.253906 6.496094 L 4.410156 6.496094 C 4.464844 6.496094 4.511719 6.453125 4.511719 6.394531 L 4.511719 5.363281 C 4.511719 5.308594 4.464844 5.265625 4.410156 5.261719 L 3.25 5.261719 C 3.195312 5.261719 3.152344 5.308594 3.152344 5.363281 L 3.152344 6.394531 C 3.152344 6.453125 3.195312 6.496094 3.25 6.496094 Z M 4.871094 6.496094 L 6.027344 6.496094 C 6.082031 6.496094 6.128906 6.449219 6.128906 6.394531 L 6.128906 5.363281 C 6.128906 5.308594 6.082031 5.265625 6.027344 5.261719 L 4.871094 5.261719 C 4.816406 5.265625 4.769531 5.308594 4.769531 5.363281 L 4.769531 6.394531 C 4.769531 6.449219 4.816406 6.496094 4.871094 6.496094 Z M 6.46875 6.496094 L 7.625 6.496094 C 7.683594 6.496094 7.726562 6.453125 7.726562 6.394531 L 7.726562 5.363281 C 7.726562 5.308594 7.683594 5.261719 7.625 5.261719 L 6.46875 5.261719 C 6.414062 5.261719 6.367188 5.308594 6.367188 5.363281 L 6.367188 6.394531 C 6.367188 6.453125 6.414062 6.496094 6.46875 6.496094 Z M 3.253906 5.015625 L 4.410156 5.015625 C 4.464844 5.015625 4.511719 4.96875 4.511719 4.914062 L 4.511719 3.882812 C 4.511719 3.828125 4.464844 3.78125 4.410156 3.78125 L 3.253906 3.78125 C 3.195312 3.78125 3.152344 3.828125 3.152344 3.882812 L 3.152344 4.914062 C 3.152344 4.96875 3.195312 5.015625 3.253906 5.015625 Z M 4.871094 5.015625 L 6.027344 5.015625 C 6.082031 5.015625 6.128906 4.96875 6.128906 4.914062 L 6.128906 3.882812 C 6.128906 3.828125 6.082031 3.78125 6.027344 3.78125 L 4.871094 3.78125 C 4.816406 3.78125 4.769531 3.828125 4.769531 3.882812 L 4.769531 4.914062 C 4.769531 4.96875 4.816406 5.015625 4.871094 5.015625 Z M 6.46875 5.015625 L 7.625 5.015625 C 7.683594 5.015625 7.726562 4.96875 7.726562 4.914062 L 7.726562 3.882812 C 7.726562 3.828125 7.683594 3.78125 7.625 3.78125 L 6.46875 3.78125 C 6.414062 3.78125 6.367188 3.828125 6.367188 3.882812 L 6.367188 4.914062 C 6.367188 4.96875 6.414062 5.015625 6.46875 5.015625 Z M 6.46875 3.53125 L 7.625 3.53125 C 7.683594 3.53125 7.726562 3.488281 7.726562 3.429688 L 7.726562 2.398438 C 7.726562 2.34375 7.683594 2.296875 7.625 2.296875 L 6.46875 2.296875 C 6.414062 2.296875 6.367188 2.34375 6.367188 2.398438 L 6.367188 3.429688 C 6.367188 3.488281 6.414062 3.53125 6.46875 3.53125 Z M 8.082031 6.496094 L 9.238281 6.496094 C 9.296875 6.496094 9.339844 6.453125 9.339844 6.394531 L 9.339844 5.363281 C 9.339844 5.308594 9.296875 5.261719 9.238281 5.261719 L 8.082031 5.261719 C 8.027344 5.261719 7.980469 5.308594 7.980469 5.363281 L 7.980469 6.394531 C 7.980469 6.449219 8.027344 6.496094 8.082031 6.496094 Z M 8.082031 6.496094 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1,269 +1,319 @@
{
"suffixes": {
"astro": "astro",
"Emakefile": "erlang",
"aac": "audio",
"accdb": "storage",
"app.src": "erlang",
"avi": "video",
"avif": "image",
"bak": "backup",
"bash": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"bash_profile": "terminal",
"bashrc": "terminal",
"bmp": "image",
"c": "code",
"cc": "code",
"cjs": "code",
"conf": "settings",
"cpp": "code",
"css": "css",
"csv": "storage",
"cts": "typescript",
"dat": "storage",
"db": "storage",
"dbf": "storage",
"dll": "storage",
"doc": "document",
"docx": "document",
"eex": "elixir",
"elm": "elm",
"erl": "erlang",
"escript": "erlang",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"ex": "elixir",
"exs": "elixir",
"fish": "terminal",
"flac": "audio",
"fmp": "storage",
"fp7": "storage",
"frm": "storage",
"gdb": "storage",
"gif": "image",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitkeep": "vcs",
"gitmodules": "vcs",
"go": "go",
"h": "code",
"handlebars": "code",
"hbs": "template",
"heex": "elixir",
"heif": "image",
"heic": "image",
"hrl": "erlang",
"hs": "haskell",
"htm": "template",
"html": "template",
"ib": "storage",
"ico": "image",
"ini": "settings",
"j2k": "image",
"java": "code",
"jfif": "image",
"jp2": "image",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"jsonc": "storage",
"jxl": "image",
"ldf": "storage",
"lock": "lock",
"log": "log",
"lua": "lua",
"m4a": "audio",
"m4v": "video",
"md": "document",
"mdb": "storage",
"mdf": "storage",
"mdx": "document",
"mkv": "video",
"mjs": "code",
"mka": "audio",
"ml": "ocaml",
"mli": "ocaml",
"mov": "video",
"mp3": "audio",
"mp4": "video",
"mts": "typescript",
"myd": "storage",
"myi": "storage",
"odp": "document",
"ods": "document",
"odt": "document",
"ogg": "audio",
"opus": "audio",
"pdb": "storage",
"pdf": "document",
"php": "php",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",
"prettierrc": "prettier",
"prisma": "prisma",
"profile": "terminal",
"ps1": "terminal",
"psd": "image",
"py": "python",
"qoi": "image",
"rb": "ruby",
"rebar.config": "erlang",
"rkt": "code",
"rs": "rust",
"rtf": "document",
"sav": "storage",
"scm": "code",
"sdf": "storage",
"sh": "terminal",
"sqlite": "storage",
"svelte": "template",
"svg": "image",
"swift": "code",
"tiff": "image",
"toml": "toml",
"ts": "typescript",
"tsv": "storage",
"tsx": "code",
"txt": "document",
"vue": "vue",
"wav": "audio",
"webm": "video",
"webp": "image",
"wma": "audio",
"wmv": "video",
"wv": "audio",
"xls": "document",
"xlsx": "document",
"xml": "template",
"xrl": "erlang",
"yaml": "settings",
"yml": "settings",
"yrl": "erlang",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
"zsh_histfile": "terminal",
"zsh_profile": "terminal",
"zshenv": "terminal",
"zshrc": "terminal"
"stems": {
"Podfile": "ruby",
"Procfile": "heroku",
"Dockerfile": "docker"
},
"suffixes": {
"astro": "astro",
"Emakefile": "erlang",
"aac": "audio",
"accdb": "storage",
"app.src": "erlang",
"avi": "video",
"avif": "image",
"bak": "backup",
"bash": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"bash_profile": "terminal",
"bashrc": "terminal",
"bmp": "image",
"c": "code",
"cc": "code",
"cjs": "code",
"conf": "settings",
"cpp": "code",
"css": "css",
"csv": "storage",
"cts": "typescript",
"dart": "dart",
"dat": "storage",
"db": "storage",
"dbf": "storage",
"dll": "storage",
"doc": "document",
"docx": "document",
"eex": "elixir",
"elm": "elm",
"erl": "erlang",
"escript": "erlang",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"ex": "elixir",
"exs": "elixir",
"fish": "terminal",
"flac": "audio",
"fmp": "storage",
"fp7": "storage",
"frm": "storage",
"gdb": "storage",
"gif": "image",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitkeep": "vcs",
"gitmodules": "vcs",
"go": "go",
"graphql": "graphql",
"h": "code",
"handlebars": "code",
"hbs": "template",
"heex": "elixir",
"heif": "image",
"heic": "image",
"hrl": "erlang",
"hs": "haskell",
"htm": "template",
"html": "template",
"ib": "storage",
"ico": "image",
"ini": "settings",
"j2k": "image",
"java": "java",
"jfif": "image",
"jp2": "image",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"jsonc": "storage",
"jxl": "image",
"kt": "kotlin",
"ldf": "storage",
"lock": "lock",
"lockb": "bun",
"log": "log",
"lua": "lua",
"m4a": "audio",
"m4v": "video",
"md": "document",
"mdb": "storage",
"mdf": "storage",
"mdx": "document",
"metadata": "code",
"mkv": "video",
"mjs": "code",
"mka": "audio",
"ml": "ocaml",
"mli": "ocaml",
"mov": "video",
"mp3": "audio",
"mp4": "video",
"mts": "typescript",
"myd": "storage",
"myi": "storage",
"nu": "terminal",
"odp": "document",
"ods": "document",
"odt": "document",
"ogg": "audio",
"opus": "audio",
"otf": "font",
"pdb": "storage",
"pdf": "document",
"php": "php",
"plist": "template",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",
"prettierrc": "prettier",
"prisma": "prisma",
"profile": "terminal",
"ps1": "terminal",
"psd": "image",
"py": "python",
"qoi": "image",
"rb": "ruby",
"rebar.config": "erlang",
"rkt": "code",
"rs": "rust",
"r": "r",
"rtf": "document",
"sav": "storage",
"scm": "code",
"sdf": "storage",
"sh": "terminal",
"sqlite": "storage",
"svelte": "template",
"svg": "image",
"swift": "swift",
"tf": "terraform",
"tfvars": "terraform",
"tiff": "image",
"toml": "toml",
"ts": "typescript",
"tsv": "storage",
"ttf": "font",
"tsx": "code",
"txt": "document",
"vue": "vue",
"wav": "audio",
"webm": "video",
"webp": "image",
"wma": "audio",
"wmv": "video",
"wv": "audio",
"xls": "document",
"xlsx": "document",
"xml": "template",
"xrl": "erlang",
"yaml": "settings",
"yml": "settings",
"yrl": "erlang",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
"zsh_histfile": "terminal",
"zsh_profile": "terminal",
"zshenv": "terminal",
"zshrc": "terminal"
},
"types": {
"astro": {
"icon": "icons/file_icons/astro.svg"
},
"types": {
"astro": {
"icon": "icons/file_icons/astro.svg"
},
"audio": {
"icon": "icons/file_icons/audio.svg"
},
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"css": {
"icon": "icons/file_icons/css.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"elixir": {
"icon": "icons/file_icons/elixir.svg"
},
"elm": {
"icon": "icons/file_icons/elm.svg"
},
"erlang": {
"icon": "icons/file_icons/erlang.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
"expanded_chevron": {
"icon": "icons/file_icons/chevron_down.svg"
},
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"haskell": {
"icon": "icons/file_icons/haskell.svg"
},
"go": {
"icon": "icons/file_icons/go.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"lua": {
"icon": "icons/file_icons/lua.svg"
},
"ocaml": {
"icon": "icons/file_icons/ocaml.svg"
},
"phoenix": {
"icon": "icons/file_icons/phoenix.svg"
},
"php": {
"icon": "icons/file_icons/php.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"prisma": {
"icon": "icons/file_icons/prisma.svg"
},
"python": {
"icon": "icons/file_icons/python.svg"
},
"ruby": {
"icon": "icons/file_icons/ruby.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
"storage": {
"icon": "icons/file_icons/database.svg"
},
"template": {
"icon": "icons/file_icons/html.svg"
},
"terminal": {
"icon": "icons/file_icons/terminal.svg"
},
"toml": {
"icon": "icons/file_icons/toml.svg"
},
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},
"video": {
"icon": "icons/file_icons/video.svg"
},
"vue": {
"icon": "icons/file_icons/vue.svg"
}
"audio": {
"icon": "icons/file_icons/audio.svg"
},
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"css": {
"icon": "icons/file_icons/css.svg"
},
"dart": {
"icon": "icons/file_icons/dart.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"docker": {
"icon": "icons/file_icons/docker.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"elixir": {
"icon": "icons/file_icons/elixir.svg"
},
"elm": {
"icon": "icons/file_icons/elm.svg"
},
"erlang": {
"icon": "icons/file_icons/erlang.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
"expanded_chevron": {
"icon": "icons/file_icons/chevron_down.svg"
},
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"font": {
"icon": "icons/file_icons/font.svg"
},
"haskell": {
"icon": "icons/file_icons/haskell.svg"
},
"heroku": {
"icon": "icons/file_icons/heroku.svg"
},
"go": {
"icon": "icons/file_icons/go.svg"
},
"graphql": {
"icon": "icons/file_icons/graphql.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"java": {
"icon": "icons/file_icons/java.svg"
},
"kotlin": {
"icon": "icons/file_icons/kotlin.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"bun": {
"icon": "icons/file_icons/bun.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"lua": {
"icon": "icons/file_icons/lua.svg"
},
"ocaml": {
"icon": "icons/file_icons/ocaml.svg"
},
"phoenix": {
"icon": "icons/file_icons/phoenix.svg"
},
"php": {
"icon": "icons/file_icons/php.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"prisma": {
"icon": "icons/file_icons/prisma.svg"
},
"python": {
"icon": "icons/file_icons/python.svg"
},
"ruby": {
"icon": "icons/file_icons/ruby.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"r": {
"icon": "icons/file_icons/r.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
"storage": {
"icon": "icons/file_icons/database.svg"
},
"swift": {
"icon": "icons/file_icons/swift.svg"
},
"template": {
"icon": "icons/file_icons/html.svg"
},
"terraform": {
"icon": "icons/file_icons/terraform.svg"
},
"terminal": {
"icon": "icons/file_icons/terminal.svg"
},
"toml": {
"icon": "icons/file_icons/toml.svg"
},
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},
"video": {
"icon": "icons/file_icons/video.svg"
},
"vue": {
"icon": "icons/file_icons/vue.svg"
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 16.001786 21 L 19.497321 21 M 5.997321 21 L 12 3 L 18.002679 21 M 4.502679 21 L 7.998214 21 M 14.997321 14.000893 L 9.002679 14.000893 " transform="matrix(0.486111,0,0,0.486111,1.166667,1.166667)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 559 B

View File

@@ -0,0 +1 @@
<svg width="14px" height="14px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="#000000"><path fill-rule="evenodd" clip-rule="evenodd" d="M50 6.90308L87.323 28.4515V71.5484L50 93.0968L12.677 71.5484V28.4515L50 6.90308ZM16.8647 30.8693V62.5251L44.2795 15.0414L16.8647 30.8693ZM50 13.5086L18.3975 68.2457H81.6025L50 13.5086ZM77.4148 72.4334H22.5852L50 88.2613L77.4148 72.4334ZM83.1353 62.5251L55.7205 15.0414L83.1353 30.8693V62.5251Z"/><circle cx="50" cy="9.3209" r="8.82"/><circle cx="85.2292" cy="29.6605" r="8.82"/><circle cx="85.2292" cy="70.3396" r="8.82"/><circle cx="50" cy="90.6791" r="8.82"/><circle cx="14.7659" cy="70.3396" r="8.82"/><circle cx="14.7659" cy="29.6605" r="8.82"/></svg>

After

Width:  |  Height:  |  Size: 709 B

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 12.023438 0 L 1.976562 0 C 1.277344 0 0.71875 0.558594 0.71875 1.261719 L 0.71875 12.738281 C 0.71875 13.441406 1.277344 14 1.976562 14 L 12.023438 14 C 12.722656 14 13.28125 13.441406 13.28125 12.738281 L 13.28125 1.261719 C 13.28125 0.558594 12.722656 0 12.023438 0 Z M 12.582031 12.738281 C 12.582031 13.054688 12.335938 13.300781 12.023438 13.300781 L 1.976562 13.300781 C 1.664062 13.300781 1.417969 13.054688 1.417969 12.738281 L 1.417969 1.261719 C 1.417969 0.945312 1.664062 0.699219 1.976562 0.699219 L 12.023438 0.699219 C 12.335938 0.699219 12.582031 0.945312 12.582031 1.261719 Z M 3.867188 11.898438 L 5.441406 10.5 L 3.867188 9.101562 Z M 9.539062 6.230469 C 9.257812 5.949219 8.734375 5.601562 7.859375 5.601562 C 6.914062 5.601562 5.933594 5.84375 5.234375 6.089844 L 5.234375 2.101562 L 3.832031 2.101562 L 3.832031 8.15625 L 4.8125 7.699219 C 4.8125 7.699219 6.421875 6.964844 7.824219 6.964844 C 8.523438 6.964844 8.699219 7.351562 8.699219 7.699219 L 8.699219 11.898438 L 10.097656 11.898438 L 10.097656 7.699219 C 10.132812 7.59375 10.132812 6.824219 9.539062 6.230469 Z M 7.683594 4.375 L 9.082031 4.375 C 9.710938 3.640625 10.027344 2.90625 10.132812 2.101562 L 8.734375 2.101562 C 8.59375 2.90625 8.242188 3.640625 7.683594 4.375 Z M 7.683594 4.375 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.644531 9.734375 C 9.640625 9.734375 8.824219 8.917969 8.824219 7.910156 C 8.824219 6.90625 9.640625 6.089844 10.644531 6.089844 C 11.652344 6.089844 12.46875 6.90625 12.46875 7.910156 C 12.46875 8.917969 11.652344 9.734375 10.644531 9.734375 Z M 10.644531 7 C 10.144531 7 9.734375 7.410156 9.734375 7.910156 C 9.734375 8.414062 10.144531 8.824219 10.644531 8.824219 C 11.148438 8.824219 11.558594 8.414062 11.558594 7.910156 C 11.558594 7.410156 11.148438 7 10.644531 7 Z M 7.457031 4.265625 L 6.542969 4.265625 L 6.542969 2.441406 L 7.457031 2.441406 Z M 6.089844 4.265625 L 5.175781 4.265625 L 5.175781 1.53125 L 6.089844 1.53125 Z M 4.722656 4.265625 L 3.808594 4.265625 L 3.808594 2.441406 L 4.722656 2.441406 Z M 4.722656 4.265625 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.632812 12.011719 C 3.617188 12.011719 1.988281 10.382812 1.988281 8.367188 L 1.988281 5.632812 L 9.277344 5.632812 L 9.277344 8.367188 C 9.277344 10.382812 7.648438 12.011719 5.632812 12.011719 Z M 5.632812 12.011719 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 12.125 12.125 L 1.875 12.125 L 1.875 1.875 L 12.125 1.875 L 7 7 Z M 12.125 12.125 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 385 B

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15px" height="14px" viewBox="0 0 15 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 14.0625 6.46875 C 14.0625 4.203125 11.125 2.367188 7.5 2.367188 C 3.875 2.367188 0.9375 4.203125 0.9375 6.46875 C 0.9375 8.488281 3.273438 10.160156 6.34375 10.503906 L 6.34375 11.8125 L 8.582031 11.8125 L 8.582031 10.511719 C 9.113281 10.457031 9.636719 10.359375 10.148438 10.21875 L 11.058594 11.8125 L 13.589844 11.8125 L 12.0625 9.410156 C 13.292969 8.667969 14.0625 7.625 14.0625 6.46875 Z M 3.515625 6.773438 C 3.515625 5.226562 5.75 3.96875 8.503906 3.96875 C 11.257812 3.96875 13.292969 4.828125 13.292969 6.773438 C 13.304688 7.757812 12.671875 8.644531 11.699219 9.015625 C 11.65625 8.988281 11.609375 8.964844 11.558594 8.941406 C 11.355469 8.851562 11.144531 8.78125 10.933594 8.71875 C 10.933594 8.71875 12.886719 8.582031 12.886719 6.765625 C 12.886719 4.949219 10.839844 4.914062 10.839844 4.914062 L 6.34375 4.914062 L 6.34375 9.300781 C 4.671875 8.847656 3.515625 7.886719 3.515625 6.773438 Z M 9.957031 7.582031 L 8.601562 7.582031 L 8.601562 6.410156 L 9.957031 6.410156 C 10.125 6.398438 10.292969 6.453125 10.410156 6.5625 C 10.53125 6.675781 10.597656 6.828125 10.585938 6.984375 C 10.589844 7.144531 10.527344 7.296875 10.410156 7.410156 C 10.289062 7.519531 10.128906 7.582031 9.957031 7.582031 Z M 8.582031 9.109375 L 9.183594 9.109375 C 9.300781 9.113281 9.40625 9.15625 9.484375 9.238281 C 9.578125 9.320312 9.65625 9.414062 9.722656 9.511719 C 9.34375 9.554688 8.964844 9.574219 8.582031 9.578125 Z M 8.582031 9.109375 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 8.269531 1.53125 C 13.078125 4.824219 11.523438 8.457031 11.523438 8.457031 C 11.523438 8.457031 12.890625 10.015625 12.335938 11.375 C 12.335938 11.375 11.773438 10.421875 10.828125 10.421875 C 9.914062 10.421875 9.378906 11.375 7.546875 11.375 C 3.460938 11.375 1.53125 7.9375 1.53125 7.9375 C 5.210938 10.375 7.722656 8.648438 7.722656 8.648438 C 6.066406 7.679688 2.539062 3.039062 2.539062 3.039062 C 5.609375 5.675781 6.9375 6.371094 6.9375 6.371094 C 6.144531 5.710938 3.921875 2.484375 3.921875 2.484375 C 5.699219 4.296875 9.230469 6.828125 9.230469 6.828125 C 10.234375 4.027344 8.269531 1.53125 8.269531 1.53125 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 926 B

View File

@@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 8.01531L19.2375 12.1073V20.2894L12.5066 16.1992V8.01531Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0294 12.1073V20.2894L27.1563 16.1992V8.01531L20.0294 12.1073Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58781 3.66V11.5787L11.7147 15.5381V7.61937L4.58781 3.66Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 25.04L19.2375 29V21.1348V21.0818L12.5066 17.1219V25.04Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 620 B

View File

@@ -55,38 +55,14 @@
"bindings": {
"alt-cmd-/": "search::ToggleRegex",
"ctrl-0": "project_panel::ToggleFocus",
"cmd-1": [
"pane::ActivateItem",
0
],
"cmd-2": [
"pane::ActivateItem",
1
],
"cmd-3": [
"pane::ActivateItem",
2
],
"cmd-4": [
"pane::ActivateItem",
3
],
"cmd-5": [
"pane::ActivateItem",
4
],
"cmd-6": [
"pane::ActivateItem",
5
],
"cmd-7": [
"pane::ActivateItem",
6
],
"cmd-8": [
"pane::ActivateItem",
7
],
"cmd-1": ["pane::ActivateItem", 0],
"cmd-2": ["pane::ActivateItem", 1],
"cmd-3": ["pane::ActivateItem", 2],
"cmd-4": ["pane::ActivateItem", 3],
"cmd-5": ["pane::ActivateItem", 4],
"cmd-6": ["pane::ActivateItem", 5],
"cmd-7": ["pane::ActivateItem", 6],
"cmd-8": ["pane::ActivateItem", 7],
"cmd-9": "pane::ActivateLastItem"
}
},

File diff suppressed because it is too large Load Diff

View File

@@ -101,14 +101,8 @@
"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",
@@ -241,123 +235,36 @@
}
],
// 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",
@@ -379,14 +286,8 @@
"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"],
"-": "pane::RevealInProjectPanel"
}
},
@@ -402,21 +303,12 @@
"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",
@@ -447,14 +339,11 @@
],
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
"r": [
"vim::PushOperator",
"Replace"
],
"r": ["vim::PushOperator", "Replace"],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
"> >": "editor::Indent",
"< <": "editor::Outdent",
"> >": "vim::Indent",
"< <": "vim::Outdent",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem"
}
@@ -462,10 +351,7 @@
{
"context": "Editor && VimCount",
"bindings": {
"0": [
"vim::Number",
0
]
"0": ["vim::Number", 0]
}
},
{
@@ -497,6 +383,7 @@
"ignorePunctuation": true
}
],
"t": "vim::Tag",
"s": "vim::Sentence",
"'": "vim::Quotes",
"`": "vim::BackQuotes",
@@ -511,7 +398,8 @@
"}": "vim::CurlyBrackets",
"shift-b": "vim::CurlyBrackets",
"<": "vim::AngleBrackets",
">": "vim::AngleBrackets"
">": "vim::AngleBrackets",
"a": "vim::Argument"
}
},
{
@@ -568,24 +456,12 @@
"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"
],
">": "editor::Indent",
"<": "editor::Outdent",
"r": ["vim::PushOperator", "Replace"],
"ctrl-c": ["vim::SwitchMode", "Normal"],
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"],
">": "vim::Indent",
"<": "vim::Outdent",
"i": [
"vim::PushOperator",
{
@@ -616,7 +492,9 @@
"ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
"ctrl-x ctrl-z": "editor::Cancel",
"ctrl-w": "editor::DeleteToPreviousWordStart",
"ctrl-u": "editor::DeleteToBeginningOfLine"
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent"
}
},
{
@@ -624,14 +502,8 @@
"bindings": {
"tab": "vim::Tab",
"enter": "vim::Enter",
"escape": [
"vim::SwitchMode",
"Normal"
],
"ctrl-[": [
"vim::SwitchMode",
"Normal"
]
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"]
}
},
{

View File

@@ -169,7 +169,13 @@
"show_type_hints": true,
"show_parameter_hints": true,
// Corresponds to null/None LSP hint type value.
"show_other_hints": true
"show_other_hints": true,
// Time to wait after editing the buffer, before requesting the hints,
// set to 0 to disable debouncing.
"edit_debounce_ms": 700,
// Time to wait after scrolling the buffer, before requesting the hints,
// set to 0 to disable debouncing.
"scroll_debounce_ms": 50
},
"project_panel": {
// Default width of the project panel.
@@ -339,9 +345,7 @@
"copilot": {
// The set of glob patterns for which copilot should be disabled
// in any matching file.
"disabled_globs": [
".env"
]
"disabled_globs": [".env"]
},
// Settings specific to journaling
"journal": {
@@ -450,12 +454,7 @@
// 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"
}
@@ -572,7 +571,8 @@
},
// Vim settings
"vim": {
"use_system_clipboard": "always"
"use_system_clipboard": "always",
"use_multiline_find": false
},
// The server to connect to. If the environment variable
// ZED_SERVER_URL is set, it will override this setting.

View File

@@ -482,7 +482,7 @@
"hidden": "#998b78ff",
"hidden.background": "#4c4642ff",
"hidden.border": "#544c48ff",
"hint": "#8c957dff",
"hint": "#6a695bff",
"hint.background": "#1e2321ff",
"hint.border": "#303a36ff",
"ignored": "#c5b597ff",

View File

@@ -17,9 +17,7 @@ futures.workspace = true
gpui.workspace = true
language.workspace = true
project.workspace = true
settings.workspace = true
smallvec.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -28,7 +28,6 @@ parking_lot.workspace = true
parse_duration = "2.1.1"
postage.workspace = true
rand.workspace = true
regex.workspace = true
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
serde.workspace = true
serde_json.workspace = true

View File

@@ -13,20 +13,17 @@ doctest = false
ai.workspace = true
anyhow.workspace = true
chrono.workspace = true
client.workspace = true
collections.workspace = true
editor.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
indoc.workspace = true
isahc.workspace = true
language.workspace = true
log.workspace = true
menu.workspace = true
multi_buffer.workspace = true
ordered-float.workspace = true
parking_lot.workspace = true
project.workspace = true
regex.workspace = true
schemars.workspace = true
@@ -36,6 +33,7 @@ serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
telemetry_events.workspace = true
theme.workspace = true
tiktoken-rs.workspace = true
ui.workspace = true

View File

@@ -15,7 +15,6 @@ use ai::{
};
use anyhow::{anyhow, Result};
use chrono::{DateTime, Local};
use client::telemetry::AssistantKind;
use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{
actions::{MoveDown, MoveUp},
@@ -52,6 +51,7 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use telemetry_events::AssistantKind;
use theme::ThemeSettings;
use ui::{
prelude::*,

View File

@@ -13,9 +13,7 @@ doctest = false
anyhow.workspace = true
collections.workspace = true
derive_more.workspace = true
futures.workspace = true
gpui.workspace = true
log.workspace = true
parking_lot.workspace = true
rodio = { version = "0.17.1", default-features = false, features = ["wav"] }
util.workspace = true

View File

@@ -16,11 +16,9 @@ db.workspace = true
editor.workspace = true
gpui.workspace = true
isahc.workspace = true
lazy_static.workspace = true
log.workspace = true
markdown_preview.workspace = true
menu.workspace = true
project.workspace = true
release_channel.workspace = true
schemars.workspace = true
serde.workspace = true
@@ -29,6 +27,5 @@ serde_json.workspace = true
settings.workspace = true
smol.workspace = true
tempfile.workspace = true
theme.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -29,7 +29,7 @@ use std::{
};
use update_notification::UpdateNotification;
use util::{
http::{HttpClient, ZedHttpClient},
http::{HttpClient, HttpClientWithUrl},
ResultExt,
};
use workspace::Workspace;
@@ -67,7 +67,7 @@ pub enum AutoUpdateStatus {
pub struct AutoUpdater {
status: AutoUpdateStatus,
current_version: SemanticVersion,
http_client: Arc<ZedHttpClient>,
http_client: Arc<HttpClientWithUrl>,
pending_poll: Option<Task<Option<()>>>,
}
@@ -115,7 +115,7 @@ struct ReleaseNotesBody {
release_notes: String,
}
pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
@@ -181,7 +181,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
let current_version = auto_updater.current_version;
let url = &auto_updater
.http_client
.zed_url(&format!("/releases/{release_channel}/{current_version}"));
.build_url(&format!("/releases/{release_channel}/{current_version}"));
cx.open_url(&url);
}
@@ -193,7 +193,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
let version = env!("CARGO_PKG_VERSION");
let client = client::Client::global(cx).http_client();
let url = client.zed_url(&format!(
let url = client.build_url(&format!(
"/api/release_notes/{}/{}",
release_channel.dev_name(),
version
@@ -283,7 +283,7 @@ impl AutoUpdater {
cx.default_global::<GlobalAutoUpdate>().0.clone()
}
fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self {
fn new(current_version: SemanticVersion, http_client: Arc<HttpClientWithUrl>) -> Self {
Self {
status: AutoUpdateStatus::Idle,
current_version,
@@ -337,7 +337,7 @@ impl AutoUpdater {
(this.http_client.clone(), this.current_version)
})?;
let mut url_string = client.zed_url(&format!(
let mut url_string = client.build_url(&format!(
"/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
OS, ARCH
));

View File

@@ -10,15 +10,10 @@ path = "src/breadcrumbs.rs"
doctest = false
[dependencies]
collections.workspace = true
editor.workspace = true
gpui.workspace = true
itertools = "0.10"
language.workspace = true
itertools.workspace = true
outline.workspace = true
project.workspace = true
search.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true
workspace.workspace = true

View File

@@ -21,26 +21,21 @@ test-support = [
[dependencies]
anyhow.workspace = true
async-broadcast = "0.4"
audio.workspace = true
client.workspace = true
collections.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
image = "0.23"
language.workspace = true
live_kit_client.workspace = true
log.workspace = true
media.workspace = true
postage.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
settings.workspace = true
smallvec.workspace = true
util.workspace = true
[dev-dependencies]

View File

@@ -5,7 +5,7 @@ pub mod room;
use anyhow::{anyhow, Result};
use audio::Audio;
use call_settings::CallSettings;
use client::{proto, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{
@@ -107,7 +107,7 @@ impl ActiveCall {
}
}
pub fn channel_id(&self, cx: &AppContext) -> Option<u64> {
pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
self.room()?.read(cx).channel_id()
}
@@ -336,7 +336,7 @@ impl ActiveCall {
pub fn join_channel(
&mut self,
channel_id: u64,
channel_id: ChannelId,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Model<Room>>>> {
if let Some(room) = self.room().cloned() {
@@ -487,7 +487,7 @@ impl ActiveCall {
pub fn report_call_event_for_room(
operation: &'static str,
room_id: u64,
channel_id: Option<u64>,
channel_id: Option<ChannelId>,
client: &Arc<Client>,
) {
let telemetry = client.telemetry();
@@ -497,7 +497,7 @@ pub fn report_call_event_for_room(
pub fn report_call_event_for_channel(
operation: &'static str,
channel_id: u64,
channel_id: ChannelId,
client: &Arc<Client>,
cx: &AppContext,
) {

View File

@@ -6,7 +6,7 @@ use anyhow::{anyhow, Result};
use audio::{Audio, Sound};
use client::{
proto::{self, PeerId},
Client, ParticipantIndex, TypedEnvelope, User, UserStore,
ChannelId, Client, ParticipantIndex, TypedEnvelope, User, UserStore,
};
use collections::{BTreeMap, HashMap, HashSet};
use fs::Fs;
@@ -27,7 +27,7 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
RoomJoined {
channel_id: Option<u64>,
channel_id: Option<ChannelId>,
},
ParticipantLocationChanged {
participant_id: proto::PeerId,
@@ -53,13 +53,13 @@ pub enum Event {
project_id: u64,
},
Left {
channel_id: Option<u64>,
channel_id: Option<ChannelId>,
},
}
pub struct Room {
id: u64,
channel_id: Option<u64>,
channel_id: Option<ChannelId>,
live_kit: Option<LiveKitRoom>,
status: RoomStatus,
shared_projects: HashSet<WeakModel<Project>>,
@@ -84,7 +84,7 @@ pub struct Room {
impl EventEmitter<Event> for Room {}
impl Room {
pub fn channel_id(&self) -> Option<u64> {
pub fn channel_id(&self) -> Option<ChannelId> {
self.channel_id
}
@@ -106,7 +106,7 @@ impl Room {
fn new(
id: u64,
channel_id: Option<u64>,
channel_id: Option<ChannelId>,
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
client: Arc<Client>,
user_store: Model<UserStore>,
@@ -273,13 +273,17 @@ impl Room {
}
pub(crate) async fn join_channel(
channel_id: u64,
channel_id: ChannelId,
client: Arc<Client>,
user_store: Model<UserStore>,
cx: AsyncAppContext,
) -> Result<Model<Self>> {
Self::from_join_response(
client.request(proto::JoinChannel { channel_id }).await?,
client
.request(proto::JoinChannel {
channel_id: channel_id.0,
})
.await?,
client,
user_store,
cx,
@@ -337,7 +341,7 @@ impl Room {
let room = cx.new_model(|cx| {
Self::new(
room_proto.id,
response.channel_id,
response.channel_id.map(ChannelId),
response.live_kit_connection_info,
client,
user_store,

View File

@@ -17,34 +17,18 @@ anyhow.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
db.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
image = "0.23"
language.workspace = true
lazy_static.workspace = true
log.workspace = true
parking_lot.workspace = true
postage.workspace = true
rand.workspace = true
release_channel.workspace = true
rpc.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
settings.workspace = true
smallvec.workspace = true
smol.workspace = true
sum_tree.workspace = true
tempfile.workspace = true
text.workspace = true
thiserror.workspace = true
time.workspace = true
tiny_http = "0.8"
url.workspace = true
util.workspace = true
uuid.workspace = true
[dev-dependencies]
collections = { workspace = true, features = ["test-support"] }

View File

@@ -11,7 +11,7 @@ pub use channel_chat::{
mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId,
MessageParams,
};
pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, ChannelStore};
pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore, HostedProjectId};
#[cfg(test)]
mod channel_store_tests;

View File

@@ -1,6 +1,6 @@
use crate::{Channel, ChannelId, ChannelStore};
use crate::{Channel, ChannelStore};
use anyhow::Result;
use client::{Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
use client::{ChannelId, Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashMap;
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
use language::proto::serialize_version;
@@ -51,7 +51,7 @@ impl ChannelBuffer {
) -> Result<Model<Self>> {
let response = client
.request(proto::JoinChannelBuffer {
channel_id: channel.id,
channel_id: channel.id.0,
})
.await?;
let buffer_id = BufferId::new(response.buffer_id)?;
@@ -68,7 +68,7 @@ impl ChannelBuffer {
})?;
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
let subscription = client.subscribe_to_entity(channel.id)?;
let subscription = client.subscribe_to_entity(channel.id.0)?;
anyhow::Ok(cx.new_model(|cx| {
cx.subscribe(&buffer, Self::on_buffer_update).detach();
@@ -97,7 +97,7 @@ impl ChannelBuffer {
}
self.client
.send(proto::LeaveChannelBuffer {
channel_id: self.channel_id,
channel_id: self.channel_id.0,
})
.log_err();
}
@@ -191,7 +191,7 @@ impl ChannelBuffer {
let operation = language::proto::serialize_operation(operation);
self.client
.send(proto::UpdateChannelBuffer {
channel_id: self.channel_id,
channel_id: self.channel_id.0,
operations: vec![operation],
})
.log_err();

View File

@@ -1,9 +1,9 @@
use crate::{Channel, ChannelId, ChannelStore};
use crate::{Channel, ChannelStore};
use anyhow::{anyhow, Result};
use client::{
proto,
user::{User, UserStore},
Client, Subscription, TypedEnvelope, UserId,
ChannelId, Client, Subscription, TypedEnvelope, UserId,
};
use collections::HashSet;
use futures::lock::Mutex;
@@ -104,10 +104,12 @@ impl ChannelChat {
mut cx: AsyncAppContext,
) -> Result<Model<Self>> {
let channel_id = channel.id;
let subscription = client.subscribe_to_entity(channel_id).unwrap();
let subscription = client.subscribe_to_entity(channel_id.0).unwrap();
let response = client
.request(proto::JoinChannelChat { channel_id })
.request(proto::JoinChannelChat {
channel_id: channel_id.0,
})
.await?;
let handle = cx.new_model(|cx| {
@@ -143,7 +145,7 @@ impl ChannelChat {
fn release(&mut self, _: &mut AppContext) {
self.rpc
.send(proto::LeaveChannelChat {
channel_id: self.channel_id,
channel_id: self.channel_id.0,
})
.log_err();
}
@@ -200,7 +202,7 @@ impl ChannelChat {
Ok(cx.spawn(move |this, mut cx| async move {
let outgoing_message_guard = outgoing_messages_lock.lock().await;
let request = rpc.request(proto::SendChannelMessage {
channel_id,
channel_id: channel_id.0,
body: message.text,
nonce: Some(nonce.into()),
mentions: mentions_to_proto(&message.mentions),
@@ -220,7 +222,7 @@ impl ChannelChat {
pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let response = self.rpc.request(proto::RemoveChannelMessage {
channel_id: self.channel_id,
channel_id: self.channel_id.0,
message_id: id,
});
cx.spawn(move |this, mut cx| async move {
@@ -245,7 +247,7 @@ impl ChannelChat {
async move {
let response = rpc
.request(proto::GetChannelMessages {
channel_id,
channel_id: channel_id.0,
before_message_id,
})
.await?;
@@ -323,7 +325,7 @@ impl ChannelChat {
{
self.rpc
.send(proto::AckChannelMessage {
channel_id: self.channel_id,
channel_id: self.channel_id.0,
message_id: latest_message_id,
})
.ok();
@@ -401,7 +403,11 @@ impl ChannelChat {
let channel_id = self.channel_id;
cx.spawn(move |this, mut cx| {
async move {
let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
let response = rpc
.request(proto::JoinChannelChat {
channel_id: channel_id.0,
})
.await?;
Self::handle_loaded_messages(
this.clone(),
user_store.clone(),
@@ -418,7 +424,7 @@ impl ChannelChat {
for pending_message in pending_messages {
let request = rpc.request(proto::SendChannelMessage {
channel_id,
channel_id: channel_id.0,
body: pending_message.body,
mentions: mentions_to_proto(&pending_message.mentions),
nonce: Some(pending_message.nonce.into()),
@@ -461,7 +467,7 @@ impl ChannelChat {
if self.acknowledged_message_ids.insert(id) {
self.rpc
.send(proto::AckChannelMessage {
channel_id: self.channel_id,
channel_id: self.channel_id.0,
message_id: id,
})
.ok();

View File

@@ -3,7 +3,7 @@ mod channel_index;
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
use anyhow::{anyhow, Result};
use channel_index::ChannelIndex;
use client::{Client, Subscription, User, UserId, UserStore};
use client::{ChannelId, Client, Subscription, User, UserId, UserStore};
use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{
@@ -19,15 +19,16 @@ use rpc::{
use std::{mem, sync::Arc, time::Duration};
use util::{async_maybe, maybe, ResultExt};
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
let channel_store =
cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
cx.set_global(GlobalChannelStore(channel_store));
}
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub type ChannelId = u64;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct HostedProjectId(pub u64);
#[derive(Debug, Clone, Default)]
struct NotesVersion {
@@ -35,11 +36,31 @@ struct NotesVersion {
version: clock::Global,
}
#[derive(Debug, Clone)]
pub struct HostedProject {
id: HostedProjectId,
channel_id: ChannelId,
name: SharedString,
_visibility: proto::ChannelVisibility,
}
impl From<proto::HostedProject> for HostedProject {
fn from(project: proto::HostedProject) -> Self {
Self {
id: HostedProjectId(project.id),
channel_id: ChannelId(project.channel_id),
_visibility: project.visibility(),
name: project.name.into(),
}
}
}
pub struct ChannelStore {
pub channel_index: ChannelIndex,
channel_invitations: Vec<Arc<Channel>>,
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
channel_states: HashMap<ChannelId, ChannelState>,
hosted_projects: HashMap<HostedProjectId, HostedProject>,
outgoing_invites: HashSet<(ChannelId, UserId)>,
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
@@ -58,7 +79,7 @@ pub struct Channel {
pub id: ChannelId,
pub name: SharedString,
pub visibility: proto::ChannelVisibility,
pub parent_path: Vec<u64>,
pub parent_path: Vec<ChannelId>,
}
#[derive(Default)]
@@ -68,6 +89,7 @@ pub struct ChannelState {
observed_chat_message: Option<u64>,
observed_notes_versions: Option<NotesVersion>,
role: Option<ChannelRole>,
projects: HashSet<HostedProjectId>,
}
impl Channel {
@@ -92,10 +114,7 @@ impl Channel {
}
pub fn root_id(&self) -> ChannelId {
self.parent_path
.first()
.map(|id| *id as ChannelId)
.unwrap_or(self.id)
self.parent_path.first().copied().unwrap_or(self.id)
}
pub fn slug(str: &str) -> String {
@@ -199,6 +218,7 @@ impl ChannelStore {
channel_invitations: Vec::default(),
channel_index: ChannelIndex::default(),
channel_participants: Default::default(),
hosted_projects: Default::default(),
outgoing_invites: Default::default(),
opened_buffers: Default::default(),
opened_chats: Default::default(),
@@ -285,6 +305,19 @@ impl ChannelStore {
self.channel_index.by_id().get(&channel_id)
}
pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, HostedProjectId)> {
let mut projects: Vec<(SharedString, HostedProjectId)> = self
.channel_states
.get(&channel_id)
.map(|state| state.projects.clone())
.unwrap_or_default()
.into_iter()
.flat_map(|id| Some((self.hosted_projects.get(&id)?.name.clone(), id)))
.collect();
projects.sort();
projects
}
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
if let Some(buffer) = self.opened_buffers.get(&channel_id) {
if let OpenedModelHandle::Open(buffer) = buffer {
@@ -562,13 +595,16 @@ impl ChannelStore {
let name = name.trim_start_matches("#").to_owned();
cx.spawn(move |this, mut cx| async move {
let response = client
.request(proto::CreateChannel { name, parent_id })
.request(proto::CreateChannel {
name,
parent_id: parent_id.map(|cid| cid.0),
})
.await?;
let channel = response
.channel
.ok_or_else(|| anyhow!("missing channel in response"))?;
let channel_id = channel.id;
let channel_id = ChannelId(channel.id);
this.update(&mut cx, |this, cx| {
let task = this.update_channels(
@@ -600,7 +636,10 @@ impl ChannelStore {
let client = self.client.clone();
cx.spawn(move |_, _| async move {
let _ = client
.request(proto::MoveChannel { channel_id, to })
.request(proto::MoveChannel {
channel_id: channel_id.0,
to: to.0,
})
.await?;
Ok(())
@@ -617,7 +656,7 @@ impl ChannelStore {
cx.spawn(move |_, _| async move {
let _ = client
.request(proto::SetChannelVisibility {
channel_id,
channel_id: channel_id.0,
visibility: visibility.into(),
})
.await?;
@@ -642,7 +681,7 @@ impl ChannelStore {
cx.spawn(move |this, mut cx| async move {
let result = client
.request(proto::InviteChannelMember {
channel_id,
channel_id: channel_id.0,
user_id,
role: role.into(),
})
@@ -674,7 +713,7 @@ impl ChannelStore {
cx.spawn(move |this, mut cx| async move {
let result = client
.request(proto::RemoveChannelMember {
channel_id,
channel_id: channel_id.0,
user_id,
})
.await;
@@ -704,7 +743,7 @@ impl ChannelStore {
cx.spawn(move |this, mut cx| async move {
let result = client
.request(proto::SetChannelMemberRole {
channel_id,
channel_id: channel_id.0,
user_id,
role: role.into(),
})
@@ -730,7 +769,10 @@ impl ChannelStore {
let name = new_name.to_string();
cx.spawn(move |this, mut cx| async move {
let channel = client
.request(proto::RenameChannel { channel_id, name })
.request(proto::RenameChannel {
channel_id: channel_id.0,
name,
})
.await?
.channel
.ok_or_else(|| anyhow!("missing channel in response"))?;
@@ -763,7 +805,10 @@ impl ChannelStore {
let client = self.client.clone();
cx.background_executor().spawn(async move {
client
.request(proto::RespondToChannelInvite { channel_id, accept })
.request(proto::RespondToChannelInvite {
channel_id: channel_id.0,
accept,
})
.await?;
Ok(())
})
@@ -778,7 +823,9 @@ impl ChannelStore {
let user_store = self.user_store.downgrade();
cx.spawn(move |_, mut cx| async move {
let response = client
.request(proto::GetChannelMembers { channel_id })
.request(proto::GetChannelMembers {
channel_id: channel_id.0,
})
.await?;
let user_ids = response.members.iter().map(|m| m.user_id).collect();
@@ -806,7 +853,11 @@ impl ChannelStore {
pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> {
let client = self.client.clone();
async move {
client.request(proto::DeleteChannel { channel_id }).await?;
client
.request(proto::DeleteChannel {
channel_id: channel_id.0,
})
.await?;
Ok(())
}
}
@@ -843,19 +894,23 @@ impl ChannelStore {
for buffer_version in message.payload.observed_channel_buffer_version {
let version = language::proto::deserialize_version(&buffer_version.version);
this.acknowledge_notes_version(
buffer_version.channel_id,
ChannelId(buffer_version.channel_id),
buffer_version.epoch,
&version,
cx,
);
}
for message_id in message.payload.observed_channel_message_id {
this.acknowledge_message_id(message_id.channel_id, message_id.message_id, cx);
this.acknowledge_message_id(
ChannelId(message_id.channel_id),
message_id.message_id,
cx,
);
}
for membership in message.payload.channel_memberships {
if let Some(role) = ChannelRole::from_i32(membership.role) {
this.channel_states
.entry(membership.channel_id)
.entry(ChannelId(membership.channel_id))
.or_insert_with(|| ChannelState::default())
.set_role(role)
}
@@ -888,7 +943,7 @@ impl ChannelStore {
let channel_buffer = buffer.read(cx);
let buffer = channel_buffer.buffer().read(cx);
buffer_versions.push(proto::ChannelBufferVersion {
channel_id: channel_buffer.channel_id,
channel_id: channel_buffer.channel_id.0,
epoch: channel_buffer.epoch(),
version: language::proto::serialize_version(&buffer.version()),
});
@@ -919,7 +974,7 @@ impl ChannelStore {
if let Some(remote_buffer) = response
.buffers
.iter_mut()
.find(|buffer| buffer.channel_id == channel_id)
.find(|buffer| buffer.channel_id == channel_id.0)
{
let channel_id = channel_buffer.channel_id;
let remote_version =
@@ -955,7 +1010,7 @@ impl ChannelStore {
{
client
.send(proto::UpdateChannelBuffer {
channel_id,
channel_id: channel_id.0,
operations: chunk,
})
.ok();
@@ -1010,12 +1065,12 @@ impl ChannelStore {
) -> Option<Task<Result<()>>> {
if !payload.remove_channel_invitations.is_empty() {
self.channel_invitations
.retain(|channel| !payload.remove_channel_invitations.contains(&channel.id));
.retain(|channel| !payload.remove_channel_invitations.contains(&channel.id.0));
}
for channel in payload.channel_invitations {
match self
.channel_invitations
.binary_search_by_key(&channel.id, |c| c.id)
.binary_search_by_key(&channel.id, |c| c.id.0)
{
Ok(ix) => {
Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
@@ -1023,10 +1078,14 @@ impl ChannelStore {
Err(ix) => self.channel_invitations.insert(
ix,
Arc::new(Channel {
id: channel.id,
id: ChannelId(channel.id),
visibility: channel.visibility(),
name: channel.name.into(),
parent_path: channel.parent_path,
parent_path: channel
.parent_path
.into_iter()
.map(|cid| ChannelId(cid))
.collect(),
}),
),
}
@@ -1035,20 +1094,27 @@ impl ChannelStore {
let channels_changed = !payload.channels.is_empty()
|| !payload.delete_channels.is_empty()
|| !payload.latest_channel_message_ids.is_empty()
|| !payload.latest_channel_buffer_versions.is_empty();
|| !payload.latest_channel_buffer_versions.is_empty()
|| !payload.hosted_projects.is_empty()
|| !payload.deleted_hosted_projects.is_empty();
if channels_changed {
if !payload.delete_channels.is_empty() {
self.channel_index.delete_channels(&payload.delete_channels);
let delete_channels: Vec<ChannelId> = payload
.delete_channels
.into_iter()
.map(|cid| ChannelId(cid))
.collect();
self.channel_index.delete_channels(&delete_channels);
self.channel_participants
.retain(|channel_id, _| !&payload.delete_channels.contains(channel_id));
.retain(|channel_id, _| !delete_channels.contains(&channel_id));
for channel_id in &payload.delete_channels {
for channel_id in &delete_channels {
let channel_id = *channel_id;
if payload
.channels
.iter()
.any(|channel| channel.id == channel_id)
.any(|channel| channel.id == channel_id.0)
{
continue;
}
@@ -1064,7 +1130,7 @@ impl ChannelStore {
let mut index = self.channel_index.bulk_insert();
for channel in payload.channels {
let id = channel.id;
let id = ChannelId(channel.id);
let channel_changed = index.insert(channel);
if channel_changed {
@@ -1079,17 +1145,45 @@ impl ChannelStore {
for latest_buffer_version in payload.latest_channel_buffer_versions {
let version = language::proto::deserialize_version(&latest_buffer_version.version);
self.channel_states
.entry(latest_buffer_version.channel_id)
.entry(ChannelId(latest_buffer_version.channel_id))
.or_default()
.update_latest_notes_version(latest_buffer_version.epoch, &version)
}
for latest_channel_message in payload.latest_channel_message_ids {
self.channel_states
.entry(latest_channel_message.channel_id)
.entry(ChannelId(latest_channel_message.channel_id))
.or_default()
.update_latest_message_id(latest_channel_message.message_id);
}
for hosted_project in payload.hosted_projects {
let hosted_project: HostedProject = hosted_project.into();
if let Some(old_project) = self
.hosted_projects
.insert(hosted_project.id, hosted_project.clone())
{
self.channel_states
.entry(old_project.channel_id)
.or_default()
.remove_hosted_project(old_project.id);
}
self.channel_states
.entry(hosted_project.channel_id)
.or_default()
.add_hosted_project(hosted_project.id);
}
for hosted_project_id in payload.deleted_hosted_projects {
let hosted_project_id = HostedProjectId(hosted_project_id);
if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) {
self.channel_states
.entry(old_project.channel_id)
.or_default()
.remove_hosted_project(old_project.id);
}
}
}
cx.notify();
@@ -1129,7 +1223,7 @@ impl ChannelStore {
participants.sort_by_key(|u| u.id);
this.channel_participants
.insert(entry.channel_id, participants);
.insert(ChannelId(entry.channel_id), participants);
}
cx.notify();
@@ -1207,4 +1301,12 @@ impl ChannelState {
version: version.clone(),
});
}
fn add_hosted_project(&mut self, project_id: HostedProjectId) {
self.projects.insert(project_id);
}
fn remove_hosted_project(&mut self, project_id: HostedProjectId) {
self.projects.remove(&project_id);
}
}

View File

@@ -1,4 +1,5 @@
use crate::{Channel, ChannelId};
use crate::Channel;
use client::ChannelId;
use collections::BTreeMap;
use rpc::proto;
use std::sync::Arc;
@@ -50,27 +51,32 @@ pub struct ChannelPathsInsertGuard<'a> {
impl<'a> ChannelPathsInsertGuard<'a> {
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
let mut ret = false;
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
let parent_path = channel_proto
.parent_path
.iter()
.map(|cid| ChannelId(*cid))
.collect();
if let Some(existing_channel) = self.channels_by_id.get_mut(&ChannelId(channel_proto.id)) {
let existing_channel = Arc::make_mut(existing_channel);
ret = existing_channel.visibility != channel_proto.visibility()
|| existing_channel.name != channel_proto.name
|| existing_channel.parent_path != channel_proto.parent_path;
|| existing_channel.parent_path != parent_path;
existing_channel.visibility = channel_proto.visibility();
existing_channel.name = channel_proto.name.into();
existing_channel.parent_path = channel_proto.parent_path.into();
existing_channel.parent_path = parent_path;
} else {
self.channels_by_id.insert(
channel_proto.id,
ChannelId(channel_proto.id),
Arc::new(Channel {
id: channel_proto.id,
id: ChannelId(channel_proto.id),
visibility: channel_proto.visibility(),
name: channel_proto.name.into(),
parent_path: channel_proto.parent_path,
parent_path,
}),
);
self.insert_root(channel_proto.id);
self.insert_root(ChannelId(channel_proto.id));
}
ret
}
@@ -94,14 +100,17 @@ impl<'a> Drop for ChannelPathsInsertGuard<'a> {
fn channel_path_sorting_key<'a>(
id: ChannelId,
channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
) -> impl Iterator<Item = &str> {
) -> impl Iterator<Item = (&str, ChannelId)> {
let (parent_path, name) = channels_by_id
.get(&id)
.map_or((&[] as &[_], None), |channel| {
(channel.parent_path.as_slice(), Some(channel.name.as_ref()))
(
channel.parent_path.as_slice(),
Some((channel.name.as_ref(), channel.id)),
)
});
parent_path
.iter()
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
.filter_map(|id| Some((channels_by_id.get(id)?.name.as_ref(), *id)))
.chain(name)
}

View File

@@ -16,13 +16,11 @@ path = "src/main.rs"
[dependencies]
anyhow.workspace = true
clap = { version = "3.1", features = ["derive"] }
dirs = "3.0"
ipc-channel = "0.16"
serde.workspace = true
serde_derive.workspace = true
util.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9"
core-foundation.workspace = true
core-services = "0.2"
plist = "1.3"

View File

@@ -156,6 +156,39 @@ mod linux {
}
}
// todo!("windows")
#[cfg(target_os = "windows")]
mod windows {
use std::path::Path;
use cli::{CliRequest, CliResponse};
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use crate::{Bundle, InfoPlist};
impl Bundle {
pub fn detect(_args_bundle_path: Option<&Path>) -> anyhow::Result<Self> {
unimplemented!()
}
pub fn plist(&self) -> &InfoPlist {
unimplemented!()
}
pub fn path(&self) -> &Path {
unimplemented!()
}
pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
unimplemented!()
}
pub fn zed_version_string(&self) -> String {
unimplemented!()
}
}
}
#[cfg(target_os = "macos")]
mod mac_os {
use anyhow::Context;

View File

@@ -13,10 +13,9 @@ doctest = false
test-support = ["clock/test-support", "collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
chrono = { workspace = true, features = ["serde"] }
clock.workspace = true
collections.workspace = true
db.workspace = true
gpui.workspace = true
util.workspace = true
release_channel.workspace = true
@@ -24,13 +23,11 @@ rpc.workspace = true
text.workspace = true
settings.workspace = true
feature_flags.workspace = true
sum_tree.workspace = true
anyhow.workspace = true
async-recursion = "0.3"
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
futures.workspace = true
image = "0.23"
lazy_static.workspace = true
log.workspace = true
once_cell = "1.19.0"
@@ -39,16 +36,15 @@ postage.workspace = true
rand.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
sha2 = "0.10"
sha2.workspace = true
smol.workspace = true
sysinfo.workspace = true
telemetry_events.workspace = true
tempfile.workspace = true
thiserror.workspace = true
time.workspace = true
tiny_http = "0.8"
uuid.workspace = true
url.workspace = true
[dev-dependencies]

View File

@@ -42,11 +42,11 @@ use std::{
use telemetry::Telemetry;
use thiserror::Error;
use url::Url;
use util::http::{HttpClient, ZedHttpClient};
use util::http::{HttpClient, HttpClientWithUrl};
use util::{ResultExt, TryFutureExt};
pub use rpc::*;
pub use telemetry::Event;
pub use telemetry_events::Event;
pub use user::*;
lazy_static! {
@@ -153,7 +153,7 @@ impl Global for GlobalClient {}
pub struct Client {
id: AtomicU64,
peer: Arc<Peer>,
http: Arc<ZedHttpClient>,
http: Arc<HttpClientWithUrl>,
telemetry: Arc<Telemetry>,
state: RwLock<ClientState>,
@@ -424,7 +424,7 @@ impl settings::Settings for TelemetrySettings {
impl Client {
pub fn new(
clock: Arc<dyn SystemClock>,
http: Arc<ZedHttpClient>,
http: Arc<HttpClientWithUrl>,
cx: &mut AppContext,
) -> Arc<Self> {
let client = Arc::new(Self {
@@ -447,7 +447,7 @@ impl Client {
self.id.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn http_client(&self) -> Arc<ZedHttpClient> {
pub fn http_client(&self) -> Arc<HttpClientWithUrl> {
self.http.clone()
}
@@ -970,14 +970,14 @@ impl Client {
}
async fn get_rpc_url(
http: Arc<ZedHttpClient>,
http: Arc<HttpClientWithUrl>,
release_channel: Option<ReleaseChannel>,
) -> Result<Url> {
if let Some(url) = &*ZED_RPC_URL {
return Url::parse(url).context("invalid rpc url");
}
let mut url = http.zed_url("/rpc");
let mut url = http.build_url("/rpc");
if let Some(preview_param) =
release_channel.and_then(|channel| channel.release_query_param())
{
@@ -1110,7 +1110,7 @@ impl Client {
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
// that the user is signing in from a Zed app running on the same device.
let mut url = http.zed_url(&format!(
let mut url = http.build_url(&format!(
"/native_app_signin?native_app_port={}&native_app_public_key={}",
port, public_key_string
));
@@ -1145,7 +1145,7 @@ impl Client {
}
let post_auth_url =
http.zed_url("/native_app_signin_succeeded");
http.build_url("/native_app_signin_succeeded");
req.respond(
tiny_http::Response::empty(302).with_header(
tiny_http::Header::from_bytes(
@@ -1187,7 +1187,7 @@ impl Client {
}
async fn authenticate_as_admin(
http: Arc<ZedHttpClient>,
http: Arc<HttpClientWithUrl>,
login: String,
mut api_token: String,
) -> Result<Credentials> {

View File

@@ -1,6 +1,6 @@
mod event_coalescer;
use crate::TelemetrySettings;
use crate::{ChannelId, TelemetrySettings};
use chrono::{DateTime, Utc};
use clock::SystemClock;
use futures::Future;
@@ -8,7 +8,6 @@ use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use release_channel::ReleaseChannel;
use serde::Serialize;
use settings::{Settings, SettingsStore};
use sha2::{Digest, Sha256};
use std::io::Write;
@@ -16,8 +15,12 @@ use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
};
use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, AssistantKind, CallEvent, CopilotEvent, CpuEvent,
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
};
use tempfile::NamedTempFile;
use util::http::{self, HttpClient, Method, ZedHttpClient};
use util::http::{self, HttpClient, HttpClientWithUrl, Method};
#[cfg(not(debug_assertions))]
use util::ResultExt;
use util::TryFutureExt;
@@ -26,7 +29,7 @@ use self::event_coalescer::EventCoalescer;
pub struct Telemetry {
clock: Arc<dyn SystemClock>,
http_client: Arc<ZedHttpClient>,
http_client: Arc<HttpClientWithUrl>,
executor: BackgroundExecutor,
state: Arc<Mutex<TelemetryState>>,
}
@@ -35,7 +38,7 @@ struct TelemetryState {
settings: TelemetrySettings,
metrics_id: Option<Arc<str>>, // Per logged-in user
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
session_id: Option<Arc<str>>, // Per app launch
session_id: Option<String>, // Per app launch
release_channel: Option<&'static str>,
app_metadata: AppMetadata,
architecture: &'static str,
@@ -48,93 +51,6 @@ struct TelemetryState {
max_queue_size: usize,
}
#[derive(Serialize, Debug)]
struct EventRequestBody {
installation_id: Option<Arc<str>>,
session_id: Option<Arc<str>>,
is_staff: Option<bool>,
app_version: Option<String>,
os_name: &'static str,
os_version: Option<String>,
architecture: &'static str,
release_channel: Option<&'static str>,
events: Vec<EventWrapper>,
}
#[derive(Serialize, Debug)]
struct EventWrapper {
signed_in: bool,
#[serde(flatten)]
event: Event,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AssistantKind {
Panel,
Inline,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(tag = "type")]
pub enum Event {
Editor {
operation: &'static str,
file_extension: Option<String>,
vim_mode: bool,
copilot_enabled: bool,
copilot_enabled_for_language: bool,
milliseconds_since_first_event: i64,
},
Copilot {
suggestion_id: Option<String>,
suggestion_accepted: bool,
file_extension: Option<String>,
milliseconds_since_first_event: i64,
},
Call {
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
milliseconds_since_first_event: i64,
},
Assistant {
conversation_id: Option<String>,
kind: AssistantKind,
model: &'static str,
milliseconds_since_first_event: i64,
},
Cpu {
usage_as_percentage: f32,
core_count: u32,
milliseconds_since_first_event: i64,
},
Memory {
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
milliseconds_since_first_event: i64,
},
App {
operation: String,
milliseconds_since_first_event: i64,
},
Setting {
setting: &'static str,
value: String,
milliseconds_since_first_event: i64,
},
Edit {
duration: i64,
environment: &'static str,
milliseconds_since_first_event: i64,
},
Action {
source: &'static str,
action: String,
milliseconds_since_first_event: i64,
},
}
#[cfg(debug_assertions)]
const MAX_QUEUE_LEN: usize = 5;
@@ -146,7 +62,6 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
#[cfg(not(debug_assertions))]
const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
option_env!("ZED_CLIENT_CHECKSUM_SEED")
.map(|s| s.as_bytes().into())
@@ -160,7 +75,7 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
impl Telemetry {
pub fn new(
clock: Arc<dyn SystemClock>,
client: Arc<ZedHttpClient>,
client: Arc<HttpClientWithUrl>,
cx: &mut AppContext,
) -> Arc<Self> {
let release_channel =
@@ -181,7 +96,7 @@ impl Telemetry {
log_file: None,
is_staff: None,
first_event_date_time: None,
event_coalescer: EventCoalescer::new(),
event_coalescer: EventCoalescer::new(clock.clone()),
max_queue_size: MAX_QUEUE_LEN,
}));
@@ -318,15 +233,13 @@ impl Telemetry {
copilot_enabled: bool,
copilot_enabled_for_language: bool,
) {
let event = Event::Editor {
let event = Event::Editor(EditorEvent {
file_extension,
vim_mode,
operation,
operation: operation.into(),
copilot_enabled,
copilot_enabled_for_language,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
@@ -337,13 +250,11 @@ impl Telemetry {
suggestion_accepted: bool,
file_extension: Option<String>,
) {
let event = Event::Copilot {
let event = Event::Copilot(CopilotEvent {
suggestion_id,
suggestion_accepted,
file_extension,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
@@ -354,13 +265,11 @@ impl Telemetry {
kind: AssistantKind,
model: &'static str,
) {
let event = Event::Assistant {
let event = Event::Assistant(AssistantEvent {
conversation_id,
kind,
model,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
model: model.to_string(),
});
self.report_event(event)
}
@@ -369,26 +278,22 @@ impl Telemetry {
self: &Arc<Self>,
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
channel_id: Option<ChannelId>,
) {
let event = Event::Call {
operation,
let event = Event::Call(CallEvent {
operation: operation.to_string(),
room_id,
channel_id,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
channel_id: channel_id.map(|cid| cid.0),
});
self.report_event(event)
}
pub fn report_cpu_event(self: &Arc<Self>, usage_as_percentage: f32, core_count: u32) {
let event = Event::Cpu {
let event = Event::Cpu(CpuEvent {
usage_as_percentage,
core_count,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
@@ -398,22 +303,16 @@ impl Telemetry {
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
) {
let event = Event::Memory {
let event = Event::Memory(MemoryEvent {
memory_in_bytes,
virtual_memory_in_bytes,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
pub fn report_app_event(self: &Arc<Self>, operation: String) -> Event {
let event = Event::App {
operation,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
let event = Event::App(AppEvent { operation });
self.report_event(event.clone());
@@ -421,12 +320,10 @@ impl Telemetry {
}
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
let event = Event::Setting {
setting,
let event = Event::Setting(SettingEvent {
setting: setting.to_string(),
value,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
@@ -437,42 +334,24 @@ impl Telemetry {
drop(state);
if let Some((start, end, environment)) = period_data {
let event = Event::Edit {
let event = Event::Edit(EditEvent {
duration: end.timestamp_millis() - start.timestamp_millis(),
environment,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
environment: environment.to_string(),
});
self.report_event(event);
}
}
pub fn report_action_event(self: &Arc<Self>, source: &'static str, action: String) {
let event = Event::Action {
source,
let event = Event::Action(ActionEvent {
source: source.to_string(),
action,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
fn milliseconds_since_first_event(self: &Arc<Self>, date_time: DateTime<Utc>) -> i64 {
let mut state = self.state.lock();
match state.first_event_date_time {
Some(first_event_date_time) => {
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
}
None => {
state.first_event_date_time = Some(date_time);
0
}
}
}
fn report_event(self: &Arc<Self>, event: Event) {
let mut state = self.state.lock();
@@ -489,8 +368,24 @@ impl Telemetry {
}));
}
let date_time = self.clock.utc_now();
let milliseconds_since_first_event = match state.first_event_date_time {
Some(first_event_date_time) => {
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
}
None => {
state.first_event_date_time = Some(date_time);
0
}
};
let signed_in = state.metrics_id.is_some();
state.events_queue.push(EventWrapper { signed_in, event });
state.events_queue.push(EventWrapper {
signed_in,
milliseconds_since_first_event,
event,
});
if state.installation_id.is_some() {
if state.events_queue.len() >= state.max_queue_size {
@@ -545,21 +440,22 @@ impl Telemetry {
{
let state = this.state.lock();
let request_body = EventRequestBody {
installation_id: state.installation_id.clone(),
installation_id: state.installation_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
is_staff: state.is_staff.clone(),
app_version: state
.app_metadata
.app_version
.map(|version| version.to_string()),
os_name: state.app_metadata.os_name,
.unwrap_or_default()
.to_string(),
os_name: state.app_metadata.os_name.to_string(),
os_version: state
.app_metadata
.os_version
.map(|version| version.to_string()),
architecture: state.architecture,
architecture: state.architecture.to_string(),
release_channel: state.release_channel,
release_channel: state.release_channel.map(Into::into),
events,
};
json_bytes.clear();
@@ -578,7 +474,7 @@ impl Telemetry {
let request = http::Request::builder()
.method(Method::POST)
.uri(&this.http_client.zed_url("/api/events"))
.uri(this.http_client.build_zed_api_url("/telemetry/events"))
.header("Content-Type", "text/plain")
.header("x-zed-checksum", checksum)
.body(json_bytes.into());
@@ -627,10 +523,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 0
}
})
);
assert_eq!(telemetry.state.lock().events_queue.len(), 1);
assert!(telemetry.state.lock().flush_events_task.is_some());
@@ -644,10 +539,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 100
}
})
);
assert_eq!(telemetry.state.lock().events_queue.len(), 2);
assert!(telemetry.state.lock().flush_events_task.is_some());
@@ -661,10 +555,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 200
}
})
);
assert_eq!(telemetry.state.lock().events_queue.len(), 3);
assert!(telemetry.state.lock().flush_events_task.is_some());
@@ -679,10 +572,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 300
}
})
);
assert!(is_empty_state(&telemetry));
@@ -712,10 +604,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 0
}
})
);
assert_eq!(telemetry.state.lock().events_queue.len(), 1);
assert!(telemetry.state.lock().flush_events_task.is_some());

View File

@@ -1,6 +1,9 @@
use chrono::{DateTime, Duration, Utc};
use std::sync::Arc;
use std::time;
use chrono::{DateTime, Duration, Utc};
use clock::SystemClock;
const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20);
const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from_millis(1);
@@ -12,30 +15,20 @@ struct PeriodData {
}
pub struct EventCoalescer {
clock: Arc<dyn SystemClock>,
state: Option<PeriodData>,
}
impl EventCoalescer {
pub fn new() -> Self {
Self { state: None }
pub fn new(clock: Arc<dyn SystemClock>) -> Self {
Self { clock, state: None }
}
pub fn log_event(
&mut self,
environment: &'static str,
) -> Option<(DateTime<Utc>, DateTime<Utc>, &'static str)> {
self.log_event_with_time(Utc::now(), environment)
}
// pub fn close_current_period(&mut self) -> Option<(DateTime<Utc>, DateTime<Utc>)> {
// self.environment.map(|env| self.log_event(env)).flatten()
// }
fn log_event_with_time(
&mut self,
log_time: DateTime<Utc>,
environment: &'static str,
) -> Option<(DateTime<Utc>, DateTime<Utc>, &'static str)> {
let log_time = self.clock.utc_now();
let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap();
let Some(state) = &mut self.state else {
@@ -78,18 +71,22 @@ impl EventCoalescer {
#[cfg(test)]
mod tests {
use chrono::TimeZone;
use clock::FakeSystemClock;
use super::*;
#[test]
fn test_same_context_exceeding_timeout() {
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
));
let environment_1 = "environment_1";
let mut event_coalescer = EventCoalescer::new();
let mut event_coalescer = EventCoalescer::new(clock.clone());
assert_eq!(event_coalescer.state, None);
let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
let period_data = event_coalescer.log_event_with_time(period_start, environment_1);
let period_start = clock.utc_now();
let period_data = event_coalescer.log_event(environment_1);
assert_eq!(period_data, None);
assert_eq!(
@@ -102,12 +99,12 @@ mod tests {
);
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
let mut period_end = period_start;
// Ensure that many calls within the timeout don't start a new period
for _ in 0..100 {
period_end += within_timeout_adjustment;
let period_data = event_coalescer.log_event_with_time(period_end, environment_1);
clock.advance(within_timeout_adjustment);
let period_data = event_coalescer.log_event(environment_1);
let period_end = clock.utc_now();
assert_eq!(period_data, None);
assert_eq!(
@@ -120,10 +117,12 @@ mod tests {
);
}
let period_end = clock.utc_now();
let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap();
// Logging an event exceeding the timeout should start a new period
let new_period_start = period_end + exceed_timeout_adjustment;
let period_data = event_coalescer.log_event_with_time(new_period_start, environment_1);
clock.advance(exceed_timeout_adjustment);
let new_period_start = clock.utc_now();
let period_data = event_coalescer.log_event(environment_1);
assert_eq!(period_data, Some((period_start, period_end, environment_1)));
assert_eq!(
@@ -138,13 +137,16 @@ mod tests {
#[test]
fn test_different_environment_under_timeout() {
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
));
let environment_1 = "environment_1";
let mut event_coalescer = EventCoalescer::new();
let mut event_coalescer = EventCoalescer::new(clock.clone());
assert_eq!(event_coalescer.state, None);
let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
let period_data = event_coalescer.log_event_with_time(period_start, environment_1);
let period_start = clock.utc_now();
let period_data = event_coalescer.log_event(environment_1);
assert_eq!(period_data, None);
assert_eq!(
@@ -157,8 +159,9 @@ mod tests {
);
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
let period_end = period_start + within_timeout_adjustment;
let period_data = event_coalescer.log_event_with_time(period_end, environment_1);
clock.advance(within_timeout_adjustment);
let period_end = clock.utc_now();
let period_data = event_coalescer.log_event(environment_1);
assert_eq!(period_data, None);
assert_eq!(
@@ -170,10 +173,12 @@ mod tests {
})
);
clock.advance(within_timeout_adjustment);
// Logging an event within the timeout but with a different environment should start a new period
let period_end = period_end + within_timeout_adjustment;
let period_end = clock.utc_now();
let environment_2 = "environment_2";
let period_data = event_coalescer.log_event_with_time(period_end, environment_2);
let period_data = event_coalescer.log_event(environment_2);
assert_eq!(period_data, Some((period_start, period_end, environment_1)));
assert_eq!(
@@ -188,13 +193,16 @@ mod tests {
#[test]
fn test_switching_environment_while_within_timeout() {
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
));
let environment_1 = "environment_1";
let mut event_coalescer = EventCoalescer::new();
let mut event_coalescer = EventCoalescer::new(clock.clone());
assert_eq!(event_coalescer.state, None);
let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
let period_data = event_coalescer.log_event_with_time(period_start, environment_1);
let period_start = clock.utc_now();
let period_data = event_coalescer.log_event(environment_1);
assert_eq!(period_data, None);
assert_eq!(
@@ -207,9 +215,10 @@ mod tests {
);
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
let period_end = period_start + within_timeout_adjustment;
clock.advance(within_timeout_adjustment);
let period_end = clock.utc_now();
let environment_2 = "environment_2";
let period_data = event_coalescer.log_event_with_time(period_end, environment_2);
let period_data = event_coalescer.log_event(environment_2);
assert_eq!(period_data, Some((period_start, period_end, environment_1)));
assert_eq!(
@@ -221,22 +230,26 @@ mod tests {
})
);
}
// // 0 20 40 60
// // |-------------------|-------------------|-------------------|-------------------
// // |--------|----------env change
// // |-------------------
// // |period_start |period_end
// // |new_period_start
// 0 20 40 60
// |-------------------|-------------------|-------------------|-------------------
// |--------|----------env change
// |-------------------
// |period_start |period_end
// |new_period_start
#[test]
fn test_switching_environment_while_exceeding_timeout() {
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
));
let environment_1 = "environment_1";
let mut event_coalescer = EventCoalescer::new();
let mut event_coalescer = EventCoalescer::new(clock.clone());
assert_eq!(event_coalescer.state, None);
let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
let period_data = event_coalescer.log_event_with_time(period_start, environment_1);
let period_start = clock.utc_now();
let period_data = event_coalescer.log_event(environment_1);
assert_eq!(period_data, None);
assert_eq!(
@@ -249,9 +262,10 @@ mod tests {
);
let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap();
let period_end = period_start + exceed_timeout_adjustment;
clock.advance(exceed_timeout_adjustment);
let period_end = clock.utc_now();
let environment_2 = "environment_2";
let period_data = event_coalescer.log_event_with_time(period_end, environment_2);
let period_data = event_coalescer.log_event(environment_2);
assert_eq!(
period_data,
@@ -270,6 +284,7 @@ mod tests {
})
);
}
// 0 20 40 60
// |-------------------|-------------------|-------------------|-------------------
// |--------|----------------------------------------env change

View File

@@ -15,6 +15,15 @@ use util::TryFutureExt as _;
pub type UserId = u64;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct ChannelId(pub u64);
impl std::fmt::Display for ChannelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParticipantIndex(pub u32);

View File

@@ -12,6 +12,12 @@ BLOB_STORE_SECRET_KEY = "the-blob-store-secret-key"
BLOB_STORE_BUCKET = "the-extensions-bucket"
BLOB_STORE_URL = "http://127.0.0.1:9000"
BLOB_STORE_REGION = "the-region"
ZED_CLIENT_CHECKSUM_SEED = "development-checksum-seed"
# CLICKHOUSE_URL = ""
# CLICKHOUSE_USER = "default"
# CLICKHOUSE_PASSWORD = ""
# CLICKHOUSE_DATABASE = "default"
# RUST_LOG=info
# LOG_JSON=true

View File

@@ -7,6 +7,9 @@ version = "0.44.0"
publish = false
license = "AGPL-3.0-or-later"
[features]
seed-support = ["reqwest"]
[[bin]]
name = "collab"
@@ -21,17 +24,16 @@ aws-config = { version = "1.1.5" }
aws-sdk-s3 = { version = "1.15.0" }
axum = { version = "0.5", features = ["json", "headers", "ws"] }
axum-extra = { version = "0.3", features = ["erased-json"] }
base64 = "0.13"
chrono.workspace = true
clap = { version = "3.1", features = ["derive"], optional = true }
clock.workspace = true
clickhouse.workspace = true
collections.workspace = true
dashmap = "5.4"
envy = "0.4.2"
futures.workspace = true
hex.workspace = true
hyper = "0.14"
lazy_static.workspace = true
lipsum = { version = "0.8", optional = true }
live_kit_server.workspace = true
log.workspace = true
nanoid = "0.4"
@@ -47,16 +49,15 @@ semver.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
sha-1 = "0.9"
smallvec.workspace = true
sha2.workspace = true
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
telemetry_events.workspace = true
text.workspace = true
time.workspace = true
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.17"
toml.workspace = true
tonic = "0.6"
tower = "0.4"
tower-http = { workspace = true, features = ["trace"] }
tracing = "0.1.34"
tracing-log = "0.1.3"
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
@@ -98,6 +99,3 @@ theme.workspace = true
unindent.workspace = true
util.workspace = true
workspace = { workspace = true, features = ["test-support"] }
[features]
seed-support = ["clap", "lipsum", "reqwest"]

View File

@@ -9,7 +9,7 @@ kind: Service
apiVersion: v1
metadata:
namespace: ${ZED_KUBE_NAMESPACE}
name: collab
name: ${ZED_SERVICE_NAME}
annotations:
service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443"
service.beta.kubernetes.io/do-loadbalancer-certificate-id: ${ZED_DO_CERTIFICATE_ID}
@@ -17,7 +17,7 @@ metadata:
spec:
type: LoadBalancer
selector:
app: collab
app: ${ZED_SERVICE_NAME}
ports:
- name: web
protocol: TCP
@@ -29,17 +29,17 @@ apiVersion: apps/v1
kind: Deployment
metadata:
namespace: ${ZED_KUBE_NAMESPACE}
name: collab
name: ${ZED_SERVICE_NAME}
spec:
replicas: 1
selector:
matchLabels:
app: collab
app: ${ZED_SERVICE_NAME}
template:
metadata:
labels:
app: collab
app: ${ZED_SERVICE_NAME}
annotations:
ad.datadoghq.com/collab.check_names: |
["openmetrics"]
@@ -55,10 +55,11 @@ spec:
]
spec:
containers:
- name: collab
- name: ${ZED_SERVICE_NAME}
image: "${ZED_IMAGE_ID}"
args:
- serve
- ${ZED_SERVICE_NAME}
ports:
- containerPort: 8080
protocol: TCP
@@ -90,6 +91,11 @@ spec:
secretKeyRef:
name: api
key: token
- name: ZED_CLIENT_CHECKSUM_SEED
valueFrom:
secretKeyRef:
name: zed-client
key: checksum-seed
- name: LIVE_KIT_SERVER
valueFrom:
secretKeyRef:
@@ -130,6 +136,26 @@ spec:
secretKeyRef:
name: blob-store
key: bucket
- name: CLICKHOUSE_URL
valueFrom:
secretKeyRef:
name: clickhouse
key: url
- name: CLICKHOUSE_USER
valueFrom:
secretKeyRef:
name: clickhouse
key: user
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: clickhouse
key: password
- name: CLICKHOUSE_DATABASE
valueFrom:
secretKeyRef:
name: clickhouse
key: database
- name: INVITE_LINK_PREFIX
value: ${INVITE_LINK_PREFIX}
- name: RUST_BACKTRACE

View File

@@ -375,3 +375,13 @@ CREATE TABLE extension_versions (
CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id");
CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count");
CREATE TABLE hosted_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL REFERENCES channels(id),
name TEXT NOT NULL,
visibility TEXT NOT NULL,
deleted_at TIMESTAMP NULL
);
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);

View File

@@ -0,0 +1,11 @@
-- Add migration script here
CREATE TABLE hosted_projects (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
channel_id INT NOT NULL REFERENCES channels(id),
name TEXT NOT NULL,
visibility TEXT NOT NULL,
deleted_at TIMESTAMP NULL
);
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);

View File

@@ -0,0 +1,3 @@
-- Add migration script here
CREATE UNIQUE INDEX uix_channels_parent_path_name ON channels(parent_path, name) WHERE (parent_path IS NOT NULL AND parent_path != '');

View File

@@ -1,4 +1,5 @@
mod extensions;
pub mod events;
pub mod extensions;
use crate::{
auth,
@@ -24,7 +25,7 @@ use tracing::instrument;
pub use extensions::fetch_extensions_from_blob_store_periodically;
pub fn routes(rpc_server: Arc<rpc::Server>, state: Arc<AppState>) -> Router<Body> {
pub fn routes(rpc_server: Option<Arc<rpc::Server>>, state: Arc<AppState>) -> Router<Body> {
Router::new()
.route("/user", get(get_authenticated_user))
.route("/users/:id/access_tokens", post(create_access_token))
@@ -32,7 +33,6 @@ pub fn routes(rpc_server: Arc<rpc::Server>, state: Arc<AppState>) -> Router<Body
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
.route("/contributors", get(get_contributors).post(add_contributor))
.route("/contributor", get(check_is_contributor))
.merge(extensions::router())
.layer(
ServiceBuilder::new()
.layer(Extension(state))
@@ -135,8 +135,12 @@ async fn trace_panic(panic: Json<Panic>) -> Result<()> {
}
async fn get_rpc_server_snapshot(
Extension(rpc_server): Extension<Arc<rpc::Server>>,
Extension(rpc_server): Extension<Option<Arc<rpc::Server>>>,
) -> Result<ErasedJson> {
let Some(rpc_server) = rpc_server else {
return Err(Error::Internal(anyhow!("rpc server is not available")));
};
Ok(ErasedJson::pretty(rpc_server.snapshot().await))
}

View File

@@ -0,0 +1,797 @@
use std::sync::Arc;
use anyhow::{anyhow, Context};
use axum::{
body::Bytes, headers::Header, http::HeaderName, routing::post, Extension, Router, TypedHeader,
};
use hyper::StatusCode;
use lazy_static::lazy_static;
use serde::{Serialize, Serializer};
use sha2::{Digest, Sha256};
use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, CallEvent, CopilotEvent, CpuEvent, EditEvent,
EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
};
use crate::{AppState, Error, Result};
pub fn router() -> Router {
Router::new().route("/telemetry/events", post(post_events))
}
lazy_static! {
static ref ZED_CHECKSUM_HEADER: HeaderName = HeaderName::from_static("x-zed-checksum");
static ref CLOUDFLARE_IP_COUNTRY_HEADER: HeaderName = HeaderName::from_static("cf-ipcountry");
}
pub struct ZedChecksumHeader(Vec<u8>);
impl Header for ZedChecksumHeader {
fn name() -> &'static HeaderName {
&ZED_CHECKSUM_HEADER
}
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
where
Self: Sized,
I: Iterator<Item = &'i axum::http::HeaderValue>,
{
let checksum = values
.next()
.ok_or_else(axum::headers::Error::invalid)?
.to_str()
.map_err(|_| axum::headers::Error::invalid())?;
let bytes = hex::decode(checksum).map_err(|_| axum::headers::Error::invalid())?;
Ok(Self(bytes))
}
fn encode<E: Extend<axum::http::HeaderValue>>(&self, _values: &mut E) {
unimplemented!()
}
}
pub struct CloudflareIpCountryHeader(String);
impl Header for CloudflareIpCountryHeader {
fn name() -> &'static HeaderName {
&CLOUDFLARE_IP_COUNTRY_HEADER
}
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
where
Self: Sized,
I: Iterator<Item = &'i axum::http::HeaderValue>,
{
let country_code = values
.next()
.ok_or_else(axum::headers::Error::invalid)?
.to_str()
.map_err(|_| axum::headers::Error::invalid())?;
Ok(Self(country_code.to_string()))
}
fn encode<E: Extend<axum::http::HeaderValue>>(&self, _values: &mut E) {
unimplemented!()
}
}
pub async fn post_events(
Extension(app): Extension<Arc<AppState>>,
TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
body: Bytes,
) -> Result<()> {
let Some(clickhouse_client) = app.clickhouse_client.clone() else {
Err(Error::Http(
StatusCode::NOT_IMPLEMENTED,
"not supported".into(),
))?
};
let Some(checksum_seed) = app.config.zed_client_checksum_seed.as_ref() else {
return Err(Error::Http(
StatusCode::INTERNAL_SERVER_ERROR,
"events not enabled".into(),
))?;
};
let mut summer = Sha256::new();
summer.update(checksum_seed);
summer.update(&body);
summer.update(checksum_seed);
if &checksum[..] != &summer.finalize()[..] {
return Err(Error::Http(
StatusCode::BAD_REQUEST,
"invalid checksum".into(),
))?;
}
let request_body: telemetry_events::EventRequestBody =
serde_json::from_slice(&body).map_err(|err| {
log::error!("can't parse event json: {err}");
Error::Internal(anyhow!(err))
})?;
let mut to_upload = ToUpload::default();
let Some(last_event) = request_body.events.last() else {
return Err(Error::Http(StatusCode::BAD_REQUEST, "no events".into()))?;
};
let country_code = country_code_header.map(|h| h.0 .0);
let first_event_at = chrono::Utc::now()
- chrono::Duration::milliseconds(last_event.milliseconds_since_first_event);
for wrapper in &request_body.events {
match &wrapper.event {
Event::Editor(event) => to_upload.editor_events.push(EditorEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
country_code.clone(),
)),
Event::Copilot(event) => to_upload.copilot_events.push(CopilotEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
country_code.clone(),
)),
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
)),
Event::Assistant(event) => {
to_upload
.assistant_events
.push(AssistantEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
))
}
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
)),
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
)),
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
)),
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
)),
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
)),
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
)),
}
}
to_upload
.upload(&clickhouse_client)
.await
.map_err(|err| Error::Internal(anyhow!(err)))?;
Ok(())
}
#[derive(Default)]
struct ToUpload {
editor_events: Vec<EditorEventRow>,
copilot_events: Vec<CopilotEventRow>,
assistant_events: Vec<AssistantEventRow>,
call_events: Vec<CallEventRow>,
cpu_events: Vec<CpuEventRow>,
memory_events: Vec<MemoryEventRow>,
app_events: Vec<AppEventRow>,
setting_events: Vec<SettingEventRow>,
edit_events: Vec<EditEventRow>,
action_events: Vec<ActionEventRow>,
}
impl ToUpload {
pub async fn upload(&self, clickhouse_client: &clickhouse::Client) -> anyhow::Result<()> {
Self::upload_to_table("editor_events", &self.editor_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'editor_events'"))?;
Self::upload_to_table("copilot_events", &self.copilot_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'copilot_events'"))?;
Self::upload_to_table(
"assistant_events",
&self.assistant_events,
clickhouse_client,
)
.await
.with_context(|| format!("failed to upload to table 'assistant_events'"))?;
Self::upload_to_table("call_events", &self.call_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'call_events'"))?;
Self::upload_to_table("cpu_events", &self.cpu_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'cpu_events'"))?;
Self::upload_to_table("memory_events", &self.memory_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'memory_events'"))?;
Self::upload_to_table("app_events", &self.app_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'app_events'"))?;
Self::upload_to_table("setting_events", &self.setting_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'setting_events'"))?;
Self::upload_to_table("edit_events", &self.edit_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'edit_events'"))?;
Self::upload_to_table("action_events", &self.action_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'action_events'"))?;
Ok(())
}
async fn upload_to_table<T: clickhouse::Row + Serialize + std::fmt::Debug>(
table: &str,
rows: &[T],
clickhouse_client: &clickhouse::Client,
) -> anyhow::Result<()> {
if !rows.is_empty() {
let mut insert = clickhouse_client.insert(table)?;
for event in rows {
insert.write(event).await?;
}
insert.end().await?;
}
Ok(())
}
}
pub fn serialize_country_code<S>(country_code: &str, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if country_code.len() != 2 {
use serde::ser::Error;
return Err(S::Error::custom(
"country_code must be exactly 2 characters",
));
}
let country_code = country_code.as_bytes();
serializer.serialize_u16(((country_code[0] as u16) << 8) + country_code[1] as u16)
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
pub installation_id: String,
pub operation: String,
pub app_version: String,
pub file_extension: String,
pub os_name: String,
pub os_version: String,
pub release_channel: String,
pub signed_in: bool,
pub vim_mode: bool,
#[serde(serialize_with = "serialize_country_code")]
pub country_code: String,
pub region_code: String,
pub city: String,
pub time: i64,
pub copilot_enabled: bool,
pub copilot_enabled_for_language: bool,
pub historical_event: bool,
pub architecture: String,
pub is_staff: Option<bool>,
pub session_id: Option<String>,
pub major: Option<i32>,
pub minor: Option<i32>,
pub patch: Option<i32>,
}
impl EditorEventRow {
fn from_event(
event: EditorEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
country_code: Option<String>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
architecture: body.architecture.clone(),
installation_id: body.installation_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
operation: event.operation,
file_extension: event.file_extension.unwrap_or_default(),
signed_in: wrapper.signed_in,
vim_mode: event.vim_mode,
copilot_enabled: event.copilot_enabled,
copilot_enabled_for_language: event.copilot_enabled_for_language,
country_code: country_code.unwrap_or("XX".to_string()),
region_code: "".to_string(),
city: "".to_string(),
historical_event: false,
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct CopilotEventRow {
pub installation_id: String,
pub suggestion_id: String,
pub suggestion_accepted: bool,
pub app_version: String,
pub file_extension: String,
pub os_name: String,
pub os_version: String,
pub release_channel: String,
pub signed_in: bool,
#[serde(serialize_with = "serialize_country_code")]
pub country_code: String,
pub region_code: String,
pub city: String,
pub time: i64,
pub is_staff: Option<bool>,
pub session_id: Option<String>,
pub major: Option<i32>,
pub minor: Option<i32>,
pub patch: Option<i32>,
}
impl CopilotEventRow {
fn from_event(
event: CopilotEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
country_code: Option<String>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
installation_id: body.installation_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
file_extension: event.file_extension.unwrap_or_default(),
signed_in: wrapper.signed_in,
country_code: country_code.unwrap_or("XX".to_string()),
region_code: "".to_string(),
city: "".to_string(),
suggestion_id: event.suggestion_id.unwrap_or_default(),
suggestion_accepted: event.suggestion_accepted,
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct CallEventRow {
// AppInfoBase
app_version: String,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
release_channel: String,
// ClientEventBase
installation_id: String,
session_id: Option<String>,
is_staff: Option<bool>,
time: i64,
// CallEventRow
operation: String,
room_id: Option<u64>,
channel_id: Option<u64>,
}
impl CallEventRow {
fn from_event(
event: CallEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
operation: event.operation,
room_id: event.room_id,
channel_id: event.channel_id,
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct AssistantEventRow {
// AppInfoBase
app_version: String,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
release_channel: String,
// ClientEventBase
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
time: i64,
// AssistantEventRow
conversation_id: String,
kind: String,
model: String,
}
impl AssistantEventRow {
fn from_event(
event: AssistantEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
conversation_id: event.conversation_id.unwrap_or_default(),
kind: event.kind.to_string(),
model: event.model,
}
}
}
#[derive(Debug, clickhouse::Row, Serialize)]
pub struct CpuEventRow {
pub installation_id: Option<String>,
pub is_staff: Option<bool>,
pub usage_as_percentage: f32,
pub core_count: u32,
pub app_version: String,
pub release_channel: String,
pub time: i64,
pub session_id: Option<String>,
// pub normalized_cpu_usage: f64, MATERIALIZED
pub major: Option<i32>,
pub minor: Option<i32>,
pub patch: Option<i32>,
}
impl CpuEventRow {
fn from_event(
event: CpuEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
usage_as_percentage: event.usage_as_percentage,
core_count: event.core_count,
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct MemoryEventRow {
// AppInfoBase
app_version: String,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
release_channel: String,
// ClientEventBase
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
time: i64,
// MemoryEventRow
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
}
impl MemoryEventRow {
fn from_event(
event: MemoryEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
memory_in_bytes: event.memory_in_bytes,
virtual_memory_in_bytes: event.virtual_memory_in_bytes,
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct AppEventRow {
// AppInfoBase
app_version: String,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
release_channel: String,
// ClientEventBase
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
time: i64,
// AppEventRow
operation: String,
}
impl AppEventRow {
fn from_event(
event: AppEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
operation: event.operation,
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct SettingEventRow {
// AppInfoBase
app_version: String,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
release_channel: String,
// ClientEventBase
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
time: i64,
// SettingEventRow
setting: String,
value: String,
}
impl SettingEventRow {
fn from_event(
event: SettingEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
setting: event.setting,
value: event.value,
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditEventRow {
// AppInfoBase
app_version: String,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
release_channel: String,
// ClientEventBase
installation_id: Option<String>,
// Note: This column name has a typo in the ClickHouse table.
#[serde(rename = "sesssion_id")]
session_id: Option<String>,
is_staff: Option<bool>,
time: i64,
// EditEventRow
period_start: i64,
period_end: i64,
environment: String,
}
impl EditEventRow {
fn from_event(
event: EditEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
let period_start = time - chrono::Duration::milliseconds(event.duration);
let period_end = time;
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
period_start: period_start.timestamp_millis(),
period_end: period_end.timestamp_millis(),
environment: event.environment,
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct ActionEventRow {
// AppInfoBase
app_version: String,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
release_channel: String,
// ClientEventBase
installation_id: Option<String>,
// Note: This column name has a typo in the ClickHouse table.
#[serde(rename = "sesssion_id")]
session_id: Option<String>,
is_staff: Option<bool>,
time: i64,
// ActionEventRow
source: String,
action: String,
}
impl ActionEventRow {
fn from_event(
event: ActionEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
source: event.source,
action: event.action,
}
}
}

View File

@@ -587,6 +587,7 @@ pub struct ChannelsForUser {
pub channels: Vec<Channel>,
pub channel_memberships: Vec<channel_member::Model>,
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
pub hosted_projects: Vec<proto::HostedProject>,
pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
pub observed_channel_messages: Vec<proto::ChannelMessageId>,

View File

@@ -88,6 +88,7 @@ id_type!(FlagId);
id_type!(ExtensionId);
id_type!(NotificationId);
id_type!(NotificationKindId);
id_type!(HostedProjectId);
/// ChannelRole gives you permissions for both channels and calls.
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]

View File

@@ -6,6 +6,7 @@ pub mod channels;
pub mod contacts;
pub mod contributors;
pub mod extensions;
pub mod hosted_projects;
pub mod messages;
pub mod notifications;
pub mod projects;

View File

@@ -652,9 +652,14 @@ impl Database {
.observed_channel_messages(&channel_ids, user_id, &*tx)
.await?;
let hosted_projects = self
.get_hosted_projects(&channel_ids, &roles_by_channel_id, &*tx)
.await?;
Ok(ChannelsForUser {
channel_memberships,
channels,
hosted_projects,
channel_participants,
latest_buffer_versions,
latest_channel_messages,

View File

@@ -0,0 +1,42 @@
use rpc::proto;
use super::*;
impl Database {
pub async fn get_hosted_projects(
&self,
channel_ids: &Vec<ChannelId>,
roles: &HashMap<ChannelId, ChannelRole>,
tx: &DatabaseTransaction,
) -> Result<Vec<proto::HostedProject>> {
Ok(hosted_project::Entity::find()
.filter(hosted_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0)))
.all(&*tx)
.await?
.into_iter()
.flat_map(|project| {
if project.deleted_at.is_some() {
return None;
}
match project.visibility {
ChannelVisibility::Public => {}
ChannelVisibility::Members => {
let is_visible = roles
.get(&project.channel_id)
.map(|role| role.can_see_all_descendants())
.unwrap_or(false);
if !is_visible {
return None;
}
}
};
Some(proto::HostedProject {
id: project.id.to_proto(),
channel_id: project.channel_id.to_proto(),
name: project.name.clone(),
visibility: project.visibility.into(),
})
})
.collect())
}
}

View File

@@ -14,6 +14,7 @@ pub mod extension;
pub mod extension_version;
pub mod feature_flag;
pub mod follower;
pub mod hosted_project;
pub mod language_server;
pub mod notification;
pub mod notification_kind;

View File

@@ -0,0 +1,18 @@
use crate::db::{ChannelId, ChannelVisibility, HostedProjectId};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "hosted_projects")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: HostedProjectId,
pub channel_id: ChannelId,
pub name: String,
pub visibility: ChannelVisibility,
pub deleted_at: Option<DateTime>,
}
impl ActiveModelBehavior for ActiveModel {}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

View File

@@ -58,11 +58,24 @@ impl From<serde_json::Error> for Error {
impl IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
match self {
Error::Http(code, message) => (code, message).into_response(),
Error::Http(code, message) => {
log::error!("HTTP error {}: {}", code, &message);
(code, message).into_response()
}
Error::Database(error) => {
log::error!(
"HTTP error {}: {:?}",
StatusCode::INTERNAL_SERVER_ERROR,
&error
);
(StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
}
Error::Internal(error) => {
log::error!(
"HTTP error {}: {:?}",
StatusCode::INTERNAL_SERVER_ERROR,
&error
);
(StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
}
}
@@ -97,6 +110,10 @@ pub struct Config {
pub database_url: String,
pub database_max_connections: u32,
pub api_token: String,
pub clickhouse_url: Option<String>,
pub clickhouse_user: Option<String>,
pub clickhouse_password: Option<String>,
pub clickhouse_database: Option<String>,
pub invite_link_prefix: String,
pub live_kit_server: Option<String>,
pub live_kit_key: Option<String>,
@@ -109,6 +126,7 @@ pub struct Config {
pub blob_store_secret_key: Option<String>,
pub blob_store_bucket: Option<String>,
pub zed_environment: Arc<str>,
pub zed_client_checksum_seed: Option<String>,
}
impl Config {
@@ -127,6 +145,7 @@ pub struct AppState {
pub db: Arc<Database>,
pub live_kit_client: Option<Arc<dyn live_kit_server::api::Client>>,
pub blob_store_client: Option<aws_sdk_s3::Client>,
pub clickhouse_client: Option<clickhouse::Client>,
pub config: Config,
}
@@ -156,6 +175,7 @@ impl AppState {
db: Arc::new(db),
live_kit_client,
blob_store_client: build_blob_store_client(&config).await.log_err(),
clickhouse_client: build_clickhouse_client(&config).log_err(),
config,
};
Ok(Arc::new(this))
@@ -196,3 +216,31 @@ async fn build_blob_store_client(config: &Config) -> anyhow::Result<aws_sdk_s3::
Ok(aws_sdk_s3::Client::new(&s3_config))
}
fn build_clickhouse_client(config: &Config) -> anyhow::Result<clickhouse::Client> {
Ok(clickhouse::Client::default()
.with_url(
config
.clickhouse_url
.as_ref()
.ok_or_else(|| anyhow!("missing clickhouse_url"))?,
)
.with_user(
config
.clickhouse_user
.as_ref()
.ok_or_else(|| anyhow!("missing clickhouse_user"))?,
)
.with_password(
config
.clickhouse_password
.as_ref()
.ok_or_else(|| anyhow!("missing clickhouse_password"))?,
)
.with_database(
config
.clickhouse_database
.as_ref()
.ok_or_else(|| anyhow!("missing clickhouse_database"))?,
))
}

View File

@@ -1,17 +1,21 @@
use anyhow::anyhow;
use axum::{routing::get, Extension, Router};
use axum::{extract::MatchedPath, routing::get, Extension, Router};
use collab::{
api::fetch_extensions_from_blob_store_periodically, db, env, executor::Executor, AppState,
Config, MigrateConfig, Result,
};
use db::Database;
use hyper::Request;
use std::{
env::args,
net::{SocketAddr, TcpListener},
path::Path,
sync::Arc,
};
#[cfg(unix)]
use tokio::signal::unix::SignalKind;
use tower_http::trace::{self, TraceLayer};
use tracing::Level;
use tracing_log::LogTracer;
use tracing_subscriber::{filter::EnvFilter, fmt::format::JsonFields, Layer};
use util::ResultExt;
@@ -28,7 +32,8 @@ async fn main() -> Result<()> {
);
}
match args().skip(1).next().as_deref() {
let mut args = args().skip(1);
match args.next().as_deref() {
Some("version") => {
println!("collab v{} ({})", VERSION, REVISION.unwrap_or("unknown"));
}
@@ -36,6 +41,17 @@ async fn main() -> Result<()> {
run_migrations().await?;
}
Some("serve") => {
let (is_api, is_collab) = if let Some(next) = args.next() {
(next == "api", next == "collab")
} else {
(true, true)
};
if !is_api && !is_collab {
Err(anyhow!(
"usage: collab <version | migrate | serve [api|collab]>"
))?;
}
let config = envy::from_env::<Config>().expect("error loading config");
init_tracing(&config);
@@ -46,24 +62,55 @@ async fn main() -> Result<()> {
let listener = TcpListener::bind(&format!("0.0.0.0:{}", state.config.http_port))
.expect("failed to bind TCP listener");
let epoch = state
.db
.create_server(&state.config.zed_environment)
.await?;
let rpc_server = collab::rpc::Server::new(epoch, state.clone(), Executor::Production);
rpc_server.start().await?;
let rpc_server = if is_collab {
let epoch = state
.db
.create_server(&state.config.zed_environment)
.await?;
let rpc_server =
collab::rpc::Server::new(epoch, state.clone(), Executor::Production);
rpc_server.start().await?;
fetch_extensions_from_blob_store_periodically(state.clone(), Executor::Production);
Some(rpc_server)
} else {
None
};
let app = collab::api::routes(rpc_server.clone(), state.clone())
.merge(collab::rpc::routes(rpc_server.clone()))
if is_api {
fetch_extensions_from_blob_store_periodically(state.clone(), Executor::Production);
}
let mut app = collab::api::routes(rpc_server.clone(), state.clone());
if let Some(rpc_server) = rpc_server.clone() {
app = app.merge(collab::rpc::routes(rpc_server))
}
app = app
.merge(
Router::new()
.route("/", get(handle_root))
.route("/healthz", get(handle_liveness_probe))
.merge(collab::api::extensions::router())
.merge(collab::api::events::router())
.layer(Extension(state.clone())),
)
.layer(
TraceLayer::new_for_http()
.make_span_with(|request: &Request<_>| {
let matched_path = request
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str);
tracing::info_span!(
"http_request",
method = ?request.method(),
matched_path,
)
})
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
);
#[cfg(unix)]
axum::Server::from_tcp(listener)?
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
.with_graceful_shutdown(async move {
@@ -76,12 +123,21 @@ async fn main() -> Result<()> {
futures::pin_mut!(sigterm, sigint);
futures::future::select(sigterm, sigint).await;
tracing::info!("Received interrupt signal");
rpc_server.teardown();
if let Some(rpc_server) = rpc_server {
rpc_server.teardown();
}
})
.await?;
// todo!("windows")
#[cfg(windows)]
unimplemented!();
}
_ => {
Err(anyhow!("usage: collab <version | migrate | serve>"))?;
Err(anyhow!(
"usage: collab <version | migrate | serve [api|collab]>"
))?;
}
}
Ok(())

View File

@@ -3396,6 +3396,9 @@ fn build_channels_update(
for channel in channel_invites {
update.channel_invitations.push(channel.to_proto());
}
for project in channels.hosted_projects {
update.hosted_projects.push(project);
}
update
}

View File

@@ -1,4 +1,5 @@
use call::Room;
use client::ChannelId;
use gpui::{Model, TestAppContext};
mod channel_buffer_tests;
@@ -43,6 +44,6 @@ fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomPartici
})
}
fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<u64> {
fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
cx.read(|cx| room.read(cx).channel_id())
}

View File

@@ -183,7 +183,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
server
.app_state
.db
.set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id), true)
.set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id.0), true)
.await
.unwrap();

View File

@@ -100,13 +100,13 @@ async fn test_basic_channel_messages(
Notification::ChannelMessageMention {
message_id,
sender_id: client_a.id(),
channel_id,
channel_id: channel_id.0,
}
);
assert_eq!(
store.notification_at(1).unwrap().notification,
Notification::ChannelInvitation {
channel_id,
channel_id: channel_id.0,
channel_name: "the-channel".to_string(),
inviter_id: client_a.id()
}

View File

@@ -4,8 +4,8 @@ use crate::{
tests::{room_participants, RoomParticipants, TestServer},
};
use call::ActiveCall;
use channel::{ChannelId, ChannelMembership, ChannelStore};
use client::User;
use channel::{ChannelMembership, ChannelStore};
use client::{ChannelId, User};
use futures::future::try_join_all;
use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
use rpc::{
@@ -281,7 +281,7 @@ async fn test_core_channels(
.app_state
.db
.rename_channel(
db::ChannelId::from_proto(channel_a_id),
db::ChannelId::from_proto(channel_a_id.0),
UserId::from_proto(client_a.id()),
"channel-a-renamed",
)
@@ -1444,7 +1444,7 @@ fn assert_channels(
fn assert_channels_list_shape(
channel_store: &Model<ChannelStore>,
cx: &TestAppContext,
expected_channels: &[(u64, usize)],
expected_channels: &[(ChannelId, usize)],
) {
let actual = cx.read(|cx| {
channel_store.read_with(cx, |store, _| {

View File

@@ -1426,6 +1426,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.defaults.inlay_hints = Some(InlayHintSettings {
enabled: true,
edit_debounce_ms: 0,
scroll_debounce_ms: 0,
show_type_hints: true,
show_parameter_hints: false,
show_other_hints: true,
@@ -1438,6 +1440,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.defaults.inlay_hints = Some(InlayHintSettings {
enabled: true,
edit_debounce_ms: 0,
scroll_debounce_ms: 0,
show_type_hints: true,
show_parameter_hints: false,
show_other_hints: true,
@@ -1695,6 +1699,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.defaults.inlay_hints = Some(InlayHintSettings {
enabled: false,
edit_debounce_ms: 0,
scroll_debounce_ms: 0,
show_type_hints: false,
show_parameter_hints: false,
show_other_hints: false,
@@ -1707,6 +1713,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
settings.defaults.inlay_hints = Some(InlayHintSettings {
enabled: true,
edit_debounce_ms: 0,
scroll_debounce_ms: 0,
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,

View File

@@ -1,5 +1,6 @@
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
use call::{ActiveCall, ParticipantLocation};
use client::ChannelId;
use collab_ui::{
channel_view::ChannelView,
notifications::project_shared_notification::ProjectSharedNotification,
@@ -2000,7 +2001,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
}
async fn join_channel(
channel_id: u64,
channel_id: ChannelId,
client: &TestClient,
cx: &mut TestAppContext,
) -> anyhow::Result<()> {

View File

@@ -137,7 +137,7 @@ async fn test_notifications(
assert_eq!(
entry.notification,
Notification::ChannelInvitation {
channel_id,
channel_id: channel_id.0,
channel_name: "the-channel".to_string(),
inviter_id: client_a.id()
}

View File

@@ -253,7 +253,7 @@ impl RandomizedTest for RandomChannelBufferTest {
.channel_buffers()
.deref()
.iter()
.find(|b| b.read(cx).channel_id == channel_id.to_proto())
.find(|b| b.read(cx).channel_id.0 == channel_id.to_proto())
{
let channel_buffer = channel_buffer.read(cx);

View File

@@ -8,7 +8,8 @@ use anyhow::anyhow;
use call::ActiveCall;
use channel::{ChannelBuffer, ChannelStore};
use client::{
self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore,
self, proto::PeerId, ChannelId, Client, Connection, Credentials, EstablishConnectionError,
UserStore,
};
use clock::FakeSystemClock;
use collab_ui::channel_view::ChannelView;
@@ -120,7 +121,7 @@ impl TestServer {
pub async fn start2(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) -> (TestServer, TestClient, TestClient, u64) {
) -> (TestServer, TestClient, TestClient, ChannelId) {
let mut server = Self::start(cx_a.executor()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -353,10 +354,10 @@ impl TestServer {
pub async fn make_channel(
&self,
channel: &str,
parent: Option<u64>,
parent: Option<ChannelId>,
admin: (&TestClient, &mut TestAppContext),
members: &mut [(&TestClient, &mut TestAppContext)],
) -> u64 {
) -> ChannelId {
let (_, admin_cx) = admin;
let channel_id = admin_cx
.read(ChannelStore::global)
@@ -399,7 +400,7 @@ impl TestServer {
channel: &str,
client: &TestClient,
cx: &mut TestAppContext,
) -> u64 {
) -> ChannelId {
let channel_id = self
.make_channel(channel, None, (client, cx), &mut [])
.await;
@@ -423,7 +424,7 @@ impl TestServer {
&self,
channels: &[(&str, Option<&str>)],
creator: (&TestClient, &mut TestAppContext),
) -> Vec<u64> {
) -> Vec<ChannelId> {
let mut observed_channels = HashMap::default();
let mut result = Vec::new();
for (channel, parent) in channels {
@@ -483,6 +484,7 @@ impl TestServer {
db: test_db.db().clone(),
live_kit_client: Some(Arc::new(fake_server.create_api_client())),
blob_store_client: None,
clickhouse_client: None,
config: Config {
http_port: 0,
database_url: "".into(),
@@ -500,6 +502,11 @@ impl TestServer {
blob_store_access_key: None,
blob_store_secret_key: None,
blob_store_bucket: None,
clickhouse_url: None,
clickhouse_user: None,
clickhouse_password: None,
clickhouse_database: None,
zed_client_checksum_seed: None,
},
})
}
@@ -671,7 +678,7 @@ impl TestClient {
pub async fn host_workspace(
&self,
workspace: &View<Workspace>,
channel_id: u64,
channel_id: ChannelId,
cx: &mut VisualTestContext,
) {
cx.update(|cx| {
@@ -692,7 +699,7 @@ impl TestClient {
pub async fn join_workspace<'a>(
&'a self,
channel_id: u64,
channel_id: ChannelId,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
@@ -771,7 +778,7 @@ impl TestClient {
}
pub fn open_channel_notes(
channel_id: u64,
channel_id: ChannelId,
cx: &mut VisualTestContext,
) -> Task<anyhow::Result<View<ChannelView>>> {
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());

View File

@@ -35,19 +35,16 @@ collections.workspace = true
db.workspace = true
editor.workspace = true
extensions_ui.workspace = true
feature_flags.workspace = true
feedback.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
language.workspace = true
lazy_static.workspace = true
log.workspace = true
menu.workspace = true
notifications.workspace = true
parking_lot.workspace = true
picker.workspace = true
postage.workspace = true
project.workspace = true
recent_projects.workspace = true
rich_text.workspace = true
@@ -61,13 +58,13 @@ smallvec.workspace = true
story = { workspace = true, optional = true }
theme.workspace = true
theme_selector.workspace = true
time_format.workspace = true
time.workspace = true
ui.workspace = true
util.workspace = true
vcs_menu.workspace = true
workspace.workspace = true
zed_actions.workspace = true
sys-locale.workspace = true
[dev-dependencies]
call = { workspace = true, features = ["test-support"] }

View File

@@ -1,9 +1,9 @@
use anyhow::Result;
use call::report_call_event_for_channel;
use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId, ChannelStore};
use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelStore};
use client::{
proto::{self, PeerId},
Collaborator, ParticipantIndex,
ChannelId, Collaborator, ParticipantIndex,
};
use collections::HashMap;
use editor::{
@@ -454,7 +454,7 @@ impl FollowableItem for ChannelView {
Some(proto::view::Variant::ChannelView(
proto::view::ChannelView {
channel_id: channel_buffer.channel_id,
channel_id: channel_buffer.channel_id.0,
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(cx)
{
@@ -480,7 +480,8 @@ impl FollowableItem for ChannelView {
unreachable!()
};
let open = ChannelView::open_in_pane(state.channel_id, None, pane, workspace, cx);
let open =
ChannelView::open_in_pane(ChannelId(state.channel_id), None, pane, workspace, cx);
Some(cx.spawn(|mut cx| async move {
let this = open.await?;

View File

@@ -2,14 +2,14 @@ use crate::{collab_panel, ChatPanelSettings};
use anyhow::Result;
use call::{room, ActiveCall};
use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore};
use client::Client;
use client::{ChannelId, Client};
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use gpui::{
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, CursorStyle,
DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, StyledText,
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, ClipboardItem,
CursorStyle, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle,
FontWeight, HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, StyledText,
Subscription, Task, View, ViewContext, VisualContext, WeakView,
};
use language::LanguageRegistry;
@@ -169,7 +169,7 @@ impl ChatPanel {
})
}
pub fn channel_id(&self, cx: &AppContext) -> Option<u64> {
pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
self.active_chat
.as_ref()
.map(|(chat, _)| chat.read(cx).channel_id)
@@ -488,11 +488,10 @@ impl ChatPanel {
.child(Label::new(message.sender.github_login.clone())),
)
.child(
Label::new(format_timestamp(
Label::new(time_format::format_localized_timestamp(
OffsetDateTime::now_utc(),
message.timestamp,
self.local_timezone,
None,
))
.size(LabelSize::Small)
.color(Color::Muted),
@@ -629,6 +628,20 @@ impl ChatPanel {
})
}),
)
.entry(
"Copy message text",
None,
cx.handler_for(&this, move |this, cx| {
this.active_chat().map(|active_chat| {
if let Some(message) =
active_chat.read(cx).find_loaded_message(message_id)
{
let text = message.body.clone();
cx.write_to_clipboard(ClipboardItem::new(text))
}
});
}),
)
.when(can_delete_message, move |menu| {
menu.entry(
"Delete message",
@@ -697,7 +710,7 @@ impl ChatPanel {
pub fn select_channel(
&mut self,
selected_channel_id: u64,
selected_channel_id: ChannelId,
scroll_to_message_id: Option<u64>,
cx: &mut ViewContext<ChatPanel>,
) -> Task<Result<()>> {
@@ -959,94 +972,13 @@ impl Panel for ChatPanel {
impl EventEmitter<PanelEvent> for ChatPanel {}
fn is_12_hour_clock(locale: String) -> bool {
[
"es-MX", "es-CO", "es-SV", "es-NI",
"es-HN", // Mexico, Colombia, El Salvador, Nicaragua, Honduras
"en-US", "en-CA", "en-AU", "en-NZ", // U.S, Canada, Australia, New Zealand
"ar-SA", "ar-EG", "ar-JO", // Saudi Arabia, Egypt, Jordan
"en-IN", "hi-IN", // India, Hindu
"en-PK", "ur-PK", // Pakistan, Urdu
"en-PH", "fil-PH", // Philippines, Filipino
"bn-BD", "ccp-BD", // Bangladesh, Chakma
"en-IE", "ga-IE", // Ireland, Irish
"en-MY", "ms-MY", // Malaysia, Malay
]
.contains(&locale.as_str())
}
fn format_timestamp(
reference: OffsetDateTime,
timestamp: OffsetDateTime,
timezone: UtcOffset,
locale: Option<String>,
) -> String {
let locale = match locale {
Some(locale) => locale,
None => sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")),
};
let timestamp_local = timestamp.to_offset(timezone);
let timestamp_local_hour = timestamp_local.hour();
let timestamp_local_minute = timestamp_local.minute();
let (hour, meridiem) = if is_12_hour_clock(locale) {
let meridiem = if timestamp_local_hour >= 12 {
"pm"
} else {
"am"
};
let hour_12 = match timestamp_local_hour {
0 => 12, // Midnight
13..=23 => timestamp_local_hour - 12, // PM hours
_ => timestamp_local_hour, // AM hours
};
(hour_12, Some(meridiem))
} else {
(timestamp_local_hour, None)
};
let formatted_time = match meridiem {
Some(meridiem) => format!("{:02}:{:02} {}", hour, timestamp_local_minute, meridiem),
None => format!("{:02}:{:02}", hour, timestamp_local_minute),
};
let reference_local = reference.to_offset(timezone);
let reference_local_date = reference_local.date();
let timestamp_local_date = timestamp_local.date();
if timestamp_local_date == reference_local_date {
return formatted_time;
}
if reference_local_date.previous_day() == Some(timestamp_local_date) {
return format!("yesterday at {}", formatted_time);
}
match meridiem {
Some(_) => format!(
"{:02}/{:02}/{}",
timestamp_local_date.month() as u32,
timestamp_local_date.day(),
timestamp_local_date.year()
),
None => format!(
"{:02}/{:02}/{}",
timestamp_local_date.day(),
timestamp_local_date.month() as u32,
timestamp_local_date.year()
),
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::HighlightStyle;
use pretty_assertions::assert_eq;
use rich_text::Highlight;
use time::{Date, OffsetDateTime, Time, UtcOffset};
use time::OffsetDateTime;
use util::test::marked_text_ranges;
#[gpui::test]
@@ -1197,150 +1129,4 @@ mod tests {
]
);
}
#[test]
fn test_format_locale() {
let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
assert_eq!(
format_timestamp(
reference,
timestamp,
test_timezone(),
Some(String::from("en-GB"))
),
"15:30"
);
}
#[test]
fn test_format_today() {
let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
assert_eq!(
format_timestamp(
reference,
timestamp,
test_timezone(),
Some(String::from("en-US"))
),
"03:30 pm"
);
}
#[test]
fn test_format_yesterday() {
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
let timestamp = create_offset_datetime(1990, 4, 11, 9, 0, 0);
assert_eq!(
format_timestamp(
reference,
timestamp,
test_timezone(),
Some(String::from("en-US"))
),
"yesterday at 09:00 am"
);
}
#[test]
fn test_format_yesterday_less_than_24_hours_ago() {
let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
let timestamp = create_offset_datetime(1990, 4, 11, 20, 0, 0);
assert_eq!(
format_timestamp(
reference,
timestamp,
test_timezone(),
Some(String::from("en-US"))
),
"yesterday at 08:00 pm"
);
}
#[test]
fn test_format_yesterday_more_than_24_hours_ago() {
let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
let timestamp = create_offset_datetime(1990, 4, 11, 18, 0, 0);
assert_eq!(
format_timestamp(
reference,
timestamp,
test_timezone(),
Some(String::from("en-US"))
),
"yesterday at 06:00 pm"
);
}
#[test]
fn test_format_yesterday_over_midnight() {
let reference = create_offset_datetime(1990, 4, 12, 0, 5, 0);
let timestamp = create_offset_datetime(1990, 4, 11, 23, 55, 0);
assert_eq!(
format_timestamp(
reference,
timestamp,
test_timezone(),
Some(String::from("en-US"))
),
"yesterday at 11:55 pm"
);
}
#[test]
fn test_format_yesterday_over_month() {
let reference = create_offset_datetime(1990, 4, 2, 9, 0, 0);
let timestamp = create_offset_datetime(1990, 4, 1, 20, 0, 0);
assert_eq!(
format_timestamp(
reference,
timestamp,
test_timezone(),
Some(String::from("en-US"))
),
"yesterday at 08:00 pm"
);
}
#[test]
fn test_format_before_yesterday() {
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
let timestamp = create_offset_datetime(1990, 4, 10, 20, 20, 0);
assert_eq!(
format_timestamp(
reference,
timestamp,
test_timezone(),
Some(String::from("en-US"))
),
"04/10/1990"
);
}
fn test_timezone() -> UtcOffset {
UtcOffset::from_hms(0, 0, 0).expect("Valid timezone offset")
}
fn create_offset_datetime(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
) -> OffsetDateTime {
let date =
Date::from_calendar_date(year, time::Month::try_from(month).unwrap(), day).unwrap();
let time = Time::from_hms(hour, minute, second).unwrap();
date.with_time(time).assume_utc() // Assume UTC for simplicity
}
}

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams};
use client::UserId;
use channel::{ChannelMembership, ChannelStore, MessageParams};
use client::{ChannelId, UserId};
use collections::{HashMap, HashSet};
use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
use fuzzy::StringMatchCandidate;
@@ -131,7 +131,7 @@ impl MessageEditor {
pub fn set_channel(
&mut self,
channel_id: u64,
channel_id: ChannelId,
channel_name: Option<SharedString>,
cx: &mut ViewContext<Self>,
) {

View File

@@ -7,8 +7,8 @@ use crate::{
CollaborationPanelSettings,
};
use call::ActiveCall;
use channel::{Channel, ChannelEvent, ChannelId, ChannelStore};
use client::{Client, Contact, User, UserStore};
use channel::{Channel, ChannelEvent, ChannelStore, HostedProjectId};
use client::{ChannelId, Client, Contact, User, UserStore};
use contact_finder::ContactFinder;
use db::kvp::KEY_VALUE_STORE;
use editor::{Editor, EditorElement, EditorStyle};
@@ -184,6 +184,10 @@ enum ListEntry {
ChannelEditor {
depth: usize,
},
HostedProject {
id: HostedProjectId,
name: SharedString,
},
Contact {
contact: Arc<Contact>,
calling: bool,
@@ -326,7 +330,10 @@ impl CollabPanel {
panel.width = serialized_panel.width;
panel.collapsed_channels = serialized_panel
.collapsed_channels
.unwrap_or_else(|| Vec::new());
.unwrap_or_else(|| Vec::new())
.iter()
.map(|cid| ChannelId(*cid))
.collect();
cx.notify();
});
}
@@ -344,7 +351,9 @@ impl CollabPanel {
COLLABORATION_PANEL_KEY.into(),
serde_json::to_string(&SerializedCollabPanel {
width,
collapsed_channels: Some(collapsed_channels),
collapsed_channels: Some(
collapsed_channels.iter().map(|cid| cid.0).collect(),
),
})?,
)
.await?;
@@ -563,6 +572,7 @@ impl CollabPanel {
}
}
let hosted_projects = channel_store.projects_for_id(channel.id);
let has_children = channel_store
.channel_at_index(mat.candidate_id + 1)
.map_or(false, |next_channel| {
@@ -596,6 +606,10 @@ impl CollabPanel {
});
}
}
for (name, id) in hosted_projects {
self.entries.push(ListEntry::HostedProject { id, name })
}
}
}
@@ -1023,6 +1037,33 @@ impl CollabPanel {
.tooltip(move |cx| Tooltip::text("Open Chat", cx))
}
fn render_channel_project(
&self,
id: HostedProjectId,
name: &SharedString,
is_selected: bool,
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
ListItem::new(ElementId::NamedInteger(
"channel-project".into(),
id.0 as usize,
))
.indent_level(2)
.indent_step_size(px(20.))
.selected(is_selected)
.on_click(cx.listener(move |_this, _, _cx| {
// todo!()
}))
.start_slot(
h_flex()
.relative()
.gap_1()
.child(IconButton::new(0, IconName::FileTree)),
)
.child(Label::new(name.clone()))
.tooltip(move |cx| Tooltip::text("Open Project", cx))
}
fn has_subchannels(&self, ix: usize) -> bool {
self.entries.get(ix).map_or(false, |entry| {
if let ListEntry::Channel { has_children, .. } = entry {
@@ -1486,6 +1527,12 @@ impl CollabPanel {
ListEntry::ChannelChat { channel_id } => {
self.join_channel_chat(*channel_id, cx)
}
ListEntry::HostedProject {
id: _id,
name: _name,
} => {
// todo!()
}
ListEntry::OutgoingRequest(_) => {}
ListEntry::ChannelEditor { .. } => {}
@@ -1923,7 +1970,7 @@ impl CollabPanel {
fn respond_to_channel_invite(
&mut self,
channel_id: u64,
channel_id: ChannelId,
accept: bool,
cx: &mut ViewContext<Self>,
) {
@@ -1942,7 +1989,7 @@ impl CollabPanel {
.detach_and_prompt_err("Call failed", cx, |_, _| None);
}
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
fn join_channel(&self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
@@ -2089,6 +2136,10 @@ impl CollabPanel {
ListEntry::ChannelChat { channel_id } => self
.render_channel_chat(*channel_id, is_selected, cx)
.into_any_element(),
ListEntry::HostedProject { id, name } => self
.render_channel_project(*id, name, is_selected, cx)
.into_any_element(),
}
}
@@ -2405,7 +2456,7 @@ impl CollabPanel {
.tooltip(|cx| Tooltip::text("Accept invite", cx)),
];
ListItem::new(("channel-invite", channel.id as usize))
ListItem::new(("channel-invite", channel.id.0 as usize))
.selected(is_selected)
.child(
h_flex()
@@ -2497,7 +2548,7 @@ impl CollabPanel {
div()
.h_6()
.id(channel_id as usize)
.id(channel_id.0 as usize)
.group("")
.flex()
.w_full()
@@ -2525,7 +2576,7 @@ impl CollabPanel {
this.move_channel(dragged_channel.id, channel_id, cx);
}))
.child(
ListItem::new(channel_id as usize)
ListItem::new(channel_id.0 as usize)
// Add one level of depth for the disclosure arrow.
.indent_level(depth + 1)
.indent_step_size(px(20.))
@@ -2572,7 +2623,7 @@ impl CollabPanel {
)
.child(
h_flex()
.id(channel_id as usize)
.id(channel_id.0 as usize)
.child(Label::new(channel.name.clone()))
.children(face_pile.map(|face_pile| face_pile.p_1())),
),
@@ -2826,6 +2877,11 @@ impl PartialEq for ListEntry {
return channel_1.id == channel_2.id;
}
}
ListEntry::HostedProject { id, .. } => {
if let ListEntry::HostedProject { id: other_id, .. } = other {
return id == other_id;
}
}
ListEntry::ChannelNotes { channel_id } => {
if let ListEntry::ChannelNotes {
channel_id: other_id,

View File

@@ -1,7 +1,7 @@
use channel::{ChannelId, ChannelMembership, ChannelStore};
use channel::{ChannelMembership, ChannelStore};
use client::{
proto::{self, ChannelRole, ChannelVisibility},
User, UserId, UserStore,
ChannelId, User, UserId, UserStore,
};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
@@ -266,7 +266,7 @@ pub struct ChannelModalDelegate {
impl PickerDelegate for ChannelModalDelegate {
type ListItem = ListItem;
fn placeholder_text(&self) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search collaborator by username...".into()
}

View File

@@ -84,7 +84,7 @@ impl PickerDelegate for ContactFinderDelegate {
self.selected_index = ix;
}
fn placeholder_text(&self) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search collaborator by username...".into()
}

View File

@@ -1,7 +1,7 @@
use crate::{chat_panel::ChatPanel, NotificationPanelSettings};
use anyhow::Result;
use channel::ChannelStore;
use client::{Client, Notification, User, UserStore};
use client::{ChannelId, Client, Notification, User, UserStore};
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use futures::StreamExt;
@@ -357,7 +357,7 @@ impl NotificationPanel {
"{} invited you to join the #{channel_name} channel",
inviter.github_login
),
needs_response: channel_store.has_channel_invitation(channel_id),
needs_response: channel_store.has_channel_invitation(ChannelId(channel_id)),
actor: Some(inviter),
can_navigate: false,
})
@@ -368,7 +368,7 @@ impl NotificationPanel {
message_id,
} => {
let sender = user_store.get_cached_user(sender_id)?;
let channel = channel_store.channel_for_id(channel_id)?;
let channel = channel_store.channel_for_id(ChannelId(channel_id))?;
let message = self
.notification_store
.read(cx)
@@ -432,7 +432,7 @@ impl NotificationPanel {
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
panel.update(cx, |panel, cx| {
panel
.select_channel(channel_id, Some(message_id), cx)
.select_channel(ChannelId(channel_id), Some(message_id), cx)
.detach_and_log_err(cx);
});
}
@@ -454,7 +454,7 @@ impl NotificationPanel {
panel.is_scrolled_to_bottom()
&& panel
.active_chat()
.map_or(false, |chat| chat.read(cx).channel_id == *channel_id)
.map_or(false, |chat| chat.read(cx).channel_id.0 == *channel_id)
} else {
false
};

View File

@@ -7,13 +7,10 @@ license = "GPL-3.0-or-later"
[features]
default = []
stories = ["dep:itertools", "dep:story"]
[lib]
path = "src/color.rs"
doctest = true
[dependencies]
itertools = { version = "0.11.0", optional = true }
palette = "0.7.3"
story = { workspace = true, optional = true }
palette.workspace = true

View File

@@ -10,15 +10,13 @@ path = "src/command_palette.rs"
doctest = false
[dependencies]
anyhow.workspace = true
client.workspace = true
collections.workspace = true
# HACK: We're only depending on `copilot` here for `CommandPaletteFilter`. See the attached comment on that type.
copilot.workspace = true
editor.workspace = true
command_palette_hooks.workspace = true
fuzzy.workspace = true
gpui.workspace = true
picker.workspace = true
postage.workspace = true
project.workspace = true
release_channel.workspace = true
serde.workspace = true
@@ -28,7 +26,6 @@ ui.workspace = true
util.workspace = true
workspace.workspace = true
zed_actions.workspace = true
postage.workspace = true
[dev-dependencies]
ctor.workspace = true

View File

@@ -6,7 +6,9 @@ use std::{
use client::telemetry::Telemetry;
use collections::HashMap;
use copilot::CommandPaletteFilter;
use command_palette_hooks::{
CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor,
};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
@@ -101,18 +103,6 @@ impl Render for CommandPalette {
}
}
pub struct CommandPaletteInterceptor(
pub Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>,
);
impl Global for CommandPaletteInterceptor {}
pub struct CommandInterceptResult {
pub action: Box<dyn Action>,
pub string: String,
pub positions: Vec<usize>,
}
pub struct CommandPaletteDelegate {
command_palette: WeakView<CommandPalette>,
all_commands: Vec<Command>,
@@ -231,7 +221,7 @@ impl CommandPaletteDelegate {
impl PickerDelegate for CommandPaletteDelegate {
type ListItem = ListItem;
fn placeholder_text(&self) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Execute a command...".into()
}

View File

@@ -0,0 +1,14 @@
[package]
name = "command_palette_hooks"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/command_palette_hooks.rs"
doctest = false
[dependencies]
collections.workspace = true
gpui.workspace = true

View File

@@ -0,0 +1,24 @@
use std::any::TypeId;
use collections::HashSet;
use gpui::{Action, AppContext, Global};
#[derive(Default)]
pub struct CommandPaletteFilter {
pub hidden_namespaces: HashSet<&'static str>,
pub hidden_action_types: HashSet<TypeId>,
}
impl Global for CommandPaletteFilter {}
pub struct CommandPaletteInterceptor(
pub Box<dyn Fn(&str, &AppContext) -> Option<CommandInterceptResult>>,
);
impl Global for CommandPaletteInterceptor {}
pub struct CommandInterceptResult {
pub action: Box<dyn Action>,
pub string: String,
pub positions: Vec<usize>,
}

View File

@@ -22,20 +22,18 @@ test-support = [
[dependencies]
anyhow.workspace = true
async-compression.workspace = true
async-tar = "0.4.2"
async-tar.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
serde.workspace = true
serde_derive.workspace = true
settings.workspace = true
smol.workspace = true
theme.workspace = true
util.workspace = true
[target.'cfg(windows)'.dependencies]

View File

@@ -3,6 +3,7 @@ use anyhow::{anyhow, Context as _, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use collections::{HashMap, HashSet};
use command_palette_hooks::CommandPaletteFilter;
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
use gpui::{
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
@@ -32,17 +33,6 @@ use util::{
ResultExt,
};
// HACK: This type is only defined in `copilot` since it is the earliest ancestor
// of the crates that use it.
//
// This is not great. Let's find a better place for it to live.
#[derive(Default)]
pub struct CommandPaletteFilter {
pub hidden_namespaces: HashSet<&'static str>,
pub hidden_action_types: HashSet<TypeId>,
}
impl Global for CommandPaletteFilter {}
actions!(
copilot,
[

View File

@@ -14,12 +14,9 @@ anyhow.workspace = true
copilot.workspace = true
editor.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
settings.workspace = true
smol.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -14,22 +14,16 @@ test-support = []
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
collections.workspace = true
gpui.workspace = true
indoc.workspace = true
lazy_static.workspace = true
log.workspace = true
parking_lot.workspace = true
release_channel.workspace = true
serde.workspace = true
serde_derive.workspace = true
smol.workspace = true
sqlez.workspace = true
sqlez_macros.workspace = true
util.workspace = true
[dev-dependencies]
env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
tempfile.workspace = true

View File

@@ -18,13 +18,10 @@ gpui.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
postage.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
settings.workspace = true
smallvec.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true

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