Compare commits

..

147 Commits

Author SHA1 Message Date
Antonio Scandurra
d5502090f8 Send only missing operations from client to server 2023-08-06 12:22:22 +02:00
Antonio Scandurra
8d7b37b743 Revisit synchronization routine to perform fewer roundtrips
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-08-04 19:42:02 +02:00
Antonio Scandurra
f1fe0007e3 Log roundtrips only when RUST_LOG=debug 2023-08-04 16:12:54 +02:00
Antonio Scandurra
d66af5c4d2 Avoid talking about trees when subdividing ranges 2023-08-04 15:49:08 +02:00
Antonio Scandurra
dc049ca7d2 💄 2023-08-04 15:01:39 +02:00
Antonio Scandurra
6251399d0a Add randomized test for synchronization 2023-08-04 11:49:14 +02:00
Antonio Scandurra
e979f753f2 Cleanup 2023-08-04 11:28:31 +02:00
Antonio Scandurra
80f9b553c7 Avoid mutating the server as we synchronize 2023-08-04 11:26:43 +02:00
Antonio Scandurra
bd0a9eb704 Test various depths, bases and min_operations in sync 2023-08-04 08:54:38 +02:00
Antonio Scandurra
52beac9fa5 💄 2023-08-03 19:33:38 +02:00
Antonio Scandurra
c603ef3f3d Get an initial implementation of sync working
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-08-03 18:57:16 +02:00
Antonio Scandurra
eb79ea6ee5 WIP
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-08-03 18:09:42 +02:00
Antonio Scandurra
26cb5e316d WIP 2023-08-03 15:22:23 +02:00
Nathan Sobo
974d07d304 Start on a different sync approach
We created a function range_digests, which creates a vector of digests for
a tree of nested ranges up to a certain depth or down to a certain range size.

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
2023-08-02 11:41:35 -06:00
Antonio Scandurra
d8f6dc3749 📝 2023-08-02 13:45:25 +02:00
Antonio Scandurra
3ebe2f97cc WIP: re-create a Checkout when reloading repo if it was published 2023-08-02 13:03:02 +02:00
Antonio Scandurra
400105cf18 Add todo 2023-08-02 09:51:12 +02:00
Antonio Scandurra
ba6f1d7281 Save dirty revisions in the background
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-08-01 17:14:08 +02:00
Antonio Scandurra
86b0ed8fda Implement Revision::load_documents
Co-Authored-By: Julia Risley <julia@zed.dev>
2023-08-01 16:18:57 +02:00
Antonio Scandurra
50f507e38e Maintain ref counts for document handles 2023-08-01 15:07:32 +02:00
Antonio Scandurra
7fbe5910b9 Remove DbSnapshot 2023-08-01 10:05:06 +02:00
Antonio Scandurra
7f49ca4adb Simplify apply_operations 2023-08-01 09:48:17 +02:00
Antonio Scandurra
b751fe9e98 💄 2023-08-01 09:21:55 +02:00
Antonio Scandurra
85c9b30e9f Experiment with a RevisionCache struct 2023-07-31 16:32:10 +02:00
Antonio Scandurra
82c022adb3 Avoid descending into trees that have already been fully loaded 2023-07-31 15:18:05 +02:00
Antonio Scandurra
7494fdc738 Remove async_recursion from crdb 2023-07-31 14:20:01 +02:00
Antonio Scandurra
1919a5bb63 Use Arc<str> as the key type for RepoSnapshot::branch_ids_by_name 2023-07-31 14:16:20 +02:00
Antonio Scandurra
308c29d377 Remove Default constraint from btree::Map keys 2023-07-31 14:14:09 +02:00
Antonio Scandurra
eacb37e200 Fix basic collaboration test 2023-07-31 13:10:50 +02:00
Antonio Scandurra
083c857fa0 Rename traverse to rewind 2023-07-31 12:53:40 +02:00
Antonio Scandurra
1d0ff4bf8e Optimize revision traversal and reconstruction 2023-07-31 12:45:34 +02:00
Antonio Scandurra
9cbe3cca9a WIP 2023-07-31 11:57:38 +02:00
Antonio Scandurra
ce17cd83cf Redesign history traversal 2023-07-31 11:39:41 +02:00
Antonio Scandurra
05ec6b89c2 WIP 2023-07-30 13:45:31 +02:00
Antonio Scandurra
93701f9c5d Avoid incrementing operation id if it's unnecessary for causality 2023-07-30 12:15:34 +02:00
Antonio Scandurra
083bfc6cbc Reify a History struct
This has to be integrated still.
2023-07-30 11:57:02 +02:00
Antonio Scandurra
a084a93ef0 Make apply_operations asynchronous 2023-07-29 15:36:52 +02:00
Antonio Scandurra
84bf6c5a16 Use pointer equality to detect changes to the repo snapshot 2023-07-29 11:54:13 +02:00
Antonio Scandurra
e99f82f855 Start on loading/saving revisions 2023-07-29 11:54:02 +02:00
Antonio Scandurra
ba3353d127 Allow loading and saving of ropes
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-07-28 19:58:31 +02:00
Antonio Scandurra
2887c4674d Load fewer items in randomized tests for partially loaded trees 2023-07-28 19:57:03 +02:00
Antonio Scandurra
22e8a67498 Load branch from kv store if necessary
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-07-28 19:14:58 +02:00
Antonio Scandurra
4848ea8e62 Spawn a background task to save repo snapshots 2023-07-28 13:08:33 +02:00
Antonio Scandurra
3349f2147a Force all changes to a repository to take place via Repo::update 2023-07-28 12:45:51 +02:00
Antonio Scandurra
103d5293be Start on loading RepoSnapshots 2023-07-28 12:34:23 +02:00
Antonio Scandurra
1046b60fd2 Avoid cycling dependency between Client and Checkout 2023-07-28 11:15:49 +02:00
Antonio Scandurra
d5932717f2 WIP 2023-07-27 19:41:56 +02:00
Antonio Scandurra
8eeaacdf0a Pass a KvStore to Client and Server
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-07-27 18:03:55 +02:00
Antonio Scandurra
2de48f96d2 Simplify KvStore trait
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-07-27 17:49:16 +02:00
Antonio Scandurra
ba42db7069 Fix test for slice and summary to account for partial trees 2023-07-27 13:01:01 +02:00
Antonio Scandurra
02946e1e8f Merge branch 'main' into crdb 2023-07-27 12:41:12 +02:00
Antonio Scandurra
32c8eb2612 Correctly maintain cursor stack position when ascending in seek 2023-07-27 12:33:13 +02:00
Antonio Scandurra
d277e214bd Re-enable randomized tests for cursor movement in partially-loaded trees 2023-07-27 12:32:07 +02:00
Antonio Scandurra
cbc5dc04ef Avoid pushing empty leaves into the tree 2023-07-27 12:28:26 +02:00
Antonio Scandurra
198be71b6c Fix panic due to accidentally descending into unloaded node in cursor 2023-07-27 09:53:31 +02:00
Antonio Scandurra
5eede853f6 Don't call push_tree_recursive on leaf when appending unloaded node 2023-07-27 09:52:17 +02:00
Antonio Scandurra
7282629f93 Ensure that loading the full tree is equivalent to the reference items 2023-07-26 18:05:06 +02:00
Antonio Scandurra
517591bced Load the relevant nodes before splicing in randomized test 2023-07-26 18:02:04 +02:00
Antonio Scandurra
4c5e248693 Start on a randomized test for persistence 2023-07-26 17:12:09 +02:00
Antonio Scandurra
b8066d444a Allow keeping nodes in the tree when pruning 2023-07-26 17:11:58 +02:00
Antonio Scandurra
f8cf74f9e8 Introduce InMemoryKv to test persistence 2023-07-26 16:50:28 +02:00
Antonio Scandurra
97cdad7f1a Implement Sequence::prune 2023-07-26 16:41:45 +02:00
Antonio Scandurra
1b803e498f Implement Sequence::load to selectively load subtrees 2023-07-26 15:57:49 +02:00
Antonio Scandurra
7a851d42ad Add a Sequence::from_root function 2023-07-26 15:41:32 +02:00
Antonio Scandurra
18412c3a06 Introduce ChildTree to identify loaded or unloaded subtrees 2023-07-26 13:41:31 +02:00
Antonio Scandurra
6e8b207104 Implement Sequence::save 2023-07-26 10:09:24 +02:00
Antonio Scandurra
3c3bf793e3 Rename SeekTarget::cmp to SeekTarget::seek_cmp
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-07-25 18:07:01 +02:00
Antonio Scandurra
ca57c1839c Fork crdb-specific copies of rope and sum_tree (renamed to btree)
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-07-25 18:04:28 +02:00
Antonio Scandurra
f3928c8c26 Allow cloning of repos to fail
Co-Authored-By: Julia Risley <julia@zed.dev>
2023-07-25 16:31:16 +02:00
Antonio Scandurra
94f7ddbcb2 Allow saving and loading of test plans for randomized test in CRDB
Co-Authored-By: Julia Risley <julia@zed.dev>
2023-07-25 16:20:01 +02:00
Antonio Scandurra
369d85b82e Print RepoId as a number in tests
We are not going to generate repository ids randomly, so we can
take advantage of that and print a nicer representation of
the repository id.

Co-Authored-By: Julia Risley <julia@zed.dev>
2023-07-25 15:33:14 +02:00
Antonio Scandurra
cbdb83fb43 Extract test operation generation and application into methods
Co-Authored-By: Julia Risley <julia@zed.dev>
2023-07-25 15:25:54 +02:00
Antonio Scandurra
309e307fa8 Remove warning
Co-Authored-By: Julia Risley <julia@zed.dev>
2023-07-25 15:25:40 +02:00
Antonio Scandurra
e9342e1b47 Deterministically assign repo IDs in tests 2023-07-25 15:10:16 +02:00
Antonio Scandurra
edc63ee3ff Avoid panicking when previous stack trace doesn't have the same length 2023-07-25 15:10:16 +02:00
Antonio Scandurra
88829dfe21 Use test-support feature for collections in dev 2023-07-25 15:10:13 +02:00
Antonio Scandurra
1ada3722e5 Avoid creating empty document fragments 2023-07-25 12:40:50 +02:00
Antonio Scandurra
1e644b7a45 Keep insertion fragments up-to-date 2023-07-25 12:02:13 +02:00
Antonio Scandurra
eb36bc5b62 Use preceding fragment when edit starts at the beginning of a fragment 2023-07-25 11:27:43 +02:00
Antonio Scandurra
31f84cef14 Broadcast operations when synchronizing with the server 2023-07-25 10:05:59 +02:00
Antonio Scandurra
9358958b1f Observe lamport timestamps when applying operations 2023-07-25 09:58:38 +02:00
Antonio Scandurra
bbe126818f Avoid skipping sentinel as it'll happen organically when applying edit 2023-07-25 09:51:56 +02:00
Antonio Scandurra
7d02462abd Broadcast operations only after server acknowledges them
There was a test failure that was caused by the following sequence of
events:

1. Client 1 generates an operation and broadcasted it.
2. Client 2 joins, but it was too late to receive the operation from the network.
3. Client 2 synchronizes with the server, but it was too early to receive
operations from the server.
4. Client 1 finally sends the operation to the server.
2023-07-24 17:53:01 +02:00
Antonio Scandurra
fa48d59de4 Generate edit operation correctly when range is at the end of fragment 2023-07-24 16:55:17 +02:00
Antonio Scandurra
439f6ba616 Use end fragment to determine the new insertion's fragment location 2023-07-24 16:46:43 +02:00
Antonio Scandurra
729ea89fef Create room before inserting repo into database
This ensures that clones of the repo will either see the repo and
be able to join the room, or not see the repo at all.
2023-07-24 16:40:09 +02:00
Antonio Scandurra
8c5eab6933 Assert branches are the same between client and server 2023-07-24 16:24:43 +02:00
Antonio Scandurra
5118218a9c Start on a randomized test for CRDB 2023-07-24 15:16:57 +02:00
Antonio Scandurra
3dee2faa44 Assign a valid location after intersecting fragments 2023-07-23 18:51:21 +02:00
Antonio Scandurra
c1f72328ff Avoid taking fragment when not fully consuming it 2023-07-23 18:50:57 +02:00
Antonio Scandurra
18b32304e2 Serialize message envelope instead of directly serializing operation 2023-07-23 18:49:54 +02:00
Nathan Sobo
3920bff5e0 WIP: Add a test for relaying edits between clients
Still not passing, but added:

- Connect the network room before creating checkouts.
- Blow up in tests when there are errors applying operations on the server.
- Avoid overshooting the last fragment when in fragment_locations.
2023-07-21 22:21:53 -06:00
Nathan Sobo
b7e79d5241 Get basic test of repository cloning working 2023-07-21 14:31:11 -06:00
Antonio Scandurra
a81fde36e0 WIP 2023-07-21 19:52:27 +02:00
Antonio Scandurra
60f5dca222 Rework operations::Edit::apply
It still doesn't work but it's in a coherent state now at least.
2023-07-21 19:32:26 +02:00
Antonio Scandurra
45037eb7a0 WIP 2023-07-21 16:55:31 +02:00
Antonio Scandurra
4e085a6f2d Apply operations only via revision 2023-07-21 12:00:38 +02:00
Antonio Scandurra
517d073806 Remove unused use statement 2023-07-21 11:28:19 +02:00
Antonio Scandurra
cbc4b90df8 Apply missing operations to the common ancestor revision
Interestingly, when applying an `Edit` operation, we need to know the
parent revision, which makes the `revision` method recursive.
2023-07-21 11:19:00 +02:00
Antonio Scandurra
1ddd3971d8 Use the new RepoSnapshot::revision method 2023-07-21 10:50:00 +02:00
Antonio Scandurra
939eaaa973 💄 2023-07-21 10:35:19 +02:00
Antonio Scandurra
baf06043cb Reify a RevisionId struct 2023-07-21 10:30:28 +02:00
Antonio Scandurra
12acc7418e Refine finding of common ancestor revision and find missing operations 2023-07-21 09:44:17 +02:00
Nathan Sobo
02ea88aa51 Start on a RepoSnapshot::revision method to find/construct a revision from an id 2023-07-20 21:38:53 -06:00
Antonio Scandurra
9d04228853 Add document_id to Edit operation 2023-07-20 17:45:31 +02:00
Antonio Scandurra
56b1aaa6cb WIP: start on Edit::apply 2023-07-20 14:23:51 +02:00
Antonio Scandurra
e771ede830 Test accessing branches and documents from a different client 2023-07-20 14:12:34 +02:00
Antonio Scandurra
55232486d7 Remove ancestors of the new operation from the head revision 2023-07-20 13:43:03 +02:00
Antonio Scandurra
8c1973c5a2 Make CreateDocument infallible 2023-07-20 13:12:44 +02:00
Antonio Scandurra
287dfc0460 Take a revision instead of a repo snapshot when applying branch ops 2023-07-20 13:11:07 +02:00
Antonio Scandurra
4a8556cd81 Save operation in the DAG only when we're able to apply it 2023-07-20 12:56:46 +02:00
Antonio Scandurra
900deaab50 Apply CreateBranch and CreateDocument operations 2023-07-20 11:56:22 +02:00
Antonio Scandurra
30bac17749 Save deferred operation ids only and store operations separately 2023-07-20 09:17:55 +02:00
Nathan Sobo
d675f10447 Flush deferred operations 2023-07-19 22:37:56 -06:00
Nathan Sobo
b774d83a50 Map sum_tree types to crdb::btree::{Map, Set, Sequence} 2023-07-19 15:49:52 -06:00
Nathan Sobo
b636398093 WIP 2023-07-19 15:38:16 -06:00
Nathan Sobo
88c46e091e Defer operations when their parent is missing 2023-07-19 15:28:22 -06:00
Antonio Scandurra
8003e84d11 WIP 2023-07-19 19:41:12 +02:00
Antonio Scandurra
2c27e875e5 Move apply_operations to Repo
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-07-19 19:40:58 +02:00
Antonio Scandurra
9e03e9d6df Sketch in a bi-directional sync (not yet tested) 2023-07-19 10:25:18 -06:00
Antonio Scandurra
27b06c1d09 Implement clone_repo and start handling synchronization requests 2023-07-19 16:04:44 +02:00
Antonio Scandurra
e6b7bbee25 Fix errors in message/request deserialization code 2023-07-19 10:47:06 +02:00
Nathan Sobo
afb0329914 Rework networking code and serialization
Tests aren't passing yet, but I need to wind down for the night.

Decide to try out `serde_bare`.

From GPT: `serde_bare` is a Rust library that provides a fast and efficient Serializer and
Deserializer for the "BARE" (Basic Ad-hoc Runtime Encoding) data format. This
format focuses on being simple, small, fast and working well with anonymous
types, making it useful for sending small ad-hoc messages between systems.

To type messages on the wire, I'm wrapping them in "envelope" enums. These envelopes
then implement an unwrap method that returns a Box<dyn Any>, and we require messages
to be Into their envelope type. It's some boilerplate, but I ultimately like leaning
on Rust more than an external schema, which adds complexity.

I also reworked network abstraction to be just in terms of bytes. Typed handlers
are moved into network-neutral code. It's still broken, but hopefully the direction
is clear.

Heads up: I turned on the `backtrace` feature for `anyhow`.
2023-07-18 23:40:17 -06:00
Nathan Sobo
8deafe90fc Start on sync module with FibDescending iterator 2023-07-18 16:52:55 -06:00
Antonio Scandurra
0ae7a9974f Wire up creation and joining of rooms
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-07-18 19:30:27 +02:00
Antonio Scandurra
be7d4d6ea9 WIP 2023-07-18 19:10:20 +02:00
Antonio Scandurra
00b0189660 Simulate request/responses and room broadcasts with random network delay
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-07-18 18:33:08 +02:00
Antonio Scandurra
5267c6d2cb WIP 2023-07-18 15:11:35 +02:00
Antonio Scandurra
6205ac27a5 WIP 2023-07-18 13:16:32 +02:00
Antonio Scandurra
f4d71b2b24 WIP
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-06-30 18:03:43 +02:00
Antonio Scandurra
cdeabcab4e Introduce branches and revisions
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-06-30 10:59:23 +02:00
Antonio Scandurra
4070f67f3b Turn visible into a method on DocumentFragment 2023-06-30 09:21:32 +02:00
Antonio Scandurra
4bc1b57d8f WIP 2023-06-29 12:06:47 +02:00
Antonio Scandurra
4c672c4e5f Add a basic test that passes when editing two documents
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-06-29 11:23:02 +02:00
Antonio Scandurra
e6bd85ffa7 WIP
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-06-28 19:28:40 +02:00
Antonio Scandurra
04cd04ff82 WIP
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-06-28 17:48:27 +02:00
Antonio Scandurra
650282f3d0 WIP 2023-06-28 12:05:42 +02:00
Nathan Sobo
3a71894360 Not how we need to do it 2023-06-28 11:51:00 +02:00
Nathan Sobo
2381e3f650 WIP 2023-06-28 11:51:00 +02:00
Nathan Sobo
1cb53805e2 WIP 2023-06-28 11:51:00 +02:00
Nathan Sobo
2791db41d7 WIP 2023-06-28 11:51:00 +02:00
Antonio Scandurra
1578f5fb35 WIP 2023-06-28 11:51:00 +02:00
Nathan Sobo
d09af1948e WIP: Deeper thinking about versions 2023-06-28 11:51:00 +02:00
Nathan Sobo
754cc01f87 WIP 2023-06-28 11:51:00 +02:00
Nathan Sobo
523ea23b10 WIP 2023-06-28 11:51:00 +02:00
Nathan Sobo
f9b5f102f8 Get sketch of major types compiling 2023-06-28 11:51:00 +02:00
Nathan Sobo
7d1b6cb49d Hello CRDB.
Sketch in types with not much implementation.

Does not compile for fairly trivial reasons.
2023-06-28 11:51:00 +02:00
531 changed files with 29295 additions and 54161 deletions

View File

@@ -11,7 +11,7 @@ env:
jobs:
publish:
name: Publish collab server image
name: Publish collab server image
runs-on:
- self-hosted
- deploy
@@ -22,9 +22,6 @@ jobs:
- name: Sign into DigitalOcean docker registry
run: doctl registry login
- name: Prune Docker system
run: docker system prune
- name: Checkout repo
uses: actions/checkout@v3
with:
@@ -44,6 +41,6 @@ jobs:
- name: Build docker image
run: docker build . --tag registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
- name: Publish docker image
run: docker push registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}

View File

@@ -6,23 +6,14 @@ jobs:
discord_release:
runs-on: ubuntu-latest
steps:
- name: Get appropriate URL
id: get-appropriate-url
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
URL="https://zed.dev/releases/preview/latest"
else
URL="https://zed.dev/releases/stable/latest"
fi
echo "::set-output name=URL::$URL"
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
if: ${{ ! github.event.release.prerelease }}
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: |
📣 Zed ${{ github.event.release.tag_name }} was just released!
Restart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it.
Restart your Zed or head to https://zed.dev/releases/stable/latest to grab it.
${{ github.event.release.body }}

View File

@@ -1,5 +0,0 @@
{
"JSON": {
"tab_size": 4
}
}

2137
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,6 @@ members = [
"crates/auto_update",
"crates/breadcrumbs",
"crates/call",
"crates/channel",
"crates/cli",
"crates/client",
"crates/clock",
@@ -14,13 +13,11 @@ members = [
"crates/collab_ui",
"crates/collections",
"crates/command_palette",
"crates/component_test",
"crates/context_menu",
"crates/copilot",
"crates/copilot_button",
"crates/crdb",
"crates/db",
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/diagnostics",
"crates/drag_and_drop",
"crates/editor",
@@ -32,10 +29,6 @@ members = [
"crates/git",
"crates/go_to_line",
"crates/gpui",
"crates/gpui_mac",
"crates/gpui_platform",
"crates/gpui/playground",
"crates/gpui/playground_macros",
"crates/gpui_macros",
"crates/install_cli",
"crates/journal",
@@ -64,7 +57,7 @@ members = [
"crates/snippet",
"crates/sqlez",
"crates/sqlez_macros",
"crates/feature_flags",
"crates/staff_mode",
"crates/sum_tree",
"crates/terminal",
"crates/text",
@@ -84,10 +77,10 @@ default-members = ["crates/zed"]
resolver = "2"
[workspace.dependencies]
anyhow = { version = "1.0.57" }
anyhow = { version = "1.0.57", features = ["backtrace"] }
async-recursion = "1.0"
async-trait = { version = "0.1" }
ctor = { version = "0.1" }
derive_more = { version = "0.99.17" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
globset = { version = "0.4" }
@@ -99,16 +92,14 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
ordered-float = { version = "2.1.1" }
parking_lot = { version = "0.11.1" }
postage = { version = "0.5", features = ["futures-traits"] }
prost = { version = "0.8" }
rand = { version = "0.8.5" }
refineable = { path = "./crates/refineable" }
regex = { version = "1.5" }
rust-embed = { version = "8.0", features = ["include-exclude"] }
rust-embed = { version = "6.3", features = ["include-exclude"] }
schemars = { version = "0.8" }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
smallvec = { version = "1.6", features = ["union"] }
smallvec = { version = "1.6", features = ["serde", "union"] }
smol = { version = "1.2" }
tempdir = { version = "0.3.7" }
thiserror = { version = "1.0.29" }
@@ -120,9 +111,9 @@ pretty_assertions = "1.3.0"
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "1b0321ee85701d5036c334a6f04761cdc672e64c" }
tree-sitter-c = "0.20.1"
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev="f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
tree-sitter-cpp = "0.20.0"
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40"}
tree-sitter-embedded-template = "0.20.0"
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
@@ -142,10 +133,9 @@ tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", r
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"}
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"}
tree-sitter-lua = "0.0.14"
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1c65ca24bc9a734ab70115188f465e12eecf224e" }
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.72-bullseye as builder
FROM rust:1.70-bullseye as builder
WORKDIR app
COPY . .

View File

@@ -1,6 +1,6 @@
User input begins on a line starting with /.
User input begins on a line starting with >.
Your output begins on a line starting with <.
Don't apologize ever.
Never say "I apologize".
Use simple language and don't flatter the users.
Keep it short.
Risk being rude.
Use simple language and don't flatter the users. Spend your tokens on valuable information.

View File

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

View File

@@ -1,23 +0,0 @@
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 8.94203V11C7.38649 11 6.61351 11 4 11V10.6812L10 5.31884V5H4V7.08696" stroke="black" stroke-width="1.25"/>
<circle cx="0.5" cy="8" r="0.5" fill="black"/>
<circle cx="1.49976" cy="5.82825" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="1.49976" cy="10.1719" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="13.5" cy="8.01581" r="0.5" fill="black"/>
<circle cx="12.5" cy="5.84387" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="12.5" cy="10.1877" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="6.99219" cy="1.48438" r="0.5" fill="black"/>
<circle cx="4.5" cy="2.5" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="0.5" cy="12.016" r="0.5" fill="black"/>
<circle cx="0.5" cy="3.98438" r="0.5" fill="black"/>
<circle cx="13.5" cy="12.016" r="0.5" fill="black"/>
<circle cx="13.5" cy="3.98438" r="0.5" fill="black"/>
<circle cx="2.49976" cy="14.516" r="0.5" fill="black"/>
<circle cx="2.48413" cy="1.48438" r="0.5" fill="black"/>
<circle cx="11.5" cy="14.516" r="0.5" fill="black"/>
<circle cx="11.5" cy="1.48438" r="0.5" fill="black"/>
<circle cx="9.49609" cy="2.48438" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="6.99219" cy="14.5" r="0.5" fill="black"/>
<circle cx="4.50391" cy="13.516" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="9.49609" cy="13.5" r="0.5" fill="black" fill-opacity="0.75"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.125 6.99344L6.35938 3.63281M3.125 6.99344L6.35938 10.3672M3.125 6.99344H11" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 275 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8906 7.00125L7.64062 3.64062M10.8906 7.00125L7.64062 10.375M10.8906 7.00125H3" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 279 B

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12px" height="12px" viewBox="0 0 12 12" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 2.976562 2.746094 L 4.226562 2.746094 L 6.105469 9.296875 L 5.285156 9.296875 L 4.804688 7.640625 L 2.386719 7.640625 L 1.914062 9.296875 L 1.097656 9.296875 Z M 4.621094 6.917969 L 3.640625 3.449219 L 3.5625 3.449219 L 2.582031 6.917969 Z M 4.621094 6.917969 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 2.878906 2.617188 L 4.324219 2.617188 L 6.277344 9.425781 L 5.191406 9.425781 L 4.707031 7.769531 L 2.484375 7.769531 L 2.011719 9.425781 L 0.925781 9.425781 Z M 3.601562 3.785156 L 2.75 6.789062 L 4.453125 6.789062 Z M 3.601562 3.785156 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 7.285156 9.378906 L 7.28125 9.378906 C 7.03125 9.277344 6.851562 9.101562 6.738281 8.859375 L 6.738281 8.855469 C 6.636719 8.621094 6.59375 8.296875 6.59375 7.894531 C 6.59375 7.421875 6.660156 7.035156 6.8125 6.757812 C 6.964844 6.464844 7.21875 6.265625 7.5625 6.152344 C 7.90625 6.039062 8.375 5.980469 8.96875 5.980469 L 9.75 5.980469 L 9.75 5.867188 C 9.75 5.605469 9.71875 5.417969 9.65625 5.292969 L 9.65625 5.289062 C 9.605469 5.175781 9.519531 5.09375 9.378906 5.039062 C 9.238281 4.984375 9.023438 4.949219 8.726562 4.949219 C 8.535156 4.949219 8.378906 4.964844 8.253906 4.988281 C 8.128906 5.011719 8.046875 5.042969 8 5.082031 L 7.996094 5.082031 C 7.902344 5.144531 7.832031 5.285156 7.820312 5.554688 L 7.8125 5.675781 L 6.746094 5.675781 L 6.746094 5.546875 C 6.746094 5.164062 6.804688 4.851562 6.925781 4.625 C 7.054688 4.382812 7.273438 4.21875 7.5625 4.128906 C 7.847656 4.03125 8.238281 3.984375 8.726562 3.984375 C 9.238281 3.984375 9.640625 4.039062 9.933594 4.148438 C 10.242188 4.261719 10.464844 4.464844 10.589844 4.75 C 10.714844 5.027344 10.773438 5.402344 10.773438 5.867188 L 10.773438 9.429688 L 9.78125 9.429688 L 9.78125 8.964844 C 9.644531 9.140625 9.488281 9.269531 9.316406 9.355469 C 9.078125 9.472656 8.730469 9.523438 8.289062 9.523438 C 7.871094 9.523438 7.53125 9.480469 7.285156 9.378906 Z M 8.058594 7.039062 C 7.914062 7.085938 7.816406 7.167969 7.753906 7.277344 C 7.699219 7.386719 7.664062 7.558594 7.664062 7.808594 C 7.664062 8.132812 7.730469 8.3125 7.824219 8.398438 C 7.921875 8.480469 8.132812 8.542969 8.496094 8.542969 C 8.84375 8.542969 9.097656 8.488281 9.261719 8.394531 C 9.421875 8.296875 9.546875 8.136719 9.621094 7.894531 C 9.691406 7.671875 9.734375 7.351562 9.742188 6.929688 L 8.777344 6.9375 C 8.460938 6.945312 8.222656 6.980469 8.0625 7.035156 Z M 8.058594 7.039062 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,6 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="10.2795" y1="2.63847" x2="7.74785" y2="11.0142" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="6.26624" y1="2.99597" x2="3.7346" y2="11.3717" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="3.15982" y1="5.3799" x2="11.9098" y2="5.3799" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="2.0983" y1="8.62407" x2="10.8483" y2="8.62407" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 571 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.98438 7.85115L6.13569 9.44983L9.98438 4.08141" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 246 B

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 8L6.5 9L9 5.5" stroke="#11181C" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="7" cy="7" r="4.875" stroke="#11181C" stroke-width="1.25"/>
</svg>

Before

Width:  |  Height:  |  Size: 287 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.63281 5.66406L6.99344 8.89844L10.3672 5.66406" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 246 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.35938 3.63281L5.125 6.99344L8.35938 10.3672" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 244 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.64062 3.64062L8.89062 7.00125L5.64062 10.375" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 245 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.63281 8.36719L6.99344 5.13281L10.3672 8.36719" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 246 B

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.46115 8.43419C7.30678 8.43419 8.92229 7.43411 8.92229 5.21171C8.92229 2.98933 7.30678 1.98926 5.46115 1.98926C3.61553 1.98926 2 2.98933 2 5.21171C2 6.028 2.21794 6.67935 2.58519 7.17685C2.7184 7.35732 2.69033 7.77795 2.58387 7.97539C2.32908 8.44793 2.81048 8.9657 3.33372 8.84571C3.72539 8.75597 4.13621 8.63447 4.49574 8.4715C4.62736 8.41181 4.7727 8.38777 4.91631 8.40402C5.09471 8.42416 5.27678 8.43419 5.46115 8.43419Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4661 10.6353C10.4697 10.7833 10.4914 10.9562 10.5423 11.1245C10.2394 11.0477 9.94714 10.9535 9.69757 10.8403C9.44717 10.7269 9.1668 10.6793 8.88556 10.7111C8.73612 10.728 8.58194 10.7365 8.42443 10.7365C7.68587 10.7365 7.04509 10.5503 6.58359 10.213C6.25127 9.97033 5.78501 10.0428 5.54218 10.3751C5.29939 10.7075 5.37193 11.1737 5.70427 11.4165C6.48017 11.9834 7.45185 12.2271 8.42443 12.2271C8.6356 12.2271 8.84564 12.2156 9.05296 12.1921C9.05904 12.1914 9.06942 12.1921 9.08212 12.1979C9.50348 12.3888 9.9667 12.5238 10.3854 12.6198C10.933 12.7453 11.4558 12.536 11.7761 12.1748C11.9716 11.9544 12.0298 11.6167 12.043 11.361C12.0564 11.1006 12.0238 10.8609 11.9375 10.6152C12.3875 9.98145 12.6308 9.18769 12.6308 8.2593C12.6308 7.23782 12.3361 6.3809 11.7994 5.72187C11.5395 5.4027 11.07 5.35466 10.7509 5.61459C10.4318 5.87448 10.3837 6.34387 10.6436 6.66305M10.4661 10.6353C10.4612 10.4326 10.4844 10.075 10.7008 9.78189C10.9613 9.42893 11.1403 8.93793 11.1403 8.2593C11.1403 7.53473 10.9364 7.0226 10.6436 6.66305" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,9 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.64063 7.67017C5.97718 7.67017 6.25 7.94437 6.25 8.28263V9.60963C6.25 9.94786 5.97718 10.2221 5.64063 10.2221C5.30408 10.2221 5.03125 9.94786 5.03125 9.60963V8.28263C5.03125 7.94437 5.30408 7.67017 5.64063 7.67017Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.37537 7.67017C8.71192 7.67017 8.98474 7.94437 8.98474 8.28263V9.60963C8.98474 9.94786 8.71192 10.2221 8.37537 10.2221C8.03882 10.2221 7.76599 9.94786 7.76599 9.60963V8.28263C7.76599 7.94437 8.03882 7.67017 8.37537 7.67017Z" fill="black"/>
<path d="M7 3.65625C7 5.84375 5.10754 6.3718 3.76562 6.3718C2.42371 6.3718 2.1405 5.3854 2.1405 4.16861C2.1405 2.95182 3.22834 1.96542 4.57025 1.96542C5.91216 1.96542 7 2.43946 7 3.65625Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.25"/>
<path d="M7 3.65625C7 5.84375 8.89246 6.3718 10.2344 6.3718C11.5763 6.3718 11.8595 5.3854 11.8595 4.16861C11.8595 2.95182 10.7717 1.96542 9.42975 1.96542C8.08784 1.96542 7 2.43946 7 3.65625Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.25"/>
<path d="M11.0156 6.01562C11.0156 6.01562 11.6735 6.43636 12 7.07348C12.3265 7.7106 12.3281 9.18621 12 9.7181C11.6719 10.25 11.2813 10.625 10.2931 11.16C9.30501 11.695 8 12.0156 8 12.0156H6C6 12.0156 4.70312 11.7344 3.70687 11.16C2.71061 10.5856 2.23437 10.2188 2 9.7181C1.76562 9.21746 1.6875 7.75 2 7.07348C2.31249 6.39695 3 6.01562 3 6.01562" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M10.4454 11.0264V6.41934L12.1671 6.99323V9.5598L10.4454 11.0264Z" fill="black" fill-opacity="0.75"/>
<path d="M3.51556 11.0264V6.41934L1.79388 6.99323V9.5598L3.51556 11.0264Z" fill="black" fill-opacity="0.75"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="5.64062" width="6.35938" height="6.35938" rx="0.5" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M8.01562 3.75H5.625V2.03125H11.9375V8.39062H10.2656V6C10.2656 4.75736 9.25827 3.75 8.01562 3.75Z" fill="black" fill-opacity="0.5"/>
<path d="M5.625 3.125V2.5C5.625 2.22386 5.84886 2 6.125 2H11.5C11.7761 2 12 2.22386 12 2.5V7.875C12 8.15114 11.7761 8.375 11.5 8.375H10.8906" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 573 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="7" cy="7" r="1" fill="black"/>
<circle cx="11" cy="7" r="1" fill="black"/>
<circle cx="3" cy="7" r="1" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 233 B

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.86396 2C8.99657 2 9.12375 2.05268 9.21751 2.14645L11.8536 4.78249C11.9473 4.87625 12 5.00343 12 5.13604L12 8.86396C12 8.99657 11.9473 9.12375 11.8536 9.21751L9.21751 11.8536C9.12375 11.9473 8.99657 12 8.86396 12L5.13604 12C5.00343 12 4.87625 11.9473 4.78249 11.8536L2.14645 9.21751C2.05268 9.12375 2 8.99657 2 8.86396L2 5.13604C2 5.00343 2.05268 4.87625 2.14645 4.78249L4.78249 2.14645C4.87625 2.05268 5.00343 2 5.13604 2L8.86396 2Z" fill="#001A33" fill-opacity="0.157" stroke="#11181C" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M8.89063 5.10938L5.10937 8.89063M8.89063 8.89063L5.10937 5.10938" stroke="#11181C" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 785 B

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.3594 7.00127L9.86062 4.5025M12.3594 7.00127L9.86062 9.50002M12.3594 7.00127L5 7.00127" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 2H2.5C2.22386 2 2 2.22386 2 2.5V11.5C2 11.7761 2.22386 12 2.5 12H6" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 427 B

View File

@@ -1,6 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 3.5C2 3.22386 2.22386 3 2.5 3H11.5C11.7761 3 12 3.22386 12 3.5V10.5C12 10.7761 11.7761 11 11.5 11H2.5C2.22386 11 2 10.7761 2 10.5V3.5Z" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M3 4L6.95312 7L11 4" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 9L5 8" stroke="black" stroke-opacity="0.5" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 9L9 8" stroke="black" stroke-opacity="0.5" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 675 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2L8.6165 2.10275C8.65805 1.8534 8.54532 1.60357 8.33085 1.46975C8.11639 1.33594 7.84243 1.34449 7.63673 1.49142L8 2ZM9.88714 8.62257C10.1098 9.73604 9.86526 10.3554 9.4569 10.7229C9.00367 11.1308 8.19498 11.375 7 11.375V12.625C8.30502 12.625 9.49633 12.3692 10.2931 11.6521C11.1347 10.8946 11.3902 9.76396 11.1129 8.37743L9.88714 8.62257ZM7 11.375C5.87824 11.375 5.17563 11.0417 4.75444 10.6206C4.32847 10.1946 4.125 9.61372 4.125 9H2.875C2.875 9.88628 3.17153 10.8054 3.87056 11.5044C4.57437 12.2083 5.62176 12.625 7 12.625V11.375ZM4.125 9C4.125 7.72699 5.00594 4.90668 8.36327 2.50858L7.63673 1.49142C3.99406 4.09332 2.875 7.27301 2.875 9H4.125ZM7.3835 1.89725C7.09577 3.62363 7.69108 4.78835 8.35497 5.78419C9.03189 6.79957 9.66859 7.52983 9.88714 8.62257L11.1129 8.37743C10.8314 6.97017 9.96811 5.95043 9.39503 5.09081C8.80892 4.21165 8.40423 3.37637 8.6165 2.10275L7.3835 1.89725Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1017 B

View File

@@ -1,192 +1,159 @@
{
"suffixes": {
"aac": "audio",
"accdb": "storage",
"bak": "backup",
"bash": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"bash_profile": "terminal",
"bashrc": "terminal",
"bmp": "image",
"c": "code",
"cc": "code",
"conf": "settings",
"cpp": "code",
"css": "code",
"csv": "storage",
"dat": "storage",
"db": "storage",
"dbf": "storage",
"dll": "storage",
"doc": "document",
"docx": "document",
"eex": "elixir",
"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",
"gitmodules": "vcs",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"heex": "elixir",
"htm": "template",
"html": "template",
"ib": "storage",
"ico": "image",
"ini": "settings",
"java": "code",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"ldf": "storage",
"lock": "lock",
"log": "log",
"md": "document",
"mdb": "storage",
"mdf": "storage",
"mdx": "document",
"mp3": "audio",
"mp4": "video",
"myd": "storage",
"myi": "storage",
"odp": "document",
"ods": "document",
"odt": "document",
"ogg": "video",
"pdb": "storage",
"pdf": "document",
"php": "code",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",
"prettierrc": "prettier",
"profile": "terminal",
"ps1": "terminal",
"psd": "image",
"py": "python",
"rb": "code",
"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",
"wav": "audio",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"yaml": "settings",
"yml": "settings",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
"zsh_histfile": "terminal",
"zsh_profile": "terminal",
"zshenv": "terminal",
"zshrc": "terminal"
"suffixes": {
"aac": "audio",
"bash": "terminal",
"bmp": "image",
"c": "code",
"conf": "settings",
"cpp": "code",
"cc": "code",
"css": "code",
"doc": "document",
"docx": "document",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"flac": "audio",
"fish": "terminal",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitmodules": "vcs",
"gif": "image",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"htm": "template",
"html": "template",
"svelte": "template",
"hpp": "code",
"ico": "image",
"ini": "settings",
"java": "code",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"lock": "lock",
"log": "log",
"md": "document",
"mdx": "document",
"mp3": "audio",
"mp4": "video",
"ods": "document",
"odp": "document",
"odt": "document",
"ogg": "video",
"pdf": "document",
"php": "code",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierrc": "prettier",
"prettierignore": "prettier",
"ps1": "terminal",
"psd": "image",
"py": "code",
"rb": "code",
"rkt": "code",
"rs": "rust",
"rtf": "document",
"scm": "code",
"sh": "terminal",
"bashrc": "terminal",
"bash_profile": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"profile": "terminal",
"zshrc": "terminal",
"zshenv": "terminal",
"zsh_profile": "terminal",
"zsh_aliases": "terminal",
"zsh_histfile": "terminal",
"zlogin": "terminal",
"sql": "code",
"svg": "image",
"swift": "code",
"tiff": "image",
"toml": "toml",
"ts": "typescript",
"tsx": "code",
"txt": "document",
"wav": "audio",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"yaml": "settings",
"yml": "settings",
"zsh": "terminal"
},
"types": {
"audio": {
"icon": "icons/file_icons/audio.svg"
},
"types": {
"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"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"elixir": {
"icon": "icons/file_icons/elixir.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"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"phoenix": {
"icon": "icons/file_icons/phoenix.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"python": {
"icon": "icons/file_icons/python.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"
}
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.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"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.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"
}
}
}

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 8C12 7.32138 11.9375 6.5 11.7188 5.75C11.0625 6.53125 9.875 7.1875 9 7.5C9.75 4.90625 8.5625 2.1875 7 2C7 3.96875 6.625 4.90625 5.5 6.5C4 4 2.5 5.5 2 6C2.5 6.5 3.21832 7.24064 3.34375 8.3125C3.6875 11.25 5.75 12 7.5 12C9.25 12 9.5 10 11.5 11" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="4.03125" cy="6.625" r="1.53125" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 502 B

View File

@@ -1,6 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.18452 1.9164C5.01625 1.9164 3.98489 2.77625 3.91991 3.9468H3.72024C2.81569 3.9468 2 4.63733 2 5.587V7.1098C2 8.05947 2.81569 8.75 3.72024 8.75H4.33631C4.67376 8.75 5.02976 8.48561 5.02976 8.06155C5.02976 7.46058 5.51694 6.9734 6.11791 6.9734H7.27976C8.18431 6.9734 9 6.28288 9 5.3332V4.0642C9 2.83419 7.93913 1.9164 6.73214 1.9164H6.18452Z" stroke="black" stroke-width="1.25"/>
<path d="M7.79613 12.0836C8.97889 12.0836 10.0103 11.2025 10.0702 10.0191H10.2738C11.1885 10.0191 12 9.31459 12 8.36187V6.8135C12 5.86077 11.1885 5.15625 10.2738 5.15625H9.65439C9.30991 5.15625 8.96057 5.42749 8.96057 5.84577C8.96057 6.46262 8.46051 6.96268 7.84365 6.96268H6.69494C5.78027 6.96268 4.96875 7.6672 4.96875 8.61993V9.91023C4.96875 11.148 6.02678 12.0836 7.24554 12.0836H7.79613Z" stroke="black" stroke-width="1.25"/>
<circle cx="6.03975" cy="3.9167" r="0.633501" fill="black"/>
<circle cx="7.92285" cy="10.0793" r="0.670898" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6749 2.40608C11.8058 2.24239 11.6893 1.99991 11.4796 1.99991H2.51996C2.31033 1.99991 2.19379 2.24239 2.32474 2.40608L5.14583 5.93246C5.34148 6.17701 5.44808 6.48087 5.44808 6.79412C5.44808 7.46881 5.44808 10.334 5.44808 11.5016C5.44808 11.7778 5.67194 11.9999 5.94808 11.9999H8.05153C8.32767 11.9999 8.55153 11.7778 8.55153 11.5016C8.55153 10.334 8.55153 7.46881 8.55153 6.79412C8.55153 6.48087 8.65815 6.17701 8.8538 5.93246L11.6749 2.40608Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 644 B

View File

@@ -1,3 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6748 1.40617C10.8058 1.24248 10.6892 1 10.4796 1H1.51991C1.31028 1 1.19374 1.24248 1.32469 1.40617L4.14578 4.93255C4.34144 5.1771 4.44803 5.48097 4.44803 5.79421C4.44803 6.4689 4.44803 9.33412 4.44803 10.5017C4.44803 10.7779 4.67189 11 4.94803 11H7.05148C7.32762 11 7.55148 10.7779 7.55148 10.5017C7.55148 9.33412 7.55148 6.4689 7.55148 5.79421C7.55148 5.48097 7.6581 5.1771 7.85376 4.93255L10.6748 1.40617Z" stroke="#787D87" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 611 B

View File

@@ -1,6 +0,0 @@
<?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:1.25;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(47.058824%,49.019608%,52.941176%);stroke-opacity:1;stroke-miterlimit:4;" d="M 10.674107 1.40625 C 10.804688 1.242188 10.690848 1.001116 10.479911 1.001116 L 1.520089 1.001116 C 1.309152 1.001116 1.195312 1.242188 1.325893 1.40625 L 4.145089 4.93192 C 4.342634 5.176339 4.446429 5.481027 4.446429 5.795759 C 4.446429 6.46875 4.446429 9.334821 4.446429 10.503348 C 4.446429 10.777902 4.670759 10.998884 4.948661 10.998884 L 7.051339 10.998884 C 7.329241 10.998884 7.550223 10.777902 7.550223 10.503348 C 7.550223 9.334821 7.550223 6.46875 7.550223 5.795759 C 7.550223 5.481027 7.657366 5.176339 7.854911 4.93192 Z M 10.674107 1.40625 " transform="matrix(1.166667,0,0,1.166667,0,0)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 991 B

View File

@@ -1,6 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="10.2795" y1="2.63847" x2="7.74786" y2="11.0142" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="6.26625" y1="2.99597" x2="3.73461" y2="11.3717" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="3.15979" y1="5.3799" x2="11.9098" y2="5.3799" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="2.09833" y1="8.62407" x2="10.8483" y2="8.62407" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 573 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.15735 3.17108L5.84271 10.8289" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<path d="M4 5L2 7L4 9" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 9L12 7L10 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 423 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="3" cy="9" r="1" fill="black"/>
<circle cx="3" cy="5" r="1" fill="black"/>
<path d="M7 3H10M13 3H10M10 3C10 3 10 11 10 11.5" stroke="black" stroke-width="1.25"/>
</svg>

Before

Width:  |  Height:  |  Size: 276 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="7" cy="7" r="1" fill="black"/>
<circle cx="11" cy="7" r="1" fill="black"/>
<circle cx="3" cy="7" r="1" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 233 B

View File

@@ -1,6 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="5" width="8" height="7" rx="0.5" stroke="black" stroke-width="1.25"/>
<path d="M4 4C4 2.89543 4.89543 2 6 2H8C9.10457 2 10 2.89543 10 4V5H4V4Z" stroke="black" stroke-opacity="0.75" stroke-width="1.25"/>
<circle cx="7" cy="8" r="1" fill="black"/>
<path d="M7 8V9.375" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 445 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12L9.41379 9.41379M2 6.31034C2 3.92981 3.92981 2 6.31034 2C8.6909 2 10.6207 3.92981 10.6207 6.31034C10.6207 8.6909 8.6909 10.6207 6.31034 10.6207C3.92981 10.6207 2 8.6909 2 6.31034Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 383 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.47087 3.20502H4.93146L7.12233 10.845H6.16733L5.60557 8.91252H2.78552L2.235 10.845H1.28L3.47087 3.20502ZM5.3921 8.06988L4.24611 4.02519H4.15622L3.01023 8.06988H5.3921Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.35784 3.05502H5.04449L7.32139 10.995H6.05473L5.49297 9.06253H2.89876L2.34823 10.995H1.08094L3.35784 3.05502ZM4.20117 4.41683L3.20863 7.91989H5.1937L4.20117 4.41683Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.49755 10.9439L8.49614 10.9433C8.20513 10.8235 7.99172 10.6192 7.86261 10.3352L7.86103 10.3317C7.74397 10.0558 7.69085 9.67797 7.69085 9.21014C7.69085 8.65676 7.77089 8.20993 7.94588 7.88453C8.12486 7.54406 8.4223 7.31025 8.82246 7.17939C9.22218 7.04366 9.77245 6.97946 10.4643 6.97946H11.3773V6.84676C11.3773 6.53978 11.3365 6.32064 11.2676 6.17645L11.2652 6.17158C11.2077 6.03931 11.105 5.94128 10.942 5.87857L10.9401 5.87785C10.7779 5.81296 10.5289 5.77548 10.1816 5.77548C9.95742 5.77548 9.77444 5.79025 9.63048 5.818C9.4849 5.84607 9.38928 5.88554 9.33128 5.92772L9.32759 5.9304C9.22055 6.00339 9.13583 6.16518 9.1215 6.4804L9.11499 6.62359H7.87178V6.47359C7.87178 6.02598 7.93666 5.66152 8.08202 5.39592C8.23181 5.11455 8.48509 4.9233 8.82297 4.81582C9.15491 4.7028 9.61083 4.64999 10.1816 4.64999C10.7762 4.64999 11.2497 4.71047 11.5915 4.84054C11.9497 4.97397 12.2081 5.20795 12.3539 5.54148C12.5023 5.86386 12.5706 6.30304 12.5706 6.84676V10.9998H11.4112V10.4612C11.2513 10.6622 11.0717 10.8156 10.8706 10.9161L10.869 10.917C10.5893 11.0526 10.1848 11.1129 9.67276 11.1129C9.18264 11.1129 8.78731 11.0598 8.49755 10.9439ZM9.40357 8.21033C9.23125 8.26777 9.11727 8.36187 9.04741 8.4893C8.98131 8.61621 8.94073 8.81734 8.94073 9.10837C8.94073 9.48881 9.01919 9.69954 9.12735 9.79866C9.24209 9.89577 9.48642 9.96479 9.91023 9.96479C10.3198 9.96479 10.6134 9.90216 10.8072 9.79296C10.9944 9.68003 11.1366 9.4918 11.226 9.21004C11.3088 8.94889 11.3567 8.57563 11.3648 8.08368L10.2403 8.09363C9.87055 8.10107 9.59539 8.14186 9.40658 8.20929L9.40357 8.21033Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.74677 9.48683L4.07035 6.03229L3.38589 9.48683H2.17618L1.00285 4.00778H2.27563L2.81571 7.41751L3.48443 4.01749H4.65869L5.31824 7.41173L5.8574 4.00778H7.13018L5.95684 9.48683H4.74677Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.64902 9.324C8.31295 9.13305 8.08208 8.81972 7.9472 8.40798C7.81465 8.00336 7.75312 7.44225 7.75312 6.73695C7.75312 6.03863 7.81136 5.48473 7.93685 5.08734L7.9375 5.0853C8.07226 4.67391 8.30335 4.36276 8.64083 4.17813C8.96801 3.99275 9.41114 3.91059 9.94955 3.91059C10.3406 3.91059 10.6631 3.95604 10.8967 4.06503C11.0079 4.11693 11.1098 4.18862 11.2033 4.27763V2.03046H12.4076V9.48579H11.2033V9.19001C11.0944 9.29092 10.9799 9.37114 10.8591 9.4277C10.6327 9.53666 10.3334 9.5827 9.97862 9.5827C9.43385 9.5827 8.98587 9.50374 8.6537 9.32658L8.64902 9.324ZM11.1139 7.85526C11.1841 7.60329 11.2226 7.23372 11.2226 6.73695C11.2226 6.2462 11.184 5.88349 11.114 5.63862C11.0456 5.39921 10.94 5.25882 10.8149 5.18284L10.8077 5.17844C10.6804 5.09361 10.4713 5.03744 10.1531 5.03744C9.8078 5.03744 9.57185 5.09378 9.42251 5.18338L9.41824 5.18594C9.27997 5.2643 9.16717 5.40621 9.09394 5.64281C9.01872 5.88584 8.97689 6.24686 8.97689 6.73695C8.97689 7.23381 9.01877 7.59792 9.09394 7.84078C9.16725 8.07763 9.28092 8.22495 9.42251 8.3099C9.57185 8.39951 9.8078 8.45585 10.1531 8.45585C10.4721 8.45585 10.683 8.40283 10.8113 8.32234C10.9395 8.23962 11.0456 8.09391 11.1139 7.85526Z" fill="black"/>
<rect x="1.14084" y="10.7188" width="11.7183" height="1.26565" rx="0.632824" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 8.5V12M2 12H5.5M2 12L6.01562 7.98437" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 5.5V2M12 2L8.5 2M12 2L8.01562 5.98437" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 373 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 8.5C10.5 8.5 9.375 10 7 10C4.625 10 3.5 8.5 3.5 8.5" stroke="black" stroke-width="1.25"/>
<rect x="5" y="2" width="4" height="5.40625" rx="2" fill="black" fill-opacity="0.25" stroke="black" stroke-width="1.25"/>
<path d="M7 10V12M7 12H9M7 12H5" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 423 B

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.01563 11.4844L6.01563 7.98438M6.01563 7.98438L2.51563 7.98437M6.01563 7.98438L2 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.01562 2.48438V5.98438M8.01562 5.98438H11.5156M8.01562 5.98438L12 2" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 447 B

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 3V11M11 7H3" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 188 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.03125 2V2.03125M2.03125 8C2.03125 10 5 10 5 10M2.03125 8V2.03125M2.03125 8L2.03125 11M2.03125 2.03125C2.03125 4 5 4 5 4" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="7.375" y="2.375" width="4.25" height="3.25" rx="1.125" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25"/>
<rect x="7.375" y="8.375" width="4.25" height="3.25" rx="1.125" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25"/>
</svg>

Before

Width:  |  Height:  |  Size: 588 B

View File

@@ -1,11 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 12C4.97279 12 3.22735 10.7936 2.4425 9.0595M7 2C9.11228 2 10.9186 3.30981 11.6512 5.16152" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="1.65625" cy="1.67188" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="3.71094" cy="1.67188" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="4.96094" cy="3.36719" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="3.71094" cy="4.79688" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="4.60156" cy="6.67188" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="1.65625" cy="4.17188" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="1.65625" cy="6.67188" r="0.625" fill="black" fill-opacity="0.75"/>
<path d="M10.7802 10.8195C10.838 10.8195 10.8906 10.8527 10.9155 10.9048L11.7174 12.5811C11.8088 12.7721 12.0017 12.8938 12.2135 12.8938H12.3394C12.7483 12.8938 13.0142 12.4635 12.8314 12.0978L12.1619 10.7589C12.1232 10.6816 12.1582 10.5823 12.241 10.5349C12.7565 10.2397 13.0695 9.66858 13.0695 9.00391C13.0695 8.43361 12.8777 7.97006 12.5248 7.64951C12.1725 7.3295 11.6652 7.15703 11.043 7.15703H9.49609C9.19234 7.15703 8.94609 7.40327 8.94609 7.70703V12.3438C8.94609 12.6475 9.19234 12.8938 9.49609 12.8938H9.60156C9.90532 12.8938 10.1516 12.6475 10.1516 12.3438V10.9695C10.1516 10.8867 10.2187 10.8195 10.3016 10.8195H10.7802ZM10.1516 8.31328C10.1516 8.23044 10.2187 8.16328 10.3016 8.16328H10.8984C11.2023 8.16328 11.4371 8.2449 11.5954 8.38814C11.7529 8.5308 11.8406 8.73993 11.8406 9.00781C11.8406 9.28155 11.751 9.49461 11.5909 9.63971C11.4302 9.7854 11.1925 9.86797 10.8867 9.86797H10.3016C10.2187 9.86797 10.1516 9.80081 10.1516 9.71797V8.31328Z" fill="black" stroke="black" stroke-width="0.1"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.10517 5.8012C4.07193 5.73172 4.00176 5.6875 3.92475 5.6875H3.44609C3.33564 5.6875 3.24609 5.77704 3.24609 5.8875V7.26172C3.24609 7.53786 3.02224 7.76172 2.74609 7.76172H2.64062C2.36448 7.76172 2.14062 7.53786 2.14062 7.26172V2.625C2.14062 2.34886 2.36448 2.125 2.64062 2.125H4.1875C5.41406 2.125 6.16406 2.80469 6.16406 3.92188C6.16406 4.57081 5.85885 5.12418 5.36073 5.40943C5.25888 5.46775 5.20921 5.59421 5.2617 5.69918L5.93117 7.03811C6.09739 7.37056 5.85564 7.76172 5.48395 7.76172H5.35806C5.16552 7.76172 4.99009 7.65117 4.907 7.47748L4.10517 5.8012ZM3.44609 3.03125C3.33564 3.03125 3.24609 3.12079 3.24609 3.23125V4.63594C3.24609 4.74639 3.33564 4.83594 3.44609 4.83594H4.03125C4.66016 4.83594 5.03516 4.49609 5.03516 3.92578C5.03516 3.36719 4.66797 3.03125 4.04297 3.03125H3.44609Z" fill="black" fill-opacity="0.75"/>
<path d="M3.92475 5.7375C3.98251 5.7375 4.03514 5.77067 4.06006 5.82277L4.8619 7.49905C4.95329 7.69011 5.14627 7.81172 5.35806 7.81172H5.48395C5.89281 7.81172 6.15873 7.38145 5.97589 7.01575L5.30642 5.67682C5.26778 5.59953 5.30269 5.50028 5.38557 5.45282C5.90107 5.15762 6.21406 4.58655 6.21406 3.92188C6.21406 3.35158 6.02226 2.88803 5.66936 2.56748C5.31705 2.24747 4.80973 2.075 4.1875 2.075H2.64062C2.33687 2.075 2.09062 2.32124 2.09062 2.625V7.26172C2.09062 7.56548 2.33687 7.81172 2.64062 7.81172H2.74609C3.04985 7.81172 3.29609 7.56548 3.29609 7.26172V5.8875C3.29609 5.80466 3.36325 5.7375 3.44609 5.7375H3.92475ZM3.29609 3.23125C3.29609 3.14841 3.36325 3.08125 3.44609 3.08125H4.04297C4.34688 3.08125 4.58164 3.16287 4.73988 3.30611C4.89748 3.44876 4.98516 3.6579 4.98516 3.92578C4.98516 4.19952 4.89553 4.41258 4.73546 4.55768C4.57475 4.70337 4.33706 4.78594 4.03125 4.78594H3.44609C3.36325 4.78594 3.29609 4.71878 3.29609 4.63594V3.23125Z" stroke="black" stroke-opacity="0.75" stroke-width="0.1"/>
<path d="M9.5 7V9.5M9.5 12V9.5M12 9.5H9.5M7 9.5H9.5M9.5 9.5L11.1667 7.83333M9.5 9.5L7.83333 11.1667M9.5 9.5L11.1667 11.1667M9.5 9.5L7.83333 7.83333" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.96454 5.6762C3.93131 5.60672 3.86114 5.5625 3.78412 5.5625H3.30547C3.19501 5.5625 3.10547 5.65204 3.10547 5.7625V7.13672C3.10547 7.41286 2.88161 7.63672 2.60547 7.63672H2.5C2.22386 7.63672 2 7.41286 2 7.13672V2.5C2 2.22386 2.22386 2 2.5 2H4.04688C5.27344 2 6.02344 2.67969 6.02344 3.79688C6.02344 4.44581 5.71823 4.99918 5.2201 5.28443C5.11826 5.34275 5.06859 5.46921 5.12107 5.57418L5.79054 6.91311C5.95677 7.24556 5.71502 7.63672 5.34333 7.63672H5.21743C5.02489 7.63672 4.84946 7.52617 4.76638 7.35248L3.96454 5.6762ZM3.30547 2.90625C3.19501 2.90625 3.10547 2.99579 3.10547 3.10625V4.51094C3.10547 4.62139 3.19501 4.71094 3.30547 4.71094H3.89062C4.51953 4.71094 4.89453 4.37109 4.89453 3.80078C4.89453 3.24219 4.52734 2.90625 3.90234 2.90625H3.30547Z" fill="black" fill-opacity="0.75"/>
<path d="M3.78412 5.6125C3.84188 5.6125 3.89451 5.64567 3.91944 5.69777L4.72127 7.37405C4.81266 7.56511 5.00564 7.68672 5.21743 7.68672H5.34333C5.75219 7.68672 6.01811 7.25645 5.83526 6.89075L5.1658 5.55182C5.12715 5.47453 5.16207 5.37528 5.24495 5.32782C5.76044 5.03262 6.07344 4.46155 6.07344 3.79688C6.07344 3.22658 5.88164 2.76303 5.52873 2.44248C5.17642 2.12247 4.6691 1.95 4.04688 1.95H2.5C2.19624 1.95 1.95 2.19624 1.95 2.5V7.13672C1.95 7.44048 2.19624 7.68672 2.5 7.68672H2.60547C2.90923 7.68672 3.15547 7.44048 3.15547 7.13672V5.7625C3.15547 5.67966 3.22263 5.6125 3.30547 5.6125H3.78412ZM3.15547 3.10625C3.15547 3.02341 3.22263 2.95625 3.30547 2.95625H3.90234C4.20626 2.95625 4.44101 3.03787 4.59926 3.18111C4.75686 3.32376 4.84453 3.5329 4.84453 3.80078C4.84453 4.07452 4.75491 4.28758 4.59484 4.43268C4.43413 4.57837 4.19643 4.66094 3.89062 4.66094H3.30547C3.22263 4.66094 3.15547 4.59378 3.15547 4.51094V3.10625Z" stroke="black" stroke-opacity="0.75" stroke-width="0.1"/>
<path d="M7.5 5.88672C9.433 5.88672 11 7.45372 11 9.38672V12M11 12L13 10M11 12L9 10" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="10" height="7" rx="0.5" fill="black" fill-opacity="0.25" stroke="black" stroke-width="1.25"/>
<path d="M7 9V12M7 12H9M7 12H5" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 314 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 2H10C11.1046 2 12 2.89543 12 4V10C12 11.1046 11.1046 12 10 12H7V2Z" fill="black" fill-opacity="0.25"/>
<rect x="2" y="2" width="10" height="10" rx="0.5" stroke="black" stroke-width="1.25"/>
<line x1="7" y1="2" x2="7" y2="12" stroke="black" stroke-width="1.25"/>
</svg>

Before

Width:  |  Height:  |  Size: 377 B

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 2.5C2 2.22386 2.22386 2 2.5 2H11.5C11.7761 2 12 2.22386 12 2.5V11.5C12 11.7761 11.7761 12 11.5 12H2.5C2.22386 12 2 11.7761 2 11.5V2.5Z" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M4.60938 7.625L6.3125 8.89062L9.35938 4.64062" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 474 B

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.65625 2.5C1.65625 2.22386 1.88011 2 2.15625 2H11.8437C12.1199 2 12.3438 2.22386 12.3438 2.5V11.5C12.3438 11.7761 12.1199 12 11.8437 12H2.15625C1.88011 12 1.65625 11.7761 1.65625 11.5V2.5Z" stroke="black" stroke-width="1.25"/>
<path d="M4.375 9L6.375 7L4.375 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.625 9L9.90625 9" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 549 B

View File

@@ -1,6 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.45563 12.3438H11.5444C11.9137 12.3438 12.1556 11.9571 11.994 11.625L10.2346 8.00952C9.77174 7.05841 8.89104 6.37821 7.85383 6.17077C7.29019 6.05804 6.70981 6.05804 6.14617 6.17077C5.10896 6.37821 4.22826 7.05841 3.76542 8.00952L2.00603 11.625C1.84442 11.9571 2.08628 12.3438 2.45563 12.3438Z" fill="#001A33" fill-opacity="0.157"/>
<path d="M9.5 6.5L11.994 11.625C12.1556 11.9571 11.9137 12.3438 11.5444 12.3438H2.45563C2.08628 12.3438 1.84442 11.9571 2.00603 11.625L4.5 6.5" stroke="#11181C" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 7L7 2" stroke="#11181C" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="7" cy="9.24219" r="0.75" fill="#11181C"/>
</svg>

Before

Width:  |  Height:  |  Size: 835 B

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12px" height="12px" viewBox="0 0 12 12" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 4.070312 8.132812 L 3.488281 5.171875 L 2.902344 8.132812 L 1.867188 8.132812 L 0.859375 3.433594 L 1.949219 3.433594 L 2.414062 6.359375 L 2.988281 3.445312 L 3.992188 3.445312 L 4.558594 6.351562 L 5.019531 3.433594 L 6.113281 3.433594 L 5.105469 8.132812 Z M 4.070312 8.132812 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 7.414062 7.992188 C 7.125 7.828125 6.925781 7.558594 6.8125 7.207031 C 6.699219 6.859375 6.644531 6.378906 6.644531 5.773438 C 6.644531 5.175781 6.695312 4.699219 6.804688 4.359375 C 6.917969 4.007812 7.117188 3.738281 7.40625 3.582031 C 7.6875 3.421875 8.066406 3.351562 8.527344 3.351562 C 8.863281 3.351562 9.140625 3.390625 9.339844 3.484375 C 9.433594 3.527344 9.523438 3.589844 9.601562 3.667969 L 9.601562 1.738281 L 10.636719 1.738281 L 10.636719 8.128906 L 9.601562 8.128906 L 9.601562 7.878906 C 9.507812 7.964844 9.410156 8.03125 9.308594 8.082031 C 9.113281 8.175781 8.855469 8.214844 8.554688 8.214844 C 8.085938 8.214844 7.703125 8.144531 7.417969 7.992188 Z M 9.527344 6.734375 C 9.585938 6.515625 9.621094 6.199219 9.621094 5.773438 C 9.621094 5.355469 9.585938 5.042969 9.527344 4.832031 C 9.46875 4.628906 9.378906 4.507812 9.269531 4.441406 L 9.265625 4.4375 C 9.15625 4.367188 8.976562 4.316406 8.703125 4.316406 C 8.40625 4.316406 8.203125 4.367188 8.078125 4.441406 L 8.074219 4.445312 C 7.953125 4.511719 7.859375 4.632812 7.792969 4.835938 C 7.730469 5.042969 7.695312 5.355469 7.695312 5.773438 C 7.695312 6.199219 7.730469 6.511719 7.792969 6.71875 C 7.859375 6.921875 7.957031 7.050781 8.078125 7.121094 C 8.203125 7.199219 8.40625 7.246094 8.703125 7.246094 C 8.976562 7.246094 9.15625 7.203125 9.265625 7.132812 C 9.375 7.0625 9.46875 6.9375 9.527344 6.734375 Z M 9.527344 6.734375 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 1.519531 9.1875 L 10.480469 9.1875 C 10.777344 9.1875 11.023438 9.429688 11.023438 9.730469 C 11.023438 10.03125 10.777344 10.273438 10.480469 10.273438 L 1.519531 10.273438 C 1.222656 10.273438 0.976562 10.03125 0.976562 9.730469 C 0.976562 9.429688 1.222656 9.1875 1.519531 9.1875 Z M 1.519531 9.1875 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,6 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.74672 9.48686L4.07031 6.03232L3.38584 9.48686H2.17614L1.00281 4.00781H2.27559L2.81566 7.41754L3.48439 4.01752H4.65865L5.31819 7.41176L5.85736 4.00781H7.13014L5.9568 9.48686H4.74672Z" fill="#787D87"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.64907 9.32382C8.313 9.13287 8.08213 8.81954 7.94725 8.4078C7.8147 8.00318 7.75317 7.44207 7.75317 6.73677C7.75317 6.03845 7.81141 5.48454 7.9369 5.08716L7.93755 5.08512C8.07231 4.67373 8.3034 4.36258 8.64088 4.17794C8.96806 3.99257 9.41119 3.9104 9.9496 3.9104C10.3406 3.9104 10.6632 3.95585 10.8967 4.06485C11.0079 4.11675 11.1099 4.18844 11.2033 4.27745V2.03027H12.4077V9.4856H11.2033V9.18983C11.0945 9.29074 10.98 9.37096 10.8591 9.42752C10.6327 9.53648 10.3335 9.58252 9.97867 9.58252C9.4339 9.58252 8.98592 9.50355 8.65375 9.3264L8.64907 9.32382ZM11.1139 7.85508C11.1841 7.60311 11.2227 7.23354 11.2227 6.73677C11.2227 6.24602 11.1841 5.88331 11.1141 5.63844C11.0457 5.39902 10.9401 5.25863 10.8149 5.18266L10.8077 5.17826C10.6804 5.09342 10.4713 5.03726 10.1531 5.03726C9.80785 5.03726 9.5719 5.09359 9.42256 5.1832L9.41829 5.18576C9.28002 5.26412 9.16722 5.40602 9.09399 5.64263C9.01876 5.88566 8.97694 6.24668 8.97694 6.73677C8.97694 7.23363 9.01882 7.59774 9.09399 7.8406C9.1673 8.07745 9.28097 8.22477 9.42256 8.30972C9.5719 8.39933 9.80785 8.45566 10.1531 8.45566C10.4721 8.45566 10.683 8.40265 10.8114 8.32216C10.9396 8.23944 11.0456 8.09373 11.1139 7.85508Z" fill="#787D87"/>
<rect x="1.14087" y="10.7188" width="11.7183" height="1.26565" rx="0.632824" fill="#787D87"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.82843 4.17157L4.17157 9.82842M9.82843 9.82842L4.17157 4.17157" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 238 B

View File

@@ -13,7 +13,6 @@
"cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast",
"enter": "menu::Confirm",
"ctrl-enter": "menu::ShowContextMenu",
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
@@ -23,7 +22,6 @@
"alt-cmd-right": "pane::ActivateNextItem",
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-t": "pane::CloseInactiveItems",
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k u": "pane::CloseCleanItems",
"cmd-k cmd-w": "pane::CloseAllItems",
"cmd-shift-w": "workspace::CloseWindow",
@@ -173,7 +171,6 @@
"context": "Editor && mode == full",
"bindings": {
"enter": "editor::Newline",
"shift-enter": "editor::Newline",
"cmd-shift-enter": "editor::NewlineAbove",
"cmd-enter": "editor::NewlineBelow",
"alt-z": "editor::ToggleSoftWrap",
@@ -226,36 +223,19 @@
"tab": "buffer_search::FocusEditor",
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches",
"alt-tab": "search::CycleMode"
}
},
{
"context": "BufferSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
"alt-enter": "search::SelectAllMatches"
}
},
{
"context": "ProjectSearchBar",
"bindings": {
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode"
}
},
{
"context": "ProjectSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
"escape": "project_search::ToggleFocus"
}
},
{
"context": "ProjectSearchView",
"bindings": {
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode"
"escape": "project_search::ToggleFocus"
}
},
{
@@ -267,8 +247,7 @@
"alt-enter": "search::SelectAllMatches",
"alt-cmd-c": "search::ToggleCaseSensitive",
"alt-cmd-w": "search::ToggleWholeWord",
"alt-tab": "search::CycleMode",
"alt-cmd-f": "project_search::ToggleFilters"
"alt-cmd-r": "search::ToggleRegex"
}
},
// Bindings from VS Code
@@ -519,10 +498,8 @@
{
"bindings": {
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
// TODO: Move this to a dock open action
"cmd-shift-c": "collab_panel::ToggleFocus",
"cmd-alt-i": "zed::DebugElements",
"ctrl-:": "editor::ToggleInlayHints",
"cmd-shift-c": "collab::ToggleContactsMenu",
"cmd-alt-i": "zed::DebugElements"
}
},
{
@@ -530,8 +507,7 @@
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist"
"cmd-shift-f8": "editor::GoToPrevHunk"
}
},
{
@@ -545,8 +521,6 @@
"bindings": {
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"cmd-n": "project_panel::NewFile",
"alt-cmd-n": "project_panel::NewDirectory",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
@@ -560,25 +534,6 @@
"alt-shift-f": "project_panel::NewSearchInDirectory"
}
},
{
"context": "CollabPanel",
"bindings": {
"ctrl-backspace": "collab_panel::Remove",
"space": "menu::Confirm"
}
},
{
"context": "ChannelModal",
"bindings": {
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "ChannelModal > Picker > Editor",
"bindings": {
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "Terminal",
"bindings": {

View File

@@ -2,6 +2,7 @@
{
"bindings": {
"cmd-shift-o": "projects::OpenRecent",
"cmd-shift-b": "branches::OpenRecent",
"cmd-alt-tab": "project_panel::ToggleFocus"
}
},
@@ -11,9 +12,8 @@
"cmd-l": "go_to_line::Toggle",
"ctrl-shift-d": "editor::DuplicateLine",
"cmd-b": "editor::GoToDefinition",
"alt-cmd-b": "editor::GoToDefinition",
"cmd-j": "editor::ScrollCursorCenter",
"cmd-enter": "editor::NewlineBelow",
"cmd-alt-enter": "editor::NewLineAbove",
"cmd-shift-l": "editor::SelectLine",
"cmd-shift-t": "outline::Toggle",
"alt-backspace": "editor::DeleteToPreviousWordStart",
@@ -51,17 +51,14 @@
}
],
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart",
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
"ctrl-w": "editor::SelectNext",
"ctrl-u": "editor::ConvertToUpperCase",
"ctrl-shift-u": "editor::ConvertToLowerCase",
"ctrl-alt-u": "editor::ConvertToUpperCamelCase",
"ctrl-_": "editor::ConvertToSnakeCase"
"ctrl-shift-right": "editor::SelectToNextSubwordEnd"
}
},
{
"context": "Editor && mode == full",
"bindings": {}
"bindings": {
"cmd-alt-enter": "editor::NewlineAbove"
}
},
{
"context": "BufferSearchBar",
@@ -88,9 +85,5 @@
{
"context": "ProjectPanel",
"bindings": {}
},
{
"context": "Dock",
"bindings": {}
}
]

View File

@@ -101,21 +101,9 @@
"vim::SwitchMode",
"Normal"
],
"v": "vim::ToggleVisual",
"shift-v": "vim::ToggleVisualLine",
"ctrl-v": "vim::ToggleVisualBlock",
"ctrl-q": "vim::ToggleVisualBlock",
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
"ctrl-f": "vim::PageDown",
"pagedown": "vim::PageDown",
"ctrl-b": "vim::PageUp",
"pageup": "vim::PageUp",
"ctrl-d": "vim::ScrollDown",
"ctrl-u": "vim::ScrollUp",
"ctrl-e": "vim::LineDown",
"ctrl-y": "vim::LineUp",
// "g" commands
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
@@ -137,67 +125,10 @@
"partialWord": true
}
],
"g j": [
"vim::Down",
{
"displayLines": true
}
],
"g down": [
"vim::Down",
{
"displayLines": true
}
],
"g k": [
"vim::Up",
{
"displayLines": true
}
],
"g up": [
"vim::Up",
{
"displayLines": true
}
],
"g $": [
"vim::EndOfLine",
{
"displayLines": true
}
],
"g end": [
"vim::EndOfLine",
{
"displayLines": true
}
],
"g 0": [
"vim::StartOfLine",
{
"displayLines": true
}
],
"g home": [
"vim::StartOfLine",
{
"displayLines": true
}
],
"g ^": [
"vim::FirstNonWhitespace",
{
"displayLines": true
}
],
// z commands
"z t": "editor::ScrollCursorTop",
"z z": "editor::ScrollCursorCenter",
"z b": "editor::ScrollCursorBottom",
"z c": "editor::Fold",
"z o": "editor::UnfoldLines",
"z f": "editor::FoldSelectedRanges",
// Count support
"1": [
"vim::Number",
@@ -305,14 +236,6 @@
"ctrl-w ctrl-q": "pane::CloseAllItems"
}
},
{
// escape is in its own section so that it cancels a pending count.
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
"bindings": {
"escape": "editor::Cancel",
"ctrl+[": "editor::Cancel"
}
},
{
"context": "Editor && vim_mode == normal && (vim_operator == none || vim_operator == n) && !VimWaiting",
"bindings": {
@@ -343,13 +266,23 @@
"o": "vim::InsertLineBelow",
"shift-o": "vim::InsertLineAbove",
"~": "vim::ChangeCase",
"p": "vim::Paste",
"shift-p": [
"vim::Paste",
"v": [
"vim::SwitchMode",
{
"before": true
"Visual": {
"line": false
}
}
],
"shift-v": [
"vim::SwitchMode",
{
"Visual": {
"line": true
}
}
],
"p": "vim::Paste",
"u": "editor::Undo",
"ctrl-r": "editor::Redo",
"/": "vim::Search",
@@ -366,6 +299,14 @@
"backwards": true
}
],
"ctrl-f": "vim::PageDown",
"pagedown": "vim::PageDown",
"ctrl-b": "vim::PageUp",
"pageup": "vim::PageUp",
"ctrl-d": "vim::ScrollDown",
"ctrl-u": "vim::ScrollUp",
"ctrl-e": "vim::LineDown",
"ctrl-y": "vim::LineUp",
"r": [
"vim::PushOperator",
"Replace"
@@ -430,29 +371,16 @@
}
},
{
"context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
"context": "Editor && vim_mode == visual && !VimWaiting",
"bindings": {
"u": "editor::Undo",
"o": "vim::OtherEnd",
"shift-o": "vim::OtherEnd",
"c": "vim::VisualChange",
"d": "vim::VisualDelete",
"x": "vim::VisualDelete",
"y": "vim::VisualYank",
"p": "vim::Paste",
"shift-p": [
"vim::Paste",
{
"preserveClipboard": true
}
],
"p": "vim::VisualPaste",
"s": "vim::Substitute",
"c": "vim::Substitute",
"~": "vim::ChangeCase",
"shift-i": [
"vim::SwitchMode",
"Insert"
],
"shift-a": "vim::InsertAfter",
"r": [
"vim::PushOperator",
"Replace"
@@ -461,36 +389,12 @@
"vim::SwitchMode",
"Normal"
],
"escape": [
"vim::SwitchMode",
"Normal"
],
"ctrl+[": [
"vim::SwitchMode",
"Normal"
],
">": "editor::Indent",
"<": "editor::Outdent",
"i": [
"vim::PushOperator",
{
"Object": {
"around": false
}
}
],
"a": [
"vim::PushOperator",
{
"Object": {
"around": true
}
}
],
"<": "editor::Outdent"
}
},
{
"context": "Editor && vim_mode == insert && !menu",
"context": "Editor && vim_mode == insert",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",

View File

@@ -98,7 +98,6 @@
// Whether to show selections in the scrollbar.
"selections": true
},
"relative_line_numbers": false,
// Inlay hint related settings
"inlay_hints": {
// Global switch to toggle hints on and off, switched off by default.
@@ -123,29 +122,13 @@
// Amount of indentation for nested items.
"indent_size": 20
},
"collaboration_panel": {
// Whether to show the collaboration panel button in the status bar.
"button": true,
// Where to dock channels panel. Can be 'left' or 'right'.
"dock": "left",
// Default width of the channels panel.
"default_width": 240
},
"assistant": {
// Whether to show the assistant panel button in the status bar.
"button": true,
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
"dock": "right",
// Default width when the assistant is docked to the left or right.
"default_width": 640,
// Default height when the assistant is docked to the bottom.
"default_height": 320,
// The default OpenAI model to use when starting new conversations. This
// setting can take two values:
//
// 1. "gpt-3.5-turbo-0613""
// 2. "gpt-4-0613""
"default_open_ai_model": "gpt-4-0613"
"default_height": 320
},
// Whether the screen sharing icon is shown in the os status bar.
"show_call_status_icon": true,
@@ -231,9 +214,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": {
@@ -285,6 +266,8 @@
// "directory": "~/zed/projects/"
// }
// }
//
//
"working_directory": "current_project_directory",
// Set the cursor blinking behavior in the terminal.
// May take 4 values:
@@ -333,32 +316,13 @@
// "line_height": {
// "custom": 2
// },
"line_height": "comfortable",
// Activate the python virtual environment, if one is found, in the
// terminal's working directory (as resolved by the working_directory
// setting). Set this to "off" to disable this behavior.
"detect_venv": {
"on": {
// 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"
],
// Can also be 'csh' and 'fish'
"activate_script": "default"
}
}
"line_height": "comfortable"
// Set the terminal's font size. If this option is not included,
// the terminal will default to matching the buffer's font size.
// "font_size": "15",
// "font_size": "15"
// Set the terminal's font family. If this option is not included,
// the terminal will default to matching the buffer's font family.
// "font_family": "Zed Mono",
// ---
// "font_family": "Zed Mono"
},
// Difference settings for semantic_index
"semantic_index": {

View File

@@ -318,7 +318,7 @@ impl View for ActivityIndicator {
on_click,
} = self.content_to_render(cx);
let mut element = MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
let theme = &theme::current(cx).workspace.status_bar.lsp_status;
let style = if state.hovered() && on_click.is_some() {
theme.hovered.as_ref().unwrap_or(&theme.default)

View File

@@ -24,9 +24,7 @@ workspace = { path = "../workspace" }
anyhow.workspace = true
chrono = { version = "0.4", features = ["serde"] }
futures.workspace = true
indoc.workspace = true
isahc.workspace = true
ordered-float.workspace = true
regex.workspace = true
schemars.workspace = true
serde.workspace = true
@@ -37,8 +35,3 @@ tiktoken-rs = "0.4"
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
log.workspace = true
rand.workspace = true

View File

@@ -1,33 +1,27 @@
pub mod assistant;
mod assistant_settings;
mod streaming_diff;
use anyhow::{anyhow, Result};
use anyhow::Result;
pub use assistant::AssistantPanel;
use assistant_settings::OpenAIModel;
use chrono::{DateTime, Local};
use collections::HashMap;
use fs::Fs;
use futures::{io::BufReader, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use gpui::{executor::Background, AppContext};
use isahc::{http::StatusCode, Request, RequestExt};
use futures::StreamExt;
use gpui::AppContext;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{
cmp::Reverse,
ffi::OsStr,
fmt::{self, Display},
io,
path::PathBuf,
sync::Arc,
};
use util::paths::CONVERSATIONS_DIR;
const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
// Data types for chat completion requests
#[derive(Debug, Serialize)]
pub struct OpenAIRequest {
struct OpenAIRequest {
model: String,
messages: Vec<RequestMessage>,
stream: bool,
@@ -66,7 +60,7 @@ struct SavedConversation {
messages: Vec<SavedMessage>,
message_metadata: HashMap<MessageId, MessageMetadata>,
summary: String,
model: OpenAIModel,
model: String,
}
impl SavedConversation {
@@ -121,7 +115,7 @@ struct RequestMessage {
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct ResponseMessage {
struct ResponseMessage {
role: Option<Role>,
content: Option<String>,
}
@@ -155,7 +149,7 @@ impl Display for Role {
}
#[derive(Deserialize, Debug)]
pub struct OpenAIResponseStreamEvent {
struct OpenAIResponseStreamEvent {
pub id: Option<String>,
pub object: String,
pub created: u32,
@@ -165,14 +159,14 @@ pub struct OpenAIResponseStreamEvent {
}
#[derive(Deserialize, Debug)]
pub struct Usage {
struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Deserialize, Debug)]
pub struct ChatChoiceDelta {
struct ChatChoiceDelta {
pub index: u32,
pub delta: ResponseMessage,
pub finish_reason: Option<String>,
@@ -196,97 +190,3 @@ struct OpenAIChoice {
pub fn init(cx: &mut AppContext) {
assistant::init(cx);
}
pub async fn stream_completion(
api_key: String,
executor: Arc<Background>,
mut request: OpenAIRequest,
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
request.stream = true;
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
let json_data = serde_json::to_string(&request)?;
let mut response = Request::post(format!("{OPENAI_API_URL}/chat/completions"))
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
.body(json_data)?
.send_async()
.await?;
let status = response.status();
if status == StatusCode::OK {
executor
.spawn(async move {
let mut lines = BufReader::new(response.body_mut()).lines();
fn parse_line(
line: Result<String, io::Error>,
) -> Result<Option<OpenAIResponseStreamEvent>> {
if let Some(data) = line?.strip_prefix("data: ") {
let event = serde_json::from_str(&data)?;
Ok(Some(event))
} else {
Ok(None)
}
}
while let Some(line) = lines.next().await {
if let Some(event) = parse_line(line).transpose() {
let done = event.as_ref().map_or(false, |event| {
event
.choices
.last()
.map_or(false, |choice| choice.finish_reason.is_some())
});
if tx.unbounded_send(event).is_err() {
break;
}
if done {
break;
}
}
}
anyhow::Ok(())
})
.detach();
Ok(rx)
} else {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
#[derive(Deserialize)]
struct OpenAIResponse {
error: OpenAIError,
}
#[derive(Deserialize)]
struct OpenAIError {
message: String,
}
match serde_json::from_str::<OpenAIResponse>(&body) {
Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
"Failed to connect to OpenAI API: {}",
response.error.message,
)),
_ => Err(anyhow!(
"Failed to connect to OpenAI API: {} {}",
response.status(),
body,
)),
}
}
}
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::init();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,37 +3,6 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Setting;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum OpenAIModel {
#[serde(rename = "gpt-3.5-turbo-0613")]
ThreePointFiveTurbo,
#[serde(rename = "gpt-4-0613")]
Four,
}
impl OpenAIModel {
pub fn full_name(&self) -> &'static str {
match self {
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo-0613",
OpenAIModel::Four => "gpt-4-0613",
}
}
pub fn short_name(&self) -> &'static str {
match self {
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo",
OpenAIModel::Four => "gpt-4",
}
}
pub fn cycle(&self) -> Self {
match self {
OpenAIModel::ThreePointFiveTurbo => OpenAIModel::Four,
OpenAIModel::Four => OpenAIModel::ThreePointFiveTurbo,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AssistantDockPosition {
@@ -44,20 +13,16 @@ pub enum AssistantDockPosition {
#[derive(Deserialize, Debug)]
pub struct AssistantSettings {
pub button: bool,
pub dock: AssistantDockPosition,
pub default_width: f32,
pub default_height: f32,
pub default_open_ai_model: OpenAIModel,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct AssistantSettingsContent {
pub button: Option<bool>,
pub dock: Option<AssistantDockPosition>,
pub default_width: Option<f32>,
pub default_height: Option<f32>,
pub default_open_ai_model: Option<OpenAIModel>,
}
impl Setting for AssistantSettings {

View File

@@ -1,293 +0,0 @@
use collections::HashMap;
use ordered_float::OrderedFloat;
use std::{
cmp,
fmt::{self, Debug},
ops::Range,
};
struct Matrix {
cells: Vec<f64>,
rows: usize,
cols: usize,
}
impl Matrix {
fn new() -> Self {
Self {
cells: Vec::new(),
rows: 0,
cols: 0,
}
}
fn resize(&mut self, rows: usize, cols: usize) {
self.cells.resize(rows * cols, 0.);
self.rows = rows;
self.cols = cols;
}
fn get(&self, row: usize, col: usize) -> f64 {
if row >= self.rows {
panic!("row out of bounds")
}
if col >= self.cols {
panic!("col out of bounds")
}
self.cells[col * self.rows + row]
}
fn set(&mut self, row: usize, col: usize, value: f64) {
if row >= self.rows {
panic!("row out of bounds")
}
if col >= self.cols {
panic!("col out of bounds")
}
self.cells[col * self.rows + row] = value;
}
}
impl Debug for Matrix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
for i in 0..self.rows {
for j in 0..self.cols {
write!(f, "{:5}", self.get(i, j))?;
}
writeln!(f)?;
}
Ok(())
}
}
#[derive(Debug)]
pub enum Hunk {
Insert { text: String },
Remove { len: usize },
Keep { len: usize },
}
pub struct StreamingDiff {
old: Vec<char>,
new: Vec<char>,
scores: Matrix,
old_text_ix: usize,
new_text_ix: usize,
equal_runs: HashMap<(usize, usize), u32>,
}
impl StreamingDiff {
const INSERTION_SCORE: f64 = -1.;
const DELETION_SCORE: f64 = -20.;
const EQUALITY_BASE: f64 = 1.8;
const MAX_EQUALITY_EXPONENT: i32 = 16;
pub fn new(old: String) -> Self {
let old = old.chars().collect::<Vec<_>>();
let mut scores = Matrix::new();
scores.resize(old.len() + 1, 1);
for i in 0..=old.len() {
scores.set(i, 0, i as f64 * Self::DELETION_SCORE);
}
Self {
old,
new: Vec::new(),
scores,
old_text_ix: 0,
new_text_ix: 0,
equal_runs: Default::default(),
}
}
pub fn push_new(&mut self, text: &str) -> Vec<Hunk> {
self.new.extend(text.chars());
self.scores.resize(self.old.len() + 1, self.new.len() + 1);
for j in self.new_text_ix + 1..=self.new.len() {
self.scores.set(0, j, j as f64 * Self::INSERTION_SCORE);
for i in 1..=self.old.len() {
let insertion_score = self.scores.get(i, j - 1) + Self::INSERTION_SCORE;
let deletion_score = self.scores.get(i - 1, j) + Self::DELETION_SCORE;
let equality_score = if self.old[i - 1] == self.new[j - 1] {
let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0);
equal_run += 1;
self.equal_runs.insert((i, j), equal_run);
let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT);
self.scores.get(i - 1, j - 1) + Self::EQUALITY_BASE.powi(exponent)
} else {
f64::NEG_INFINITY
};
let score = insertion_score.max(deletion_score).max(equality_score);
self.scores.set(i, j, score);
}
}
let mut max_score = f64::NEG_INFINITY;
let mut next_old_text_ix = self.old_text_ix;
let next_new_text_ix = self.new.len();
for i in self.old_text_ix..=self.old.len() {
let score = self.scores.get(i, next_new_text_ix);
if score > max_score {
max_score = score;
next_old_text_ix = i;
}
}
let hunks = self.backtrack(next_old_text_ix, next_new_text_ix);
self.old_text_ix = next_old_text_ix;
self.new_text_ix = next_new_text_ix;
hunks
}
fn backtrack(&self, old_text_ix: usize, new_text_ix: usize) -> Vec<Hunk> {
let mut pending_insert: Option<Range<usize>> = None;
let mut hunks = Vec::new();
let mut i = old_text_ix;
let mut j = new_text_ix;
while (i, j) != (self.old_text_ix, self.new_text_ix) {
let insertion_score = if j > self.new_text_ix {
Some((i, j - 1))
} else {
None
};
let deletion_score = if i > self.old_text_ix {
Some((i - 1, j))
} else {
None
};
let equality_score = if i > self.old_text_ix && j > self.new_text_ix {
if self.old[i - 1] == self.new[j - 1] {
Some((i - 1, j - 1))
} else {
None
}
} else {
None
};
let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]
.iter()
.max_by_key(|cell| cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j))))
.unwrap()
.unwrap();
if prev_i == i && prev_j == j - 1 {
if let Some(pending_insert) = pending_insert.as_mut() {
pending_insert.start = prev_j;
} else {
pending_insert = Some(prev_j..j);
}
} else {
if let Some(range) = pending_insert.take() {
hunks.push(Hunk::Insert {
text: self.new[range].iter().collect(),
});
}
let char_len = self.old[i - 1].len_utf8();
if prev_i == i - 1 && prev_j == j {
if let Some(Hunk::Remove { len }) = hunks.last_mut() {
*len += char_len;
} else {
hunks.push(Hunk::Remove { len: char_len })
}
} else {
if let Some(Hunk::Keep { len }) = hunks.last_mut() {
*len += char_len;
} else {
hunks.push(Hunk::Keep { len: char_len })
}
}
}
i = prev_i;
j = prev_j;
}
if let Some(range) = pending_insert.take() {
hunks.push(Hunk::Insert {
text: self.new[range].iter().collect(),
});
}
hunks.reverse();
hunks
}
pub fn finish(self) -> Vec<Hunk> {
self.backtrack(self.old.len(), self.new.len())
}
}
#[cfg(test)]
mod tests {
use std::env;
use super::*;
use rand::prelude::*;
#[gpui::test(iterations = 100)]
fn test_random_diffs(mut rng: StdRng) {
let old_text_len = env::var("OLD_TEXT_LEN")
.map(|i| i.parse().expect("invalid `OLD_TEXT_LEN` variable"))
.unwrap_or(10);
let new_text_len = env::var("NEW_TEXT_LEN")
.map(|i| i.parse().expect("invalid `NEW_TEXT_LEN` variable"))
.unwrap_or(10);
let old = util::RandomCharIter::new(&mut rng)
.take(old_text_len)
.collect::<String>();
log::info!("old text: {:?}", old);
let mut diff = StreamingDiff::new(old.clone());
let mut hunks = Vec::new();
let mut new_len = 0;
let mut new = String::new();
while new_len < new_text_len {
let new_chunk_len = rng.gen_range(1..=new_text_len - new_len);
let new_chunk = util::RandomCharIter::new(&mut rng)
.take(new_len)
.collect::<String>();
log::info!("new chunk: {:?}", new_chunk);
new_len += new_chunk_len;
new.push_str(&new_chunk);
let new_hunks = diff.push_new(&new_chunk);
log::info!("hunks: {:?}", new_hunks);
hunks.extend(new_hunks);
}
let final_hunks = diff.finish();
log::info!("final hunks: {:?}", final_hunks);
hunks.extend(final_hunks);
log::info!("new text: {:?}", new);
let mut old_ix = 0;
let mut new_ix = 0;
let mut patched = String::new();
for hunk in hunks {
match hunk {
Hunk::Keep { len } => {
assert_eq!(&old[old_ix..old_ix + len], &new[new_ix..new_ix + len]);
patched.push_str(&old[old_ix..old_ix + len]);
old_ix += len;
new_ix += len;
}
Hunk::Remove { len } => {
old_ix += len;
}
Hunk::Insert { text } => {
assert_eq!(text, &new[new_ix..new_ix + text.len()]);
patched.push_str(&text);
new_ix += text.len();
}
}
}
assert_eq!(patched, new);
}
}

View File

@@ -13,7 +13,7 @@ gpui = { path = "../gpui" }
collections = { path = "../collections" }
util = { path = "../util" }
rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
rodio = "0.17.1"
log.workspace = true

View File

@@ -39,43 +39,29 @@ pub struct Audio {
impl Audio {
pub fn new() -> Self {
let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
Self {
_output_stream: None,
output_handle: None,
_output_stream,
output_handle,
}
}
fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
if self.output_handle.is_none() {
let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
self.output_handle = output_handle;
self._output_stream = _output_stream;
}
self.output_handle.as_ref()
}
pub fn play_sound(sound: Sound, cx: &mut AppContext) {
pub fn play_sound(sound: Sound, cx: &AppContext) {
if !cx.has_global::<Self>() {
return;
}
cx.update_global::<Self, _, _>(|this, cx| {
let output_handle = this.ensure_output_exists()?;
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
output_handle.play_raw(source).log_err()?;
Some(())
});
}
let this = cx.global::<Self>();
pub fn end_call(cx: &mut AppContext) {
if !cx.has_global::<Self>() {
let Some(output_handle) = this.output_handle.as_ref() else {
return;
}
};
cx.update_global::<Self, _, _>(|this, _| {
this._output_stream.take();
this.output_handle.take();
});
let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else {
return;
};
output_handle.play_raw(source).log_err();
}
}

View File

@@ -31,7 +31,7 @@ impl View for UpdateNotification {
let app_name = cx.global::<ReleaseChannel>().display_name();
MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
Flex::column()
.with_child(
Flex::row()
@@ -48,7 +48,7 @@ impl View for UpdateNotification {
.flex(1., true),
)
.with_child(
MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)

View File

@@ -50,7 +50,7 @@ impl View for Breadcrumbs {
let not_editor = active_item.downcast::<editor::Editor>().is_none();
let theme = theme::current(cx).clone();
let style = &theme.workspace.toolbar.breadcrumbs;
let style = &theme.workspace.breadcrumbs;
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
Some(breadcrumbs) => breadcrumbs,
@@ -60,7 +60,7 @@ impl View for Breadcrumbs {
.map(|breadcrumb| {
Text::new(
breadcrumb.text,
theme.workspace.toolbar.breadcrumbs.default.text.clone(),
theme.workspace.breadcrumbs.default.text.clone(),
)
.with_highlights(breadcrumb.highlights.unwrap_or_default())
.into_any()
@@ -68,10 +68,10 @@ impl View for Breadcrumbs {
let crumbs = Flex::row()
.with_children(Itertools::intersperse_with(breadcrumbs, || {
Label::new(" ", style.default.text.clone()).into_any()
Label::new(" ", style.default.text.clone()).into_any()
}))
.constrained()
.with_height(theme.workspace.toolbar.breadcrumb_height)
.with_height(theme.workspace.breadcrumb_height)
.contained();
if not_editor || !self.pane_focused {
@@ -82,7 +82,7 @@ impl View for Breadcrumbs {
.into_any();
}
MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
let style = style.style_for(state);
crumbs.with_style(style.container)
})

View File

@@ -13,7 +13,6 @@ test-support = [
"client/test-support",
"collections/test-support",
"gpui/test-support",
"gpui_platform",
"live_kit_client/test-support",
"project/test-support",
"util/test-support"
@@ -21,11 +20,9 @@ test-support = [
[dependencies]
audio = { path = "../audio" }
channel = { path = "../channel" }
client = { path = "../client" }
collections = { path = "../collections" }
gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
log.workspace = true
live_kit_client = { path = "../live_kit_client" }
fs = { path = "../fs" }

View File

@@ -5,9 +5,7 @@ pub mod room;
use std::sync::Arc;
use anyhow::{anyhow, Result};
use audio::Audio;
use call_settings::CallSettings;
use channel::ChannelId;
use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore};
use collections::HashSet;
use futures::{future::Shared, FutureExt};
@@ -77,10 +75,6 @@ impl ActiveCall {
}
}
pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
self.room()?.read(cx).channel_id()
}
async fn handle_incoming_call(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::IncomingCall>,
@@ -273,43 +267,16 @@ impl ActiveCall {
.borrow_mut()
.take()
.ok_or_else(|| anyhow!("no incoming call"))?;
Self::report_call_event_for_room("decline incoming", call.room_id, None, &self.client, cx);
Self::report_call_event_for_room("decline incoming", call.room_id, &self.client, cx);
self.client.send(proto::DeclineCall {
room_id: call.room_id,
})?;
Ok(())
}
pub fn join_channel(
&mut self,
channel_id: u64,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if let Some(room) = self.room().cloned() {
if room.read(cx).channel_id() == Some(channel_id) {
return Task::ready(Ok(()));
} else {
room.update(cx, |room, cx| room.clear_state(cx));
}
}
let join = Room::join_channel(channel_id, self.client.clone(), self.user_store.clone(), cx);
cx.spawn(|this, mut cx| async move {
let room = join.await?;
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("join channel", cx)
});
Ok(())
})
}
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
cx.notify();
self.report_call_event("hang up", cx);
Audio::end_call(cx);
if let Some((room, _)) = self.room.take() {
room.update(cx, |room, cx| room.leave(cx))
} else {
@@ -405,31 +372,19 @@ impl ActiveCall {
fn report_call_event(&self, operation: &'static str, cx: &AppContext) {
if let Some(room) = self.room() {
let room = room.read(cx);
Self::report_call_event_for_room(
operation,
room.id(),
room.channel_id(),
&self.client,
cx,
)
Self::report_call_event_for_room(operation, room.read(cx).id(), &self.client, cx)
}
}
pub fn report_call_event_for_room(
operation: &'static str,
room_id: u64,
channel_id: Option<u64>,
client: &Arc<Client>,
cx: &AppContext,
) {
let telemetry = client.telemetry();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Call {
operation,
room_id,
channel_id,
};
let event = ClickhouseEvent::Call { operation, room_id };
telemetry.report_clickhouse_event(event, telemetry_settings);
}
}

View File

@@ -49,7 +49,6 @@ pub enum Event {
pub struct Room {
id: u64,
channel_id: Option<u64>,
live_kit: Option<LiveKitRoom>,
status: RoomStatus,
shared_projects: HashSet<WeakModelHandle<Project>>,
@@ -94,25 +93,8 @@ impl Entity for Room {
}
impl Room {
pub fn channel_id(&self) -> Option<u64> {
self.channel_id
}
#[cfg(any(test, feature = "test-support"))]
pub fn is_connected(&self) -> bool {
if let Some(live_kit) = self.live_kit.as_ref() {
matches!(
*live_kit.room.status().borrow(),
live_kit_client::ConnectionState::Connected { .. }
)
} else {
false
}
}
fn new(
id: u64,
channel_id: Option<u64>,
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
@@ -203,7 +185,6 @@ impl Room {
Self {
id,
channel_id,
live_kit: live_kit_room,
status: RoomStatus::Online,
shared_projects: Default::default(),
@@ -236,7 +217,6 @@ impl Room {
let room = cx.add_model(|cx| {
Self::new(
room_proto.id,
None,
response.live_kit_connection_info,
client,
user_store,
@@ -268,62 +248,33 @@ impl Room {
})
}
pub(crate) fn join_channel(
channel_id: u64,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
cx: &mut AppContext,
) -> Task<Result<ModelHandle<Self>>> {
cx.spawn(|cx| async move {
Self::from_join_response(
client.request(proto::JoinChannel { channel_id }).await?,
client,
user_store,
cx,
)
})
}
pub(crate) fn join(
call: &IncomingCall,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
cx: &mut AppContext,
) -> Task<Result<ModelHandle<Self>>> {
let id = call.room_id;
cx.spawn(|cx| async move {
Self::from_join_response(
client.request(proto::JoinRoom { id }).await?,
client,
user_store,
cx,
)
})
}
let room_id = call.room_id;
cx.spawn(|mut cx| async move {
let response = client.request(proto::JoinRoom { id: room_id }).await?;
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.add_model(|cx| {
Self::new(
room_id,
response.live_kit_connection_info,
client,
user_store,
cx,
)
});
room.update(&mut cx, |room, cx| {
room.leave_when_empty = true;
room.apply_room_update(room_proto, cx)?;
anyhow::Ok(())
})?;
fn from_join_response(
response: proto::JoinRoomResponse,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
mut cx: AsyncAppContext,
) -> Result<ModelHandle<Self>> {
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.add_model(|cx| {
Self::new(
room_proto.id,
response.channel_id,
response.live_kit_connection_info,
client,
user_store,
cx,
)
});
room.update(&mut cx, |room, cx| {
room.leave_when_empty = room.channel_id.is_none();
room.apply_room_update(room_proto, cx)?;
anyhow::Ok(())
})?;
Ok(room)
Ok(room)
})
}
fn should_leave(&self) -> bool {
@@ -346,18 +297,7 @@ impl Room {
}
log::info!("leaving room");
Audio::play_sound(Sound::Leave, cx);
self.clear_state(cx);
let leave_room = self.client.request(proto::LeaveRoom {});
cx.background().spawn(async move {
leave_room.await?;
anyhow::Ok(())
})
}
pub(crate) fn clear_state(&mut self, cx: &mut AppContext) {
for project in self.shared_projects.drain() {
if let Some(project) = project.upgrade(cx) {
project.update(cx, |project, cx| {
@@ -374,6 +314,8 @@ impl Room {
}
}
Audio::play_sound(Sound::Leave, cx);
self.status = RoomStatus::Offline;
self.remote_participants.clear();
self.pending_participants.clear();
@@ -382,6 +324,12 @@ impl Room {
self.live_kit.take();
self.pending_room_update.take();
self.maintain_connection.take();
let leave_room = self.client.request(proto::LeaveRoom {});
cx.background().spawn(async move {
leave_room.await?;
anyhow::Ok(())
})
}
async fn maintain_connection(
@@ -644,9 +592,7 @@ impl Room {
if let Some(participants) = remote_participants.log_err() {
for (participant, user) in room.participants.into_iter().zip(participants) {
let Some(peer_id) = participant.peer_id else {
continue;
};
let Some(peer_id) = participant.peer_id else { continue };
this.participant_user_ids.insert(participant.user_id);
let old_projects = this
@@ -1120,11 +1066,11 @@ impl Room {
})
}
pub fn is_muted(&self, cx: &AppContext) -> bool {
pub fn is_muted(&self) -> bool {
self.live_kit
.as_ref()
.and_then(|live_kit| match &live_kit.microphone_track {
LocalTrack::None => Some(settings::get::<CallSettings>(cx).mute_on_join),
LocalTrack::None => Some(true),
LocalTrack::Pending { muted, .. } => Some(*muted),
LocalTrack::Published { muted, .. } => Some(*muted),
})
@@ -1314,7 +1260,7 @@ impl Room {
}
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
let should_mute = !self.is_muted(cx);
let should_mute = !self.is_muted();
if let Some(live_kit) = self.live_kit.as_mut() {
if matches!(live_kit.microphone_track, LocalTrack::None) {
return Ok(self.share_microphone(cx));

View File

@@ -1,51 +0,0 @@
[package]
name = "channel"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/channel.rs"
doctest = false
[features]
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies]
client = { path = "../client" }
collections = { path = "../collections" }
db = { path = "../db" }
gpui = { path = "../gpui" }
util = { path = "../util" }
rpc = { path = "../rpc" }
text = { path = "../text" }
language = { path = "../language" }
settings = { path = "../settings" }
feature_flags = { path = "../feature_flags" }
sum_tree = { path = "../sum_tree" }
anyhow.workspace = true
futures.workspace = true
image = "0.23"
lazy_static.workspace = true
log.workspace = true
parking_lot.workspace = true
postage.workspace = true
rand.workspace = true
schemars.workspace = true
smol.workspace = true
thiserror.workspace = true
time.workspace = true
tiny_http = "0.8"
uuid = { version = "1.1.2", features = ["v4"] }
url = "2.2"
serde.workspace = true
serde_derive.workspace = true
tempfile = "3"
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }

View File

@@ -1,14 +0,0 @@
mod channel_store;
pub mod channel_buffer;
use std::sync::Arc;
pub use channel_store::*;
use client::Client;
#[cfg(test)]
mod channel_store_tests;
pub fn init(client: &Arc<Client>) {
channel_buffer::init(client);
}

View File

@@ -1,197 +0,0 @@
use crate::Channel;
use anyhow::Result;
use client::Client;
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle};
use rpc::{proto, TypedEnvelope};
use std::sync::Arc;
use util::ResultExt;
pub(crate) fn init(client: &Arc<Client>) {
client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer);
client.add_model_message_handler(ChannelBuffer::handle_add_channel_buffer_collaborator);
client.add_model_message_handler(ChannelBuffer::handle_remove_channel_buffer_collaborator);
}
pub struct ChannelBuffer {
pub(crate) channel: Arc<Channel>,
connected: bool,
collaborators: Vec<proto::Collaborator>,
buffer: ModelHandle<language::Buffer>,
client: Arc<Client>,
subscription: Option<client::Subscription>,
}
pub enum Event {
CollaboratorsChanged,
Disconnected,
}
impl Entity for ChannelBuffer {
type Event = Event;
fn release(&mut self, _: &mut AppContext) {
if self.connected {
self.client
.send(proto::LeaveChannelBuffer {
channel_id: self.channel.id,
})
.log_err();
}
}
}
impl ChannelBuffer {
pub(crate) async fn new(
channel: Arc<Channel>,
client: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<ModelHandle<Self>> {
let response = client
.request(proto::JoinChannelBuffer {
channel_id: channel.id,
})
.await?;
let base_text = response.base_text;
let operations = response
.operations
.into_iter()
.map(language::proto::deserialize_operation)
.collect::<Result<Vec<_>, _>>()?;
let collaborators = response.collaborators;
let buffer = cx.add_model(|_| {
language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
});
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
let subscription = client.subscribe_to_entity(channel.id)?;
anyhow::Ok(cx.add_model(|cx| {
cx.subscribe(&buffer, Self::on_buffer_update).detach();
Self {
buffer,
client,
connected: true,
collaborators,
channel,
subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())),
}
}))
}
async fn handle_update_channel_buffer(
this: ModelHandle<Self>,
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let ops = update_channel_buffer
.payload
.operations
.into_iter()
.map(language::proto::deserialize_operation)
.collect::<Result<Vec<_>, _>>()?;
this.update(&mut cx, |this, cx| {
cx.notify();
this.buffer
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
})?;
Ok(())
}
async fn handle_add_channel_buffer_collaborator(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::AddChannelBufferCollaborator>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let collaborator = envelope.payload.collaborator.ok_or_else(|| {
anyhow::anyhow!(
"Should have gotten a collaborator in the AddChannelBufferCollaborator message"
)
})?;
this.update(&mut cx, |this, cx| {
this.collaborators.push(collaborator);
cx.emit(Event::CollaboratorsChanged);
cx.notify();
});
Ok(())
}
async fn handle_remove_channel_buffer_collaborator(
this: ModelHandle<Self>,
message: TypedEnvelope<proto::RemoveChannelBufferCollaborator>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.collaborators.retain(|collaborator| {
if collaborator.peer_id == message.payload.peer_id {
this.buffer.update(cx, |buffer, cx| {
buffer.remove_peer(collaborator.replica_id as u16, cx)
});
false
} else {
true
}
});
cx.emit(Event::CollaboratorsChanged);
cx.notify();
});
Ok(())
}
fn on_buffer_update(
&mut self,
_: ModelHandle<language::Buffer>,
event: &language::Event,
_: &mut ModelContext<Self>,
) {
if let language::Event::Operation(operation) = event {
let operation = language::proto::serialize_operation(operation);
self.client
.send(proto::UpdateChannelBuffer {
channel_id: self.channel.id,
operations: vec![operation],
})
.log_err();
}
}
pub fn buffer(&self) -> ModelHandle<language::Buffer> {
self.buffer.clone()
}
pub fn collaborators(&self) -> &[proto::Collaborator] {
&self.collaborators
}
pub fn channel(&self) -> Arc<Channel> {
self.channel.clone()
}
pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) {
if self.connected {
self.connected = false;
self.subscription.take();
cx.emit(Event::Disconnected);
cx.notify()
}
}
pub fn is_connected(&self) -> bool {
self.connected
}
pub fn replica_id(&self, cx: &AppContext) -> u16 {
self.buffer.read(cx).replica_id()
}
}

View File

@@ -1,656 +0,0 @@
use crate::channel_buffer::ChannelBuffer;
use anyhow::{anyhow, Result};
use client::{Client, Status, Subscription, User, UserId, UserStore};
use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
use rpc::{proto, TypedEnvelope};
use std::sync::Arc;
use util::ResultExt;
pub type ChannelId = u64;
pub struct ChannelStore {
channels_by_id: HashMap<ChannelId, Arc<Channel>>,
channel_paths: Vec<Vec<ChannelId>>,
channel_invitations: Vec<Arc<Channel>>,
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
channels_with_admin_privileges: HashSet<ChannelId>,
outgoing_invites: HashSet<(ChannelId, UserId)>,
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
opened_buffers: HashMap<ChannelId, OpenedChannelBuffer>,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
_rpc_subscription: Subscription,
_watch_connection_status: Task<()>,
_update_channels: Task<()>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Channel {
pub id: ChannelId,
pub name: String,
}
pub struct ChannelMembership {
pub user: Arc<User>,
pub kind: proto::channel_member::Kind,
pub admin: bool,
}
pub enum ChannelEvent {
ChannelCreated(ChannelId),
ChannelRenamed(ChannelId),
}
impl Entity for ChannelStore {
type Event = ChannelEvent;
}
pub enum ChannelMemberStatus {
Invited,
Member,
NotMember,
}
enum OpenedChannelBuffer {
Open(WeakModelHandle<ChannelBuffer>),
Loading(Shared<Task<Result<ModelHandle<ChannelBuffer>, Arc<anyhow::Error>>>>),
}
impl ChannelStore {
pub fn new(
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
cx: &mut ModelContext<Self>,
) -> Self {
let rpc_subscription =
client.add_message_handler(cx.handle(), Self::handle_update_channels);
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
let mut connection_status = client.status();
let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
while let Some(status) = connection_status.next().await {
if !status.is_connected() {
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
if matches!(status, Status::ConnectionLost | Status::SignedOut) {
this.handle_disconnect(cx);
} else {
this.disconnect_buffers(cx);
}
});
} else {
break;
}
}
}
});
Self {
channels_by_id: HashMap::default(),
channel_invitations: Vec::default(),
channel_paths: Vec::default(),
channel_participants: Default::default(),
channels_with_admin_privileges: Default::default(),
outgoing_invites: Default::default(),
opened_buffers: Default::default(),
update_channels_tx,
client,
user_store,
_rpc_subscription: rpc_subscription,
_watch_connection_status: watch_connection_status,
_update_channels: cx.spawn_weak(|this, mut cx| async move {
while let Some(update_channels) = update_channels_rx.next().await {
if let Some(this) = this.upgrade(&cx) {
let update_task = this.update(&mut cx, |this, cx| {
this.update_channels(update_channels, cx)
});
if let Some(update_task) = update_task {
update_task.await.log_err();
}
}
}
}),
}
}
pub fn has_children(&self, channel_id: ChannelId) -> bool {
self.channel_paths.iter().any(|path| {
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
path.len() > ix + 1
} else {
false
}
})
}
pub fn channel_count(&self) -> usize {
self.channel_paths.len()
}
pub fn channels(&self) -> impl '_ + Iterator<Item = (usize, &Arc<Channel>)> {
self.channel_paths.iter().map(move |path| {
let id = path.last().unwrap();
let channel = self.channel_for_id(*id).unwrap();
(path.len() - 1, channel)
})
}
pub fn channel_at_index(&self, ix: usize) -> Option<(usize, &Arc<Channel>)> {
let path = self.channel_paths.get(ix)?;
let id = path.last().unwrap();
let channel = self.channel_for_id(*id).unwrap();
Some((path.len() - 1, channel))
}
pub fn channel_invitations(&self) -> &[Arc<Channel>] {
&self.channel_invitations
}
pub fn channel_for_id(&self, channel_id: ChannelId) -> Option<&Arc<Channel>> {
self.channels_by_id.get(&channel_id)
}
pub fn open_channel_buffer(
&mut self,
channel_id: ChannelId,
cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<ChannelBuffer>>> {
// Make sure that a given channel buffer is only opened once per
// app instance, even if this method is called multiple times
// with the same channel id while the first task is still running.
let task = loop {
match self.opened_buffers.entry(channel_id) {
hash_map::Entry::Occupied(e) => match e.get() {
OpenedChannelBuffer::Open(buffer) => {
if let Some(buffer) = buffer.upgrade(cx) {
break Task::ready(Ok(buffer)).shared();
} else {
self.opened_buffers.remove(&channel_id);
continue;
}
}
OpenedChannelBuffer::Loading(task) => break task.clone(),
},
hash_map::Entry::Vacant(e) => {
let client = self.client.clone();
let task = cx
.spawn(|this, cx| async move {
let channel = this.read_with(&cx, |this, _| {
this.channel_for_id(channel_id).cloned().ok_or_else(|| {
Arc::new(anyhow!("no channel for id: {}", channel_id))
})
})?;
ChannelBuffer::new(channel, client, cx)
.await
.map_err(Arc::new)
})
.shared();
e.insert(OpenedChannelBuffer::Loading(task.clone()));
cx.spawn({
let task = task.clone();
|this, mut cx| async move {
let result = task.await;
this.update(&mut cx, |this, cx| match result {
Ok(buffer) => {
cx.observe_release(&buffer, move |this, _, _| {
this.opened_buffers.remove(&channel_id);
})
.detach();
this.opened_buffers.insert(
channel_id,
OpenedChannelBuffer::Open(buffer.downgrade()),
);
}
Err(error) => {
log::error!("failed to open channel buffer {error:?}");
this.opened_buffers.remove(&channel_id);
}
});
}
})
.detach();
break task;
}
}
};
cx.foreground()
.spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) })
}
pub fn is_user_admin(&self, channel_id: ChannelId) -> bool {
self.channel_paths.iter().any(|path| {
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
path[..=ix]
.iter()
.any(|id| self.channels_with_admin_privileges.contains(id))
} else {
false
}
})
}
pub fn channel_participants(&self, channel_id: ChannelId) -> &[Arc<User>] {
self.channel_participants
.get(&channel_id)
.map_or(&[], |v| v.as_slice())
}
pub fn create_channel(
&self,
name: &str,
parent_id: Option<ChannelId>,
cx: &mut ModelContext<Self>,
) -> Task<Result<ChannelId>> {
let client = self.client.clone();
let name = name.trim_start_matches("#").to_owned();
cx.spawn(|this, mut cx| async move {
let channel = client
.request(proto::CreateChannel { name, parent_id })
.await?
.channel
.ok_or_else(|| anyhow!("missing channel in response"))?;
let channel_id = channel.id;
this.update(&mut cx, |this, cx| {
let task = this.update_channels(
proto::UpdateChannels {
channels: vec![channel],
..Default::default()
},
cx,
);
assert!(task.is_none());
// This event is emitted because the collab panel wants to clear the pending edit state
// before this frame is rendered. But we can't guarantee that the collab panel's future
// will resolve before this flush_effects finishes. Synchronously emitting this event
// ensures that the collab panel will observe this creation before the frame completes
cx.emit(ChannelEvent::ChannelCreated(channel_id));
});
Ok(channel_id)
})
}
pub fn invite_member(
&mut self,
channel_id: ChannelId,
user_id: UserId,
admin: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("invite request already in progress")));
}
cx.notify();
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
let result = client
.request(proto::InviteChannelMember {
channel_id,
user_id,
admin,
})
.await;
this.update(&mut cx, |this, cx| {
this.outgoing_invites.remove(&(channel_id, user_id));
cx.notify();
});
result?;
Ok(())
})
}
pub fn remove_member(
&mut self,
channel_id: ChannelId,
user_id: u64,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("invite request already in progress")));
}
cx.notify();
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
let result = client
.request(proto::RemoveChannelMember {
channel_id,
user_id,
})
.await;
this.update(&mut cx, |this, cx| {
this.outgoing_invites.remove(&(channel_id, user_id));
cx.notify();
});
result?;
Ok(())
})
}
pub fn set_member_admin(
&mut self,
channel_id: ChannelId,
user_id: UserId,
admin: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("member request already in progress")));
}
cx.notify();
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
let result = client
.request(proto::SetChannelMemberAdmin {
channel_id,
user_id,
admin,
})
.await;
this.update(&mut cx, |this, cx| {
this.outgoing_invites.remove(&(channel_id, user_id));
cx.notify();
});
result?;
Ok(())
})
}
pub fn rename(
&mut self,
channel_id: ChannelId,
new_name: &str,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let client = self.client.clone();
let name = new_name.to_string();
cx.spawn(|this, mut cx| async move {
let channel = client
.request(proto::RenameChannel { channel_id, name })
.await?
.channel
.ok_or_else(|| anyhow!("missing channel in response"))?;
this.update(&mut cx, |this, cx| {
let task = this.update_channels(
proto::UpdateChannels {
channels: vec![channel],
..Default::default()
},
cx,
);
assert!(task.is_none());
// This event is emitted because the collab panel wants to clear the pending edit state
// before this frame is rendered. But we can't guarantee that the collab panel's future
// will resolve before this flush_effects finishes. Synchronously emitting this event
// ensures that the collab panel will observe this creation before the frame complete
cx.emit(ChannelEvent::ChannelRenamed(channel_id))
});
Ok(())
})
}
pub fn respond_to_channel_invite(
&mut self,
channel_id: ChannelId,
accept: bool,
) -> impl Future<Output = Result<()>> {
let client = self.client.clone();
async move {
client
.request(proto::RespondToChannelInvite { channel_id, accept })
.await?;
Ok(())
}
}
pub fn get_channel_member_details(
&self,
channel_id: ChannelId,
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<ChannelMembership>>> {
let client = self.client.clone();
let user_store = self.user_store.downgrade();
cx.spawn(|_, mut cx| async move {
let response = client
.request(proto::GetChannelMembers { channel_id })
.await?;
let user_ids = response.members.iter().map(|m| m.user_id).collect();
let user_store = user_store
.upgrade(&cx)
.ok_or_else(|| anyhow!("user store dropped"))?;
let users = user_store
.update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
.await?;
Ok(users
.into_iter()
.zip(response.members)
.filter_map(|(user, member)| {
Some(ChannelMembership {
user,
admin: member.admin,
kind: proto::channel_member::Kind::from_i32(member.kind)?,
})
})
.collect())
})
}
pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> {
let client = self.client.clone();
async move {
client.request(proto::RemoveChannel { channel_id }).await?;
Ok(())
}
}
pub fn has_pending_channel_invite_response(&self, _: &Arc<Channel>) -> bool {
false
}
pub fn has_pending_channel_invite(&self, channel_id: ChannelId, user_id: UserId) -> bool {
self.outgoing_invites.contains(&(channel_id, user_id))
}
async fn handle_update_channels(
this: ModelHandle<Self>,
message: TypedEnvelope<proto::UpdateChannels>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, _| {
this.update_channels_tx
.unbounded_send(message.payload)
.unwrap();
});
Ok(())
}
fn handle_disconnect(&mut self, cx: &mut ModelContext<'_, ChannelStore>) {
self.disconnect_buffers(cx);
self.channels_by_id.clear();
self.channel_invitations.clear();
self.channel_participants.clear();
self.channels_with_admin_privileges.clear();
self.channel_paths.clear();
self.outgoing_invites.clear();
cx.notify();
}
fn disconnect_buffers(&mut self, cx: &mut ModelContext<ChannelStore>) {
for (_, buffer) in self.opened_buffers.drain() {
if let OpenedChannelBuffer::Open(buffer) = buffer {
if let Some(buffer) = buffer.upgrade(cx) {
buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
}
}
}
}
pub(crate) fn update_channels(
&mut self,
payload: proto::UpdateChannels,
cx: &mut ModelContext<ChannelStore>,
) -> Option<Task<Result<()>>> {
if !payload.remove_channel_invitations.is_empty() {
self.channel_invitations
.retain(|channel| !payload.remove_channel_invitations.contains(&channel.id));
}
for channel in payload.channel_invitations {
match self
.channel_invitations
.binary_search_by_key(&channel.id, |c| c.id)
{
Ok(ix) => Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name,
Err(ix) => self.channel_invitations.insert(
ix,
Arc::new(Channel {
id: channel.id,
name: channel.name,
}),
),
}
}
let channels_changed = !payload.channels.is_empty() || !payload.remove_channels.is_empty();
if channels_changed {
if !payload.remove_channels.is_empty() {
self.channels_by_id
.retain(|channel_id, _| !payload.remove_channels.contains(channel_id));
self.channel_participants
.retain(|channel_id, _| !payload.remove_channels.contains(channel_id));
self.channels_with_admin_privileges
.retain(|channel_id| !payload.remove_channels.contains(channel_id));
for channel_id in &payload.remove_channels {
let channel_id = *channel_id;
if let Some(OpenedChannelBuffer::Open(buffer)) =
self.opened_buffers.remove(&channel_id)
{
if let Some(buffer) = buffer.upgrade(cx) {
buffer.update(cx, ChannelBuffer::disconnect);
}
}
}
}
for channel_proto in payload.channels {
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
Arc::make_mut(existing_channel).name = channel_proto.name;
} else {
let channel = Arc::new(Channel {
id: channel_proto.id,
name: channel_proto.name,
});
self.channels_by_id.insert(channel.id, channel.clone());
if let Some(parent_id) = channel_proto.parent_id {
let mut ix = 0;
while ix < self.channel_paths.len() {
let path = &self.channel_paths[ix];
if path.ends_with(&[parent_id]) {
let mut new_path = path.clone();
new_path.push(channel.id);
self.channel_paths.insert(ix + 1, new_path);
ix += 1;
}
ix += 1;
}
} else {
self.channel_paths.push(vec![channel.id]);
}
}
}
self.channel_paths.sort_by(|a, b| {
let a = Self::channel_path_sorting_key(a, &self.channels_by_id);
let b = Self::channel_path_sorting_key(b, &self.channels_by_id);
a.cmp(b)
});
self.channel_paths.dedup();
self.channel_paths.retain(|path| {
path.iter()
.all(|channel_id| self.channels_by_id.contains_key(channel_id))
});
}
for permission in payload.channel_permissions {
if permission.is_admin {
self.channels_with_admin_privileges
.insert(permission.channel_id);
} else {
self.channels_with_admin_privileges
.remove(&permission.channel_id);
}
}
cx.notify();
if payload.channel_participants.is_empty() {
return None;
}
let mut all_user_ids = Vec::new();
let channel_participants = payload.channel_participants;
for entry in &channel_participants {
for user_id in entry.participant_user_ids.iter() {
if let Err(ix) = all_user_ids.binary_search(user_id) {
all_user_ids.insert(ix, *user_id);
}
}
}
let users = self
.user_store
.update(cx, |user_store, cx| user_store.get_users(all_user_ids, cx));
Some(cx.spawn(|this, mut cx| async move {
let users = users.await?;
this.update(&mut cx, |this, cx| {
for entry in &channel_participants {
let mut participants: Vec<_> = entry
.participant_user_ids
.iter()
.filter_map(|user_id| {
users
.binary_search_by_key(&user_id, |user| &user.id)
.ok()
.map(|ix| users[ix].clone())
})
.collect();
participants.sort_by_key(|u| u.id);
this.channel_participants
.insert(entry.channel_id, participants);
}
cx.notify();
});
anyhow::Ok(())
}))
}
fn channel_path_sorting_key<'a>(
path: &'a [ChannelId],
channels_by_id: &'a HashMap<ChannelId, Arc<Channel>>,
) -> impl 'a + Iterator<Item = Option<&'a str>> {
path.iter()
.map(|id| Some(channels_by_id.get(id)?.name.as_str()))
}
}

View File

@@ -1,168 +0,0 @@
use super::*;
use client::{Client, UserStore};
use gpui::{AppContext, ModelHandle};
use rpc::proto;
use util::http::FakeHttpClient;
#[gpui::test]
fn test_update_channels(cx: &mut AppContext) {
let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone(), cx);
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx));
update_channels(
&channel_store,
proto::UpdateChannels {
channels: vec![
proto::Channel {
id: 1,
name: "b".to_string(),
parent_id: None,
},
proto::Channel {
id: 2,
name: "a".to_string(),
parent_id: None,
},
],
channel_permissions: vec![proto::ChannelPermission {
channel_id: 1,
is_admin: true,
}],
..Default::default()
},
cx,
);
assert_channels(
&channel_store,
&[
//
(0, "a".to_string(), false),
(0, "b".to_string(), true),
],
cx,
);
update_channels(
&channel_store,
proto::UpdateChannels {
channels: vec![
proto::Channel {
id: 3,
name: "x".to_string(),
parent_id: Some(1),
},
proto::Channel {
id: 4,
name: "y".to_string(),
parent_id: Some(2),
},
],
..Default::default()
},
cx,
);
assert_channels(
&channel_store,
&[
(0, "a".to_string(), false),
(1, "y".to_string(), false),
(0, "b".to_string(), true),
(1, "x".to_string(), true),
],
cx,
);
}
#[gpui::test]
fn test_dangling_channel_paths(cx: &mut AppContext) {
let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone(), cx);
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx));
update_channels(
&channel_store,
proto::UpdateChannels {
channels: vec![
proto::Channel {
id: 0,
name: "a".to_string(),
parent_id: None,
},
proto::Channel {
id: 1,
name: "b".to_string(),
parent_id: Some(0),
},
proto::Channel {
id: 2,
name: "c".to_string(),
parent_id: Some(1),
},
],
channel_permissions: vec![proto::ChannelPermission {
channel_id: 0,
is_admin: true,
}],
..Default::default()
},
cx,
);
// Sanity check
assert_channels(
&channel_store,
&[
//
(0, "a".to_string(), true),
(1, "b".to_string(), true),
(2, "c".to_string(), true),
],
cx,
);
update_channels(
&channel_store,
proto::UpdateChannels {
remove_channels: vec![1, 2],
..Default::default()
},
cx,
);
// Make sure that the 1/2/3 path is gone
assert_channels(&channel_store, &[(0, "a".to_string(), true)], cx);
}
fn update_channels(
channel_store: &ModelHandle<ChannelStore>,
message: proto::UpdateChannels,
cx: &mut AppContext,
) {
let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
assert!(task.is_none());
}
#[track_caller]
fn assert_channels(
channel_store: &ModelHandle<ChannelStore>,
expected_channels: &[(usize, String, bool)],
cx: &AppContext,
) {
let actual = channel_store.read_with(cx, |store, _| {
store
.channels()
.map(|(depth, channel)| {
(
depth,
channel.name.to_string(),
store.is_user_admin(channel.id),
)
})
.collect::<Vec<_>>()
});
assert_eq!(actual, expected_channels);
}

View File

@@ -9,22 +9,20 @@ path = "src/client.rs"
doctest = false
[features]
test-support = ["collections/test-support", "gpui/test-support", "gpui_platform", "rpc/test-support"]
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies]
collections = { path = "../collections" }
db = { path = "../db" }
gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
util = { path = "../util" }
rpc = { path = "../rpc" }
text = { path = "../text" }
settings = { path = "../settings" }
feature_flags = { path = "../feature_flags" }
staff_mode = { path = "../staff_mode" }
sum_tree = { path = "../sum_tree" }
anyhow.workspace = true
async-recursion = "0.3"
async-recursion.workspace = true
async-tungstenite = { version = "0.16", features = ["async-tls"] }
futures.workspace = true
image = "0.23"

View File

@@ -535,7 +535,6 @@ impl Client {
}
}
#[track_caller]
pub fn add_message_handler<M, E, H, F>(
self: &Arc<Self>,
model: ModelHandle<E>,
@@ -571,13 +570,7 @@ impl Client {
}),
);
if prev_handler.is_some() {
let location = std::panic::Location::caller();
panic!(
"{}:{} registered handler for the same message {} twice",
location.file(),
location.line(),
std::any::type_name::<M>()
);
panic!("registered handler for the same message twice");
}
Subscription::Message {
@@ -755,7 +748,7 @@ impl Client {
#[async_recursion(?Send)]
pub async fn authenticate_and_connect(
self: &Arc<Self>,
self: &'async_recursion Arc<Self>,
try_keychain: bool,
cx: &AsyncAppContext,
) -> anyhow::Result<()> {

View File

@@ -74,7 +74,6 @@ pub enum ClickhouseEvent {
Call {
operation: &'static str,
room_id: u64,
channel_id: Option<u64>,
},
}
@@ -135,6 +134,8 @@ impl Telemetry {
}
}
/// This method takes the entire TelemetrySettings struct in order to force client code
/// to pull the struct out of the settings global. Do not remove!
pub fn set_authenticated_user_info(
self: &Arc<Self>,
metrics_id: Option<String>,

View File

@@ -168,7 +168,6 @@ impl FakeServer {
GetPrivateUserInfoResponse {
metrics_id: "the-metrics-id".into(),
staff: false,
flags: Default::default(),
},
)
.await;

View File

@@ -1,20 +1,18 @@
use super::{proto, Client, Status, TypedEnvelope};
use anyhow::{anyhow, Context, Result};
use collections::{hash_map::Entry, HashMap, HashSet};
use feature_flags::FeatureFlagAppExt;
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse};
use staff_mode::StaffMode;
use std::sync::{Arc, Weak};
use util::http::HttpClient;
use util::TryFutureExt as _;
pub type UserId = u64;
#[derive(Default, Debug)]
pub struct User {
pub id: UserId,
pub id: u64,
pub github_login: String,
pub avatar: Option<Arc<ImageData>>,
}
@@ -145,48 +143,39 @@ impl UserStore {
let fetch_metrics_id =
client.request(proto::GetPrivateUserInfo {}).log_err();
let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
cx.read(|cx| {
client.telemetry.set_authenticated_user_info(
info.as_ref().map(|info| info.metrics_id.clone()),
info.as_ref().map(|info| info.staff).unwrap_or(false),
cx,
)
});
if let Some(info) = info {
cx.update(|cx| {
cx.update_flags(info.staff, info.flags);
client.telemetry.set_authenticated_user_info(
Some(info.metrics_id.clone()),
info.staff,
cx,
)
cx.update(|cx| {
cx.update_default_global(|staff_mode: &mut StaffMode, _| {
if !staff_mode.0 {
*staff_mode = StaffMode(
info.as_ref()
.map(|info| info.staff)
.unwrap_or_default(),
)
}
()
});
} else {
cx.read(|cx| {
client
.telemetry
.set_authenticated_user_info(None, false, cx)
});
}
});
current_user_tx.send(user).await.ok();
this.update(&mut cx, |_, cx| {
cx.notify();
});
}
}
Status::SignedOut => {
current_user_tx.send(None).await.ok();
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
cx.notify();
this.clear_contacts()
})
.await;
this.update(&mut cx, |this, _| this.clear_contacts()).await;
}
}
Status::ConnectionLost => {
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
cx.notify();
this.clear_contacts()
})
.await;
this.update(&mut cx, |this, _| this.clear_contacts()).await;
}
}
_ => {}

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.19.0"
version = "0.16.0"
publish = false
[[bin]]
@@ -14,10 +14,8 @@ name = "seed"
required-features = ["seed-support"]
[dependencies]
clock = { path = "../clock" }
collections = { path = "../collections" }
live_kit_server = { path = "../live_kit_server" }
text = { path = "../text" }
rpc = { path = "../rpc" }
util = { path = "../util" }
@@ -37,7 +35,6 @@ log.workspace = true
nanoid = "0.4"
parking_lot.workspace = true
prometheus = "0.13"
prost.workspace = true
rand.workspace = true
reqwest = { version = "0.11", features = ["json"], optional = true }
scrypt = "0.7"
@@ -65,7 +62,6 @@ collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
call = { path = "../call", features = ["test-support"] }
client = { path = "../client", features = ["test-support"] }
channel = { path = "../channel" }
editor = { path = "../editor", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
@@ -78,7 +74,6 @@ rpc = { path = "../rpc", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
theme = { path = "../theme" }
workspace = { path = "../workspace", features = ["test-support"] }
collab_ui = { path = "../collab_ui", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true

View File

@@ -36,8 +36,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b");
CREATE TABLE "rooms" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"live_kit_room" VARCHAR NOT NULL,
"channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE
"live_kit_room" VARCHAR NOT NULL
);
CREATE TABLE "projects" (
@@ -185,86 +184,3 @@ CREATE UNIQUE INDEX
"index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id"
ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id");
CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");
CREATE TABLE "channels" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" VARCHAR NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT now
);
CREATE TABLE "channel_paths" (
"id_path" TEXT NOT NULL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE
);
CREATE INDEX "index_channel_paths_on_channel_id" ON "channel_paths" ("channel_id");
CREATE TABLE "channel_members" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"admin" BOOLEAN NOT NULL DEFAULT false,
"accepted" BOOLEAN NOT NULL DEFAULT false,
"updated_at" TIMESTAMP NOT NULL DEFAULT now
);
CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");
CREATE TABLE "buffers" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX "index_buffers_on_channel_id" ON "buffers" ("channel_id");
CREATE TABLE "buffer_operations" (
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"replica_id" INTEGER NOT NULL,
"lamport_timestamp" INTEGER NOT NULL,
"value" BLOB NOT NULL,
PRIMARY KEY(buffer_id, epoch, lamport_timestamp, replica_id)
);
CREATE TABLE "buffer_snapshots" (
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"text" TEXT NOT NULL,
"operation_serialization_version" INTEGER NOT NULL,
PRIMARY KEY(buffer_id, epoch)
);
CREATE TABLE "channel_buffer_collaborators" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"connection_id" INTEGER NOT NULL,
"connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
"connection_lost" BOOLEAN NOT NULL DEFAULT false,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"replica_id" INTEGER NOT NULL
);
CREATE INDEX "index_channel_buffer_collaborators_on_channel_id" ON "channel_buffer_collaborators" ("channel_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_and_replica_id" ON "channel_buffer_collaborators" ("channel_id", "replica_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_server_id" ON "channel_buffer_collaborators" ("connection_server_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_id" ON "channel_buffer_collaborators" ("connection_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection_id_and_server_id" ON "channel_buffer_collaborators" ("channel_id", "connection_id", "connection_server_id");
CREATE TABLE "feature_flags" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"flag" TEXT NOT NULL UNIQUE
);
CREATE INDEX "index_feature_flags" ON "feature_flags" ("id");
CREATE TABLE "user_features" (
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"feature_id" INTEGER NOT NULL REFERENCES feature_flags (id) ON DELETE CASCADE,
PRIMARY KEY (user_id, feature_id)
);
CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");

View File

@@ -1,30 +0,0 @@
DROP TABLE "channel_messages";
DROP TABLE "channel_memberships";
DROP TABLE "org_memberships";
DROP TABLE "orgs";
DROP TABLE "channels";
CREATE TABLE "channels" (
"id" SERIAL PRIMARY KEY,
"name" VARCHAR NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT now()
);
CREATE TABLE "channel_paths" (
"id_path" VARCHAR NOT NULL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE
);
CREATE INDEX "index_channel_paths_on_channel_id" ON "channel_paths" ("channel_id");
CREATE TABLE "channel_members" (
"id" SERIAL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"admin" BOOLEAN NOT NULL DEFAULT false,
"accepted" BOOLEAN NOT NULL DEFAULT false,
"updated_at" TIMESTAMP NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");
ALTER TABLE rooms ADD COLUMN "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE;

View File

@@ -1,40 +0,0 @@
CREATE TABLE "buffers" (
"id" SERIAL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX "index_buffers_on_channel_id" ON "buffers" ("channel_id");
CREATE TABLE "buffer_operations" (
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"replica_id" INTEGER NOT NULL,
"lamport_timestamp" INTEGER NOT NULL,
"value" BYTEA NOT NULL,
PRIMARY KEY(buffer_id, epoch, lamport_timestamp, replica_id)
);
CREATE TABLE "buffer_snapshots" (
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"text" TEXT NOT NULL,
"operation_serialization_version" INTEGER NOT NULL,
PRIMARY KEY(buffer_id, epoch)
);
CREATE TABLE "channel_buffer_collaborators" (
"id" SERIAL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"connection_id" INTEGER NOT NULL,
"connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
"connection_lost" BOOLEAN NOT NULL DEFAULT FALSE,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"replica_id" INTEGER NOT NULL
);
CREATE INDEX "index_channel_buffer_collaborators_on_channel_id" ON "channel_buffer_collaborators" ("channel_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_and_replica_id" ON "channel_buffer_collaborators" ("channel_id", "replica_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_server_id" ON "channel_buffer_collaborators" ("connection_server_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_id" ON "channel_buffer_collaborators" ("connection_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection_id_and_server_id" ON "channel_buffer_collaborators" ("channel_id", "connection_id", "connection_server_id");

View File

@@ -1,16 +0,0 @@
CREATE TABLE "feature_flags" (
"id" SERIAL PRIMARY KEY,
"flag" VARCHAR(255) NOT NULL UNIQUE
);
CREATE UNIQUE INDEX "index_feature_flags" ON "feature_flags" ("id");
CREATE TABLE "user_features" (
"user_id" INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
"feature_id" INTEGER NOT NULL REFERENCES feature_flags(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, feature_id)
);
CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");

View File

@@ -64,9 +64,9 @@ async fn main() {
.expect("failed to fetch user")
.is_none()
{
if admin {
if let Some(email) = &github_user.email {
db.create_user(
&format!("{}@zed.dev", github_user.login),
email,
admin,
db::NewUserParams {
github_login: github_user.login,
@@ -76,11 +76,15 @@ async fn main() {
)
.await
.expect("failed to insert user");
} else {
db.get_or_create_user_by_github_account(
&github_user.login,
Some(github_user.id),
github_user.email.as_deref(),
} else if admin {
db.create_user(
&format!("{}@zed.dev", github_user.login),
admin,
db::NewUserParams {
github_login: github_user.login,
github_user_id: github_user.id,
invite_count: 5,
},
)
.await
.expect("failed to insert user");

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
use crate::db::{AccessTokenId, UserId};
use super::{AccessTokenId, UserId};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]

View File

@@ -1,4 +1,4 @@
use crate::db::{ContactId, UserId};
use super::{ContactId, UserId};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
@@ -30,3 +30,29 @@ pub enum Relation {
}
impl ActiveModelBehavior for ActiveModel {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Contact {
Accepted {
user_id: UserId,
should_notify: bool,
busy: bool,
},
Outgoing {
user_id: UserId,
},
Incoming {
user_id: UserId,
should_notify: bool,
},
}
impl Contact {
pub fn user_id(&self) -> UserId {
match self {
Contact::Accepted { user_id, .. } => *user_id,
Contact::Outgoing { user_id } => *user_id,
Contact::Incoming { user_id, .. } => *user_id,
}
}
}

View File

@@ -1,8 +1,9 @@
use crate::db::{FollowerId, ProjectId, RoomId, ServerId};
use super::{FollowerId, ProjectId, RoomId, ServerId};
use rpc::ConnectionId;
use sea_orm::entity::prelude::*;
use serde::Serialize;
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel, Serialize)]
#[sea_orm(table_name = "followers")]
pub struct Model {
#[sea_orm(primary_key)]

View File

@@ -1,128 +0,0 @@
use crate::Result;
use sea_orm::DbErr;
use sea_query::{Value, ValueTypeErr};
use serde::{Deserialize, Serialize};
macro_rules! id_type {
($name:ident) => {
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
)]
#[serde(transparent)]
pub struct $name(pub i32);
impl $name {
#[allow(unused)]
pub const MAX: Self = Self(i32::MAX);
#[allow(unused)]
pub fn from_proto(value: u64) -> Self {
Self(value as i32)
}
#[allow(unused)]
pub fn to_proto(self) -> u64 {
self.0 as u64
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<$name> for sea_query::Value {
fn from(value: $name) -> Self {
sea_query::Value::Int(Some(value.0))
}
}
impl sea_orm::TryGetable for $name {
fn try_get(
res: &sea_orm::QueryResult,
pre: &str,
col: &str,
) -> Result<Self, sea_orm::TryGetError> {
Ok(Self(i32::try_get(res, pre, col)?))
}
}
impl sea_query::ValueType for $name {
fn try_from(v: Value) -> Result<Self, sea_query::ValueTypeErr> {
Ok(Self(value_to_integer(v)?))
}
fn type_name() -> String {
stringify!($name).into()
}
fn array_type() -> sea_query::ArrayType {
sea_query::ArrayType::Int
}
fn column_type() -> sea_query::ColumnType {
sea_query::ColumnType::Integer(None)
}
}
impl sea_orm::TryFromU64 for $name {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
Ok(Self(n.try_into().map_err(|_| {
DbErr::ConvertFromU64(concat!(
"error converting ",
stringify!($name),
" to u64"
))
})?))
}
}
impl sea_query::Nullable for $name {
fn null() -> Value {
Value::Int(None)
}
}
};
}
fn value_to_integer(v: Value) -> Result<i32, ValueTypeErr> {
match v {
Value::TinyInt(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::SmallInt(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::Int(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::BigInt(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::TinyUnsigned(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::SmallUnsigned(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::Unsigned(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::BigUnsigned(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
_ => Err(ValueTypeErr),
}
}
id_type!(BufferId);
id_type!(AccessTokenId);
id_type!(ChannelId);
id_type!(ChannelMemberId);
id_type!(ContactId);
id_type!(FollowerId);
id_type!(RoomId);
id_type!(RoomParticipantId);
id_type!(ProjectId);
id_type!(ProjectCollaboratorId);
id_type!(ReplicaId);
id_type!(ServerId);
id_type!(SignupId);
id_type!(UserId);
id_type!(ChannelBufferCollaboratorId);
id_type!(FlagId);

View File

@@ -1,4 +1,4 @@
use crate::db::ProjectId;
use super::ProjectId;
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]

View File

@@ -1,4 +1,4 @@
use crate::db::{ProjectId, Result, RoomId, ServerId, UserId};
use super::{ProjectId, Result, RoomId, ServerId, UserId};
use anyhow::anyhow;
use rpc::ConnectionId;
use sea_orm::entity::prelude::*;

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