Compare commits
321 Commits
post-layou
...
dynamic-ru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0a20690fb | ||
|
|
e9613e552a | ||
|
|
a3174be565 | ||
|
|
2f6b290084 | ||
|
|
9906b31691 | ||
|
|
9a7a267203 | ||
|
|
bd8896a3dc | ||
|
|
d545fe9fe4 | ||
|
|
9765260567 | ||
|
|
9ae5366979 | ||
|
|
e2bcb28286 | ||
|
|
89caf06dbe | ||
|
|
f93272efe8 | ||
|
|
cb75c57fc0 | ||
|
|
ac31c824e4 | ||
|
|
91b011a2c6 | ||
|
|
3036c0cab7 | ||
|
|
83bc24b480 | ||
|
|
24677b11ed | ||
|
|
c70cb5a911 | ||
|
|
7acd6879db | ||
|
|
b884658e49 | ||
|
|
7cbdea2ca0 | ||
|
|
ddca6a3fb7 | ||
|
|
cbdc07dcd0 | ||
|
|
af14bc7a27 | ||
|
|
8fc2431a2a | ||
|
|
f3fa3b910a | ||
|
|
a42b987929 | ||
|
|
c31626717f | ||
|
|
8cf36ae603 | ||
|
|
079c31fcac | ||
|
|
bbc4ed9cab | ||
|
|
8536ba54c3 | ||
|
|
a0c8debd35 | ||
|
|
009384f948 | ||
|
|
72009de309 | ||
|
|
bdf59b8f06 | ||
|
|
e01a606616 | ||
|
|
510f4328f2 | ||
|
|
4b7bd03db7 | ||
|
|
935938a27c | ||
|
|
d4584a10b6 | ||
|
|
3b2e315ead | ||
|
|
43163a0154 | ||
|
|
cd8ede542b | ||
|
|
7d0c515be9 | ||
|
|
011ae8536c | ||
|
|
fcd0571ab4 | ||
|
|
f27d59896f | ||
|
|
f0f49db969 | ||
|
|
a44fc24445 | ||
|
|
f54bb32a7f | ||
|
|
d8902ca5d6 | ||
|
|
b575fbb449 | ||
|
|
d8276b0f0d | ||
|
|
841e010fa4 | ||
|
|
ffdda588b4 | ||
|
|
6c5dfd1061 | ||
|
|
6ef32374d6 | ||
|
|
b29946130e | ||
|
|
368fec2822 | ||
|
|
934af6ad45 | ||
|
|
053b6cc715 | ||
|
|
37a12a366f | ||
|
|
633e31a47f | ||
|
|
882cd6e52f | ||
|
|
83f493b387 | ||
|
|
dbe1f48f95 | ||
|
|
401798d9b8 | ||
|
|
35bec9803a | ||
|
|
d4973846c0 | ||
|
|
16d826a3f4 | ||
|
|
c29ea9bdbc | ||
|
|
cf3b875922 | ||
|
|
3ddf2f27d3 | ||
|
|
d1b58601a1 | ||
|
|
d895388809 | ||
|
|
76196694b7 | ||
|
|
ba4e1699ae | ||
|
|
58463b2e97 | ||
|
|
03b0764df4 | ||
|
|
c59aab5090 | ||
|
|
2e616f8388 | ||
|
|
dc7e14f888 | ||
|
|
7599933f30 | ||
|
|
622cae19eb | ||
|
|
e06ff5f507 | ||
|
|
9b44ba9382 | ||
|
|
aef299be3d | ||
|
|
885ae2d863 | ||
|
|
cab8b5a9a3 | ||
|
|
d993dd3b2c | ||
|
|
351e6a5de2 | ||
|
|
41949d7b6c | ||
|
|
0fbd0d6649 | ||
|
|
69c7d0e549 | ||
|
|
d3a38c69cd | ||
|
|
7c514d044f | ||
|
|
a11ebe01ff | ||
|
|
c5bb032224 | ||
|
|
58fd84308d | ||
|
|
008d99d206 | ||
|
|
3bc7cd66b7 | ||
|
|
b0872b5b57 | ||
|
|
602fd58929 | ||
|
|
ed3bb68206 | ||
|
|
522176d414 | ||
|
|
f19ab464c7 | ||
|
|
6d91224882 | ||
|
|
351c8c9a36 | ||
|
|
c90065e6ea | ||
|
|
0f584cb353 | ||
|
|
7cf0696c89 | ||
|
|
576f8d3ef3 | ||
|
|
42ac9880c6 | ||
|
|
65318cb6ac | ||
|
|
71557f3eb3 | ||
|
|
a588f674db | ||
|
|
50dd38bd02 | ||
|
|
caa156ab13 | ||
|
|
a82f4857f4 | ||
|
|
0de8672044 | ||
|
|
cc8e3c2286 | ||
|
|
347f68887f | ||
|
|
a475d8640f | ||
|
|
991c9ec441 | ||
|
|
250df707bf | ||
|
|
ba6b319046 | ||
|
|
bd94a0e921 | ||
|
|
40bbd0031d | ||
|
|
946f4a312a | ||
|
|
af06063d31 | ||
|
|
5c4f3c0cea | ||
|
|
c6826a61a0 | ||
|
|
fa2c92d190 | ||
|
|
20b10fdca9 | ||
|
|
4f40d3c801 | ||
|
|
b716035d02 | ||
|
|
94bc216bbd | ||
|
|
95d5ea7edc | ||
|
|
aff858bd00 | ||
|
|
583d85cf66 | ||
|
|
36586b77ec | ||
|
|
587788b9a0 | ||
|
|
6f36527bc6 | ||
|
|
aa34e306f7 | ||
|
|
e5d971f4c7 | ||
|
|
38c3a93f0c | ||
|
|
f930969411 | ||
|
|
266bb62813 | ||
|
|
6e897d9969 | ||
|
|
d90b052162 | ||
|
|
49a53e7654 | ||
|
|
3220986fc9 | ||
|
|
9fcda5a5ac | ||
|
|
5e43290aa1 | ||
|
|
f895d66d1c | ||
|
|
7bf16f263e | ||
|
|
d3745a3931 | ||
|
|
0c939e5dfc | ||
|
|
b9151b9506 | ||
|
|
2679457b02 | ||
|
|
45e2c01773 | ||
|
|
fd9823898f | ||
|
|
d5aba2795b | ||
|
|
92b2e5608b | ||
|
|
c58d72ea2b | ||
|
|
58a5a1eb8f | ||
|
|
cd640a87a9 | ||
|
|
c97ecc7326 | ||
|
|
48f0f387f8 | ||
|
|
2ec910f772 | ||
|
|
8a73bc4c7d | ||
|
|
8f5d7db875 | ||
|
|
389d26d974 | ||
|
|
e580e2ff0a | ||
|
|
3d9503a454 | ||
|
|
5c7cec9f85 | ||
|
|
b028231aea | ||
|
|
f7d2cb1818 | ||
|
|
78dcd72790 | ||
|
|
d51a0b60be | ||
|
|
8178d347b6 | ||
|
|
33ecb424af | ||
|
|
91b97387b6 | ||
|
|
0a40a21c74 | ||
|
|
b14d576349 | ||
|
|
db0eaca2e5 | ||
|
|
80db468720 | ||
|
|
0d2ad67b27 | ||
|
|
7065d6c46d | ||
|
|
6c714c13b3 | ||
|
|
c54d6aff6c | ||
|
|
48a6fb9e84 | ||
|
|
e9f400a8bd | ||
|
|
fc101c1fb3 | ||
|
|
4616d66e1d | ||
|
|
3ef8a9910d | ||
|
|
1e44bac418 | ||
|
|
aad7761038 | ||
|
|
0422d43798 | ||
|
|
b00b65b330 | ||
|
|
fddb778e5f | ||
|
|
77974a4367 | ||
|
|
0037f0b2fd | ||
|
|
37f6a706cc | ||
|
|
f4bafd5899 | ||
|
|
b3d3a00a14 | ||
|
|
0a5df7d597 | ||
|
|
4d1585b917 | ||
|
|
99559f3975 | ||
|
|
5783497c21 | ||
|
|
e27c2fc946 | ||
|
|
f17d0b5729 | ||
|
|
ca251babcd | ||
|
|
c33efe8cd0 | ||
|
|
2b56c43f2d | ||
|
|
bd137b01ad | ||
|
|
4e1e26b696 | ||
|
|
12b12ba17a | ||
|
|
8acd4d122e | ||
|
|
251218954d | ||
|
|
3ca6f7572f | ||
|
|
b91d6da6b6 | ||
|
|
a041e07c99 | ||
|
|
6d9b8cc595 | ||
|
|
4c781b6455 | ||
|
|
8aa5319210 | ||
|
|
f19378135a | ||
|
|
7804be0286 | ||
|
|
98ffdca32e | ||
|
|
cd4d2f7900 | ||
|
|
43a845cbbf | ||
|
|
3cbc18895a | ||
|
|
f82b2741cd | ||
|
|
9ad1862f2f | ||
|
|
1c361ac579 | ||
|
|
bea36918f4 | ||
|
|
43e8fdbe82 | ||
|
|
5df1318e75 | ||
|
|
577b244b03 | ||
|
|
2e0d18ee76 | ||
|
|
c5a23faf7c | ||
|
|
86f81c4db3 | ||
|
|
07501d9cfa | ||
|
|
07c7778cff | ||
|
|
aa6926e57a | ||
|
|
ae577c9d5c | ||
|
|
f4f72a1136 | ||
|
|
4310b0b8de | ||
|
|
a161a7d0c9 | ||
|
|
ab6b9196e1 | ||
|
|
ef551cedef | ||
|
|
9ef83a2557 | ||
|
|
32fdff0285 | ||
|
|
4094562321 | ||
|
|
6869b62af3 | ||
|
|
21a7421ee0 | ||
|
|
96dcc385dd | ||
|
|
e6766e102e | ||
|
|
694e18417e | ||
|
|
94426c4393 | ||
|
|
bf1bcd027c | ||
|
|
23132b5ab1 | ||
|
|
a8d5864524 | ||
|
|
ea322e1d1c | ||
|
|
e1ae0d46da | ||
|
|
bdc2558eac | ||
|
|
a41fb29e01 | ||
|
|
aa319ccfd0 | ||
|
|
f01763a1fa | ||
|
|
2dffc5f6e1 | ||
|
|
ed791c4fc1 | ||
|
|
7c6b34cb73 | ||
|
|
3921259b6c | ||
|
|
e93dca5ec3 | ||
|
|
c6626627c2 | ||
|
|
db86f4006e | ||
|
|
41372a96ed | ||
|
|
f62baeda64 | ||
|
|
6e6ae0ef21 | ||
|
|
a3300aed31 | ||
|
|
cbbc8cad84 | ||
|
|
8e52cf1495 | ||
|
|
65a1938e52 | ||
|
|
855acb948c | ||
|
|
54f82eb166 | ||
|
|
ac59b9b02f | ||
|
|
8f7a26f397 | ||
|
|
181f556269 | ||
|
|
a02bdd0a9f | ||
|
|
6876ea44ac | ||
|
|
16849f48e6 | ||
|
|
7a6d01e113 | ||
|
|
b14fbd4ddc | ||
|
|
b47aff4c14 | ||
|
|
7ac055627e | ||
|
|
db0455beb0 | ||
|
|
017b2db630 | ||
|
|
75eac4783b | ||
|
|
7956a9a547 | ||
|
|
c357e37dde | ||
|
|
c3176392c6 | ||
|
|
e9b95fde68 | ||
|
|
e73e93f333 | ||
|
|
a2144faf9c | ||
|
|
d744aa896f | ||
|
|
2294d99046 | ||
|
|
ecd9b93cb1 | ||
|
|
fecb5a82f1 | ||
|
|
33f713a8ab | ||
|
|
798c9a7d8b | ||
|
|
98fff014da | ||
|
|
ea51536e0f | ||
|
|
a1899bac4e | ||
|
|
04fc0dde1a | ||
|
|
b800fe96d2 | ||
|
|
21d2b5fe50 | ||
|
|
d13a731cd6 | ||
|
|
ede9600ab4 |
@@ -1,5 +1,7 @@
|
||||
name: Bug Report
|
||||
description: "Tip: open this issue template from within Zed with the `file bug report` command palette action"
|
||||
description: |
|
||||
Use this template for **non-crash-related** bug reports.
|
||||
Tip: open this issue template from within Zed with the `file bug report` command palette action.
|
||||
labels: ["admin read", "triage", "defect"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
47
.github/ISSUE_TEMPLATE/1_language_support.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: Language Support
|
||||
description: Request language support
|
||||
title: "<name_of_language> support"
|
||||
labels:
|
||||
[
|
||||
"admin read",
|
||||
"triage",
|
||||
"enhancement",
|
||||
"language",
|
||||
"unsupported language",
|
||||
"potential plugin",
|
||||
]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Language
|
||||
description: What language do you want support for?
|
||||
placeholder: HTML
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Tree Sitter parser link
|
||||
description: If applicable, provide a link to the appropriate tree sitter parser. Look here first - https://tree-sitter.github.io/tree-sitter/#available-parsers
|
||||
placeholder: https://github.com/tree-sitter/tree-sitter-html
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Language server link
|
||||
description: If applicable, provide a link to the appropriate language server. Look here first - https://microsoft.github.io/language-server-protocol/implementors/servers/
|
||||
placeholder: https://github.com/Microsoft/vscode/tree/main/extensions/html-language-features/server
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Misc notes
|
||||
description: Provide any additional things the team should consider when adding support for this language
|
||||
validations:
|
||||
required: false
|
||||
39
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Crash Report
|
||||
description: |
|
||||
Use this template for crash reports.
|
||||
labels: ["admin read", "triage", "defect", "panic / crash"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug / provide steps to reproduce it
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||
description: Drag issues into the text input below
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
description: Drag Zed.log into the text input below
|
||||
validations:
|
||||
required: false
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,10 @@
|
||||
contact_links:
|
||||
- name: Language Request
|
||||
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E
|
||||
about: Request a language in the extensions repository
|
||||
- name: Theme Request
|
||||
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=theme&projects=&template=0_theme_request.yml&title=%3Cname_of_theme%3E+theme
|
||||
about: Request a theme in the extensions repository
|
||||
- name: Top-Ranking Issues
|
||||
url: https://github.com/zed-industries/zed/issues/5393
|
||||
about: See an overview of the most popular Zed issues
|
||||
|
||||
20
.github/actions/check_style/action.yml
vendored
@@ -2,14 +2,14 @@ name: "Check formatting"
|
||||
description: "Checks code formatting use cargo fmt"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: cargo fmt
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo fmt --all -- --check
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: cargo fmt
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Find modified migrations
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
|
||||
. ./script/squawk
|
||||
- name: Find modified migrations
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
|
||||
. ./script/squawk
|
||||
|
||||
32
.github/actions/run_tests/action.yml
vendored
@@ -2,22 +2,22 @@ name: "Run tests"
|
||||
description: "Runs the tests"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Rust
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
cargo install cargo-nextest
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Rust
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
cargo install cargo-nextest
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Limit target directory size
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
- name: Limit target directory size
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
|
||||
2
.github/pull_request_template.md
vendored
@@ -4,6 +4,8 @@ Release Notes:
|
||||
|
||||
- Added/Fixed/Improved ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/zed/issues/<public_issue_number_if_exists>)).
|
||||
|
||||
Optionally, include screenshots / media showcasing your addition that can be included in the release notes.
|
||||
|
||||
**or**
|
||||
|
||||
- N/A
|
||||
|
||||
385
.github/workflows/ci.yml
vendored
@@ -1,204 +1,231 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "v[0-9]+.[0-9]+.x"
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "v[0-9]+.[0-9]+.x"
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
concurrency:
|
||||
# Allow only one workflow per any non-`main` branch.
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
|
||||
cancel-in-progress: true
|
||||
# Allow only one workflow per any non-`main` branch.
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
style:
|
||||
name: Check formatting and spelling
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
style:
|
||||
name: Check formatting and spelling
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up default .cargo/config.toml
|
||||
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
||||
- name: Remove untracked files
|
||||
run: git clean -df
|
||||
|
||||
- name: Check spelling
|
||||
run: |
|
||||
if ! which typos > /dev/null; then
|
||||
cargo install typos-cli
|
||||
fi
|
||||
typos
|
||||
- name: Set up default .cargo/config.toml
|
||||
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
- name: Check spelling
|
||||
run: |
|
||||
if ! which typos > /dev/null; then
|
||||
cargo install typos-cli
|
||||
fi
|
||||
typos
|
||||
|
||||
- name: Ensure fresh merge
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
if [ -z "$GITHUB_BASE_REF" ];
|
||||
then
|
||||
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> $GITHUB_ENV
|
||||
else
|
||||
git checkout -B temp
|
||||
git merge -q origin/$GITHUB_BASE_REF -m "merge main into temp"
|
||||
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
- uses: bufbuild/buf-setup-action@v1
|
||||
- uses: bufbuild/buf-breaking-action@v1
|
||||
with:
|
||||
input: "crates/rpc/proto/"
|
||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/rpc/proto/"
|
||||
- name: Check unused dependencies
|
||||
uses: bnjbvr/cargo-machete@main
|
||||
|
||||
macos_tests:
|
||||
name: (macOS) Run Clippy and tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
- name: Ensure fresh merge
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
if [ -z "$GITHUB_BASE_REF" ];
|
||||
then
|
||||
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> $GITHUB_ENV
|
||||
else
|
||||
git checkout -B temp
|
||||
git merge -q origin/$GITHUB_BASE_REF -m "merge main into temp"
|
||||
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
- uses: bufbuild/buf-setup-action@v1
|
||||
with:
|
||||
version: v1.29.0
|
||||
- uses: bufbuild/buf-breaking-action@v1
|
||||
with:
|
||||
input: "crates/rpc/proto/"
|
||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/rpc/proto/"
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
macos_tests:
|
||||
name: (macOS) Run Clippy and tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Build collab
|
||||
run: cargo build -p collab
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
|
||||
- name: Build other binaries
|
||||
run: cargo build --workspace --bins --all-features
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
# todo!(linux): Actually run the tests
|
||||
linux_tests:
|
||||
name: (Linux) Run Clippy and tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
- name: Build collab
|
||||
run: cargo build -p collab
|
||||
|
||||
- name: Restore from cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/rust-toolchain.toml') }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-${{ hashFiles('**/rust-toolchain.toml') }}-
|
||||
- name: Build other binaries and features
|
||||
run: cargo build --workspace --bins --all-features; cargo check -p gpui --features "macos-blade"
|
||||
|
||||
- name: configure linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/linux
|
||||
# todo!(linux): Actually run the tests
|
||||
linux_tests:
|
||||
name: (Linux) Run Clippy and tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
- name: Cache dependencies
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Build Zed
|
||||
run: cargo build -p zed
|
||||
bundle:
|
||||
name: Bundle app
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
||||
needs: [macos_tests, linux_tests]
|
||||
- name: configure linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/linux
|
||||
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
|
||||
- name: Build Zed
|
||||
run: cargo build -p zed
|
||||
|
||||
# todo!(windows): Actually run the tests
|
||||
windows_tests:
|
||||
name: (Windows) Run Clippy and tests
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
|
||||
- name: Build Zed
|
||||
run: cargo build -p zed
|
||||
|
||||
bundle:
|
||||
name: Bundle macOS app
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
||||
needs: [macos_tests]
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
version=$(script/get-crate-version zed)
|
||||
channel=$(cat crates/zed/RELEASE_CHANNEL)
|
||||
echo "Publishing version: ${version} on release channel ${channel}"
|
||||
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
|
||||
|
||||
expected_tag_name=""
|
||||
case ${channel} in
|
||||
stable)
|
||||
expected_tag_name="v${version}";;
|
||||
preview)
|
||||
expected_tag_name="v${version}-pre";;
|
||||
nightly)
|
||||
expected_tag_name="v${version}-nightly";;
|
||||
*)
|
||||
echo "can't publish a release on channel ${channel}"
|
||||
exit 1;;
|
||||
esac
|
||||
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
|
||||
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
- name: Create app bundle
|
||||
run: script/bundle
|
||||
|
||||
- name: Upload app bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
path: target/release/Zed.dmg
|
||||
|
||||
- uses: softprops/action-gh-release@v1
|
||||
name: Upload app bundle to release
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
files: target/release/Zed.dmg
|
||||
body: ""
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
version=$(script/get-crate-version zed)
|
||||
channel=$(cat crates/zed/RELEASE_CHANNEL)
|
||||
echo "Publishing version: ${version} on release channel ${channel}"
|
||||
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
|
||||
|
||||
expected_tag_name=""
|
||||
case ${channel} in
|
||||
stable)
|
||||
expected_tag_name="v${version}";;
|
||||
preview)
|
||||
expected_tag_name="v${version}-pre";;
|
||||
nightly)
|
||||
expected_tag_name="v${version}-nightly";;
|
||||
*)
|
||||
echo "can't publish a release on channel ${channel}"
|
||||
exit 1;;
|
||||
esac
|
||||
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
|
||||
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
- name: Create app bundle
|
||||
run: script/bundle
|
||||
|
||||
- name: Upload app bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
|
||||
path: target/release/Zed.dmg
|
||||
|
||||
- uses: softprops/action-gh-release@v1
|
||||
name: Upload app bundle to release
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
files: target/release/Zed.dmg
|
||||
body: ""
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
50
.github/workflows/danger.yml
vendored
@@ -1,35 +1,35 @@
|
||||
name: Danger
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- edited
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
danger:
|
||||
runs-on: ubuntu-latest
|
||||
danger:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "script/danger/pnpm-lock.yaml"
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "script/danger/pnpm-lock.yaml"
|
||||
|
||||
- run: pnpm install --dir script/danger
|
||||
- run: pnpm install --dir script/danger
|
||||
|
||||
- name: Run Danger
|
||||
run: pnpm run --dir script/danger danger ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- name: Run Danger
|
||||
run: pnpm run --dir script/danger danger ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
46
.github/workflows/deploy_collab.yml
vendored
@@ -45,8 +45,18 @@ jobs:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install cargo nextest
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
cargo install cargo-nextest
|
||||
|
||||
- name: Limit target directory size
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --package collab --no-fail-fast
|
||||
|
||||
publish:
|
||||
name: Publish collab server image
|
||||
@@ -63,9 +73,6 @@ jobs:
|
||||
- name: Sign into DigitalOcean docker registry
|
||||
run: doctl registry login
|
||||
|
||||
- name: Prune Docker system
|
||||
run: docker system prune --filter 'until=720h' -f
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -78,6 +85,9 @@ jobs:
|
||||
- name: Publish docker image
|
||||
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}
|
||||
|
||||
- name: Prune Docker system
|
||||
run: docker system prune --filter 'until=72h' -f
|
||||
|
||||
deploy:
|
||||
name: Deploy new server image
|
||||
needs:
|
||||
@@ -90,22 +100,32 @@ jobs:
|
||||
- name: Sign into Kubernetes
|
||||
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 ${{ secrets.CLUSTER_NAME }}
|
||||
|
||||
- name: Determine namespace
|
||||
- name: Start rollout
|
||||
run: |
|
||||
set -eu
|
||||
if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
|
||||
echo "Deploying collab:$GITHUB_SHA to production"
|
||||
echo "KUBE_NAMESPACE=production" >> $GITHUB_ENV
|
||||
export ZED_KUBE_NAMESPACE=production
|
||||
elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
|
||||
echo "Deploying collab:$GITHUB_SHA to staging"
|
||||
echo "KUBE_NAMESPACE=staging" >> $GITHUB_ENV
|
||||
export ZED_KUBE_NAMESPACE=staging
|
||||
else
|
||||
echo "cowardly refusing to deploy from an unknown branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Start rollout
|
||||
run: kubectl -n "$KUBE_NAMESPACE" set image deployment/collab collab=registry.digitalocean.com/zed/collab:${GITHUB_SHA}
|
||||
echo "Deploying collab:$GITHUB_SHA to $ZED_KUBE_NAMESPACE"
|
||||
|
||||
- name: Wait for rollout to finish
|
||||
run: kubectl -n "$KUBE_NAMESPACE" rollout status deployment/collab
|
||||
source script/lib/deploy-helpers.sh
|
||||
export_vars_for_environment $ZED_KUBE_NAMESPACE
|
||||
|
||||
export ZED_DO_CERTIFICATE_ID=$(doctl compute certificate list --format ID --no-header)
|
||||
export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
|
||||
|
||||
export ZED_SERVICE_NAME=collab
|
||||
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
|
||||
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
|
||||
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
|
||||
|
||||
export ZED_SERVICE_NAME=api
|
||||
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
|
||||
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
|
||||
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
|
||||
|
||||
52
.github/workflows/randomized_tests.yml
vendored
@@ -3,35 +3,35 @@ name: Randomized Tests
|
||||
concurrency: randomized-tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- randomized-tests-runner
|
||||
# schedule:
|
||||
# - cron: '0 * * * *'
|
||||
push:
|
||||
branches:
|
||||
- randomized-tests-runner
|
||||
# schedule:
|
||||
# - cron: '0 * * * *'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
ZED_SERVER_URL: https://zed.dev
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
ZED_SERVER_URL: https://zed.dev
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Run randomized tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- randomized-tests
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
tests:
|
||||
name: Run randomized tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- randomized-tests
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Run randomized tests
|
||||
run: script/randomized-test-ci
|
||||
- name: Run randomized tests
|
||||
run: script/randomized-test-ci
|
||||
|
||||
162
.github/workflows/release_nightly.yml
vendored
@@ -1,98 +1,98 @@
|
||||
name: Release Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
|
||||
- cron: "0 7 * * *"
|
||||
push:
|
||||
tags:
|
||||
- "nightly"
|
||||
schedule:
|
||||
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
|
||||
- cron: "0 7 * * *"
|
||||
push:
|
||||
tags:
|
||||
- "nightly"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
style:
|
||||
name: Check formatting and Clippy lints
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
style:
|
||||
name: Check formatting and Clippy lints
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
- name: Run clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
tests:
|
||||
name: Run tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
needs: style
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
- name: Run clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
tests:
|
||||
name: Run tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
needs: style
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
bundle:
|
||||
name: Bundle app
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
needs: tests
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
bundle:
|
||||
name: Bundle app
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
needs: tests
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
- name: Set release channel to nightly
|
||||
run: |
|
||||
set -eu
|
||||
version=$(git rev-parse --short HEAD)
|
||||
echo "Publishing version: ${version} on release channel nightly"
|
||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
- name: Create app bundle
|
||||
run: script/bundle
|
||||
- name: Create app bundle
|
||||
run: script/bundle
|
||||
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly
|
||||
- name: Upload Zed Nightly
|
||||
run: script/upload-nightly
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */12 * * *"
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 */12 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10.5"
|
||||
architecture: "x64"
|
||||
cache: "pip"
|
||||
- run: pip install -r script/update_top_ranking_issues/requirements.txt
|
||||
- run: python script/update_top_ranking_issues/main.py 5393 --github-token ${{ secrets.GITHUB_TOKEN }} --prod
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10.5"
|
||||
architecture: "x64"
|
||||
cache: "pip"
|
||||
- run: pip install -r script/update_top_ranking_issues/requirements.txt
|
||||
- run: python script/update_top_ranking_issues/main.py 5393 --github-token ${{ secrets.GITHUB_TOKEN }} --prod
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 15 * * *"
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 15 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10.5"
|
||||
architecture: "x64"
|
||||
cache: "pip"
|
||||
- run: pip install -r script/update_top_ranking_issues/requirements.txt
|
||||
- run: python script/update_top_ranking_issues/main.py 6952 --github-token ${{ secrets.GITHUB_TOKEN }} --prod --query-day-interval 7
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10.5"
|
||||
architecture: "x64"
|
||||
cache: "pip"
|
||||
- run: pip install -r script/update_top_ranking_issues/requirements.txt
|
||||
- run: python script/update_top_ranking_issues/main.py 6952 --github-token ${{ secrets.GITHUB_TOKEN }} --prod --query-day-interval 7
|
||||
|
||||
5
.gitignore
vendored
@@ -5,12 +5,8 @@
|
||||
.DS_Store
|
||||
/plugins/bin
|
||||
/script/node_modules
|
||||
/styles/node_modules
|
||||
/styles/src/types/zed.ts
|
||||
/crates/theme/schemas/theme.json
|
||||
/crates/collab/static/styles.css
|
||||
/crates/collab/.admins.json
|
||||
/vendor/bin
|
||||
/assets/*licenses.md
|
||||
**/venv
|
||||
.build
|
||||
@@ -25,3 +21,4 @@ DerivedData/
|
||||
**/*.db
|
||||
.pytest_cache
|
||||
.venv
|
||||
.blob_store
|
||||
|
||||
2
.mailmap
@@ -11,6 +11,8 @@
|
||||
|
||||
Antonio Scandurra <me@as-cii.com>
|
||||
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
||||
Christian Bergschneider <christian.bergschneider@gmx.de>
|
||||
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
|
||||
Conrad Irwin <conrad@zed.dev>
|
||||
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
||||
Greg Morenz <greg-morenz@droid.cafe>
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
{
|
||||
"JSON": {
|
||||
"tab_size": 4
|
||||
"languages": {
|
||||
"Markdown": {
|
||||
"tab_size": 2,
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"TOML": {
|
||||
"formatter": "prettier",
|
||||
"format_on_save": "off"
|
||||
},
|
||||
"YAML": {
|
||||
"tab_size": 2,
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"JSON": {
|
||||
"tab_size": 2,
|
||||
"formatter": "prettier"
|
||||
}
|
||||
},
|
||||
"formatter": "auto"
|
||||
}
|
||||
|
||||
2752
Cargo.lock
generated
86
Cargo.toml
@@ -16,12 +16,14 @@ members = [
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/command_palette_hooks",
|
||||
"crates/copilot",
|
||||
"crates/copilot_ui",
|
||||
"crates/db",
|
||||
"crates/diagnostics",
|
||||
"crates/editor",
|
||||
"crates/extension",
|
||||
"crates/extensions_ui",
|
||||
"crates/feature_flags",
|
||||
"crates/feedback",
|
||||
"crates/file_finder",
|
||||
@@ -37,6 +39,7 @@ members = [
|
||||
"crates/language",
|
||||
"crates/language_selector",
|
||||
"crates/language_tools",
|
||||
"crates/languages",
|
||||
"crates/live_kit_client",
|
||||
"crates/live_kit_server",
|
||||
"crates/lsp",
|
||||
@@ -48,10 +51,9 @@ members = [
|
||||
"crates/notifications",
|
||||
"crates/outline",
|
||||
"crates/picker",
|
||||
"crates/plugin",
|
||||
"crates/plugin_macros",
|
||||
"crates/prettier",
|
||||
"crates/project",
|
||||
"crates/project_core",
|
||||
"crates/project_panel",
|
||||
"crates/project_symbols",
|
||||
"crates/quick_action_bar",
|
||||
@@ -62,6 +64,8 @@ members = [
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
"crates/search",
|
||||
"crates/semantic_index",
|
||||
"crates/settings",
|
||||
@@ -77,6 +81,8 @@ members = [
|
||||
"crates/theme",
|
||||
"crates/theme_importer",
|
||||
"crates/theme_selector",
|
||||
"crates/telemetry_events",
|
||||
"crates/time_format",
|
||||
"crates/ui",
|
||||
"crates/util",
|
||||
"crates/vcs_menu",
|
||||
@@ -107,12 +113,14 @@ collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
color = { path = "crates/color" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
copilot_ui = { path = "crates/copilot_ui" }
|
||||
db = { path = "crates/db" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
editor = { path = "crates/editor" }
|
||||
extension = { path = "crates/extension" }
|
||||
extensions_ui = { path = "crates/extensions_ui" }
|
||||
feature_flags = { path = "crates/feature_flags" }
|
||||
feedback = { path = "crates/feedback" }
|
||||
file_finder = { path = "crates/file_finder" }
|
||||
@@ -128,6 +136,7 @@ journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
language_tools = { path = "crates/language_tools" }
|
||||
languages = { path = "crates/languages" }
|
||||
live_kit_client = { path = "crates/live_kit_client" }
|
||||
live_kit_server = { path = "crates/live_kit_server" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
@@ -143,6 +152,7 @@ plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
project_core = { path = "crates/project_core" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
quick_action_bar = { path = "crates/quick_action_bar" }
|
||||
@@ -151,6 +161,8 @@ release_channel = { path = "crates/release_channel" }
|
||||
rich_text = { path = "crates/rich_text" }
|
||||
rope = { path = "crates/rope" }
|
||||
rpc = { path = "crates/rpc" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
search = { path = "crates/search" }
|
||||
semantic_index = { path = "crates/semantic_index" }
|
||||
settings = { path = "crates/settings" }
|
||||
@@ -166,6 +178,8 @@ text = { path = "crates/text" }
|
||||
theme = { path = "crates/theme" }
|
||||
theme_importer = { path = "crates/theme_importer" }
|
||||
theme_selector = { path = "crates/theme_selector" }
|
||||
telemetry_events = { path = "crates/telemetry_events" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
ui = { path = "crates/ui" }
|
||||
util = { path = "crates/util" }
|
||||
vcs_menu = { path = "crates/vcs_menu" }
|
||||
@@ -177,42 +191,52 @@ zed_actions = { path = "crates/zed_actions" }
|
||||
|
||||
anyhow = "1.0.57"
|
||||
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
|
||||
async-tar = "0.4.2"
|
||||
async-trait = "0.1"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" }
|
||||
blade-rwh = { package = "raw-window-handle", version = "0.5" }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clickhouse = { version = "0.11.6" }
|
||||
ctor = "0.2.6"
|
||||
core-foundation = { version = "0.9.3" }
|
||||
core-foundation-sys = "0.8.6"
|
||||
derive_more = "0.99.17"
|
||||
env_logger = "0.9"
|
||||
futures = "0.3"
|
||||
git2 = { version = "0.15", default-features = false }
|
||||
globset = "0.4"
|
||||
hex = "0.4.3"
|
||||
ignore = "0.4.22"
|
||||
indoc = "1"
|
||||
# We explicitly disable a http2 support in isahc.
|
||||
isahc = { version = "1.7.2", default-features = false, features = [
|
||||
"static-curl",
|
||||
"text-decoding",
|
||||
] }
|
||||
# We explicitly disable http2 support in isahc.
|
||||
isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
|
||||
itertools = "0.11.0"
|
||||
lazy_static = "1.4.0"
|
||||
linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
ordered-float = "2.1.1"
|
||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
parking_lot = "0.11.1"
|
||||
profiling = "1"
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
prost = "0.8"
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||
pulldown-cmark = { version = "0.10.0", default-features = false }
|
||||
rand = "0.8.5"
|
||||
refineable = { path = "./crates/refineable" }
|
||||
regex = "1.5"
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
||||
schemars = "0.8"
|
||||
semver = "1.0"
|
||||
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"] }
|
||||
serde_json_lenient = { version = "0.1", features = [
|
||||
"preserve_order",
|
||||
"raw_value",
|
||||
] }
|
||||
serde_json_lenient = { version = "0.1", features = ["preserve_order", "raw_value"] }
|
||||
serde_repr = "0.1"
|
||||
sha2 = "0.10"
|
||||
shellexpand = "2.1.0"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "1.2"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
@@ -220,17 +244,19 @@ sysinfo = "0.29.10"
|
||||
tempfile = "3.9.0"
|
||||
thiserror = "1.0.29"
|
||||
tiktoken-rs = "0.5.7"
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
toml = "0.5"
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known", "formatting"] }
|
||||
toml = "0.8"
|
||||
tower-http = "0.4.4"
|
||||
tree-sitter = { version = "0.20", features = ["wasm"] }
|
||||
tree-sitter-astro = { git = "https://github.com/virchau13/tree-sitter-astro.git", rev = "e924787e12e8a03194f36a113290ac11d6dc10f3" }
|
||||
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
|
||||
tree-sitter-beancount = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "da1bf8c6eb0ae7a97588affde7227630bcd678b6" }
|
||||
tree-sitter-c = "0.20.1"
|
||||
tree-sitter-clojure = { git = "https://github.com/prcastro/tree-sitter-clojure", branch = "update-ts"}
|
||||
tree-sitter-clojure = { git = "https://github.com/prcastro/tree-sitter-clojure", branch = "update-ts" }
|
||||
tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" }
|
||||
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
|
||||
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
||||
tree-sitter-dockerfile = { git = "https://github.com/camdencheek/tree-sitter-dockerfile", rev = "33e22c33bcdbfc33d42806ee84cfd0b1248cc392" }
|
||||
tree-sitter-dart = { git = "https://github.com/agent3bood/tree-sitter-dart", rev = "48934e3bf757a9b78f17bdfaa3e2b4284656fdc7" }
|
||||
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
|
||||
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
|
||||
tree-sitter-embedded-template = "0.20.0"
|
||||
@@ -241,7 +267,7 @@ tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev
|
||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||
tree-sitter-gomod = { git = "https://github.com/camdencheek/tree-sitter-go-mod" }
|
||||
tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work" }
|
||||
tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "cf98de23e4285b8e6bcb57b050ef2326e2cc284b" }
|
||||
tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "8a99848fc734f9c4ea523b3f2a07df133cbbcec2" }
|
||||
tree-sitter-hcl = { git = "https://github.com/MichaHoffmann/tree-sitter-hcl", rev = "v1.1.0" }
|
||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
|
||||
tree-sitter-html = "0.19.0"
|
||||
@@ -249,32 +275,35 @@ tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", re
|
||||
tree-sitter-lua = "0.0.14"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
|
||||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "26bbaecda0039df4067861ab38ea8ea169f7f5aa" }
|
||||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "7dd29f9616822e5fc259f5b4ae6c4ded9a71a132" }
|
||||
tree-sitter-ocaml = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "4abfdc1c7af2c6c77a370aee974627be1c285b3b" }
|
||||
tree-sitter-php = "0.21.1"
|
||||
tree-sitter-prisma-io = { git = "https://github.com/victorhqc/tree-sitter-prisma" }
|
||||
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
|
||||
tree-sitter-purescript = { git = "https://github.com/ivanmoreau/tree-sitter-purescript", rev = "a37140f0c7034977b90faa73c94fcb8a5e45ed08" }
|
||||
tree-sitter-purescript = { git = "https://github.com/postsolar/tree-sitter-purescript", rev = "v0.1.0" }
|
||||
tree-sitter-python = "0.20.2"
|
||||
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a" }
|
||||
tree-sitter-ruby = "0.20.0"
|
||||
tree-sitter-rust = "0.20.3"
|
||||
tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9" }
|
||||
tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca" }
|
||||
tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "bd60db7d3d06f89b6ec3b287c9a6e9190b5564bd" }
|
||||
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
|
||||
tree-sitter-uiua = { git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2" }
|
||||
tree-sitter-uiua = { git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "21dc2db39494585bf29a3f86d5add6e9d11a22ba" }
|
||||
tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42" }
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
|
||||
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
|
||||
unindent = "0.1.7"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
wasmtime = "16"
|
||||
wasmtime = "18.0"
|
||||
which = "6.0.0"
|
||||
sys-locale = "0.3.1"
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1d8975319c2d5de1bf710e7e21db25b0eee4bc66" }
|
||||
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "e4a23971ec3071a09c1e84816954c98f96e98e52" }
|
||||
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
|
||||
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "e4fcda0d5259d0acf902aee6de7d2501f2bd6629" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
@@ -285,10 +314,15 @@ debug = "limited"
|
||||
split-debuginfo = "off"
|
||||
debug = "full"
|
||||
|
||||
[profile.dev.package.taffy]
|
||||
opt-level = 3
|
||||
[profile.dev.package]
|
||||
taffy = { opt-level = 3 }
|
||||
cranelift-codegen = { opt-level = 3 }
|
||||
wasmtime-cranelift = { opt-level = 3 }
|
||||
|
||||
[profile.release]
|
||||
debug = "limited"
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.metadata.cargo-machete]
|
||||
ignored = ["bindgen", "cbindgen", "prost_build", "serde"]
|
||||
|
||||
@@ -11,6 +11,7 @@ ARG GITHUB_SHA
|
||||
ENV GITHUB_SHA=$GITHUB_SHA
|
||||
RUN --mount=type=cache,target=./script/node_modules \
|
||||
--mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
--mount=type=cache,target=./target \
|
||||
cargo build --release --package collab --bin collab
|
||||
|
||||
|
||||
3
Procfile
@@ -1,2 +1,3 @@
|
||||
collab: cd crates/collab && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve
|
||||
collab: RUST_LOG=${RUST_LOG:-warn,tower_http=info,collab=info} cargo run --package=collab serve
|
||||
livekit: livekit-server --dev
|
||||
blob_store: MINIO_ROOT_USER=the-blob-store-access-key MINIO_ROOT_PASSWORD=the-blob-store-secret-key minio server .blob_store
|
||||
|
||||
@@ -21,7 +21,8 @@ brew install zed
|
||||
|
||||
## Developing Zed
|
||||
|
||||
- [Building Zed](./docs/src/developing_zed__building_zed.md)
|
||||
- [Building Zed for macOS](./docs/src/developing_zed__building_zed_macos.md)
|
||||
- [Building Zed for Linux](./docs/src/developing_zed__building_zed_linux.md)
|
||||
- [Running Collaboration Locally](./docs/src/developing_zed__local_collaboration.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
3
assets/icons/file_icons/bun.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
6
assets/icons/file_icons/dart.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 9.203125 3.410156 C 8.792969 3 8.378906 2.597656 7.957031 2.207031 C 7.835938 2.078125 7.664062 1.996094 7.476562 1.996094 C 7.472656 1.996094 7.472656 1.996094 7.46875 1.996094 C 7.324219 2.011719 7.1875 2.042969 7.0625 2.089844 L 7.074219 2.085938 L 4.425781 3.40625 Z M 3.71875 3.71875 L 3.71875 9.078125 C 3.714844 9.109375 3.710938 9.144531 3.710938 9.179688 C 3.710938 9.40625 3.800781 9.613281 3.945312 9.765625 L 6.183594 12.003906 L 10.066406 12.003906 L 10.066406 10.066406 Z M 3.40625 3.40625 C 3.40625 3.40625 5.707031 2.257812 6.855469 1.683594 C 7.039062 1.59375 7.25 1.539062 7.476562 1.539062 C 7.496094 1.539062 7.515625 1.539062 7.53125 1.539062 C 7.824219 1.597656 8.082031 1.722656 8.296875 1.902344 L 8.292969 1.898438 L 12.460938 6.066406 L 12.460938 10.519531 L 10.519531 10.519531 L 10.519531 12.460938 L 5.996094 12.460938 L 1.898438 8.367188 C 1.683594 8.140625 1.546875 7.839844 1.542969 7.503906 C 1.558594 7.316406 1.609375 7.148438 1.6875 6.992188 L 1.683594 7 Z M 3.40625 3.40625 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
6
assets/icons/file_icons/docker.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 13.421875 5.847656 C 13.152344 5.671875 12.824219 5.566406 12.46875 5.566406 C 12.429688 5.566406 12.390625 5.566406 12.351562 5.570312 L 12.355469 5.570312 C 12.160156 5.570312 11.96875 5.585938 11.785156 5.621094 L 11.804688 5.617188 C 11.699219 5.023438 11.355469 4.523438 10.875 4.21875 L 10.867188 4.214844 L 10.679688 4.105469 L 10.554688 4.285156 C 10.410156 4.507812 10.292969 4.769531 10.226562 5.050781 L 10.222656 5.066406 C 10.183594 5.207031 10.160156 5.371094 10.160156 5.539062 C 10.160156 5.902344 10.265625 6.242188 10.445312 6.527344 L 10.441406 6.519531 C 10.164062 6.648438 9.839844 6.734375 9.496094 6.75 L 0.859375 6.75 C 0.632812 6.75 0.449219 6.929688 0.449219 7.15625 C 0.449219 7.179688 0.449219 7.210938 0.449219 7.238281 C 0.449219 8.003906 0.585938 8.738281 0.839844 9.417969 L 0.828125 9.375 C 1.070312 10.089844 1.53125 10.675781 2.128906 11.070312 L 2.144531 11.078125 C 2.90625 11.476562 3.808594 11.707031 4.765625 11.707031 C 4.855469 11.707031 4.945312 11.703125 5.035156 11.699219 L 5.023438 11.699219 C 5.03125 11.699219 5.039062 11.699219 5.050781 11.699219 C 5.605469 11.699219 6.148438 11.648438 6.675781 11.546875 L 6.621094 11.554688 C 7.40625 11.410156 8.109375 11.144531 8.742188 10.78125 L 8.710938 10.796875 C 9.261719 10.476562 9.730469 10.085938 10.128906 9.636719 L 10.136719 9.628906 C 10.714844 8.949219 11.1875 8.152344 11.511719 7.28125 L 11.527344 7.226562 L 11.648438 7.226562 C 11.671875 7.230469 11.703125 7.230469 11.730469 7.230469 C 12.265625 7.230469 12.753906 7.019531 13.113281 6.675781 C 13.277344 6.519531 13.410156 6.332031 13.496094 6.117188 L 13.5 6.105469 L 13.550781 5.949219 Z M 1.65625 6.496094 L 2.816406 6.496094 C 2.871094 6.496094 2.917969 6.449219 2.917969 6.394531 L 2.917969 5.363281 C 2.917969 5.308594 2.871094 5.265625 2.816406 5.261719 L 1.65625 5.261719 C 1.601562 5.265625 1.558594 5.308594 1.558594 5.363281 L 1.558594 6.394531 C 1.558594 6.453125 1.601562 6.496094 1.65625 6.496094 C 1.65625 6.496094 1.65625 6.496094 1.660156 6.496094 Z M 3.253906 6.496094 L 4.410156 6.496094 C 4.464844 6.496094 4.511719 6.453125 4.511719 6.394531 L 4.511719 5.363281 C 4.511719 5.308594 4.464844 5.265625 4.410156 5.261719 L 3.25 5.261719 C 3.195312 5.261719 3.152344 5.308594 3.152344 5.363281 L 3.152344 6.394531 C 3.152344 6.453125 3.195312 6.496094 3.25 6.496094 Z M 4.871094 6.496094 L 6.027344 6.496094 C 6.082031 6.496094 6.128906 6.449219 6.128906 6.394531 L 6.128906 5.363281 C 6.128906 5.308594 6.082031 5.265625 6.027344 5.261719 L 4.871094 5.261719 C 4.816406 5.265625 4.769531 5.308594 4.769531 5.363281 L 4.769531 6.394531 C 4.769531 6.449219 4.816406 6.496094 4.871094 6.496094 Z M 6.46875 6.496094 L 7.625 6.496094 C 7.683594 6.496094 7.726562 6.453125 7.726562 6.394531 L 7.726562 5.363281 C 7.726562 5.308594 7.683594 5.261719 7.625 5.261719 L 6.46875 5.261719 C 6.414062 5.261719 6.367188 5.308594 6.367188 5.363281 L 6.367188 6.394531 C 6.367188 6.453125 6.414062 6.496094 6.46875 6.496094 Z M 3.253906 5.015625 L 4.410156 5.015625 C 4.464844 5.015625 4.511719 4.96875 4.511719 4.914062 L 4.511719 3.882812 C 4.511719 3.828125 4.464844 3.78125 4.410156 3.78125 L 3.253906 3.78125 C 3.195312 3.78125 3.152344 3.828125 3.152344 3.882812 L 3.152344 4.914062 C 3.152344 4.96875 3.195312 5.015625 3.253906 5.015625 Z M 4.871094 5.015625 L 6.027344 5.015625 C 6.082031 5.015625 6.128906 4.96875 6.128906 4.914062 L 6.128906 3.882812 C 6.128906 3.828125 6.082031 3.78125 6.027344 3.78125 L 4.871094 3.78125 C 4.816406 3.78125 4.769531 3.828125 4.769531 3.882812 L 4.769531 4.914062 C 4.769531 4.96875 4.816406 5.015625 4.871094 5.015625 Z M 6.46875 5.015625 L 7.625 5.015625 C 7.683594 5.015625 7.726562 4.96875 7.726562 4.914062 L 7.726562 3.882812 C 7.726562 3.828125 7.683594 3.78125 7.625 3.78125 L 6.46875 3.78125 C 6.414062 3.78125 6.367188 3.828125 6.367188 3.882812 L 6.367188 4.914062 C 6.367188 4.96875 6.414062 5.015625 6.46875 5.015625 Z M 6.46875 3.53125 L 7.625 3.53125 C 7.683594 3.53125 7.726562 3.488281 7.726562 3.429688 L 7.726562 2.398438 C 7.726562 2.34375 7.683594 2.296875 7.625 2.296875 L 6.46875 2.296875 C 6.414062 2.296875 6.367188 2.34375 6.367188 2.398438 L 6.367188 3.429688 C 6.367188 3.488281 6.414062 3.53125 6.46875 3.53125 Z M 8.082031 6.496094 L 9.238281 6.496094 C 9.296875 6.496094 9.339844 6.453125 9.339844 6.394531 L 9.339844 5.363281 C 9.339844 5.308594 9.296875 5.261719 9.238281 5.261719 L 8.082031 5.261719 C 8.027344 5.261719 7.980469 5.308594 7.980469 5.363281 L 7.980469 6.394531 C 7.980469 6.449219 8.027344 6.496094 8.082031 6.496094 Z M 8.082031 6.496094 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -1,261 +1,319 @@
|
||||
{
|
||||
"suffixes": {
|
||||
"astro": "astro",
|
||||
"Emakefile": "erlang",
|
||||
"aac": "audio",
|
||||
"accdb": "storage",
|
||||
"app.src": "erlang",
|
||||
"avi": "video",
|
||||
"avif": "image",
|
||||
"bak": "backup",
|
||||
"bash": "terminal",
|
||||
"bash_aliases": "terminal",
|
||||
"bash_logout": "terminal",
|
||||
"bash_profile": "terminal",
|
||||
"bashrc": "terminal",
|
||||
"bmp": "image",
|
||||
"c": "code",
|
||||
"cc": "code",
|
||||
"conf": "settings",
|
||||
"cpp": "code",
|
||||
"css": "css",
|
||||
"csv": "storage",
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
"dbf": "storage",
|
||||
"dll": "storage",
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"eex": "elixir",
|
||||
"elm": "elm",
|
||||
"erl": "erlang",
|
||||
"escript": "erlang",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
"ex": "elixir",
|
||||
"exs": "elixir",
|
||||
"fish": "terminal",
|
||||
"flac": "audio",
|
||||
"fmp": "storage",
|
||||
"fp7": "storage",
|
||||
"frm": "storage",
|
||||
"gdb": "storage",
|
||||
"gif": "image",
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gitkeep": "vcs",
|
||||
"gitmodules": "vcs",
|
||||
"go": "go",
|
||||
"h": "code",
|
||||
"handlebars": "code",
|
||||
"hbs": "template",
|
||||
"heex": "elixir",
|
||||
"heif": "image",
|
||||
"heic": "image",
|
||||
"hrl": "erlang",
|
||||
"hs": "haskell",
|
||||
"htm": "template",
|
||||
"html": "template",
|
||||
"ib": "storage",
|
||||
"ico": "image",
|
||||
"ini": "settings",
|
||||
"j2k": "image",
|
||||
"java": "code",
|
||||
"jfif": "image",
|
||||
"jp2": "image",
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"js": "code",
|
||||
"json": "storage",
|
||||
"jsonc": "storage",
|
||||
"jxl": "image",
|
||||
"ldf": "storage",
|
||||
"lock": "lock",
|
||||
"log": "log",
|
||||
"m4a": "audio",
|
||||
"m4v": "video",
|
||||
"md": "document",
|
||||
"mdb": "storage",
|
||||
"mdf": "storage",
|
||||
"mdx": "document",
|
||||
"mkv": "video",
|
||||
"mka": "audio",
|
||||
"ml": "ocaml",
|
||||
"mli": "ocaml",
|
||||
"mov": "video",
|
||||
"mp3": "audio",
|
||||
"mp4": "video",
|
||||
"myd": "storage",
|
||||
"myi": "storage",
|
||||
"odp": "document",
|
||||
"ods": "document",
|
||||
"odt": "document",
|
||||
"ogg": "audio",
|
||||
"opus": "audio",
|
||||
"pdb": "storage",
|
||||
"pdf": "document",
|
||||
"php": "php",
|
||||
"png": "image",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"prettierignore": "prettier",
|
||||
"prettierrc": "prettier",
|
||||
"prisma": "prisma",
|
||||
"profile": "terminal",
|
||||
"ps1": "terminal",
|
||||
"psd": "image",
|
||||
"py": "python",
|
||||
"qoi": "image",
|
||||
"rb": "ruby",
|
||||
"rebar.config": "erlang",
|
||||
"rkt": "code",
|
||||
"rs": "rust",
|
||||
"rtf": "document",
|
||||
"sav": "storage",
|
||||
"scm": "code",
|
||||
"sdf": "storage",
|
||||
"sh": "terminal",
|
||||
"sqlite": "storage",
|
||||
"svelte": "template",
|
||||
"svg": "image",
|
||||
"swift": "code",
|
||||
"tiff": "image",
|
||||
"toml": "toml",
|
||||
"ts": "typescript",
|
||||
"tsv": "storage",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
"webp": "image",
|
||||
"wma": "audio",
|
||||
"wmv": "video",
|
||||
"wv": "audio",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
"xml": "template",
|
||||
"xrl": "erlang",
|
||||
"yaml": "settings",
|
||||
"yml": "settings",
|
||||
"yrl": "erlang",
|
||||
"zlogin": "terminal",
|
||||
"zsh": "terminal",
|
||||
"zsh_aliases": "terminal",
|
||||
"zsh_histfile": "terminal",
|
||||
"zsh_profile": "terminal",
|
||||
"zshenv": "terminal",
|
||||
"zshrc": "terminal"
|
||||
"stems": {
|
||||
"Podfile": "ruby",
|
||||
"Procfile": "heroku",
|
||||
"Dockerfile": "docker"
|
||||
},
|
||||
"suffixes": {
|
||||
"astro": "astro",
|
||||
"Emakefile": "erlang",
|
||||
"aac": "audio",
|
||||
"accdb": "storage",
|
||||
"app.src": "erlang",
|
||||
"avi": "video",
|
||||
"avif": "image",
|
||||
"bak": "backup",
|
||||
"bash": "terminal",
|
||||
"bash_aliases": "terminal",
|
||||
"bash_logout": "terminal",
|
||||
"bash_profile": "terminal",
|
||||
"bashrc": "terminal",
|
||||
"bmp": "image",
|
||||
"c": "code",
|
||||
"cc": "code",
|
||||
"cjs": "code",
|
||||
"conf": "settings",
|
||||
"cpp": "code",
|
||||
"css": "css",
|
||||
"csv": "storage",
|
||||
"cts": "typescript",
|
||||
"dart": "dart",
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
"dbf": "storage",
|
||||
"dll": "storage",
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"eex": "elixir",
|
||||
"elm": "elm",
|
||||
"erl": "erlang",
|
||||
"escript": "erlang",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
"ex": "elixir",
|
||||
"exs": "elixir",
|
||||
"fish": "terminal",
|
||||
"flac": "audio",
|
||||
"fmp": "storage",
|
||||
"fp7": "storage",
|
||||
"frm": "storage",
|
||||
"gdb": "storage",
|
||||
"gif": "image",
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gitkeep": "vcs",
|
||||
"gitmodules": "vcs",
|
||||
"go": "go",
|
||||
"graphql": "graphql",
|
||||
"h": "code",
|
||||
"handlebars": "code",
|
||||
"hbs": "template",
|
||||
"heex": "elixir",
|
||||
"heif": "image",
|
||||
"heic": "image",
|
||||
"hrl": "erlang",
|
||||
"hs": "haskell",
|
||||
"htm": "template",
|
||||
"html": "template",
|
||||
"ib": "storage",
|
||||
"ico": "image",
|
||||
"ini": "settings",
|
||||
"j2k": "image",
|
||||
"java": "java",
|
||||
"jfif": "image",
|
||||
"jp2": "image",
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"js": "code",
|
||||
"json": "storage",
|
||||
"jsonc": "storage",
|
||||
"jxl": "image",
|
||||
"kt": "kotlin",
|
||||
"ldf": "storage",
|
||||
"lock": "lock",
|
||||
"lockb": "bun",
|
||||
"log": "log",
|
||||
"lua": "lua",
|
||||
"m4a": "audio",
|
||||
"m4v": "video",
|
||||
"md": "document",
|
||||
"mdb": "storage",
|
||||
"mdf": "storage",
|
||||
"mdx": "document",
|
||||
"metadata": "code",
|
||||
"mkv": "video",
|
||||
"mjs": "code",
|
||||
"mka": "audio",
|
||||
"ml": "ocaml",
|
||||
"mli": "ocaml",
|
||||
"mov": "video",
|
||||
"mp3": "audio",
|
||||
"mp4": "video",
|
||||
"mts": "typescript",
|
||||
"myd": "storage",
|
||||
"myi": "storage",
|
||||
"nu": "terminal",
|
||||
"odp": "document",
|
||||
"ods": "document",
|
||||
"odt": "document",
|
||||
"ogg": "audio",
|
||||
"opus": "audio",
|
||||
"otf": "font",
|
||||
"pdb": "storage",
|
||||
"pdf": "document",
|
||||
"php": "php",
|
||||
"plist": "template",
|
||||
"png": "image",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"prettierignore": "prettier",
|
||||
"prettierrc": "prettier",
|
||||
"prisma": "prisma",
|
||||
"profile": "terminal",
|
||||
"ps1": "terminal",
|
||||
"psd": "image",
|
||||
"py": "python",
|
||||
"qoi": "image",
|
||||
"rb": "ruby",
|
||||
"rebar.config": "erlang",
|
||||
"rkt": "code",
|
||||
"rs": "rust",
|
||||
"r": "r",
|
||||
"rtf": "document",
|
||||
"sav": "storage",
|
||||
"scm": "code",
|
||||
"sdf": "storage",
|
||||
"sh": "terminal",
|
||||
"sqlite": "storage",
|
||||
"svelte": "template",
|
||||
"svg": "image",
|
||||
"swift": "swift",
|
||||
"tf": "terraform",
|
||||
"tfvars": "terraform",
|
||||
"tiff": "image",
|
||||
"toml": "toml",
|
||||
"ts": "typescript",
|
||||
"tsv": "storage",
|
||||
"ttf": "font",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
"webp": "image",
|
||||
"wma": "audio",
|
||||
"wmv": "video",
|
||||
"wv": "audio",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
"xml": "template",
|
||||
"xrl": "erlang",
|
||||
"yaml": "settings",
|
||||
"yml": "settings",
|
||||
"yrl": "erlang",
|
||||
"zlogin": "terminal",
|
||||
"zsh": "terminal",
|
||||
"zsh_aliases": "terminal",
|
||||
"zsh_histfile": "terminal",
|
||||
"zsh_profile": "terminal",
|
||||
"zshenv": "terminal",
|
||||
"zshrc": "terminal"
|
||||
},
|
||||
"types": {
|
||||
"astro": {
|
||||
"icon": "icons/file_icons/astro.svg"
|
||||
},
|
||||
"types": {
|
||||
"astro": {
|
||||
"icon": "icons/file_icons/astro.svg"
|
||||
},
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/audio.svg"
|
||||
},
|
||||
"code": {
|
||||
"icon": "icons/file_icons/code.svg"
|
||||
},
|
||||
"collapsed_chevron": {
|
||||
"icon": "icons/file_icons/chevron_right.svg"
|
||||
},
|
||||
"collapsed_folder": {
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"document": {
|
||||
"icon": "icons/file_icons/book.svg"
|
||||
},
|
||||
"elixir": {
|
||||
"icon": "icons/file_icons/elixir.svg"
|
||||
},
|
||||
"elm": {
|
||||
"icon": "icons/file_icons/elm.svg"
|
||||
},
|
||||
"erlang": {
|
||||
"icon": "icons/file_icons/erlang.svg"
|
||||
},
|
||||
"eslint": {
|
||||
"icon": "icons/file_icons/eslint.svg"
|
||||
},
|
||||
"expanded_chevron": {
|
||||
"icon": "icons/file_icons/chevron_down.svg"
|
||||
},
|
||||
"expanded_folder": {
|
||||
"icon": "icons/file_icons/folder_open.svg"
|
||||
},
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
"go": {
|
||||
"icon": "icons/file_icons/go.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
"lock": {
|
||||
"icon": "icons/file_icons/lock.svg"
|
||||
},
|
||||
"log": {
|
||||
"icon": "icons/file_icons/info.svg"
|
||||
},
|
||||
"ocaml": {
|
||||
"icon": "icons/file_icons/ocaml.svg"
|
||||
},
|
||||
"phoenix": {
|
||||
"icon": "icons/file_icons/phoenix.svg"
|
||||
},
|
||||
"php": {
|
||||
"icon": "icons/file_icons/php.svg"
|
||||
},
|
||||
"prettier": {
|
||||
"icon": "icons/file_icons/prettier.svg"
|
||||
},
|
||||
"prisma": {
|
||||
"icon": "icons/file_icons/prisma.svg"
|
||||
},
|
||||
"python": {
|
||||
"icon": "icons/file_icons/python.svg"
|
||||
},
|
||||
"ruby": {
|
||||
"icon": "icons/file_icons/ruby.svg"
|
||||
},
|
||||
"rust": {
|
||||
"icon": "icons/file_icons/rust.svg"
|
||||
},
|
||||
"settings": {
|
||||
"icon": "icons/file_icons/settings.svg"
|
||||
},
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"template": {
|
||||
"icon": "icons/file_icons/html.svg"
|
||||
},
|
||||
"terminal": {
|
||||
"icon": "icons/file_icons/terminal.svg"
|
||||
},
|
||||
"toml": {
|
||||
"icon": "icons/file_icons/toml.svg"
|
||||
},
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
"video": {
|
||||
"icon": "icons/file_icons/video.svg"
|
||||
},
|
||||
"vue": {
|
||||
"icon": "icons/file_icons/vue.svg"
|
||||
}
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/audio.svg"
|
||||
},
|
||||
"code": {
|
||||
"icon": "icons/file_icons/code.svg"
|
||||
},
|
||||
"collapsed_chevron": {
|
||||
"icon": "icons/file_icons/chevron_right.svg"
|
||||
},
|
||||
"collapsed_folder": {
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"dart": {
|
||||
"icon": "icons/file_icons/dart.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"docker": {
|
||||
"icon": "icons/file_icons/docker.svg"
|
||||
},
|
||||
"document": {
|
||||
"icon": "icons/file_icons/book.svg"
|
||||
},
|
||||
"elixir": {
|
||||
"icon": "icons/file_icons/elixir.svg"
|
||||
},
|
||||
"elm": {
|
||||
"icon": "icons/file_icons/elm.svg"
|
||||
},
|
||||
"erlang": {
|
||||
"icon": "icons/file_icons/erlang.svg"
|
||||
},
|
||||
"eslint": {
|
||||
"icon": "icons/file_icons/eslint.svg"
|
||||
},
|
||||
"expanded_chevron": {
|
||||
"icon": "icons/file_icons/chevron_down.svg"
|
||||
},
|
||||
"expanded_folder": {
|
||||
"icon": "icons/file_icons/folder_open.svg"
|
||||
},
|
||||
"font": {
|
||||
"icon": "icons/file_icons/font.svg"
|
||||
},
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
"heroku": {
|
||||
"icon": "icons/file_icons/heroku.svg"
|
||||
},
|
||||
"go": {
|
||||
"icon": "icons/file_icons/go.svg"
|
||||
},
|
||||
"graphql": {
|
||||
"icon": "icons/file_icons/graphql.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
"java": {
|
||||
"icon": "icons/file_icons/java.svg"
|
||||
},
|
||||
"kotlin": {
|
||||
"icon": "icons/file_icons/kotlin.svg"
|
||||
},
|
||||
"lock": {
|
||||
"icon": "icons/file_icons/lock.svg"
|
||||
},
|
||||
"bun": {
|
||||
"icon": "icons/file_icons/bun.svg"
|
||||
},
|
||||
"log": {
|
||||
"icon": "icons/file_icons/info.svg"
|
||||
},
|
||||
"lua": {
|
||||
"icon": "icons/file_icons/lua.svg"
|
||||
},
|
||||
"ocaml": {
|
||||
"icon": "icons/file_icons/ocaml.svg"
|
||||
},
|
||||
"phoenix": {
|
||||
"icon": "icons/file_icons/phoenix.svg"
|
||||
},
|
||||
"php": {
|
||||
"icon": "icons/file_icons/php.svg"
|
||||
},
|
||||
"prettier": {
|
||||
"icon": "icons/file_icons/prettier.svg"
|
||||
},
|
||||
"prisma": {
|
||||
"icon": "icons/file_icons/prisma.svg"
|
||||
},
|
||||
"python": {
|
||||
"icon": "icons/file_icons/python.svg"
|
||||
},
|
||||
"ruby": {
|
||||
"icon": "icons/file_icons/ruby.svg"
|
||||
},
|
||||
"rust": {
|
||||
"icon": "icons/file_icons/rust.svg"
|
||||
},
|
||||
"r": {
|
||||
"icon": "icons/file_icons/r.svg"
|
||||
},
|
||||
"settings": {
|
||||
"icon": "icons/file_icons/settings.svg"
|
||||
},
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"swift": {
|
||||
"icon": "icons/file_icons/swift.svg"
|
||||
},
|
||||
"template": {
|
||||
"icon": "icons/file_icons/html.svg"
|
||||
},
|
||||
"terraform": {
|
||||
"icon": "icons/file_icons/terraform.svg"
|
||||
},
|
||||
"terminal": {
|
||||
"icon": "icons/file_icons/terminal.svg"
|
||||
},
|
||||
"toml": {
|
||||
"icon": "icons/file_icons/toml.svg"
|
||||
},
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
"video": {
|
||||
"icon": "icons/file_icons/video.svg"
|
||||
},
|
||||
"vue": {
|
||||
"icon": "icons/file_icons/vue.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
assets/icons/file_icons/font.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style="fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 16.001786 21 L 19.497321 21 M 5.997321 21 L 12 3 L 18.002679 21 M 4.502679 21 L 7.998214 21 M 14.997321 14.000893 L 9.002679 14.000893 " transform="matrix(0.486111,0,0,0.486111,1.166667,1.166667)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 559 B |
1
assets/icons/file_icons/graphql.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="14px" height="14px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="#000000"><path fill-rule="evenodd" clip-rule="evenodd" d="M50 6.90308L87.323 28.4515V71.5484L50 93.0968L12.677 71.5484V28.4515L50 6.90308ZM16.8647 30.8693V62.5251L44.2795 15.0414L16.8647 30.8693ZM50 13.5086L18.3975 68.2457H81.6025L50 13.5086ZM77.4148 72.4334H22.5852L50 88.2613L77.4148 72.4334ZM83.1353 62.5251L55.7205 15.0414L83.1353 30.8693V62.5251Z"/><circle cx="50" cy="9.3209" r="8.82"/><circle cx="85.2292" cy="29.6605" r="8.82"/><circle cx="85.2292" cy="70.3396" r="8.82"/><circle cx="50" cy="90.6791" r="8.82"/><circle cx="14.7659" cy="70.3396" r="8.82"/><circle cx="14.7659" cy="29.6605" r="8.82"/></svg>
|
||||
|
After Width: | Height: | Size: 709 B |
6
assets/icons/file_icons/heroku.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 12.023438 0 L 1.976562 0 C 1.277344 0 0.71875 0.558594 0.71875 1.261719 L 0.71875 12.738281 C 0.71875 13.441406 1.277344 14 1.976562 14 L 12.023438 14 C 12.722656 14 13.28125 13.441406 13.28125 12.738281 L 13.28125 1.261719 C 13.28125 0.558594 12.722656 0 12.023438 0 Z M 12.582031 12.738281 C 12.582031 13.054688 12.335938 13.300781 12.023438 13.300781 L 1.976562 13.300781 C 1.664062 13.300781 1.417969 13.054688 1.417969 12.738281 L 1.417969 1.261719 C 1.417969 0.945312 1.664062 0.699219 1.976562 0.699219 L 12.023438 0.699219 C 12.335938 0.699219 12.582031 0.945312 12.582031 1.261719 Z M 3.867188 11.898438 L 5.441406 10.5 L 3.867188 9.101562 Z M 9.539062 6.230469 C 9.257812 5.949219 8.734375 5.601562 7.859375 5.601562 C 6.914062 5.601562 5.933594 5.84375 5.234375 6.089844 L 5.234375 2.101562 L 3.832031 2.101562 L 3.832031 8.15625 L 4.8125 7.699219 C 4.8125 7.699219 6.421875 6.964844 7.824219 6.964844 C 8.523438 6.964844 8.699219 7.351562 8.699219 7.699219 L 8.699219 11.898438 L 10.097656 11.898438 L 10.097656 7.699219 C 10.132812 7.59375 10.132812 6.824219 9.539062 6.230469 Z M 7.683594 4.375 L 9.082031 4.375 C 9.710938 3.640625 10.027344 2.90625 10.132812 2.101562 L 8.734375 2.101562 C 8.59375 2.90625 8.242188 3.640625 7.683594 4.375 Z M 7.683594 4.375 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
7
assets/icons/file_icons/java.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.644531 9.734375 C 9.640625 9.734375 8.824219 8.917969 8.824219 7.910156 C 8.824219 6.90625 9.640625 6.089844 10.644531 6.089844 C 11.652344 6.089844 12.46875 6.90625 12.46875 7.910156 C 12.46875 8.917969 11.652344 9.734375 10.644531 9.734375 Z M 10.644531 7 C 10.144531 7 9.734375 7.410156 9.734375 7.910156 C 9.734375 8.414062 10.144531 8.824219 10.644531 8.824219 C 11.148438 8.824219 11.558594 8.414062 11.558594 7.910156 C 11.558594 7.410156 11.148438 7 10.644531 7 Z M 7.457031 4.265625 L 6.542969 4.265625 L 6.542969 2.441406 L 7.457031 2.441406 Z M 6.089844 4.265625 L 5.175781 4.265625 L 5.175781 1.53125 L 6.089844 1.53125 Z M 4.722656 4.265625 L 3.808594 4.265625 L 3.808594 2.441406 L 4.722656 2.441406 Z M 4.722656 4.265625 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.632812 12.011719 C 3.617188 12.011719 1.988281 10.382812 1.988281 8.367188 L 1.988281 5.632812 L 9.277344 5.632812 L 9.277344 8.367188 C 9.277344 10.382812 7.648438 12.011719 5.632812 12.011719 Z M 5.632812 12.011719 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
6
assets/icons/file_icons/kotlin.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 12.125 12.125 L 1.875 12.125 L 1.875 1.875 L 12.125 1.875 L 7 7 Z M 12.125 12.125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 385 B |
10
assets/icons/file_icons/lua.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_34_7)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2625 7.00157C12.2625 4.09565 9.90677 1.73994 7.00085 1.73994C4.09494 1.73994 1.73923 4.09565 1.73923 7.00157C1.73923 9.90749 4.09494 12.2632 7.00085 12.2632C9.90677 12.2632 12.2625 9.90749 12.2625 7.00157ZM10.7213 4.82205C10.7213 3.97095 10.0313 3.281 9.18023 3.281C8.32913 3.281 7.63917 3.97095 7.63917 4.82205C7.63917 5.67315 8.32913 6.3631 9.18023 6.3631C10.0313 6.3631 10.7213 5.67315 10.7213 4.82205ZM11.9704 11.3383L12.2849 11.6123C12.4856 11.3819 12.6732 11.1354 12.8424 10.8799L12.4947 10.6496C12.3355 10.8899 12.1591 11.1216 11.9704 11.3383ZM3.09019 12.8218C2.83557 12.6512 2.59021 12.4623 2.36091 12.2602L2.63665 11.9473C2.85224 12.1372 3.08295 12.3149 3.3224 12.4754L3.09019 12.8218ZM13.2533 9.09685L13.6488 9.22944C13.5516 9.51944 13.4341 9.806 13.2995 10.0812L12.9248 9.89792C13.0514 9.63912 13.1619 9.3696 13.2533 9.09685ZM11.3601 11.9516L11.6357 12.2647C11.4062 12.4667 11.1607 12.6555 10.9061 12.8259L10.6741 12.4793C10.9135 12.3191 11.1443 12.1415 11.3601 11.9516ZM9.92464 12.9125L10.1093 13.2865C9.83502 13.4219 9.54901 13.5406 9.25919 13.6391L9.12488 13.2442C9.39754 13.1515 9.66662 13.0399 9.92464 12.9125ZM0.698663 10.0754C0.564769 9.80041 0.447705 9.51376 0.350717 9.22346L0.746321 9.09128C0.837577 9.36442 0.947715 9.63411 1.0737 9.89286L0.698663 10.0754ZM4.7358 13.6379C4.44725 13.539 4.16148 13.4197 3.88645 13.2833L4.07178 12.9096C4.33065 13.038 4.59955 13.1503 4.871 13.2433L4.7358 13.6379ZM7.50907 0.425028L7.54103 0.00914471C7.84635 0.0326099 8.15299 0.0767322 8.45243 0.140276L8.36583 0.548303C8.08435 0.488557 7.79609 0.447081 7.50907 0.425028ZM8.28868 13.469L8.37042 13.878C8.07006 13.9381 7.76308 13.9787 7.45798 13.999L7.4304 13.5828C7.71737 13.5638 8.00614 13.5255 8.28868 13.469ZM6.56475 13.5831L6.5374 13.9993C6.23179 13.9792 5.92469 13.9385 5.62462 13.8783L5.70667 13.4693C5.98876 13.5259 6.27745 13.5642 6.56475 13.5831ZM1.71207 11.6074C1.51131 11.3768 1.32385 11.1303 1.15489 10.8748L1.5028 10.6447C1.66168 10.885 1.83795 11.1167 2.0267 11.3336L1.71207 11.6074ZM1.54706 3.2931L1.20185 3.05901C1.3739 2.80528 1.56421 2.56096 1.76748 2.33281L2.07891 2.61029C1.8878 2.82479 1.70885 3.05452 1.54706 3.2931ZM13.5829 7.4008L13.9993 7.42603C13.9807 7.73163 13.9418 8.03889 13.8834 8.33925L13.474 8.25969C13.5289 7.97721 13.5655 7.68824 13.5829 7.4008ZM2.69596 2.00373L2.42386 1.68762C2.65562 1.48813 2.90314 1.30202 3.15957 1.13448L3.38773 1.48365C3.14661 1.64119 2.91386 1.81619 2.69596 2.00373ZM4.14194 1.05867L3.96141 0.682649C4.23716 0.550267 4.52444 0.434775 4.8153 0.339358L4.9453 0.735689C4.67165 0.825447 4.40135 0.934116 4.14194 1.05867ZM6.64336 0.415732C6.35624 0.431589 6.06708 0.466692 5.78392 0.520066L5.70665 0.110177C6.00769 0.0534275 6.3151 0.0161137 6.62038 -0.00073251L6.64336 0.415732ZM9.33849 0.390492C9.62584 0.492485 9.91028 0.614901 10.1839 0.754324L9.99453 1.12595C9.73698 0.994735 9.46932 0.879534 9.19896 0.783565L9.33849 0.390492ZM0.116692 8.3334C0.0582983 8.03277 0.0192821 7.72556 0.000734408 7.42027L0.417082 7.39499C0.434525 7.68212 0.471213 7.97108 0.526144 8.25385L0.116692 8.3334ZM13.8694 5.59362C13.9308 5.89342 13.9729 6.20022 13.9945 6.50553L13.5785 6.53503C13.5581 6.24787 13.5185 5.95929 13.4607 5.67733L13.8694 5.59362ZM0.421578 6.52929L0.00551996 6.49964C0.0272978 6.19421 0.0697176 5.88734 0.131602 5.58753L0.540095 5.67184C0.48192 5.95369 0.442046 6.24218 0.421578 6.52929ZM13.268 3.85795C13.4053 4.1315 13.5257 4.41681 13.6258 4.70597L13.2317 4.84251C13.1375 4.57059 13.0243 4.30226 12.8952 4.045L13.268 3.85795ZM0.770731 4.83746L0.376902 4.70007C0.477381 4.41205 0.598242 4.12695 0.736138 3.85268L1.10878 4.04006C0.979007 4.29819 0.86526 4.56647 0.770731 4.83746ZM10.7213 1.73979C10.7213 0.888612 11.4113 0.198592 12.2625 0.198592C13.1137 0.198592 13.8037 0.888612 13.8037 1.73979C13.8037 2.59098 13.1137 3.281 12.2625 3.281C11.4113 3.281 10.7213 2.59098 10.7213 1.73979Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_34_7">
|
||||
<rect width="14" height="14" fill="white" transform="matrix(-1 0 0 1 14 0)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
6
assets/icons/file_icons/r.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15px" height="14px" viewBox="0 0 15 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 14.0625 6.46875 C 14.0625 4.203125 11.125 2.367188 7.5 2.367188 C 3.875 2.367188 0.9375 4.203125 0.9375 6.46875 C 0.9375 8.488281 3.273438 10.160156 6.34375 10.503906 L 6.34375 11.8125 L 8.582031 11.8125 L 8.582031 10.511719 C 9.113281 10.457031 9.636719 10.359375 10.148438 10.21875 L 11.058594 11.8125 L 13.589844 11.8125 L 12.0625 9.410156 C 13.292969 8.667969 14.0625 7.625 14.0625 6.46875 Z M 3.515625 6.773438 C 3.515625 5.226562 5.75 3.96875 8.503906 3.96875 C 11.257812 3.96875 13.292969 4.828125 13.292969 6.773438 C 13.304688 7.757812 12.671875 8.644531 11.699219 9.015625 C 11.65625 8.988281 11.609375 8.964844 11.558594 8.941406 C 11.355469 8.851562 11.144531 8.78125 10.933594 8.71875 C 10.933594 8.71875 12.886719 8.582031 12.886719 6.765625 C 12.886719 4.949219 10.839844 4.914062 10.839844 4.914062 L 6.34375 4.914062 L 6.34375 9.300781 C 4.671875 8.847656 3.515625 7.886719 3.515625 6.773438 Z M 9.957031 7.582031 L 8.601562 7.582031 L 8.601562 6.410156 L 9.957031 6.410156 C 10.125 6.398438 10.292969 6.453125 10.410156 6.5625 C 10.53125 6.675781 10.597656 6.828125 10.585938 6.984375 C 10.589844 7.144531 10.527344 7.296875 10.410156 7.410156 C 10.289062 7.519531 10.128906 7.582031 9.957031 7.582031 Z M 8.582031 9.109375 L 9.183594 9.109375 C 9.300781 9.113281 9.40625 9.15625 9.484375 9.238281 C 9.578125 9.320312 9.65625 9.414062 9.722656 9.511719 C 9.34375 9.554688 8.964844 9.574219 8.582031 9.578125 Z M 8.582031 9.109375 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
6
assets/icons/file_icons/swift.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 8.269531 1.53125 C 13.078125 4.824219 11.523438 8.457031 11.523438 8.457031 C 11.523438 8.457031 12.890625 10.015625 12.335938 11.375 C 12.335938 11.375 11.773438 10.421875 10.828125 10.421875 C 9.914062 10.421875 9.378906 11.375 7.546875 11.375 C 3.460938 11.375 1.53125 7.9375 1.53125 7.9375 C 5.210938 10.375 7.722656 8.648438 7.722656 8.648438 C 6.066406 7.679688 2.539062 3.039062 2.539062 3.039062 C 5.609375 5.675781 6.9375 6.371094 6.9375 6.371094 C 6.144531 5.710938 3.921875 2.484375 3.921875 2.484375 C 5.699219 4.296875 9.230469 6.828125 9.230469 6.828125 C 10.234375 4.027344 8.269531 1.53125 8.269531 1.53125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 926 B |
6
assets/icons/file_icons/terraform.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 8.01531L19.2375 12.1073V20.2894L12.5066 16.1992V8.01531Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0294 12.1073V20.2894L27.1563 16.1992V8.01531L20.0294 12.1073Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58781 3.66V11.5787L11.7147 15.5381V7.61937L4.58781 3.66Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 25.04L19.2375 29V21.1348V21.0818L12.5066 17.1219V25.04Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 620 B |
1
assets/icons/play.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.24182 2.32181C3.3919 2.23132 3.5784 2.22601 3.73338 2.30781L12.7334 7.05781C12.8974 7.14436 13 7.31457 13 7.5C13 7.68543 12.8974 7.85564 12.7334 7.94219L3.73338 12.6922C3.5784 12.774 3.3919 12.7687 3.24182 12.6782C3.09175 12.5877 3 12.4252 3 12.25V2.75C3 2.57476 3.09175 2.4123 3.24182 2.32181ZM4 3.57925V11.4207L11.4288 7.5L4 3.57925Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
||||
|
After Width: | Height: | Size: 518 B |
@@ -55,38 +55,14 @@
|
||||
"bindings": {
|
||||
"alt-cmd-/": "search::ToggleRegex",
|
||||
"ctrl-0": "project_panel::ToggleFocus",
|
||||
"cmd-1": [
|
||||
"pane::ActivateItem",
|
||||
0
|
||||
],
|
||||
"cmd-2": [
|
||||
"pane::ActivateItem",
|
||||
1
|
||||
],
|
||||
"cmd-3": [
|
||||
"pane::ActivateItem",
|
||||
2
|
||||
],
|
||||
"cmd-4": [
|
||||
"pane::ActivateItem",
|
||||
3
|
||||
],
|
||||
"cmd-5": [
|
||||
"pane::ActivateItem",
|
||||
4
|
||||
],
|
||||
"cmd-6": [
|
||||
"pane::ActivateItem",
|
||||
5
|
||||
],
|
||||
"cmd-7": [
|
||||
"pane::ActivateItem",
|
||||
6
|
||||
],
|
||||
"cmd-8": [
|
||||
"pane::ActivateItem",
|
||||
7
|
||||
],
|
||||
"cmd-1": ["pane::ActivateItem", 0],
|
||||
"cmd-2": ["pane::ActivateItem", 1],
|
||||
"cmd-3": ["pane::ActivateItem", 2],
|
||||
"cmd-4": ["pane::ActivateItem", 3],
|
||||
"cmd-5": ["pane::ActivateItem", 4],
|
||||
"cmd-6": ["pane::ActivateItem", 5],
|
||||
"cmd-7": ["pane::ActivateItem", 6],
|
||||
"cmd-8": ["pane::ActivateItem", 7],
|
||||
"cmd-9": "pane::ActivateLastItem"
|
||||
}
|
||||
},
|
||||
|
||||
561
assets/keymaps/default-linux.json
Normal file
@@ -0,0 +1,561 @@
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"up": "menu::SelectPrev",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectFirst",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"ctrl-up": "menu::SelectFirst",
|
||||
"ctrl-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm",
|
||||
"shift-f10": "menu::ShowContextMenu",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"ctrl-shift-w": "workspace::CloseWindow",
|
||||
"shift-escape": "workspace::ToggleZoom",
|
||||
"ctrl-o": "workspace::Open",
|
||||
"ctrl-=": "zed::IncreaseBufferFontSize",
|
||||
"ctrl-+": "zed::IncreaseBufferFontSize",
|
||||
"ctrl--": "zed::DecreaseBufferFontSize",
|
||||
"ctrl-0": "zed::ResetBufferFontSize",
|
||||
"ctrl-,": "zed::OpenSettings",
|
||||
"ctrl-q": "zed::Quit",
|
||||
"ctrl-h": "zed::Hide",
|
||||
"alt-ctrl-h": "zed::HideOthers",
|
||||
"ctrl-m": "zed::Minimize",
|
||||
"f11": "zed::ToggleFullScreen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"escape": "editor::Cancel",
|
||||
"backspace": "editor::Backspace",
|
||||
"shift-backspace": "editor::Backspace",
|
||||
"ctrl-h": "editor::Backspace",
|
||||
"delete": "editor::Delete",
|
||||
"ctrl-d": "editor::Delete",
|
||||
"tab": "editor::Tab",
|
||||
"shift-tab": "editor::TabPrev",
|
||||
"ctrl-k": "editor::CutToEndOfLine",
|
||||
"ctrl-t": "editor::Transpose",
|
||||
"ctrl-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-delete": "editor::DeleteToEndOfLine",
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"alt-delete": "editor::DeleteToNextWordEnd",
|
||||
"alt-h": "editor::DeleteToPreviousWordStart",
|
||||
"alt-d": "editor::DeleteToNextWordEnd",
|
||||
"ctrl-x": "editor::Cut",
|
||||
"ctrl-c": "editor::Copy",
|
||||
"ctrl-v": "editor::Paste",
|
||||
"ctrl-z": "editor::Undo",
|
||||
"ctrl-shift-z": "editor::Redo",
|
||||
"ctrl-y": "editor::Redo",
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::MoveToStartOfParagraph",
|
||||
"pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::MovePageUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"ctrl-down": "editor::MoveToEndOfParagraph",
|
||||
"pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::MovePageDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
"ctrl-p": "editor::MoveUp",
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"ctrl-shift-l": "editor::NextScreen", // todo!(linux): What is this
|
||||
"alt-left": "editor::MoveToPreviousWordStart",
|
||||
"alt-b": "editor::MoveToPreviousWordStart",
|
||||
"alt-right": "editor::MoveToNextWordEnd",
|
||||
"alt-f": "editor::MoveToNextWordEnd",
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"ctrl-home": "editor::MoveToBeginning",
|
||||
"ctrl-=end": "editor::MoveToEnd",
|
||||
"shift-up": "editor::SelectUp",
|
||||
"ctrl-shift-p": "editor::SelectUp",
|
||||
"shift-down": "editor::SelectDown",
|
||||
"ctrl-shift-n": "editor::SelectDown",
|
||||
"shift-left": "editor::SelectLeft",
|
||||
"ctrl-shift-b": "editor::SelectLeft",
|
||||
"shift-right": "editor::SelectRight",
|
||||
"ctrl-shift-f": "editor::SelectRight",
|
||||
"alt-shift-left": "editor::SelectToPreviousWordStart",
|
||||
"alt-shift-b": "editor::SelectToPreviousWordStart",
|
||||
"alt-shift-right": "editor::SelectToNextWordEnd",
|
||||
"alt-shift-f": "editor::SelectToNextWordEnd",
|
||||
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
||||
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
||||
"ctrl-shift-home": "editor::SelectToBeginning",
|
||||
"ctrl-shift-end": "editor::SelectToEnd",
|
||||
"ctrl-a": "editor::SelectAll",
|
||||
"ctrl-l": "editor::SelectLine",
|
||||
"ctrl-shift-i": "editor::Format",
|
||||
"shift-home": [
|
||||
"editor::SelectToBeginningOfLine",
|
||||
{
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
],
|
||||
"shift-end": [
|
||||
"editor::SelectToEndOfLine",
|
||||
{
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
],
|
||||
"ctrl-shift-e": [
|
||||
"editor::SelectToEndOfLine",
|
||||
{
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"shift-enter": "editor::Newline",
|
||||
"ctrl-shift-enter": "editor::NewlineAbove",
|
||||
"ctrl-enter": "editor::NewlineBelow",
|
||||
"alt-z": "editor::ToggleSoftWrap",
|
||||
"ctrl-f": [
|
||||
"buffer_search::Deploy",
|
||||
{
|
||||
"focus": true
|
||||
}
|
||||
],
|
||||
"alt-\\": "copilot::Suggest",
|
||||
"alt-]": "copilot::NextSuggestion",
|
||||
"alt-[": "copilot::PreviousSuggestion",
|
||||
"ctrl->": "assistant::QuoteSelection"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == auto_height",
|
||||
"bindings": {
|
||||
"ctrl-enter": "editor::Newline",
|
||||
"shift-enter": "editor::Newline",
|
||||
"ctrl-shift-enter": "editor::NewlineBelow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
"f3": "search::SelectNextMatch",
|
||||
"shift-f3": "search::SelectPrevMatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ConversationEditor > Editor",
|
||||
"bindings": {
|
||||
"ctrl-enter": "assistant::Assist",
|
||||
"ctrl-s": "workspace::Save",
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"shift-enter": "assistant::Split",
|
||||
"ctrl-r": "assistant::CycleMessageRole"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"bindings": {
|
||||
"escape": "buffer_search::Dismiss",
|
||||
"tab": "buffer_search::FocusEditor",
|
||||
"enter": "search::SelectNextMatch",
|
||||
"shift-enter": "search::SelectPrevMatch",
|
||||
"alt-enter": "search::SelectAllMatches",
|
||||
"alt-tab": "search::CycleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && in_replace",
|
||||
"bindings": {
|
||||
"enter": "search::ReplaceNext",
|
||||
"ctrl-enter": "search::ReplaceAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace > Editor",
|
||||
"bindings": {
|
||||
"up": "search::PreviousHistoryQuery",
|
||||
"down": "search::NextHistoryQuery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchBar",
|
||||
"bindings": {
|
||||
"escape": "project_search::ToggleFocus",
|
||||
"alt-tab": "search::CycleMode",
|
||||
"ctrl-shift-h": "search::ToggleReplace",
|
||||
"ctrl-alt-g": "search::ActivateRegexMode",
|
||||
"ctrl-alt-s": "search::ActivateSemanticMode",
|
||||
"ctrl-alt-x": "search::ActivateTextMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchBar > Editor",
|
||||
"bindings": {
|
||||
"up": "search::PreviousHistoryQuery",
|
||||
"down": "search::NextHistoryQuery"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchBar && in_replace",
|
||||
"bindings": {
|
||||
"enter": "search::ReplaceNext",
|
||||
"ctrl-enter": "search::ReplaceAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchView",
|
||||
"bindings": {
|
||||
"escape": "project_search::ToggleFocus",
|
||||
"alt-tab": "search::CycleMode",
|
||||
"ctrl-shift-h": "search::ToggleReplace",
|
||||
"ctrl-alt-g": "search::ActivateRegexMode",
|
||||
"ctrl-alt-s": "search::ActivateSemanticMode",
|
||||
"ctrl-alt-x": "search::ActivateTextMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"ctrl-{": "pane::ActivatePrevItem",
|
||||
"ctrl-}": "pane::ActivateNextItem",
|
||||
"ctrl-alt-left": "pane::ActivatePrevItem",
|
||||
"ctrl-alt-right": "pane::ActivateNextItem",
|
||||
"ctrl-w": "pane::CloseActiveItem",
|
||||
"ctrl-alt-t": "pane::CloseInactiveItems",
|
||||
"ctrl-alt-shift-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-k u": "pane::CloseCleanItems",
|
||||
"ctrl-k ctrl-w": "pane::CloseAllItems",
|
||||
"ctrl-f": "project_search::ToggleFocus",
|
||||
"f3": "search::SelectNextMatch",
|
||||
"shift-f3": "search::SelectPrevMatch",
|
||||
"ctrl-shift-h": "search::ToggleReplace",
|
||||
"alt-enter": "search::SelectAllMatches",
|
||||
"ctrl-alt-c": "search::ToggleCaseSensitive",
|
||||
"ctrl-alt-w": "search::ToggleWholeWord",
|
||||
"alt-tab": "search::CycleMode",
|
||||
"ctrl-alt-f": "project_search::ToggleFilters",
|
||||
"ctrl-alt-g": "search::ActivateRegexMode",
|
||||
"ctrl-alt-s": "search::ActivateSemanticMode",
|
||||
"ctrl-alt-x": "search::ActivateTextMode"
|
||||
}
|
||||
},
|
||||
// Bindings from VS Code
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-[": "editor::Outdent",
|
||||
"ctrl-]": "editor::Indent",
|
||||
"ctrl-alt-up": "editor::AddSelectionAbove",
|
||||
"ctrl-alt-down": "editor::AddSelectionBelow",
|
||||
"ctrl-d": [
|
||||
"editor::SelectNext",
|
||||
{
|
||||
"replace_newest": false
|
||||
}
|
||||
],
|
||||
"ctrl-shift-l": "editor::SelectAllMatches",
|
||||
"ctrl-shift-d": [
|
||||
"editor::SelectPrevious",
|
||||
{
|
||||
"replace_newest": false
|
||||
}
|
||||
],
|
||||
"ctrl-k ctrl-d": [
|
||||
"editor::SelectNext",
|
||||
{
|
||||
"replace_newest": true
|
||||
}
|
||||
],
|
||||
"ctrl-k ctrl-shift-d": [
|
||||
"editor::SelectPrevious",
|
||||
{
|
||||
"replace_newest": true
|
||||
}
|
||||
],
|
||||
"ctrl-k ctrl-i": "editor::Hover",
|
||||
"ctrl-/": [
|
||||
"editor::ToggleComments",
|
||||
{
|
||||
"advance_downwards": false
|
||||
}
|
||||
],
|
||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
||||
"ctrl-u": "editor::UndoSelection",
|
||||
"ctrl-shift-u": "editor::RedoSelection",
|
||||
"f8": "editor::GoToDiagnostic",
|
||||
"shift-f8": "editor::GoToPrevDiagnostic",
|
||||
"f2": "editor::Rename",
|
||||
"f12": "editor::GoToDefinition",
|
||||
"alt-f12": "editor::GoToDefinitionSplit",
|
||||
"ctrl-f12": "editor::GoToTypeDefinition",
|
||||
"ctrl-alt-f12": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"ctrl-alt-[": "editor::Fold",
|
||||
"ctrl-alt-]": "editor::UnfoldLines",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"ctrl-alt-r": "editor::RevealInFinder",
|
||||
"ctrl-alt-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"ctrl-shift-o": "outline::Toggle",
|
||||
"ctrl-g": "go_to_line::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"ctrl-1": ["pane::ActivateItem", 0],
|
||||
"ctrl-2": ["pane::ActivateItem", 1],
|
||||
"ctrl-3": ["pane::ActivateItem", 2],
|
||||
"ctrl-4": ["pane::ActivateItem", 3],
|
||||
"ctrl-5": ["pane::ActivateItem", 4],
|
||||
"ctrl-6": ["pane::ActivateItem", 5],
|
||||
"ctrl-7": ["pane::ActivateItem", 6],
|
||||
"ctrl-8": ["pane::ActivateItem", 7],
|
||||
"ctrl-9": ["pane::ActivateItem", 8],
|
||||
"ctrl-0": "pane::ActivateLastItem",
|
||||
"ctrl--": "pane::GoBack",
|
||||
"ctrl-_": "pane::GoForward",
|
||||
"ctrl-shift-t": "pane::ReopenClosedItem",
|
||||
"ctrl-shift-f": "project_search::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"ctrl-alt-o": "projects::OpenRecent",
|
||||
"ctrl-alt-b": "branches::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"ctrl-s": "workspace::Save",
|
||||
"ctrl-shift-s": "workspace::SaveAs",
|
||||
"ctrl-n": "workspace::NewFile",
|
||||
"ctrl-shift-n": "workspace::NewWindow",
|
||||
"ctrl-`": "terminal_panel::ToggleFocus",
|
||||
"ctrl-1": ["workspace::ActivatePane", 0],
|
||||
"ctrl-2": ["workspace::ActivatePane", 1],
|
||||
"ctrl-3": ["workspace::ActivatePane", 2],
|
||||
"ctrl-4": ["workspace::ActivatePane", 3],
|
||||
"ctrl-5": ["workspace::ActivatePane", 4],
|
||||
"ctrl-6": ["workspace::ActivatePane", 5],
|
||||
"ctrl-7": ["workspace::ActivatePane", 6],
|
||||
"ctrl-8": ["workspace::ActivatePane", 7],
|
||||
"ctrl-9": ["workspace::ActivatePane", 8],
|
||||
"ctrl-b": "workspace::ToggleLeftDock",
|
||||
"ctrl-r": "workspace::ToggleRightDock",
|
||||
"ctrl-j": "workspace::ToggleBottomDock",
|
||||
"ctrl-alt-y": "workspace::CloseAllDocks",
|
||||
"ctrl-shift-f": "pane::DeploySearch",
|
||||
"ctrl-k ctrl-t": "theme_selector::Toggle",
|
||||
"ctrl-k ctrl-s": "zed::OpenKeymap",
|
||||
"ctrl-t": "project_symbols::Toggle",
|
||||
"ctrl-p": "file_finder::Toggle",
|
||||
"ctrl-shift-p": "command_palette::Toggle",
|
||||
"ctrl-shift-m": "diagnostics::Deploy",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-?": "assistant::ToggleFocus",
|
||||
"ctrl-alt-s": "workspace::SaveAll",
|
||||
"ctrl-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow",
|
||||
"ctrl-k ctrl-left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-k ctrl-right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-k ctrl-up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-k ctrl-down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"ctrl-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"ctrl-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"ctrl-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"alt-t": "task::Rerun",
|
||||
"alt-shift-t": "task::Spawn"
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
// todo!(linux) make sure these match linux bindings or remove above comment?
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"ctrl-shift-k": "editor::DeleteLine",
|
||||
"ctrl-shift-d": "editor::DuplicateLine",
|
||||
"ctrl-j": "editor::JoinLines",
|
||||
"ctrl-alt-up": "editor::MoveLineUp",
|
||||
"ctrl-alt-down": "editor::MoveLineDown",
|
||||
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
|
||||
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
|
||||
"ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
|
||||
"ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
|
||||
"ctrl-alt-right": "editor::MoveToNextSubwordEnd",
|
||||
"ctrl-alt-f": "editor::MoveToNextSubwordEnd",
|
||||
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
|
||||
"ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart",
|
||||
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
|
||||
"ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd"
|
||||
}
|
||||
},
|
||||
// Bindings from Atom
|
||||
// todo!(linux) make sure these match linux bindings or remove above comment?
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"ctrl-k up": "pane::SplitUp",
|
||||
"ctrl-k down": "pane::SplitDown",
|
||||
"ctrl-k left": "pane::SplitLeft",
|
||||
"ctrl-k right": "pane::SplitRight"
|
||||
}
|
||||
},
|
||||
// Bindings that should be unified with bindings for more general actions
|
||||
{
|
||||
"context": "Editor && renaming",
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmRename"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_completions",
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"tab": "editor::ConfirmCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && showing_code_actions",
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCodeAction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && (showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"up": "editor::ContextMenuPrev",
|
||||
"ctrl-p": "editor::ContextMenuPrev",
|
||||
"down": "editor::ContextMenuNext",
|
||||
"ctrl-n": "editor::ContextMenuNext",
|
||||
"pageup": "editor::ContextMenuFirst",
|
||||
"pagedown": "editor::ContextMenuLast"
|
||||
}
|
||||
},
|
||||
// Custom bindings
|
||||
{
|
||||
"bindings": {
|
||||
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
|
||||
// TODO: Move this to a dock open action
|
||||
"ctrl-alt-c": "collab_panel::ToggleFocus",
|
||||
"ctrl-alt-i": "zed::DebugElements",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectSearchBar && !in_replace",
|
||||
"bindings": {
|
||||
"ctrl-enter": "project_search::SearchInNew"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"left": "project_panel::CollapseSelectedEntry",
|
||||
"right": "project_panel::ExpandSelectedEntry",
|
||||
"ctrl-n": "project_panel::NewFile",
|
||||
"ctrl-alt-n": "project_panel::NewDirectory",
|
||||
"ctrl-x": "project_panel::Cut",
|
||||
"ctrl-c": "project_panel::Copy",
|
||||
"ctrl-v": "project_panel::Paste",
|
||||
"ctrl-alt-c": "project_panel::CopyPath",
|
||||
"ctrl-alt-shift-c": "project_panel::CopyRelativePath",
|
||||
"f2": "project_panel::Rename",
|
||||
"enter": "project_panel::Rename",
|
||||
"backspace": "project_panel::Delete",
|
||||
"ctrl-alt-r": "project_panel::RevealInFinder",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel && not_editing",
|
||||
"bindings": {
|
||||
"space": "project_panel::Open"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "CollabPanel && not_editing",
|
||||
"bindings": {
|
||||
"ctrl-backspace": "collab_panel::Remove",
|
||||
"space": "menu::Confirm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "(CollabPanel && editing) > Editor",
|
||||
"bindings": {
|
||||
"space": "collab_panel::InsertSpace"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ChannelModal",
|
||||
"bindings": {
|
||||
"tab": "channel_modal::ToggleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ChannelModal > Picker > Editor",
|
||||
"bindings": {
|
||||
"tab": "channel_modal::ToggleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ChatPanel > MessageEditor",
|
||||
"bindings": {
|
||||
"escape": "chat_panel::CloseReplyPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Terminal",
|
||||
"bindings": {
|
||||
"ctrl-alt-space": "terminal::ShowCharacterPalette",
|
||||
"ctrl-shift-c": "terminal::Copy",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-k": "terminal::Clear",
|
||||
// Some nice conveniences
|
||||
"ctrl-backspace": ["terminal::SendText", "\u0015"],
|
||||
"ctrl-right": ["terminal::SendText", "\u0005"],
|
||||
"ctrl-left": ["terminal::SendText", "\u0001"],
|
||||
// Terminal.app compatibility
|
||||
"alt-left": ["terminal::SendText", "\u001bb"],
|
||||
"alt-right": ["terminal::SendText", "\u001bf"],
|
||||
// There are conflicting bindings for these keys in the global context.
|
||||
// these bindings override them, remove at your own risk:
|
||||
"up": ["terminal::SendKeystroke", "up"],
|
||||
"pageup": ["terminal::SendKeystroke", "pageup"],
|
||||
"down": ["terminal::SendKeystroke", "down"],
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -423,7 +423,9 @@
|
||||
"cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"]
|
||||
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"alt-t": "task::Rerun",
|
||||
"alt-shift-t": "task::Spawn"
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
@@ -529,7 +531,8 @@
|
||||
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
|
||||
"f2": "project_panel::Rename",
|
||||
"enter": "project_panel::Rename",
|
||||
"backspace": "project_panel::Delete",
|
||||
"delete": "project_panel::Delete",
|
||||
"cmd-backspace": "project_panel::Delete",
|
||||
"alt-cmd-r": "project_panel::RevealInFinder",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory"
|
||||
}
|
||||
23
assets/keymaps/storybook.json
Normal file
@@ -0,0 +1,23 @@
|
||||
[
|
||||
// Standard macOS bindings
|
||||
{
|
||||
"bindings": {
|
||||
"up": "menu::SelectPrev",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectFirst",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"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",
|
||||
"cmd-q": "storybook::Quit"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -117,12 +117,15 @@
|
||||
"ctrl-e": "vim::LineDown",
|
||||
"ctrl-y": "vim::LineUp",
|
||||
// "g" commands
|
||||
"g e": "vim::PreviousWordEnd",
|
||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||
"g g": "vim::StartOfDocument",
|
||||
"g h": "editor::Hover",
|
||||
"g t": "pane::ActivateNextItem",
|
||||
"g shift-t": "pane::ActivatePrevItem",
|
||||
"g d": "editor::GoToDefinition",
|
||||
"g shift-d": "editor::GoToTypeDefinition",
|
||||
"g x": "editor::OpenUrl",
|
||||
"g n": "vim::SelectNext",
|
||||
"g shift-n": "vim::SelectPrevious",
|
||||
"g >": [
|
||||
@@ -339,8 +342,8 @@
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"> >": "editor::Indent",
|
||||
"< <": "editor::Outdent",
|
||||
"> >": "vim::Indent",
|
||||
"< <": "vim::Outdent",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem"
|
||||
}
|
||||
@@ -380,6 +383,7 @@
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"t": "vim::Tag",
|
||||
"s": "vim::Sentence",
|
||||
"'": "vim::Quotes",
|
||||
"`": "vim::BackQuotes",
|
||||
@@ -394,7 +398,8 @@
|
||||
"}": "vim::CurlyBrackets",
|
||||
"shift-b": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets"
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -455,8 +460,8 @@
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
">": "editor::Indent",
|
||||
"<": "editor::Outdent",
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"i": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
@@ -485,7 +490,11 @@
|
||||
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
|
||||
"ctrl-x ctrl-c": "copilot::Suggest", // zed specific
|
||||
"ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
|
||||
"ctrl-x ctrl-z": "editor::Cancel"
|
||||
"ctrl-x ctrl-z": "editor::Cancel",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-t": "vim::Indent",
|
||||
"ctrl-d": "vim::Outdent"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -503,5 +512,27 @@
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
}
|
||||
},
|
||||
{
|
||||
// netrw compatibility
|
||||
"context": "ProjectPanel && not_editing",
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"%": "project_panel::NewFile",
|
||||
"/": "project_panel::NewSearchInDirectory",
|
||||
"d": "project_panel::NewDirectory",
|
||||
"enter": "project_panel::Open",
|
||||
"escape": "project_panel::ToggleFocus",
|
||||
"h": "project_panel::CollapseSelectedEntry",
|
||||
"j": "menu::SelectNext",
|
||||
"k": "menu::SelectPrev",
|
||||
"l": "project_panel::ExpandSelectedEntry",
|
||||
"o": "project_panel::Open",
|
||||
"shift-d": "project_panel::Delete",
|
||||
"shift-r": "project_panel::Rename",
|
||||
"t": "project_panel::Open",
|
||||
"v": "project_panel::Open",
|
||||
"x": "project_panel::RevealInFinder"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -104,8 +104,10 @@
|
||||
"show_whitespaces": "selection",
|
||||
// Settings related to calls in Zed
|
||||
"calls": {
|
||||
// Join calls with the microphone muted by default
|
||||
"mute_on_join": false
|
||||
// Join calls with the microphone live by default
|
||||
"mute_on_join": false,
|
||||
// Share your project when you are the first to join a channel
|
||||
"share_on_join": true
|
||||
},
|
||||
// Toolbar related settings
|
||||
"toolbar": {
|
||||
@@ -138,6 +140,14 @@
|
||||
// Whether to show diagnostic indicators in the scrollbar.
|
||||
"diagnostics": true
|
||||
},
|
||||
"gutter": {
|
||||
// Whether to show line numbers in the gutter.
|
||||
"line_numbers": true,
|
||||
// Whether to show code action buttons in the gutter.
|
||||
"code_actions": true,
|
||||
// Whether to show fold buttons in the gutter.
|
||||
"folds": true
|
||||
},
|
||||
// The number of lines to keep above/below the cursor when scrolling.
|
||||
"vertical_scroll_margin": 3,
|
||||
"relative_line_numbers": false,
|
||||
@@ -159,7 +169,13 @@
|
||||
"show_type_hints": true,
|
||||
"show_parameter_hints": true,
|
||||
// Corresponds to null/None LSP hint type value.
|
||||
"show_other_hints": true
|
||||
"show_other_hints": true,
|
||||
// Time to wait after editing the buffer, before requesting the hints,
|
||||
// set to 0 to disable debouncing.
|
||||
"edit_debounce_ms": 700,
|
||||
// Time to wait after scrolling the buffer, before requesting the hints,
|
||||
// set to 0 to disable debouncing.
|
||||
"scroll_debounce_ms": 50
|
||||
},
|
||||
"project_panel": {
|
||||
// Default width of the project panel.
|
||||
@@ -449,7 +465,10 @@
|
||||
// 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",
|
||||
// ---
|
||||
// Sets the maximum number of lines in the terminal's scrollback buffer.
|
||||
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
|
||||
// Existing terminals will not pick up this change until they are recreated.
|
||||
// "max_scroll_history_lines": 10000,
|
||||
},
|
||||
// Difference settings for semantic_index
|
||||
"semantic_index": {
|
||||
@@ -480,6 +499,7 @@
|
||||
"deno": {
|
||||
"enable": false
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
"Plain Text": {
|
||||
@@ -488,11 +508,18 @@
|
||||
"Elixir": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Gleam": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Go": {
|
||||
"tab_size": 4,
|
||||
"hard_tabs": true
|
||||
"hard_tabs": true,
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"Markdown": {
|
||||
"tab_size": 2,
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"JavaScript": {
|
||||
@@ -534,14 +561,19 @@
|
||||
"lsp": {
|
||||
// Specify the LSP name as a key here.
|
||||
// "rust-analyzer": {
|
||||
// //These initialization options are merged into Zed's defaults
|
||||
// // These initialization options are merged into Zed's defaults
|
||||
// "initialization_options": {
|
||||
// "checkOnSave": {
|
||||
// "command": "clippy"
|
||||
// "check": {
|
||||
// "command": "clippy" // rust-analyzer.check.command (default: "check")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
// Vim settings
|
||||
"vim": {
|
||||
"use_system_clipboard": "always",
|
||||
"use_multiline_find": false
|
||||
},
|
||||
// The server to connect to. If the environment variable
|
||||
// ZED_SERVER_URL is set, it will override this setting.
|
||||
"server_url": "https://zed.dev",
|
||||
|
||||
19
assets/settings/initial_tasks.json
Normal file
@@ -0,0 +1,19 @@
|
||||
// Static tasks configuration.
|
||||
//
|
||||
// Example:
|
||||
[
|
||||
{
|
||||
"label": "Example task",
|
||||
"command": "bash",
|
||||
// rest of the parameters are optional
|
||||
"args": ["-c", "for i in {1..5}; do echo \"Hello $i/5\"; sleep 1; done"],
|
||||
// Env overrides for the command, will be appended to the terminal's environment from the settings.
|
||||
"env": { "foo": "bar" },
|
||||
// Current working directory to spawn the command into, defaults to current project root.
|
||||
//"cwd": "/path/to/working/directory",
|
||||
// Whether to use a new terminal tab or reuse the existing one to spawn the process, defaults to `false`.
|
||||
"use_new_terminal": false,
|
||||
// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`.
|
||||
"allow_concurrent_runs": false
|
||||
}
|
||||
]
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#21242bff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#f7f7f84c",
|
||||
"scrollbar.thumb.background": "#f7f7f84c",
|
||||
"scrollbar.thumb.hover_background": "#252931ff",
|
||||
"scrollbar.thumb.border": "#252931ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#221f26ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#efecf44c",
|
||||
"scrollbar.thumb.background": "#efecf44c",
|
||||
"scrollbar.thumb.hover_background": "#332f38ff",
|
||||
"scrollbar.thumb.border": "#332f38ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -430,7 +430,7 @@
|
||||
"panel.background": "#e6e3ebff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#19171c4c",
|
||||
"scrollbar.thumb.background": "#19171c4c",
|
||||
"scrollbar.thumb.hover_background": "#cbc8d1ff",
|
||||
"scrollbar.thumb.border": "#cbc8d1ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -814,7 +814,7 @@
|
||||
"panel.background": "#262622ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#fefbec4c",
|
||||
"scrollbar.thumb.background": "#fefbec4c",
|
||||
"scrollbar.thumb.hover_background": "#3b3933ff",
|
||||
"scrollbar.thumb.border": "#3b3933ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -1198,7 +1198,7 @@
|
||||
"panel.background": "#eeebd7ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#20201d4c",
|
||||
"scrollbar.thumb.background": "#20201d4c",
|
||||
"scrollbar.thumb.hover_background": "#d7d3beff",
|
||||
"scrollbar.thumb.border": "#d7d3beff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -1582,7 +1582,7 @@
|
||||
"panel.background": "#2c2b23ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#f4f3ec4c",
|
||||
"scrollbar.thumb.background": "#f4f3ec4c",
|
||||
"scrollbar.thumb.hover_background": "#3c3b31ff",
|
||||
"scrollbar.thumb.border": "#3c3b31ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -1966,7 +1966,7 @@
|
||||
"panel.background": "#ebeae3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#22221b4c",
|
||||
"scrollbar.thumb.background": "#22221b4c",
|
||||
"scrollbar.thumb.hover_background": "#d0cfc5ff",
|
||||
"scrollbar.thumb.border": "#d0cfc5ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -2350,7 +2350,7 @@
|
||||
"panel.background": "#27211eff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#f0eeed4c",
|
||||
"scrollbar.thumb.background": "#f0eeed4c",
|
||||
"scrollbar.thumb.hover_background": "#3b3431ff",
|
||||
"scrollbar.thumb.border": "#3b3431ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -2734,7 +2734,7 @@
|
||||
"panel.background": "#e9e6e4ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#1b19184c",
|
||||
"scrollbar.thumb.background": "#1b19184c",
|
||||
"scrollbar.thumb.hover_background": "#d6d1cfff",
|
||||
"scrollbar.thumb.border": "#d6d1cfff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -3118,7 +3118,7 @@
|
||||
"panel.background": "#252025ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#f7f3f74c",
|
||||
"scrollbar.thumb.background": "#f7f3f74c",
|
||||
"scrollbar.thumb.hover_background": "#393239ff",
|
||||
"scrollbar.thumb.border": "#393239ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -3502,7 +3502,7 @@
|
||||
"panel.background": "#e0d5e0ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#1b181b4c",
|
||||
"scrollbar.thumb.background": "#1b181b4c",
|
||||
"scrollbar.thumb.hover_background": "#ccbdccff",
|
||||
"scrollbar.thumb.border": "#ccbdccff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -3886,7 +3886,7 @@
|
||||
"panel.background": "#1c2529ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#ebf8ff4c",
|
||||
"scrollbar.thumb.background": "#ebf8ff4c",
|
||||
"scrollbar.thumb.hover_background": "#2c3b42ff",
|
||||
"scrollbar.thumb.border": "#2c3b42ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -4270,7 +4270,7 @@
|
||||
"panel.background": "#cdeaf9ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#161b1d4c",
|
||||
"scrollbar.thumb.background": "#161b1d4c",
|
||||
"scrollbar.thumb.hover_background": "#b0d3e5ff",
|
||||
"scrollbar.thumb.border": "#b0d3e5ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -4654,7 +4654,7 @@
|
||||
"panel.background": "#252020ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#f4ecec4c",
|
||||
"scrollbar.thumb.background": "#f4ecec4c",
|
||||
"scrollbar.thumb.hover_background": "#352f2fff",
|
||||
"scrollbar.thumb.border": "#352f2fff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -5038,7 +5038,7 @@
|
||||
"panel.background": "#ebe3e3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#1b18184c",
|
||||
"scrollbar.thumb.background": "#1b18184c",
|
||||
"scrollbar.thumb.hover_background": "#cfc7c7ff",
|
||||
"scrollbar.thumb.border": "#cfc7c7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -5422,7 +5422,7 @@
|
||||
"panel.background": "#1f2621ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#ecf4ee4c",
|
||||
"scrollbar.thumb.background": "#ecf4ee4c",
|
||||
"scrollbar.thumb.hover_background": "#2f3832ff",
|
||||
"scrollbar.thumb.border": "#2f3832ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -5806,7 +5806,7 @@
|
||||
"panel.background": "#e3ebe6ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#171c194c",
|
||||
"scrollbar.thumb.background": "#171c194c",
|
||||
"scrollbar.thumb.hover_background": "#c8d1cbff",
|
||||
"scrollbar.thumb.border": "#c8d1cbff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -6190,7 +6190,7 @@
|
||||
"panel.background": "#1f231fff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#f3faf34c",
|
||||
"scrollbar.thumb.background": "#f3faf34c",
|
||||
"scrollbar.thumb.hover_background": "#333b33ff",
|
||||
"scrollbar.thumb.border": "#333b33ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -6574,7 +6574,7 @@
|
||||
"panel.background": "#daeedaff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#1315134c",
|
||||
"scrollbar.thumb.background": "#1315134c",
|
||||
"scrollbar.thumb.hover_background": "#bed7beff",
|
||||
"scrollbar.thumb.border": "#bed7beff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -6958,7 +6958,7 @@
|
||||
"panel.background": "#262f51ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#f5f7ff4c",
|
||||
"scrollbar.thumb.background": "#f5f7ff4c",
|
||||
"scrollbar.thumb.hover_background": "#363f62ff",
|
||||
"scrollbar.thumb.border": "#363f62ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -7342,7 +7342,7 @@
|
||||
"panel.background": "#e5e8f5ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#2026464c",
|
||||
"scrollbar.thumb.background": "#2026464c",
|
||||
"scrollbar.thumb.hover_background": "#ccd0e1ff",
|
||||
"scrollbar.thumb.border": "#ccd0e1ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#1f2127ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#bfbdb64c",
|
||||
"scrollbar.thumb.background": "#bfbdb64c",
|
||||
"scrollbar.thumb.hover_background": "#2d2f34ff",
|
||||
"scrollbar.thumb.border": "#2d2f34ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -415,7 +415,7 @@
|
||||
"panel.background": "#ececedff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#5c61664c",
|
||||
"scrollbar.thumb.background": "#5c61664c",
|
||||
"scrollbar.thumb.hover_background": "#dfe0e1ff",
|
||||
"scrollbar.thumb.border": "#dfe0e1ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -784,7 +784,7 @@
|
||||
"panel.background": "#353944ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#cccac24c",
|
||||
"scrollbar.thumb.background": "#cccac24c",
|
||||
"scrollbar.thumb.hover_background": "#43464fff",
|
||||
"scrollbar.thumb.border": "#43464fff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#3a3735ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -420,7 +420,7 @@
|
||||
"panel.background": "#393634ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -482,7 +482,7 @@
|
||||
"hidden": "#998b78ff",
|
||||
"hidden.background": "#4c4642ff",
|
||||
"hidden.border": "#544c48ff",
|
||||
"hint": "#8c957dff",
|
||||
"hint": "#6a695bff",
|
||||
"hint.background": "#1e2321ff",
|
||||
"hint.border": "#303a36ff",
|
||||
"ignored": "#c5b597ff",
|
||||
@@ -794,7 +794,7 @@
|
||||
"panel.background": "#3b3735ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.background": "#fbf1c74c",
|
||||
"scrollbar.thumb.hover_background": "#494340ff",
|
||||
"scrollbar.thumb.border": "#494340ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -1168,7 +1168,7 @@
|
||||
"panel.background": "#ecddb4ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -1542,7 +1542,7 @@
|
||||
"panel.background": "#ecddb5ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -1916,7 +1916,7 @@
|
||||
"panel.background": "#ecdcb3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.background": "#2828284c",
|
||||
"scrollbar.thumb.hover_background": "#ddcca7ff",
|
||||
"scrollbar.thumb.border": "#ddcca7ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#2f343eff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#c8ccd44c",
|
||||
"scrollbar.thumb.background": "#c8ccd44c",
|
||||
"scrollbar.thumb.hover_background": "#363c46ff",
|
||||
"scrollbar.thumb.border": "#363c46ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -420,7 +420,7 @@
|
||||
"panel.background": "#ebebecff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#383a414c",
|
||||
"scrollbar.thumb.background": "#383a414c",
|
||||
"scrollbar.thumb.hover_background": "#dfdfe0ff",
|
||||
"scrollbar.thumb.border": "#dfdfe0ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#1c1b2aff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#e0def44c",
|
||||
"scrollbar.thumb.background": "#e0def44c",
|
||||
"scrollbar.thumb.hover_background": "#232132ff",
|
||||
"scrollbar.thumb.border": "#232132ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -425,7 +425,7 @@
|
||||
"panel.background": "#fef9f2ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#5752794c",
|
||||
"scrollbar.thumb.background": "#5752794c",
|
||||
"scrollbar.thumb.hover_background": "#e5e0dfff",
|
||||
"scrollbar.thumb.border": "#e5e0dfff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -804,7 +804,7 @@
|
||||
"panel.background": "#28253cff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#e0def44c",
|
||||
"scrollbar.thumb.background": "#e0def44c",
|
||||
"scrollbar.thumb.hover_background": "#322f48ff",
|
||||
"scrollbar.thumb.border": "#322f48ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#2b3038ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#fdf4c14c",
|
||||
"scrollbar.thumb.background": "#fdf4c14c",
|
||||
"scrollbar.thumb.hover_background": "#313741ff",
|
||||
"scrollbar.thumb.border": "#313741ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#04313bff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#fdf6e34c",
|
||||
"scrollbar.thumb.background": "#fdf6e34c",
|
||||
"scrollbar.thumb.hover_background": "#053541ff",
|
||||
"scrollbar.thumb.border": "#053541ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
@@ -415,7 +415,7 @@
|
||||
"panel.background": "#f3eddaff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#002a354c",
|
||||
"scrollbar.thumb.background": "#002a354c",
|
||||
"scrollbar.thumb.hover_background": "#dcdacbff",
|
||||
"scrollbar.thumb.border": "#dcdacbff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"panel.background": "#231f16ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
"scrollbar_thumb.background": "#f8f5de4c",
|
||||
"scrollbar.thumb.background": "#f8f5de4c",
|
||||
"scrollbar.thumb.hover_background": "#29251bff",
|
||||
"scrollbar.thumb.border": "#29251bff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
|
||||
@@ -17,9 +17,7 @@ futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
project.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -28,7 +28,6 @@ parking_lot.workspace = true
|
||||
parse_duration = "2.1.1"
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
regex.workspace = true
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -13,20 +13,17 @@ doctest = false
|
||||
ai.workspace = true
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
editor.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
indoc.workspace = true
|
||||
isahc.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
@@ -36,6 +33,7 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
theme.workspace = true
|
||||
tiktoken-rs.workspace = true
|
||||
ui.workspace = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod assistant_panel;
|
||||
mod assistant_settings;
|
||||
pub mod assistant_settings;
|
||||
mod codegen;
|
||||
mod prompts;
|
||||
mod streaming_diff;
|
||||
|
||||
@@ -15,7 +15,6 @@ use ai::{
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Local};
|
||||
use client::telemetry::AssistantKind;
|
||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
||||
use editor::{
|
||||
actions::{MoveDown, MoveUp},
|
||||
@@ -52,6 +51,7 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use telemetry_events::AssistantKind;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*,
|
||||
@@ -122,16 +122,13 @@ impl AssistantPanel {
|
||||
.await
|
||||
.log_err()
|
||||
.unwrap_or_default();
|
||||
let (api_url, model_name) = cx
|
||||
.update(|cx| {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
(
|
||||
settings.openai_api_url.clone(),
|
||||
settings.default_open_ai_model.full_name().to_string(),
|
||||
)
|
||||
})
|
||||
.log_err()
|
||||
.unwrap();
|
||||
let (api_url, model_name) = cx.update(|cx| {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
(
|
||||
settings.openai_api_url.clone(),
|
||||
settings.default_open_ai_model.full_name().to_string(),
|
||||
)
|
||||
})?;
|
||||
let completion_provider = OpenAiCompletionProvider::new(
|
||||
api_url,
|
||||
model_name,
|
||||
@@ -365,7 +362,7 @@ impl AssistantPanel {
|
||||
move |cx: &mut BlockContext| {
|
||||
measurements.set(BlockMeasurements {
|
||||
anchor_x: cx.anchor_x,
|
||||
gutter_width: cx.gutter_width,
|
||||
gutter_width: cx.gutter_dimensions.width,
|
||||
});
|
||||
inline_assistant.clone().into_any_element()
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ doctest = false
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rodio = { version = "0.17.1", default-features = false, features = ["wav"] }
|
||||
util.workspace = true
|
||||
|
||||
@@ -13,12 +13,12 @@ doctest = false
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
isahc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
markdown_preview.workspace = true
|
||||
menu.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -27,6 +27,5 @@ serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
theme.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -4,12 +4,14 @@ use anyhow::{anyhow, Context, Result};
|
||||
use client::{Client, TelemetrySettings, ZED_APP_PATH};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use db::RELEASE_CHANNEL;
|
||||
use editor::{Editor, MultiBuffer};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
|
||||
SemanticVersion, Task, ViewContext, VisualContext, WindowContext,
|
||||
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
|
||||
};
|
||||
use isahc::AsyncBody;
|
||||
|
||||
use markdown_preview::markdown_preview_view::MarkdownPreviewView;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde_derive::Serialize;
|
||||
@@ -26,13 +28,24 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use update_notification::UpdateNotification;
|
||||
use util::http::{HttpClient, ZedHttpClient};
|
||||
use util::{
|
||||
http::{HttpClient, HttpClientWithUrl},
|
||||
ResultExt,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
|
||||
const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
|
||||
|
||||
actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]);
|
||||
actions!(
|
||||
auto_update,
|
||||
[
|
||||
Check,
|
||||
DismissErrorMessage,
|
||||
ViewReleaseNotes,
|
||||
ViewReleaseNotesLocally
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct UpdateRequestBody {
|
||||
@@ -54,7 +67,7 @@ pub enum AutoUpdateStatus {
|
||||
pub struct AutoUpdater {
|
||||
status: AutoUpdateStatus,
|
||||
current_version: SemanticVersion,
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
pending_poll: Option<Task<Option<()>>>,
|
||||
}
|
||||
|
||||
@@ -96,7 +109,13 @@ struct GlobalAutoUpdate(Option<Model<AutoUpdater>>);
|
||||
|
||||
impl Global for GlobalAutoUpdate {}
|
||||
|
||||
pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
|
||||
#[derive(Deserialize)]
|
||||
struct ReleaseNotesBody {
|
||||
title: String,
|
||||
release_notes: String,
|
||||
}
|
||||
|
||||
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
|
||||
AutoUpdateSetting::register(cx);
|
||||
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
@@ -105,6 +124,10 @@ pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
|
||||
workspace.register_action(|_, action, cx| {
|
||||
view_release_notes(action, cx);
|
||||
});
|
||||
|
||||
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
|
||||
view_release_notes_locally(workspace, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -158,13 +181,78 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
||||
let current_version = auto_updater.current_version;
|
||||
let url = &auto_updater
|
||||
.http_client
|
||||
.zed_url(&format!("/releases/{release_channel}/{current_version}"));
|
||||
.build_url(&format!("/releases/{release_channel}/{current_version}"));
|
||||
cx.open_url(&url);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
let client = client::Client::global(cx).http_client();
|
||||
let url = client.build_url(&format!(
|
||||
"/api/release_notes/{}/{}",
|
||||
release_channel.dev_name(),
|
||||
version
|
||||
));
|
||||
|
||||
let markdown = workspace
|
||||
.app_state()
|
||||
.languages
|
||||
.language_for_name("Markdown");
|
||||
|
||||
workspace
|
||||
.with_local_workspace(cx, move |_, cx| {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let markdown = markdown.await.log_err();
|
||||
let response = client.get(&url, Default::default(), true).await;
|
||||
let Some(mut response) = response.log_err() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await.ok();
|
||||
|
||||
let body: serde_json::Result<ReleaseNotesBody> =
|
||||
serde_json::from_slice(body.as_slice());
|
||||
|
||||
if let Ok(body) = body {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.create_buffer("", markdown, cx))
|
||||
.expect("creating buffers on a local workspace always succeeds");
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, body.release_notes)], None, cx)
|
||||
});
|
||||
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
|
||||
let tab_description = SharedString::from(body.title.to_string());
|
||||
let editor = cx
|
||||
.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx));
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
|
||||
editor,
|
||||
workspace_handle,
|
||||
Some(tab_description),
|
||||
cx,
|
||||
);
|
||||
workspace.add_item(Box::new(view.clone()), cx);
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
|
||||
let updater = AutoUpdater::get(cx)?;
|
||||
let version = updater.read(cx).current_version;
|
||||
@@ -195,7 +283,7 @@ impl AutoUpdater {
|
||||
cx.default_global::<GlobalAutoUpdate>().0.clone()
|
||||
}
|
||||
|
||||
fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self {
|
||||
fn new(current_version: SemanticVersion, http_client: Arc<HttpClientWithUrl>) -> Self {
|
||||
Self {
|
||||
status: AutoUpdateStatus::Idle,
|
||||
current_version,
|
||||
@@ -249,7 +337,7 @@ impl AutoUpdater {
|
||||
(this.http_client.clone(), this.current_version)
|
||||
})?;
|
||||
|
||||
let mut url_string = client.zed_url(&format!(
|
||||
let mut url_string = client.build_url(&format!(
|
||||
"/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
|
||||
OS, ARCH
|
||||
));
|
||||
|
||||
@@ -10,15 +10,10 @@ path = "src/breadcrumbs.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools = "0.10"
|
||||
language.workspace = true
|
||||
itertools.workspace = true
|
||||
outline.workspace = true
|
||||
project.workspace = true
|
||||
search.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -21,26 +21,21 @@ test-support = [
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-broadcast = "0.4"
|
||||
audio.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
image = "0.23"
|
||||
language.workspace = true
|
||||
live_kit_client.workspace = true
|
||||
log.workspace = true
|
||||
media.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -5,7 +5,7 @@ pub mod room;
|
||||
use anyhow::{anyhow, Result};
|
||||
use audio::Audio;
|
||||
use call_settings::CallSettings;
|
||||
use client::{proto, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
|
||||
use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
|
||||
use collections::HashSet;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
||||
use gpui::{
|
||||
@@ -84,7 +84,6 @@ pub struct ActiveCall {
|
||||
),
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
pending_channel_id: Option<u64>,
|
||||
_subscriptions: Vec<client::Subscription>,
|
||||
}
|
||||
|
||||
@@ -98,7 +97,6 @@ impl ActiveCall {
|
||||
location: None,
|
||||
pending_invites: Default::default(),
|
||||
incoming_call: watch::channel(),
|
||||
pending_channel_id: None,
|
||||
_join_debouncer: OneAtATime { cancel: None },
|
||||
_subscriptions: vec![
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
||||
@@ -109,14 +107,10 @@ impl ActiveCall {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channel_id(&self, cx: &AppContext) -> Option<u64> {
|
||||
pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
|
||||
self.room()?.read(cx).channel_id()
|
||||
}
|
||||
|
||||
pub fn pending_channel_id(&self) -> Option<u64> {
|
||||
self.pending_channel_id
|
||||
}
|
||||
|
||||
async fn handle_incoming_call(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||
@@ -342,16 +336,14 @@ impl ActiveCall {
|
||||
|
||||
pub fn join_channel(
|
||||
&mut self,
|
||||
channel_id: u64,
|
||||
channel_id: ChannelId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Option<Model<Room>>>> {
|
||||
let mut leave = None;
|
||||
if let Some(room) = self.room().cloned() {
|
||||
if room.read(cx).channel_id() == Some(channel_id) {
|
||||
return Task::ready(Ok(Some(room)));
|
||||
} else {
|
||||
let (room, _) = self.room.take().unwrap();
|
||||
leave = room.update(cx, |room, cx| Some(room.leave(cx)));
|
||||
room.update(cx, |room, cx| room.clear_state(cx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,21 +353,14 @@ impl ActiveCall {
|
||||
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
self.pending_channel_id = Some(channel_id);
|
||||
let join = self._join_debouncer.spawn(cx, move |cx| async move {
|
||||
if let Some(task) = leave {
|
||||
task.await?
|
||||
}
|
||||
Room::join_channel(channel_id, client, user_store, cx).await
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let room = join.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_channel_id.take();
|
||||
this.set_room(room.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("join channel", cx)
|
||||
})?;
|
||||
@@ -502,7 +487,7 @@ impl ActiveCall {
|
||||
pub fn report_call_event_for_room(
|
||||
operation: &'static str,
|
||||
room_id: u64,
|
||||
channel_id: Option<u64>,
|
||||
channel_id: Option<ChannelId>,
|
||||
client: &Arc<Client>,
|
||||
) {
|
||||
let telemetry = client.telemetry();
|
||||
@@ -512,7 +497,7 @@ pub fn report_call_event_for_room(
|
||||
|
||||
pub fn report_call_event_for_channel(
|
||||
operation: &'static str,
|
||||
channel_id: u64,
|
||||
channel_id: ChannelId,
|
||||
client: &Arc<Client>,
|
||||
cx: &AppContext,
|
||||
) {
|
||||
|
||||
@@ -7,6 +7,7 @@ use settings::Settings;
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CallSettings {
|
||||
pub mute_on_join: bool,
|
||||
pub share_on_join: bool,
|
||||
}
|
||||
|
||||
/// Configuration of voice calls in Zed.
|
||||
@@ -16,6 +17,11 @@ pub struct CallSettingsContent {
|
||||
///
|
||||
/// Default: false
|
||||
pub mute_on_join: Option<bool>,
|
||||
|
||||
/// Whether your current project should be shared when joining an empty channel.
|
||||
///
|
||||
/// Default: true
|
||||
pub share_on_join: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for CallSettings {
|
||||
|
||||
@@ -49,7 +49,6 @@ pub struct RemoteParticipant {
|
||||
pub participant_index: ParticipantIndex,
|
||||
pub muted: bool,
|
||||
pub speaking: bool,
|
||||
pub in_call: bool,
|
||||
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
||||
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use anyhow::{anyhow, Result};
|
||||
use audio::{Audio, Sound};
|
||||
use client::{
|
||||
proto::{self, PeerId},
|
||||
Client, ParticipantIndex, TypedEnvelope, User, UserStore,
|
||||
ChannelId, Client, ParticipantIndex, TypedEnvelope, User, UserStore,
|
||||
};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
@@ -27,7 +27,7 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
RoomJoined {
|
||||
channel_id: Option<u64>,
|
||||
channel_id: Option<ChannelId>,
|
||||
},
|
||||
ParticipantLocationChanged {
|
||||
participant_id: proto::PeerId,
|
||||
@@ -53,15 +53,14 @@ pub enum Event {
|
||||
project_id: u64,
|
||||
},
|
||||
Left {
|
||||
channel_id: Option<u64>,
|
||||
channel_id: Option<ChannelId>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Room {
|
||||
id: u64,
|
||||
channel_id: Option<u64>,
|
||||
channel_id: Option<ChannelId>,
|
||||
live_kit: Option<LiveKitRoom>,
|
||||
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||
status: RoomStatus,
|
||||
shared_projects: HashSet<WeakModel<Project>>,
|
||||
joined_projects: HashSet<WeakModel<Project>>,
|
||||
@@ -85,7 +84,7 @@ pub struct Room {
|
||||
impl EventEmitter<Event> for Room {}
|
||||
|
||||
impl Room {
|
||||
pub fn channel_id(&self) -> Option<u64> {
|
||||
pub fn channel_id(&self) -> Option<ChannelId> {
|
||||
self.channel_id
|
||||
}
|
||||
|
||||
@@ -107,24 +106,97 @@ impl Room {
|
||||
|
||||
fn new(
|
||||
id: u64,
|
||||
channel_id: Option<u64>,
|
||||
channel_id: Option<ChannelId>,
|
||||
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
||||
let room = live_kit_client::Room::new();
|
||||
let mut status = room.status();
|
||||
// Consume the initial status of the room.
|
||||
let _ = status.try_recv();
|
||||
let _maintain_room = cx.spawn(|this, mut cx| async move {
|
||||
while let Some(status) = status.next().await {
|
||||
let this = if let Some(this) = this.upgrade() {
|
||||
this
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
if status == live_kit_client::ConnectionState::Disconnected {
|
||||
this.update(&mut cx, |this, cx| this.leave(cx).log_err())
|
||||
.ok();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _handle_updates = cx.spawn({
|
||||
let room = room.clone();
|
||||
move |this, mut cx| async move {
|
||||
let mut updates = room.updates();
|
||||
while let Some(update) = updates.next().await {
|
||||
let this = if let Some(this) = this.upgrade() {
|
||||
this
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.live_kit_room_updated(update, cx).log_err()
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let connect = room.connect(&connection_info.server_url, &connection_info.token);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
connect.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.can_use_microphone() {
|
||||
if let Some(live_kit) = &this.live_kit {
|
||||
if !live_kit.muted_by_user && !live_kit.deafened {
|
||||
return this.share_microphone(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::ready(Ok(()))
|
||||
})?
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
Some(LiveKitRoom {
|
||||
room,
|
||||
screen_track: LocalTrack::None,
|
||||
microphone_track: LocalTrack::None,
|
||||
next_publish_id: 0,
|
||||
muted_by_user: Self::mute_on_join(cx),
|
||||
deafened: false,
|
||||
speaking: false,
|
||||
_maintain_room,
|
||||
_handle_updates,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let maintain_connection = cx.spawn({
|
||||
let client = client.clone();
|
||||
move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
|
||||
});
|
||||
|
||||
Audio::play_sound(Sound::Joined, cx);
|
||||
|
||||
let (room_update_completed_tx, room_update_completed_rx) = watch::channel();
|
||||
|
||||
let mut this = Self {
|
||||
Self {
|
||||
id,
|
||||
channel_id,
|
||||
live_kit: None,
|
||||
live_kit_connection_info,
|
||||
live_kit: live_kit_room,
|
||||
status: RoomStatus::Online,
|
||||
shared_projects: Default::default(),
|
||||
joined_projects: Default::default(),
|
||||
@@ -148,11 +220,7 @@ impl Room {
|
||||
maintain_connection: Some(maintain_connection),
|
||||
room_update_completed_tx,
|
||||
room_update_completed_rx,
|
||||
};
|
||||
if this.live_kit_connection_info.is_some() {
|
||||
this.join_call(cx).detach_and_log_err(cx);
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn create(
|
||||
@@ -205,13 +273,17 @@ impl Room {
|
||||
}
|
||||
|
||||
pub(crate) async fn join_channel(
|
||||
channel_id: u64,
|
||||
channel_id: ChannelId,
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
Self::from_join_response(
|
||||
client.request(proto::JoinChannel2 { channel_id }).await?,
|
||||
client
|
||||
.request(proto::JoinChannel {
|
||||
channel_id: channel_id.0,
|
||||
})
|
||||
.await?,
|
||||
client,
|
||||
user_store,
|
||||
cx,
|
||||
@@ -256,7 +328,7 @@ impl Room {
|
||||
}
|
||||
|
||||
pub fn mute_on_join(cx: &AppContext) -> bool {
|
||||
CallSettings::get_global(cx).mute_on_join
|
||||
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
|
||||
}
|
||||
|
||||
fn from_join_response(
|
||||
@@ -269,7 +341,7 @@ impl Room {
|
||||
let room = cx.new_model(|cx| {
|
||||
Self::new(
|
||||
room_proto.id,
|
||||
response.channel_id,
|
||||
response.channel_id.map(ChannelId),
|
||||
response.live_kit_connection_info,
|
||||
client,
|
||||
user_store,
|
||||
@@ -306,9 +378,7 @@ impl Room {
|
||||
}
|
||||
|
||||
log::info!("leaving room");
|
||||
if self.live_kit.is_some() {
|
||||
Audio::play_sound(Sound::Leave, cx);
|
||||
}
|
||||
Audio::play_sound(Sound::Leave, cx);
|
||||
|
||||
self.clear_state(cx);
|
||||
|
||||
@@ -527,24 +597,6 @@ impl Room {
|
||||
&self.remote_participants
|
||||
}
|
||||
|
||||
pub fn call_participants(&self, cx: &AppContext) -> Vec<Arc<User>> {
|
||||
self.remote_participants()
|
||||
.values()
|
||||
.filter_map(|participant| {
|
||||
if participant.in_call {
|
||||
Some(participant.user.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.chain(if self.in_call() {
|
||||
self.user_store.read(cx).current_user()
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn remote_participant_for_peer_id(&self, peer_id: PeerId) -> Option<&RemoteParticipant> {
|
||||
self.remote_participants
|
||||
.values()
|
||||
@@ -569,6 +621,10 @@ impl Room {
|
||||
self.local_participant.role == proto::ChannelRole::Admin
|
||||
}
|
||||
|
||||
pub fn local_participant_is_guest(&self) -> bool {
|
||||
self.local_participant.role == proto::ChannelRole::Guest
|
||||
}
|
||||
|
||||
pub fn set_participant_role(
|
||||
&mut self,
|
||||
user_id: u64,
|
||||
@@ -776,7 +832,6 @@ impl Room {
|
||||
}
|
||||
|
||||
let role = participant.role();
|
||||
let in_call = participant.in_call;
|
||||
let location = ParticipantLocation::from_proto(participant.location)
|
||||
.unwrap_or(ParticipantLocation::External);
|
||||
if let Some(remote_participant) =
|
||||
@@ -787,15 +842,9 @@ impl Room {
|
||||
remote_participant.participant_index = participant_index;
|
||||
if location != remote_participant.location
|
||||
|| role != remote_participant.role
|
||||
|| in_call != remote_participant.in_call
|
||||
{
|
||||
if in_call && !remote_participant.in_call {
|
||||
Audio::play_sound(Sound::Joined, cx);
|
||||
}
|
||||
remote_participant.location = location;
|
||||
remote_participant.role = role;
|
||||
remote_participant.in_call = participant.in_call;
|
||||
|
||||
cx.emit(Event::ParticipantLocationChanged {
|
||||
participant_id: peer_id,
|
||||
});
|
||||
@@ -812,15 +861,12 @@ impl Room {
|
||||
role,
|
||||
muted: true,
|
||||
speaking: false,
|
||||
in_call: participant.in_call,
|
||||
video_tracks: Default::default(),
|
||||
audio_tracks: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
if participant.in_call {
|
||||
Audio::play_sound(Sound::Joined, cx);
|
||||
}
|
||||
Audio::play_sound(Sound::Joined, cx);
|
||||
|
||||
if let Some(live_kit) = this.live_kit.as_ref() {
|
||||
let video_tracks =
|
||||
@@ -1009,6 +1055,15 @@ impl Room {
|
||||
}
|
||||
|
||||
RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => {
|
||||
if let Some(live_kit) = &self.live_kit {
|
||||
if live_kit.deafened {
|
||||
track.stop();
|
||||
cx.foreground_executor()
|
||||
.spawn(publication.set_enabled(false))
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
let user_id = track.publisher_id().parse()?;
|
||||
let track_id = track.sid().to_string();
|
||||
let participant = self
|
||||
@@ -1155,7 +1210,7 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn share_project(
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
@@ -1257,19 +1312,18 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_muted(&self) -> bool {
|
||||
self.live_kit
|
||||
.as_ref()
|
||||
.map_or(true, |live_kit| match &live_kit.microphone_track {
|
||||
LocalTrack::None => true,
|
||||
LocalTrack::Pending { .. } => true,
|
||||
LocalTrack::Published { track_publication } => track_publication.is_muted(),
|
||||
})
|
||||
pub fn is_sharing_mic(&self) -> bool {
|
||||
self.live_kit.as_ref().map_or(false, |live_kit| {
|
||||
!matches!(live_kit.microphone_track, LocalTrack::None)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_only(&self) -> bool {
|
||||
!(self.local_participant().role == proto::ChannelRole::Member
|
||||
|| self.local_participant().role == proto::ChannelRole::Admin)
|
||||
pub fn is_muted(&self) -> bool {
|
||||
self.live_kit.as_ref().map_or(false, |live_kit| {
|
||||
matches!(live_kit.microphone_track, LocalTrack::None)
|
||||
|| live_kit.muted_by_user
|
||||
|| live_kit.deafened
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_speaking(&self) -> bool {
|
||||
@@ -1278,8 +1332,24 @@ impl Room {
|
||||
.map_or(false, |live_kit| live_kit.speaking)
|
||||
}
|
||||
|
||||
pub fn in_call(&self) -> bool {
|
||||
self.live_kit.is_some()
|
||||
pub fn is_deafened(&self) -> Option<bool> {
|
||||
self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
|
||||
}
|
||||
|
||||
pub fn can_use_microphone(&self) -> bool {
|
||||
use proto::ChannelRole::*;
|
||||
match self.local_participant.role {
|
||||
Admin | Member | Talker => true,
|
||||
Guest | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_share_projects(&self) -> bool {
|
||||
use proto::ChannelRole::*;
|
||||
match self.local_participant.role {
|
||||
Admin | Member => true,
|
||||
Guest | Banned | Talker => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -1332,8 +1402,12 @@ impl Room {
|
||||
Ok(publication) => {
|
||||
if canceled {
|
||||
live_kit.room.unpublish_track(publication);
|
||||
live_kit.microphone_track = LocalTrack::None;
|
||||
} else {
|
||||
if live_kit.muted_by_user || live_kit.deafened {
|
||||
cx.background_executor()
|
||||
.spawn(publication.set_mute(true))
|
||||
.detach();
|
||||
}
|
||||
live_kit.microphone_track = LocalTrack::Published {
|
||||
track_publication: publication,
|
||||
};
|
||||
@@ -1437,140 +1511,50 @@ impl Room {
|
||||
}
|
||||
|
||||
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let muted = !self.is_muted();
|
||||
if let Some(task) = self.set_mute(muted, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
// When unmuting, undeafen if the user was deafened before.
|
||||
let was_deafened = live_kit.deafened;
|
||||
if live_kit.muted_by_user
|
||||
|| live_kit.deafened
|
||||
|| matches!(live_kit.microphone_track, LocalTrack::None)
|
||||
{
|
||||
live_kit.muted_by_user = false;
|
||||
live_kit.deafened = false;
|
||||
} else {
|
||||
live_kit.muted_by_user = true;
|
||||
}
|
||||
let muted = live_kit.muted_by_user;
|
||||
let should_undeafen = was_deafened && !live_kit.deafened;
|
||||
|
||||
if let Some(task) = self.set_mute(muted, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
if should_undeafen {
|
||||
if let Some(task) = self.set_deafened(false, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join_call(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.live_kit.is_some() {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
// When deafening, mute the microphone if it was not already muted.
|
||||
// When un-deafening, unmute the microphone, unless it was explicitly muted.
|
||||
let deafened = !live_kit.deafened;
|
||||
live_kit.deafened = deafened;
|
||||
let should_change_mute = !live_kit.muted_by_user;
|
||||
|
||||
let room = live_kit_client::Room::new();
|
||||
let mut status = room.status();
|
||||
// Consume the initial status of the room.
|
||||
let _ = status.try_recv();
|
||||
let _maintain_room = cx.spawn(|this, mut cx| async move {
|
||||
while let Some(status) = status.next().await {
|
||||
let this = if let Some(this) = this.upgrade() {
|
||||
this
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
if let Some(task) = self.set_deafened(deafened, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
if status == live_kit_client::ConnectionState::Disconnected {
|
||||
this.update(&mut cx, |this, cx| this.leave(cx).log_err())
|
||||
.ok();
|
||||
break;
|
||||
if should_change_mute {
|
||||
if let Some(task) = self.set_mute(deafened, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _handle_updates = cx.spawn({
|
||||
let room = room.clone();
|
||||
move |this, mut cx| async move {
|
||||
let mut updates = room.updates();
|
||||
while let Some(update) = updates.next().await {
|
||||
let this = if let Some(this) = this.upgrade() {
|
||||
this
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.live_kit_room_updated(update, cx).log_err()
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.live_kit = Some(LiveKitRoom {
|
||||
room: room.clone(),
|
||||
screen_track: LocalTrack::None,
|
||||
microphone_track: LocalTrack::None,
|
||||
next_publish_id: 0,
|
||||
speaking: false,
|
||||
_maintain_room,
|
||||
_handle_updates,
|
||||
});
|
||||
|
||||
cx.spawn({
|
||||
let client = self.client.clone();
|
||||
let share_microphone = !self.read_only() && !Self::mute_on_join(cx);
|
||||
let connection_info = self.live_kit_connection_info.clone();
|
||||
let channel_id = self.channel_id;
|
||||
|
||||
move |this, mut cx| async move {
|
||||
let connection_info = if let Some(connection_info) = connection_info {
|
||||
connection_info.clone()
|
||||
} else if let Some(channel_id) = channel_id {
|
||||
if let Some(connection_info) = client
|
||||
.request(proto::JoinChannelCall { channel_id })
|
||||
.await?
|
||||
.live_kit_connection_info
|
||||
{
|
||||
connection_info
|
||||
} else {
|
||||
return Err(anyhow!("failed to get connection info from server"));
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"tried to connect to livekit without connection info"
|
||||
));
|
||||
};
|
||||
room.connect(&connection_info.server_url, &connection_info.token)
|
||||
.await?;
|
||||
|
||||
let track_updates = this.update(&mut cx, |this, cx| {
|
||||
Audio::play_sound(Sound::Joined, cx);
|
||||
let Some(live_kit) = this.live_kit.as_mut() else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let mut track_updates = Vec::new();
|
||||
for participant in this.remote_participants.values() {
|
||||
for publication in live_kit
|
||||
.room
|
||||
.remote_audio_track_publications(&participant.user.id.to_string())
|
||||
{
|
||||
track_updates.push(publication.set_enabled(true));
|
||||
}
|
||||
|
||||
for track in participant.audio_tracks.values() {
|
||||
track.start();
|
||||
}
|
||||
}
|
||||
track_updates
|
||||
})?;
|
||||
|
||||
if share_microphone {
|
||||
this.update(&mut cx, |this, cx| this.share_microphone(cx))?
|
||||
.await?
|
||||
};
|
||||
|
||||
for result in futures::future::join_all(track_updates).await {
|
||||
result?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn leave_call(&mut self, cx: &mut ModelContext<Self>) {
|
||||
Audio::play_sound(Sound::Leave, cx);
|
||||
if let Some(channel_id) = self.channel_id() {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor()
|
||||
.spawn(client.request(proto::LeaveChannelCall { channel_id }))
|
||||
.detach_and_log_err(cx);
|
||||
self.live_kit.take();
|
||||
self.live_kit_connection_info.take();
|
||||
cx.notify();
|
||||
} else {
|
||||
self.leave(cx).detach_and_log_err(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1601,6 +1585,40 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_deafened(
|
||||
&mut self,
|
||||
deafened: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let live_kit = self.live_kit.as_mut()?;
|
||||
cx.notify();
|
||||
|
||||
let mut track_updates = Vec::new();
|
||||
for participant in self.remote_participants.values() {
|
||||
for publication in live_kit
|
||||
.room
|
||||
.remote_audio_track_publications(&participant.user.id.to_string())
|
||||
{
|
||||
track_updates.push(publication.set_enabled(!deafened));
|
||||
}
|
||||
|
||||
for track in participant.audio_tracks.values() {
|
||||
if deafened {
|
||||
track.stop();
|
||||
} else {
|
||||
track.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(cx.foreground_executor().spawn(async move {
|
||||
for result in futures::future::join_all(track_updates).await {
|
||||
result?;
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
fn set_mute(
|
||||
&mut self,
|
||||
should_mute: bool,
|
||||
@@ -1645,6 +1663,9 @@ struct LiveKitRoom {
|
||||
room: Arc<live_kit_client::Room>,
|
||||
screen_track: LocalTrack,
|
||||
microphone_track: LocalTrack,
|
||||
/// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user.
|
||||
muted_by_user: bool,
|
||||
deafened: bool,
|
||||
speaking: bool,
|
||||
next_publish_id: usize,
|
||||
_maintain_room: Task<()>,
|
||||
|
||||
@@ -17,34 +17,18 @@ anyhow.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
image = "0.23"
|
||||
language.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
sum_tree.workspace = true
|
||||
tempfile.workspace = true
|
||||
text.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -11,7 +11,7 @@ pub use channel_chat::{
|
||||
mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId,
|
||||
MessageParams,
|
||||
};
|
||||
pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, ChannelStore};
|
||||
pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore, HostedProjectId};
|
||||
|
||||
#[cfg(test)]
|
||||
mod channel_store_tests;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{Channel, ChannelId, ChannelStore};
|
||||
use crate::{Channel, ChannelStore};
|
||||
use anyhow::Result;
|
||||
use client::{Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
|
||||
use client::{ChannelId, Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
||||
use language::proto::serialize_version;
|
||||
@@ -51,7 +51,7 @@ impl ChannelBuffer {
|
||||
) -> Result<Model<Self>> {
|
||||
let response = client
|
||||
.request(proto::JoinChannelBuffer {
|
||||
channel_id: channel.id,
|
||||
channel_id: channel.id.0,
|
||||
})
|
||||
.await?;
|
||||
let buffer_id = BufferId::new(response.buffer_id)?;
|
||||
@@ -68,7 +68,7 @@ impl ChannelBuffer {
|
||||
})?;
|
||||
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
|
||||
|
||||
let subscription = client.subscribe_to_entity(channel.id)?;
|
||||
let subscription = client.subscribe_to_entity(channel.id.0)?;
|
||||
|
||||
anyhow::Ok(cx.new_model(|cx| {
|
||||
cx.subscribe(&buffer, Self::on_buffer_update).detach();
|
||||
@@ -97,7 +97,7 @@ impl ChannelBuffer {
|
||||
}
|
||||
self.client
|
||||
.send(proto::LeaveChannelBuffer {
|
||||
channel_id: self.channel_id,
|
||||
channel_id: self.channel_id.0,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
@@ -191,7 +191,7 @@ impl ChannelBuffer {
|
||||
let operation = language::proto::serialize_operation(operation);
|
||||
self.client
|
||||
.send(proto::UpdateChannelBuffer {
|
||||
channel_id: self.channel_id,
|
||||
channel_id: self.channel_id.0,
|
||||
operations: vec![operation],
|
||||
})
|
||||
.log_err();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{Channel, ChannelId, ChannelStore};
|
||||
use crate::{Channel, ChannelStore};
|
||||
use anyhow::{anyhow, Result};
|
||||
use client::{
|
||||
proto,
|
||||
user::{User, UserStore},
|
||||
Client, Subscription, TypedEnvelope, UserId,
|
||||
ChannelId, Client, Subscription, TypedEnvelope, UserId,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use futures::lock::Mutex;
|
||||
@@ -104,10 +104,12 @@ impl ChannelChat {
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
let channel_id = channel.id;
|
||||
let subscription = client.subscribe_to_entity(channel_id).unwrap();
|
||||
let subscription = client.subscribe_to_entity(channel_id.0).unwrap();
|
||||
|
||||
let response = client
|
||||
.request(proto::JoinChannelChat { channel_id })
|
||||
.request(proto::JoinChannelChat {
|
||||
channel_id: channel_id.0,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let handle = cx.new_model(|cx| {
|
||||
@@ -143,7 +145,7 @@ impl ChannelChat {
|
||||
fn release(&mut self, _: &mut AppContext) {
|
||||
self.rpc
|
||||
.send(proto::LeaveChannelChat {
|
||||
channel_id: self.channel_id,
|
||||
channel_id: self.channel_id.0,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
@@ -200,7 +202,7 @@ impl ChannelChat {
|
||||
Ok(cx.spawn(move |this, mut cx| async move {
|
||||
let outgoing_message_guard = outgoing_messages_lock.lock().await;
|
||||
let request = rpc.request(proto::SendChannelMessage {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
body: message.text,
|
||||
nonce: Some(nonce.into()),
|
||||
mentions: mentions_to_proto(&message.mentions),
|
||||
@@ -220,7 +222,7 @@ impl ChannelChat {
|
||||
|
||||
pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let response = self.rpc.request(proto::RemoveChannelMessage {
|
||||
channel_id: self.channel_id,
|
||||
channel_id: self.channel_id.0,
|
||||
message_id: id,
|
||||
});
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
@@ -245,7 +247,7 @@ impl ChannelChat {
|
||||
async move {
|
||||
let response = rpc
|
||||
.request(proto::GetChannelMessages {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
before_message_id,
|
||||
})
|
||||
.await?;
|
||||
@@ -323,7 +325,7 @@ impl ChannelChat {
|
||||
{
|
||||
self.rpc
|
||||
.send(proto::AckChannelMessage {
|
||||
channel_id: self.channel_id,
|
||||
channel_id: self.channel_id.0,
|
||||
message_id: latest_message_id,
|
||||
})
|
||||
.ok();
|
||||
@@ -401,7 +403,11 @@ impl ChannelChat {
|
||||
let channel_id = self.channel_id;
|
||||
cx.spawn(move |this, mut cx| {
|
||||
async move {
|
||||
let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
|
||||
let response = rpc
|
||||
.request(proto::JoinChannelChat {
|
||||
channel_id: channel_id.0,
|
||||
})
|
||||
.await?;
|
||||
Self::handle_loaded_messages(
|
||||
this.clone(),
|
||||
user_store.clone(),
|
||||
@@ -418,7 +424,7 @@ impl ChannelChat {
|
||||
|
||||
for pending_message in pending_messages {
|
||||
let request = rpc.request(proto::SendChannelMessage {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
body: pending_message.body,
|
||||
mentions: mentions_to_proto(&pending_message.mentions),
|
||||
nonce: Some(pending_message.nonce.into()),
|
||||
@@ -461,7 +467,7 @@ impl ChannelChat {
|
||||
if self.acknowledged_message_ids.insert(id) {
|
||||
self.rpc
|
||||
.send(proto::AckChannelMessage {
|
||||
channel_id: self.channel_id,
|
||||
channel_id: self.channel_id.0,
|
||||
message_id: id,
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -3,7 +3,7 @@ mod channel_index;
|
||||
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
|
||||
use anyhow::{anyhow, Result};
|
||||
use channel_index::ChannelIndex;
|
||||
use client::{Client, Subscription, User, UserId, UserStore};
|
||||
use client::{ChannelId, Client, Subscription, User, UserId, UserStore};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
@@ -19,15 +19,16 @@ use rpc::{
|
||||
use std::{mem, sync::Arc, time::Duration};
|
||||
use util::{async_maybe, maybe, ResultExt};
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||
let channel_store =
|
||||
cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
|
||||
cx.set_global(GlobalChannelStore(channel_store));
|
||||
}
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
pub type ChannelId = u64;
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub struct HostedProjectId(pub u64);
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NotesVersion {
|
||||
@@ -35,11 +36,31 @@ struct NotesVersion {
|
||||
version: clock::Global,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HostedProject {
|
||||
id: HostedProjectId,
|
||||
channel_id: ChannelId,
|
||||
name: SharedString,
|
||||
_visibility: proto::ChannelVisibility,
|
||||
}
|
||||
|
||||
impl From<proto::HostedProject> for HostedProject {
|
||||
fn from(project: proto::HostedProject) -> Self {
|
||||
Self {
|
||||
id: HostedProjectId(project.id),
|
||||
channel_id: ChannelId(project.channel_id),
|
||||
_visibility: project.visibility(),
|
||||
name: project.name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChannelStore {
|
||||
pub channel_index: ChannelIndex,
|
||||
channel_invitations: Vec<Arc<Channel>>,
|
||||
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
|
||||
channel_states: HashMap<ChannelId, ChannelState>,
|
||||
hosted_projects: HashMap<HostedProjectId, HostedProject>,
|
||||
|
||||
outgoing_invites: HashSet<(ChannelId, UserId)>,
|
||||
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
|
||||
@@ -58,7 +79,7 @@ pub struct Channel {
|
||||
pub id: ChannelId,
|
||||
pub name: SharedString,
|
||||
pub visibility: proto::ChannelVisibility,
|
||||
pub parent_path: Vec<u64>,
|
||||
pub parent_path: Vec<ChannelId>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -68,6 +89,7 @@ pub struct ChannelState {
|
||||
observed_chat_message: Option<u64>,
|
||||
observed_notes_versions: Option<NotesVersion>,
|
||||
role: Option<ChannelRole>,
|
||||
projects: HashSet<HostedProjectId>,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
@@ -92,10 +114,7 @@ impl Channel {
|
||||
}
|
||||
|
||||
pub fn root_id(&self) -> ChannelId {
|
||||
self.parent_path
|
||||
.first()
|
||||
.map(|id| *id as ChannelId)
|
||||
.unwrap_or(self.id)
|
||||
self.parent_path.first().copied().unwrap_or(self.id)
|
||||
}
|
||||
|
||||
pub fn slug(str: &str) -> String {
|
||||
@@ -120,7 +139,8 @@ impl ChannelMembership {
|
||||
proto::ChannelRole::Admin => 0,
|
||||
proto::ChannelRole::Member => 1,
|
||||
proto::ChannelRole::Banned => 2,
|
||||
proto::ChannelRole::Guest => 3,
|
||||
proto::ChannelRole::Talker => 3,
|
||||
proto::ChannelRole::Guest => 4,
|
||||
},
|
||||
kind_order: match self.kind {
|
||||
proto::channel_member::Kind::Member => 0,
|
||||
@@ -198,6 +218,7 @@ impl ChannelStore {
|
||||
channel_invitations: Vec::default(),
|
||||
channel_index: ChannelIndex::default(),
|
||||
channel_participants: Default::default(),
|
||||
hosted_projects: Default::default(),
|
||||
outgoing_invites: Default::default(),
|
||||
opened_buffers: Default::default(),
|
||||
opened_chats: Default::default(),
|
||||
@@ -284,6 +305,19 @@ impl ChannelStore {
|
||||
self.channel_index.by_id().get(&channel_id)
|
||||
}
|
||||
|
||||
pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, HostedProjectId)> {
|
||||
let mut projects: Vec<(SharedString, HostedProjectId)> = self
|
||||
.channel_states
|
||||
.get(&channel_id)
|
||||
.map(|state| state.projects.clone())
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.flat_map(|id| Some((self.hosted_projects.get(&id)?.name.clone(), id)))
|
||||
.collect();
|
||||
projects.sort();
|
||||
projects
|
||||
}
|
||||
|
||||
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
|
||||
if let Some(buffer) = self.opened_buffers.get(&channel_id) {
|
||||
if let OpenedModelHandle::Open(buffer) = buffer {
|
||||
@@ -348,6 +382,21 @@ impl ChannelStore {
|
||||
.is_some_and(|state| state.has_new_messages())
|
||||
}
|
||||
|
||||
pub fn last_acknowledge_message_id(&self, channel_id: ChannelId) -> Option<u64> {
|
||||
self.channel_states.get(&channel_id).and_then(|state| {
|
||||
if let Some(last_message_id) = state.latest_chat_message {
|
||||
if state
|
||||
.last_acknowledged_message_id()
|
||||
.is_some_and(|id| id < last_message_id)
|
||||
{
|
||||
return state.last_acknowledged_message_id();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn acknowledge_message_id(
|
||||
&mut self,
|
||||
channel_id: ChannelId,
|
||||
@@ -546,13 +595,16 @@ impl ChannelStore {
|
||||
let name = name.trim_start_matches("#").to_owned();
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let response = client
|
||||
.request(proto::CreateChannel { name, parent_id })
|
||||
.request(proto::CreateChannel {
|
||||
name,
|
||||
parent_id: parent_id.map(|cid| cid.0),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let channel = response
|
||||
.channel
|
||||
.ok_or_else(|| anyhow!("missing channel in response"))?;
|
||||
let channel_id = channel.id;
|
||||
let channel_id = ChannelId(channel.id);
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let task = this.update_channels(
|
||||
@@ -584,7 +636,10 @@ impl ChannelStore {
|
||||
let client = self.client.clone();
|
||||
cx.spawn(move |_, _| async move {
|
||||
let _ = client
|
||||
.request(proto::MoveChannel { channel_id, to })
|
||||
.request(proto::MoveChannel {
|
||||
channel_id: channel_id.0,
|
||||
to: to.0,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -601,7 +656,7 @@ impl ChannelStore {
|
||||
cx.spawn(move |_, _| async move {
|
||||
let _ = client
|
||||
.request(proto::SetChannelVisibility {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
visibility: visibility.into(),
|
||||
})
|
||||
.await?;
|
||||
@@ -626,7 +681,7 @@ impl ChannelStore {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::InviteChannelMember {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
user_id,
|
||||
role: role.into(),
|
||||
})
|
||||
@@ -658,7 +713,7 @@ impl ChannelStore {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::RemoveChannelMember {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
user_id,
|
||||
})
|
||||
.await;
|
||||
@@ -688,7 +743,7 @@ impl ChannelStore {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::SetChannelMemberRole {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
user_id,
|
||||
role: role.into(),
|
||||
})
|
||||
@@ -714,7 +769,10 @@ impl ChannelStore {
|
||||
let name = new_name.to_string();
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let channel = client
|
||||
.request(proto::RenameChannel { channel_id, name })
|
||||
.request(proto::RenameChannel {
|
||||
channel_id: channel_id.0,
|
||||
name,
|
||||
})
|
||||
.await?
|
||||
.channel
|
||||
.ok_or_else(|| anyhow!("missing channel in response"))?;
|
||||
@@ -747,7 +805,10 @@ impl ChannelStore {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::RespondToChannelInvite { channel_id, accept })
|
||||
.request(proto::RespondToChannelInvite {
|
||||
channel_id: channel_id.0,
|
||||
accept,
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -762,7 +823,9 @@ impl ChannelStore {
|
||||
let user_store = self.user_store.downgrade();
|
||||
cx.spawn(move |_, mut cx| async move {
|
||||
let response = client
|
||||
.request(proto::GetChannelMembers { channel_id })
|
||||
.request(proto::GetChannelMembers {
|
||||
channel_id: channel_id.0,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let user_ids = response.members.iter().map(|m| m.user_id).collect();
|
||||
@@ -790,7 +853,11 @@ impl ChannelStore {
|
||||
pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> {
|
||||
let client = self.client.clone();
|
||||
async move {
|
||||
client.request(proto::DeleteChannel { channel_id }).await?;
|
||||
client
|
||||
.request(proto::DeleteChannel {
|
||||
channel_id: channel_id.0,
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -827,19 +894,23 @@ impl ChannelStore {
|
||||
for buffer_version in message.payload.observed_channel_buffer_version {
|
||||
let version = language::proto::deserialize_version(&buffer_version.version);
|
||||
this.acknowledge_notes_version(
|
||||
buffer_version.channel_id,
|
||||
ChannelId(buffer_version.channel_id),
|
||||
buffer_version.epoch,
|
||||
&version,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
for message_id in message.payload.observed_channel_message_id {
|
||||
this.acknowledge_message_id(message_id.channel_id, message_id.message_id, cx);
|
||||
this.acknowledge_message_id(
|
||||
ChannelId(message_id.channel_id),
|
||||
message_id.message_id,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
for membership in message.payload.channel_memberships {
|
||||
if let Some(role) = ChannelRole::from_i32(membership.role) {
|
||||
this.channel_states
|
||||
.entry(membership.channel_id)
|
||||
.entry(ChannelId(membership.channel_id))
|
||||
.or_insert_with(|| ChannelState::default())
|
||||
.set_role(role)
|
||||
}
|
||||
@@ -872,7 +943,7 @@ impl ChannelStore {
|
||||
let channel_buffer = buffer.read(cx);
|
||||
let buffer = channel_buffer.buffer().read(cx);
|
||||
buffer_versions.push(proto::ChannelBufferVersion {
|
||||
channel_id: channel_buffer.channel_id,
|
||||
channel_id: channel_buffer.channel_id.0,
|
||||
epoch: channel_buffer.epoch(),
|
||||
version: language::proto::serialize_version(&buffer.version()),
|
||||
});
|
||||
@@ -903,7 +974,7 @@ impl ChannelStore {
|
||||
if let Some(remote_buffer) = response
|
||||
.buffers
|
||||
.iter_mut()
|
||||
.find(|buffer| buffer.channel_id == channel_id)
|
||||
.find(|buffer| buffer.channel_id == channel_id.0)
|
||||
{
|
||||
let channel_id = channel_buffer.channel_id;
|
||||
let remote_version =
|
||||
@@ -939,7 +1010,7 @@ impl ChannelStore {
|
||||
{
|
||||
client
|
||||
.send(proto::UpdateChannelBuffer {
|
||||
channel_id,
|
||||
channel_id: channel_id.0,
|
||||
operations: chunk,
|
||||
})
|
||||
.ok();
|
||||
@@ -994,12 +1065,12 @@ impl ChannelStore {
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if !payload.remove_channel_invitations.is_empty() {
|
||||
self.channel_invitations
|
||||
.retain(|channel| !payload.remove_channel_invitations.contains(&channel.id));
|
||||
.retain(|channel| !payload.remove_channel_invitations.contains(&channel.id.0));
|
||||
}
|
||||
for channel in payload.channel_invitations {
|
||||
match self
|
||||
.channel_invitations
|
||||
.binary_search_by_key(&channel.id, |c| c.id)
|
||||
.binary_search_by_key(&channel.id, |c| c.id.0)
|
||||
{
|
||||
Ok(ix) => {
|
||||
Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
|
||||
@@ -1007,10 +1078,14 @@ impl ChannelStore {
|
||||
Err(ix) => self.channel_invitations.insert(
|
||||
ix,
|
||||
Arc::new(Channel {
|
||||
id: channel.id,
|
||||
id: ChannelId(channel.id),
|
||||
visibility: channel.visibility(),
|
||||
name: channel.name.into(),
|
||||
parent_path: channel.parent_path,
|
||||
parent_path: channel
|
||||
.parent_path
|
||||
.into_iter()
|
||||
.map(|cid| ChannelId(cid))
|
||||
.collect(),
|
||||
}),
|
||||
),
|
||||
}
|
||||
@@ -1019,20 +1094,27 @@ impl ChannelStore {
|
||||
let channels_changed = !payload.channels.is_empty()
|
||||
|| !payload.delete_channels.is_empty()
|
||||
|| !payload.latest_channel_message_ids.is_empty()
|
||||
|| !payload.latest_channel_buffer_versions.is_empty();
|
||||
|| !payload.latest_channel_buffer_versions.is_empty()
|
||||
|| !payload.hosted_projects.is_empty()
|
||||
|| !payload.deleted_hosted_projects.is_empty();
|
||||
|
||||
if channels_changed {
|
||||
if !payload.delete_channels.is_empty() {
|
||||
self.channel_index.delete_channels(&payload.delete_channels);
|
||||
let delete_channels: Vec<ChannelId> = payload
|
||||
.delete_channels
|
||||
.into_iter()
|
||||
.map(|cid| ChannelId(cid))
|
||||
.collect();
|
||||
self.channel_index.delete_channels(&delete_channels);
|
||||
self.channel_participants
|
||||
.retain(|channel_id, _| !&payload.delete_channels.contains(channel_id));
|
||||
.retain(|channel_id, _| !delete_channels.contains(&channel_id));
|
||||
|
||||
for channel_id in &payload.delete_channels {
|
||||
for channel_id in &delete_channels {
|
||||
let channel_id = *channel_id;
|
||||
if payload
|
||||
.channels
|
||||
.iter()
|
||||
.any(|channel| channel.id == channel_id)
|
||||
.any(|channel| channel.id == channel_id.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1048,7 +1130,7 @@ impl ChannelStore {
|
||||
|
||||
let mut index = self.channel_index.bulk_insert();
|
||||
for channel in payload.channels {
|
||||
let id = channel.id;
|
||||
let id = ChannelId(channel.id);
|
||||
let channel_changed = index.insert(channel);
|
||||
|
||||
if channel_changed {
|
||||
@@ -1063,17 +1145,45 @@ impl ChannelStore {
|
||||
for latest_buffer_version in payload.latest_channel_buffer_versions {
|
||||
let version = language::proto::deserialize_version(&latest_buffer_version.version);
|
||||
self.channel_states
|
||||
.entry(latest_buffer_version.channel_id)
|
||||
.entry(ChannelId(latest_buffer_version.channel_id))
|
||||
.or_default()
|
||||
.update_latest_notes_version(latest_buffer_version.epoch, &version)
|
||||
}
|
||||
|
||||
for latest_channel_message in payload.latest_channel_message_ids {
|
||||
self.channel_states
|
||||
.entry(latest_channel_message.channel_id)
|
||||
.entry(ChannelId(latest_channel_message.channel_id))
|
||||
.or_default()
|
||||
.update_latest_message_id(latest_channel_message.message_id);
|
||||
}
|
||||
|
||||
for hosted_project in payload.hosted_projects {
|
||||
let hosted_project: HostedProject = hosted_project.into();
|
||||
if let Some(old_project) = self
|
||||
.hosted_projects
|
||||
.insert(hosted_project.id, hosted_project.clone())
|
||||
{
|
||||
self.channel_states
|
||||
.entry(old_project.channel_id)
|
||||
.or_default()
|
||||
.remove_hosted_project(old_project.id);
|
||||
}
|
||||
self.channel_states
|
||||
.entry(hosted_project.channel_id)
|
||||
.or_default()
|
||||
.add_hosted_project(hosted_project.id);
|
||||
}
|
||||
|
||||
for hosted_project_id in payload.deleted_hosted_projects {
|
||||
let hosted_project_id = HostedProjectId(hosted_project_id);
|
||||
|
||||
if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) {
|
||||
self.channel_states
|
||||
.entry(old_project.channel_id)
|
||||
.or_default()
|
||||
.remove_hosted_project(old_project.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -1113,7 +1223,7 @@ impl ChannelStore {
|
||||
participants.sort_by_key(|u| u.id);
|
||||
|
||||
this.channel_participants
|
||||
.insert(entry.channel_id, participants);
|
||||
.insert(ChannelId(entry.channel_id), participants);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -1152,6 +1262,10 @@ impl ChannelState {
|
||||
})
|
||||
}
|
||||
|
||||
fn last_acknowledged_message_id(&self) -> Option<u64> {
|
||||
self.observed_chat_message
|
||||
}
|
||||
|
||||
fn acknowledge_message_id(&mut self, message_id: u64) {
|
||||
let observed = self.observed_chat_message.get_or_insert(message_id);
|
||||
*observed = (*observed).max(message_id);
|
||||
@@ -1187,4 +1301,12 @@ impl ChannelState {
|
||||
version: version.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
fn add_hosted_project(&mut self, project_id: HostedProjectId) {
|
||||
self.projects.insert(project_id);
|
||||
}
|
||||
|
||||
fn remove_hosted_project(&mut self, project_id: HostedProjectId) {
|
||||
self.projects.remove(&project_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{Channel, ChannelId};
|
||||
use crate::Channel;
|
||||
use client::ChannelId;
|
||||
use collections::BTreeMap;
|
||||
use rpc::proto;
|
||||
use std::sync::Arc;
|
||||
@@ -50,27 +51,32 @@ pub struct ChannelPathsInsertGuard<'a> {
|
||||
impl<'a> ChannelPathsInsertGuard<'a> {
|
||||
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
|
||||
let mut ret = false;
|
||||
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
|
||||
let parent_path = channel_proto
|
||||
.parent_path
|
||||
.iter()
|
||||
.map(|cid| ChannelId(*cid))
|
||||
.collect();
|
||||
if let Some(existing_channel) = self.channels_by_id.get_mut(&ChannelId(channel_proto.id)) {
|
||||
let existing_channel = Arc::make_mut(existing_channel);
|
||||
|
||||
ret = existing_channel.visibility != channel_proto.visibility()
|
||||
|| existing_channel.name != channel_proto.name
|
||||
|| existing_channel.parent_path != channel_proto.parent_path;
|
||||
|| existing_channel.parent_path != parent_path;
|
||||
|
||||
existing_channel.visibility = channel_proto.visibility();
|
||||
existing_channel.name = channel_proto.name.into();
|
||||
existing_channel.parent_path = channel_proto.parent_path.into();
|
||||
existing_channel.parent_path = parent_path;
|
||||
} else {
|
||||
self.channels_by_id.insert(
|
||||
channel_proto.id,
|
||||
ChannelId(channel_proto.id),
|
||||
Arc::new(Channel {
|
||||
id: channel_proto.id,
|
||||
id: ChannelId(channel_proto.id),
|
||||
visibility: channel_proto.visibility(),
|
||||
name: channel_proto.name.into(),
|
||||
parent_path: channel_proto.parent_path,
|
||||
parent_path,
|
||||
}),
|
||||
);
|
||||
self.insert_root(channel_proto.id);
|
||||
self.insert_root(ChannelId(channel_proto.id));
|
||||
}
|
||||
ret
|
||||
}
|
||||
@@ -94,14 +100,17 @@ impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
||||
fn channel_path_sorting_key<'a>(
|
||||
id: ChannelId,
|
||||
channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
|
||||
) -> impl Iterator<Item = &str> {
|
||||
) -> impl Iterator<Item = (&str, ChannelId)> {
|
||||
let (parent_path, name) = channels_by_id
|
||||
.get(&id)
|
||||
.map_or((&[] as &[_], None), |channel| {
|
||||
(channel.parent_path.as_slice(), Some(channel.name.as_ref()))
|
||||
(
|
||||
channel.parent_path.as_slice(),
|
||||
Some((channel.name.as_ref(), channel.id)),
|
||||
)
|
||||
});
|
||||
parent_path
|
||||
.iter()
|
||||
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
|
||||
.filter_map(|id| Some((channels_by_id.get(id)?.name.as_ref(), *id)))
|
||||
.chain(name)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::channel_chat::ChannelChatEvent;
|
||||
|
||||
use super::*;
|
||||
use client::{test::FakeServer, Client, UserStore};
|
||||
use clock::FakeSystemClock;
|
||||
use gpui::{AppContext, Context, Model, TestAppContext};
|
||||
use rpc::proto::{self};
|
||||
use settings::SettingsStore;
|
||||
@@ -337,8 +338,9 @@ fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
|
||||
let clock = Arc::new(FakeSystemClock::default());
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
let client = Client::new(http.clone(), cx);
|
||||
let client = Client::new(clock, http.clone(), cx);
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||
|
||||
client::init(&client, cx);
|
||||
|
||||
@@ -16,13 +16,11 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap = { version = "3.1", features = ["derive"] }
|
||||
dirs = "3.0"
|
||||
ipc-channel = "0.16"
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
core-foundation.workspace = true
|
||||
core-services = "0.2"
|
||||
plist = "1.3"
|
||||
|
||||
@@ -156,6 +156,39 @@ mod linux {
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows {
|
||||
use std::path::Path;
|
||||
|
||||
use cli::{CliRequest, CliResponse};
|
||||
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
||||
|
||||
use crate::{Bundle, InfoPlist};
|
||||
|
||||
impl Bundle {
|
||||
pub fn detect(_args_bundle_path: Option<&Path>) -> anyhow::Result<Self> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn plist(&self) -> &InfoPlist {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn zed_version_string(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac_os {
|
||||
use anyhow::Context;
|
||||
|
||||
@@ -10,12 +10,12 @@ path = "src/client.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
|
||||
test-support = ["clock/test-support", "collections/test-support", "gpui/test-support", "rpc/test-support"]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
gpui.workspace = true
|
||||
util.workspace = true
|
||||
release_channel.workspace = true
|
||||
@@ -23,13 +23,11 @@ rpc.workspace = true
|
||||
text.workspace = true
|
||||
settings.workspace = true
|
||||
feature_flags.workspace = true
|
||||
sum_tree.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
async-recursion = "0.3"
|
||||
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
|
||||
futures.workspace = true
|
||||
image = "0.23"
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
once_cell = "1.19.0"
|
||||
@@ -38,19 +36,19 @@ postage.workspace = true
|
||||
rand.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2 = "0.10"
|
||||
sha2.workspace = true
|
||||
smol.workspace = true
|
||||
sysinfo.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
tempfile.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
uuid.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -10,6 +10,7 @@ use async_tungstenite::tungstenite::{
|
||||
error::Error as WebsocketError,
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use clock::SystemClock;
|
||||
use collections::HashMap;
|
||||
use futures::{
|
||||
channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt,
|
||||
@@ -41,11 +42,11 @@ use std::{
|
||||
use telemetry::Telemetry;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use util::http::{HttpClient, ZedHttpClient};
|
||||
use util::http::{HttpClient, HttpClientWithUrl};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub use rpc::*;
|
||||
pub use telemetry::Event;
|
||||
pub use telemetry_events::Event;
|
||||
pub use user::*;
|
||||
|
||||
lazy_static! {
|
||||
@@ -152,7 +153,7 @@ impl Global for GlobalClient {}
|
||||
pub struct Client {
|
||||
id: AtomicU64,
|
||||
peer: Arc<Peer>,
|
||||
http: Arc<ZedHttpClient>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
state: RwLock<ClientState>,
|
||||
|
||||
@@ -421,11 +422,15 @@ impl settings::Settings for TelemetrySettings {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(http: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||
pub fn new(
|
||||
clock: Arc<dyn SystemClock>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
cx: &mut AppContext,
|
||||
) -> Arc<Self> {
|
||||
let client = Arc::new(Self {
|
||||
id: AtomicU64::new(0),
|
||||
peer: Peer::new(0),
|
||||
telemetry: Telemetry::new(http.clone(), cx),
|
||||
telemetry: Telemetry::new(clock, http.clone(), cx),
|
||||
http,
|
||||
state: Default::default(),
|
||||
|
||||
@@ -442,7 +447,7 @@ impl Client {
|
||||
self.id.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn http_client(&self) -> Arc<ZedHttpClient> {
|
||||
pub fn http_client(&self) -> Arc<HttpClientWithUrl> {
|
||||
self.http.clone()
|
||||
}
|
||||
|
||||
@@ -965,14 +970,14 @@ impl Client {
|
||||
}
|
||||
|
||||
async fn get_rpc_url(
|
||||
http: Arc<ZedHttpClient>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
release_channel: Option<ReleaseChannel>,
|
||||
) -> Result<Url> {
|
||||
if let Some(url) = &*ZED_RPC_URL {
|
||||
return Url::parse(url).context("invalid rpc url");
|
||||
}
|
||||
|
||||
let mut url = http.zed_url("/rpc");
|
||||
let mut url = http.build_url("/rpc");
|
||||
if let Some(preview_param) =
|
||||
release_channel.and_then(|channel| channel.release_query_param())
|
||||
{
|
||||
@@ -1105,7 +1110,7 @@ impl Client {
|
||||
|
||||
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
|
||||
// that the user is signing in from a Zed app running on the same device.
|
||||
let mut url = http.zed_url(&format!(
|
||||
let mut url = http.build_url(&format!(
|
||||
"/native_app_signin?native_app_port={}&native_app_public_key={}",
|
||||
port, public_key_string
|
||||
));
|
||||
@@ -1140,7 +1145,7 @@ impl Client {
|
||||
}
|
||||
|
||||
let post_auth_url =
|
||||
http.zed_url("/native_app_signin_succeeded");
|
||||
http.build_url("/native_app_signin_succeeded");
|
||||
req.respond(
|
||||
tiny_http::Response::empty(302).with_header(
|
||||
tiny_http::Header::from_bytes(
|
||||
@@ -1182,7 +1187,7 @@ impl Client {
|
||||
}
|
||||
|
||||
async fn authenticate_as_admin(
|
||||
http: Arc<ZedHttpClient>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
login: String,
|
||||
mut api_token: String,
|
||||
) -> Result<Credentials> {
|
||||
@@ -1455,6 +1460,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test::FakeServer;
|
||||
|
||||
use clock::FakeSystemClock;
|
||||
use gpui::{BackgroundExecutor, Context, TestAppContext};
|
||||
use parking_lot::Mutex;
|
||||
use settings::SettingsStore;
|
||||
@@ -1465,7 +1471,13 @@ mod tests {
|
||||
async fn test_reconnection(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||
let mut status = client.status();
|
||||
assert!(matches!(
|
||||
@@ -1500,7 +1512,13 @@ mod tests {
|
||||
async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let mut status = client.status();
|
||||
|
||||
// Time out when client tries to connect.
|
||||
@@ -1573,7 +1591,13 @@ mod tests {
|
||||
init_test(cx);
|
||||
let auth_count = Arc::new(Mutex::new(0));
|
||||
let dropped_auth_count = Arc::new(Mutex::new(0));
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
client.override_authenticate({
|
||||
let auth_count = auth_count.clone();
|
||||
let dropped_auth_count = dropped_auth_count.clone();
|
||||
@@ -1621,7 +1645,13 @@ mod tests {
|
||||
async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||
|
||||
let (done_tx1, mut done_rx1) = smol::channel::unbounded();
|
||||
@@ -1675,7 +1705,13 @@ mod tests {
|
||||
async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||
|
||||
let model = cx.new_model(|_| TestModel::default());
|
||||
@@ -1704,7 +1740,13 @@ mod tests {
|
||||
async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||
|
||||
let model = cx.new_model(|_| TestModel::default());
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
mod event_coalescer;
|
||||
|
||||
use crate::TelemetrySettings;
|
||||
use crate::{ChannelId, TelemetrySettings};
|
||||
use chrono::{DateTime, Utc};
|
||||
use clock::SystemClock;
|
||||
use futures::Future;
|
||||
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use release_channel::ReleaseChannel;
|
||||
use serde::Serialize;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::Write;
|
||||
@@ -15,8 +15,12 @@ use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sysinfo::{
|
||||
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
|
||||
};
|
||||
use telemetry_events::{
|
||||
ActionEvent, AppEvent, AssistantEvent, AssistantKind, CallEvent, CopilotEvent, CpuEvent,
|
||||
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use util::http::{self, HttpClient, Method, ZedHttpClient};
|
||||
use util::http::{self, HttpClient, HttpClientWithUrl, Method};
|
||||
#[cfg(not(debug_assertions))]
|
||||
use util::ResultExt;
|
||||
use util::TryFutureExt;
|
||||
@@ -24,7 +28,8 @@ use util::TryFutureExt;
|
||||
use self::event_coalescer::EventCoalescer;
|
||||
|
||||
pub struct Telemetry {
|
||||
http_client: Arc<ZedHttpClient>,
|
||||
clock: Arc<dyn SystemClock>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
executor: BackgroundExecutor,
|
||||
state: Arc<Mutex<TelemetryState>>,
|
||||
}
|
||||
@@ -33,7 +38,7 @@ struct TelemetryState {
|
||||
settings: TelemetrySettings,
|
||||
metrics_id: Option<Arc<str>>, // Per logged-in user
|
||||
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
||||
session_id: Option<Arc<str>>, // Per app launch
|
||||
session_id: Option<String>, // Per app launch
|
||||
release_channel: Option<&'static str>,
|
||||
app_metadata: AppMetadata,
|
||||
architecture: &'static str,
|
||||
@@ -46,93 +51,6 @@ struct TelemetryState {
|
||||
max_queue_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct EventRequestBody {
|
||||
installation_id: Option<Arc<str>>,
|
||||
session_id: Option<Arc<str>>,
|
||||
is_staff: Option<bool>,
|
||||
app_version: Option<String>,
|
||||
os_name: &'static str,
|
||||
os_version: Option<String>,
|
||||
architecture: &'static str,
|
||||
release_channel: Option<&'static str>,
|
||||
events: Vec<EventWrapper>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct EventWrapper {
|
||||
signed_in: bool,
|
||||
#[serde(flatten)]
|
||||
event: Event,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AssistantKind {
|
||||
Panel,
|
||||
Inline,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Event {
|
||||
Editor {
|
||||
operation: &'static str,
|
||||
file_extension: Option<String>,
|
||||
vim_mode: bool,
|
||||
copilot_enabled: bool,
|
||||
copilot_enabled_for_language: bool,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
Copilot {
|
||||
suggestion_id: Option<String>,
|
||||
suggestion_accepted: bool,
|
||||
file_extension: Option<String>,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
Call {
|
||||
operation: &'static str,
|
||||
room_id: Option<u64>,
|
||||
channel_id: Option<u64>,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
Assistant {
|
||||
conversation_id: Option<String>,
|
||||
kind: AssistantKind,
|
||||
model: &'static str,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
Cpu {
|
||||
usage_as_percentage: f32,
|
||||
core_count: u32,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
Memory {
|
||||
memory_in_bytes: u64,
|
||||
virtual_memory_in_bytes: u64,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
App {
|
||||
operation: String,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
Setting {
|
||||
setting: &'static str,
|
||||
value: String,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
Edit {
|
||||
duration: i64,
|
||||
environment: &'static str,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
Action {
|
||||
source: &'static str,
|
||||
action: String,
|
||||
milliseconds_since_first_event: i64,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
const MAX_QUEUE_LEN: usize = 5;
|
||||
|
||||
@@ -144,7 +62,6 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
|
||||
|
||||
static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
|
||||
option_env!("ZED_CLIENT_CHECKSUM_SEED")
|
||||
.map(|s| s.as_bytes().into())
|
||||
@@ -156,7 +73,11 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
|
||||
});
|
||||
|
||||
impl Telemetry {
|
||||
pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||
pub fn new(
|
||||
clock: Arc<dyn SystemClock>,
|
||||
client: Arc<HttpClientWithUrl>,
|
||||
cx: &mut AppContext,
|
||||
) -> Arc<Self> {
|
||||
let release_channel =
|
||||
ReleaseChannel::try_global(cx).map(|release_channel| release_channel.display_name());
|
||||
|
||||
@@ -175,7 +96,7 @@ impl Telemetry {
|
||||
log_file: None,
|
||||
is_staff: None,
|
||||
first_event_date_time: None,
|
||||
event_coalescer: EventCoalescer::new(),
|
||||
event_coalescer: EventCoalescer::new(clock.clone()),
|
||||
max_queue_size: MAX_QUEUE_LEN,
|
||||
}));
|
||||
|
||||
@@ -205,6 +126,7 @@ impl Telemetry {
|
||||
|
||||
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
||||
let this = Arc::new(Self {
|
||||
clock,
|
||||
http_client: client,
|
||||
executor: cx.background_executor().clone(),
|
||||
state,
|
||||
@@ -311,14 +233,13 @@ impl Telemetry {
|
||||
copilot_enabled: bool,
|
||||
copilot_enabled_for_language: bool,
|
||||
) {
|
||||
let event = Event::Editor {
|
||||
let event = Event::Editor(EditorEvent {
|
||||
file_extension,
|
||||
vim_mode,
|
||||
operation,
|
||||
operation: operation.into(),
|
||||
copilot_enabled,
|
||||
copilot_enabled_for_language,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
@@ -329,12 +250,11 @@ impl Telemetry {
|
||||
suggestion_accepted: bool,
|
||||
file_extension: Option<String>,
|
||||
) {
|
||||
let event = Event::Copilot {
|
||||
let event = Event::Copilot(CopilotEvent {
|
||||
suggestion_id,
|
||||
suggestion_accepted,
|
||||
file_extension,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
@@ -345,12 +265,11 @@ impl Telemetry {
|
||||
kind: AssistantKind,
|
||||
model: &'static str,
|
||||
) {
|
||||
let event = Event::Assistant {
|
||||
let event = Event::Assistant(AssistantEvent {
|
||||
conversation_id,
|
||||
kind,
|
||||
model,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
model: model.to_string(),
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
@@ -359,24 +278,22 @@ impl Telemetry {
|
||||
self: &Arc<Self>,
|
||||
operation: &'static str,
|
||||
room_id: Option<u64>,
|
||||
channel_id: Option<u64>,
|
||||
channel_id: Option<ChannelId>,
|
||||
) {
|
||||
let event = Event::Call {
|
||||
operation,
|
||||
let event = Event::Call(CallEvent {
|
||||
operation: operation.to_string(),
|
||||
room_id,
|
||||
channel_id,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
channel_id: channel_id.map(|cid| cid.0),
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_cpu_event(self: &Arc<Self>, usage_as_percentage: f32, core_count: u32) {
|
||||
let event = Event::Cpu {
|
||||
let event = Event::Cpu(CpuEvent {
|
||||
usage_as_percentage,
|
||||
core_count,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
@@ -386,28 +303,16 @@ impl Telemetry {
|
||||
memory_in_bytes: u64,
|
||||
virtual_memory_in_bytes: u64,
|
||||
) {
|
||||
let event = Event::Memory {
|
||||
let event = Event::Memory(MemoryEvent {
|
||||
memory_in_bytes,
|
||||
virtual_memory_in_bytes,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_app_event(self: &Arc<Self>, operation: String) {
|
||||
self.report_app_event_with_date_time(operation, Utc::now());
|
||||
}
|
||||
|
||||
fn report_app_event_with_date_time(
|
||||
self: &Arc<Self>,
|
||||
operation: String,
|
||||
date_time: DateTime<Utc>,
|
||||
) -> Event {
|
||||
let event = Event::App {
|
||||
operation,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(date_time),
|
||||
};
|
||||
pub fn report_app_event(self: &Arc<Self>, operation: String) -> Event {
|
||||
let event = Event::App(AppEvent { operation });
|
||||
|
||||
self.report_event(event.clone());
|
||||
|
||||
@@ -415,11 +320,10 @@ impl Telemetry {
|
||||
}
|
||||
|
||||
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
|
||||
let event = Event::Setting {
|
||||
setting,
|
||||
let event = Event::Setting(SettingEvent {
|
||||
setting: setting.to_string(),
|
||||
value,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
@@ -430,40 +334,24 @@ impl Telemetry {
|
||||
drop(state);
|
||||
|
||||
if let Some((start, end, environment)) = period_data {
|
||||
let event = Event::Edit {
|
||||
let event = Event::Edit(EditEvent {
|
||||
duration: end.timestamp_millis() - start.timestamp_millis(),
|
||||
environment,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
environment: environment.to_string(),
|
||||
});
|
||||
|
||||
self.report_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_action_event(self: &Arc<Self>, source: &'static str, action: String) {
|
||||
let event = Event::Action {
|
||||
source,
|
||||
let event = Event::Action(ActionEvent {
|
||||
source: source.to_string(),
|
||||
action,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
});
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
fn milliseconds_since_first_event(self: &Arc<Self>, date_time: DateTime<Utc>) -> i64 {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
match state.first_event_date_time {
|
||||
Some(first_event_date_time) => {
|
||||
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
|
||||
}
|
||||
None => {
|
||||
state.first_event_date_time = Some(date_time);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn report_event(self: &Arc<Self>, event: Event) {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
@@ -480,8 +368,24 @@ impl Telemetry {
|
||||
}));
|
||||
}
|
||||
|
||||
let date_time = self.clock.utc_now();
|
||||
|
||||
let milliseconds_since_first_event = match state.first_event_date_time {
|
||||
Some(first_event_date_time) => {
|
||||
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
|
||||
}
|
||||
None => {
|
||||
state.first_event_date_time = Some(date_time);
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let signed_in = state.metrics_id.is_some();
|
||||
state.events_queue.push(EventWrapper { signed_in, event });
|
||||
state.events_queue.push(EventWrapper {
|
||||
signed_in,
|
||||
milliseconds_since_first_event,
|
||||
event,
|
||||
});
|
||||
|
||||
if state.installation_id.is_some() {
|
||||
if state.events_queue.len() >= state.max_queue_size {
|
||||
@@ -536,21 +440,22 @@ impl Telemetry {
|
||||
{
|
||||
let state = this.state.lock();
|
||||
let request_body = EventRequestBody {
|
||||
installation_id: state.installation_id.clone(),
|
||||
installation_id: state.installation_id.as_deref().map(Into::into),
|
||||
session_id: state.session_id.clone(),
|
||||
is_staff: state.is_staff.clone(),
|
||||
app_version: state
|
||||
.app_metadata
|
||||
.app_version
|
||||
.map(|version| version.to_string()),
|
||||
os_name: state.app_metadata.os_name,
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
os_name: state.app_metadata.os_name.to_string(),
|
||||
os_version: state
|
||||
.app_metadata
|
||||
.os_version
|
||||
.map(|version| version.to_string()),
|
||||
architecture: state.architecture,
|
||||
architecture: state.architecture.to_string(),
|
||||
|
||||
release_channel: state.release_channel,
|
||||
release_channel: state.release_channel.map(Into::into),
|
||||
events,
|
||||
};
|
||||
json_bytes.clear();
|
||||
@@ -569,7 +474,7 @@ impl Telemetry {
|
||||
|
||||
let request = http::Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(&this.http_client.zed_url("/api/events"))
|
||||
.uri(this.http_client.build_zed_api_url("/telemetry/events"))
|
||||
.header("Content-Type", "text/plain")
|
||||
.header("x-zed-checksum", checksum)
|
||||
.body(json_bytes.into());
|
||||
@@ -590,35 +495,37 @@ impl Telemetry {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::TimeZone;
|
||||
use clock::FakeSystemClock;
|
||||
use gpui::TestAppContext;
|
||||
use util::http::FakeHttpClient;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||
));
|
||||
let http = FakeHttpClient::with_200_response();
|
||||
let installation_id = Some("installation_id".to_string());
|
||||
let session_id = "session_id".to_string();
|
||||
|
||||
cx.update(|cx| {
|
||||
let telemetry = Telemetry::new(http, cx);
|
||||
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
||||
|
||||
telemetry.state.lock().max_queue_size = 4;
|
||||
telemetry.start(installation_id, session_id, cx);
|
||||
|
||||
assert!(is_empty_state(&telemetry));
|
||||
|
||||
let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap();
|
||||
let first_date_time = clock.utc_now();
|
||||
let operation = "test".to_string();
|
||||
|
||||
let event =
|
||||
telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
|
||||
let event = telemetry.report_app_event(operation.clone());
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
Event::App(AppEvent {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 0
|
||||
}
|
||||
})
|
||||
);
|
||||
assert_eq!(telemetry.state.lock().events_queue.len(), 1);
|
||||
assert!(telemetry.state.lock().flush_events_task.is_some());
|
||||
@@ -627,15 +534,14 @@ mod tests {
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
let mut date_time = first_date_time + chrono::Duration::milliseconds(100);
|
||||
clock.advance(chrono::Duration::milliseconds(100));
|
||||
|
||||
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
|
||||
let event = telemetry.report_app_event(operation.clone());
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
Event::App(AppEvent {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 100
|
||||
}
|
||||
})
|
||||
);
|
||||
assert_eq!(telemetry.state.lock().events_queue.len(), 2);
|
||||
assert!(telemetry.state.lock().flush_events_task.is_some());
|
||||
@@ -644,15 +550,14 @@ mod tests {
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
date_time += chrono::Duration::milliseconds(100);
|
||||
clock.advance(chrono::Duration::milliseconds(100));
|
||||
|
||||
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
|
||||
let event = telemetry.report_app_event(operation.clone());
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
Event::App(AppEvent {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 200
|
||||
}
|
||||
})
|
||||
);
|
||||
assert_eq!(telemetry.state.lock().events_queue.len(), 3);
|
||||
assert!(telemetry.state.lock().flush_events_task.is_some());
|
||||
@@ -661,16 +566,15 @@ mod tests {
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
date_time += chrono::Duration::milliseconds(100);
|
||||
clock.advance(chrono::Duration::milliseconds(100));
|
||||
|
||||
// Adding a 4th event should cause a flush
|
||||
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
|
||||
let event = telemetry.report_app_event(operation.clone());
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
Event::App(AppEvent {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 300
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
assert!(is_empty_state(&telemetry));
|
||||
@@ -680,28 +584,29 @@ mod tests {
|
||||
#[gpui::test]
|
||||
async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||
));
|
||||
let http = FakeHttpClient::with_200_response();
|
||||
let installation_id = Some("installation_id".to_string());
|
||||
let session_id = "session_id".to_string();
|
||||
|
||||
cx.update(|cx| {
|
||||
let telemetry = Telemetry::new(http, cx);
|
||||
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
||||
telemetry.state.lock().max_queue_size = 4;
|
||||
telemetry.start(installation_id, session_id, cx);
|
||||
|
||||
assert!(is_empty_state(&telemetry));
|
||||
|
||||
let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap();
|
||||
let first_date_time = clock.utc_now();
|
||||
let operation = "test".to_string();
|
||||
|
||||
let event =
|
||||
telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
|
||||
let event = telemetry.report_app_event(operation.clone());
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
Event::App(AppEvent {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 0
|
||||
}
|
||||
})
|
||||
);
|
||||
assert_eq!(telemetry.state.lock().events_queue.len(), 1);
|
||||
assert!(telemetry.state.lock().flush_events_task.is_some());
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use clock::SystemClock;
|
||||
|
||||
const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20);
|
||||
const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from_millis(1);
|
||||
|
||||
@@ -12,30 +15,20 @@ struct PeriodData {
|
||||
}
|
||||
|
||||
pub struct EventCoalescer {
|
||||
clock: Arc<dyn SystemClock>,
|
||||
state: Option<PeriodData>,
|
||||
}
|
||||
|
||||
impl EventCoalescer {
|
||||
pub fn new() -> Self {
|
||||
Self { state: None }
|
||||
pub fn new(clock: Arc<dyn SystemClock>) -> Self {
|
||||
Self { clock, state: None }
|
||||
}
|
||||
|
||||
pub fn log_event(
|
||||
&mut self,
|
||||
environment: &'static str,
|
||||
) -> Option<(DateTime<Utc>, DateTime<Utc>, &'static str)> {
|
||||
self.log_event_with_time(Utc::now(), environment)
|
||||
}
|
||||
|
||||
// pub fn close_current_period(&mut self) -> Option<(DateTime<Utc>, DateTime<Utc>)> {
|
||||
// self.environment.map(|env| self.log_event(env)).flatten()
|
||||
// }
|
||||
|
||||
fn log_event_with_time(
|
||||
&mut self,
|
||||
log_time: DateTime<Utc>,
|
||||
environment: &'static str,
|
||||
) -> Option<(DateTime<Utc>, DateTime<Utc>, &'static str)> {
|
||||
let log_time = self.clock.utc_now();
|
||||
let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap();
|
||||
|
||||
let Some(state) = &mut self.state else {
|
||||
@@ -78,18 +71,22 @@ impl EventCoalescer {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::TimeZone;
|
||||
use clock::FakeSystemClock;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_same_context_exceeding_timeout() {
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
|
||||
));
|
||||
let environment_1 = "environment_1";
|
||||
let mut event_coalescer = EventCoalescer::new();
|
||||
let mut event_coalescer = EventCoalescer::new(clock.clone());
|
||||
|
||||
assert_eq!(event_coalescer.state, None);
|
||||
|
||||
let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
|
||||
let period_data = event_coalescer.log_event_with_time(period_start, environment_1);
|
||||
let period_start = clock.utc_now();
|
||||
let period_data = event_coalescer.log_event(environment_1);
|
||||
|
||||
assert_eq!(period_data, None);
|
||||
assert_eq!(
|
||||
@@ -102,12 +99,12 @@ mod tests {
|
||||
);
|
||||
|
||||
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
|
||||
let mut period_end = period_start;
|
||||
|
||||
// Ensure that many calls within the timeout don't start a new period
|
||||
for _ in 0..100 {
|
||||
period_end += within_timeout_adjustment;
|
||||
let period_data = event_coalescer.log_event_with_time(period_end, environment_1);
|
||||
clock.advance(within_timeout_adjustment);
|
||||
let period_data = event_coalescer.log_event(environment_1);
|
||||
let period_end = clock.utc_now();
|
||||
|
||||
assert_eq!(period_data, None);
|
||||
assert_eq!(
|
||||
@@ -120,10 +117,12 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
let period_end = clock.utc_now();
|
||||
let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap();
|
||||
// Logging an event exceeding the timeout should start a new period
|
||||
let new_period_start = period_end + exceed_timeout_adjustment;
|
||||
let period_data = event_coalescer.log_event_with_time(new_period_start, environment_1);
|
||||
clock.advance(exceed_timeout_adjustment);
|
||||
let new_period_start = clock.utc_now();
|
||||
let period_data = event_coalescer.log_event(environment_1);
|
||||
|
||||
assert_eq!(period_data, Some((period_start, period_end, environment_1)));
|
||||
assert_eq!(
|
||||
@@ -138,13 +137,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_different_environment_under_timeout() {
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
|
||||
));
|
||||
let environment_1 = "environment_1";
|
||||
let mut event_coalescer = EventCoalescer::new();
|
||||
let mut event_coalescer = EventCoalescer::new(clock.clone());
|
||||
|
||||
assert_eq!(event_coalescer.state, None);
|
||||
|
||||
let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
|
||||
let period_data = event_coalescer.log_event_with_time(period_start, environment_1);
|
||||
let period_start = clock.utc_now();
|
||||
let period_data = event_coalescer.log_event(environment_1);
|
||||
|
||||
assert_eq!(period_data, None);
|
||||
assert_eq!(
|
||||
@@ -157,8 +159,9 @@ mod tests {
|
||||
);
|
||||
|
||||
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
|
||||
let period_end = period_start + within_timeout_adjustment;
|
||||
let period_data = event_coalescer.log_event_with_time(period_end, environment_1);
|
||||
clock.advance(within_timeout_adjustment);
|
||||
let period_end = clock.utc_now();
|
||||
let period_data = event_coalescer.log_event(environment_1);
|
||||
|
||||
assert_eq!(period_data, None);
|
||||
assert_eq!(
|
||||
@@ -170,10 +173,12 @@ mod tests {
|
||||
})
|
||||
);
|
||||
|
||||
clock.advance(within_timeout_adjustment);
|
||||
|
||||
// Logging an event within the timeout but with a different environment should start a new period
|
||||
let period_end = period_end + within_timeout_adjustment;
|
||||
let period_end = clock.utc_now();
|
||||
let environment_2 = "environment_2";
|
||||
let period_data = event_coalescer.log_event_with_time(period_end, environment_2);
|
||||
let period_data = event_coalescer.log_event(environment_2);
|
||||
|
||||
assert_eq!(period_data, Some((period_start, period_end, environment_1)));
|
||||
assert_eq!(
|
||||
@@ -188,13 +193,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_switching_environment_while_within_timeout() {
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
|
||||
));
|
||||
let environment_1 = "environment_1";
|
||||
let mut event_coalescer = EventCoalescer::new();
|
||||
let mut event_coalescer = EventCoalescer::new(clock.clone());
|
||||
|
||||
assert_eq!(event_coalescer.state, None);
|
||||
|
||||
let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
|
||||
let period_data = event_coalescer.log_event_with_time(period_start, environment_1);
|
||||
let period_start = clock.utc_now();
|
||||
let period_data = event_coalescer.log_event(environment_1);
|
||||
|
||||
assert_eq!(period_data, None);
|
||||
assert_eq!(
|
||||
@@ -207,9 +215,10 @@ mod tests {
|
||||
);
|
||||
|
||||
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
|
||||
let period_end = period_start + within_timeout_adjustment;
|
||||
clock.advance(within_timeout_adjustment);
|
||||
let period_end = clock.utc_now();
|
||||
let environment_2 = "environment_2";
|
||||
let period_data = event_coalescer.log_event_with_time(period_end, environment_2);
|
||||
let period_data = event_coalescer.log_event(environment_2);
|
||||
|
||||
assert_eq!(period_data, Some((period_start, period_end, environment_1)));
|
||||
assert_eq!(
|
||||
@@ -221,22 +230,26 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
// // 0 20 40 60
|
||||
// // |-------------------|-------------------|-------------------|-------------------
|
||||
// // |--------|----------env change
|
||||
// // |-------------------
|
||||
// // |period_start |period_end
|
||||
// // |new_period_start
|
||||
|
||||
// 0 20 40 60
|
||||
// |-------------------|-------------------|-------------------|-------------------
|
||||
// |--------|----------env change
|
||||
// |-------------------
|
||||
// |period_start |period_end
|
||||
// |new_period_start
|
||||
|
||||
#[test]
|
||||
fn test_switching_environment_while_exceeding_timeout() {
|
||||
let clock = Arc::new(FakeSystemClock::new(
|
||||
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
|
||||
));
|
||||
let environment_1 = "environment_1";
|
||||
let mut event_coalescer = EventCoalescer::new();
|
||||
let mut event_coalescer = EventCoalescer::new(clock.clone());
|
||||
|
||||
assert_eq!(event_coalescer.state, None);
|
||||
|
||||
let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap();
|
||||
let period_data = event_coalescer.log_event_with_time(period_start, environment_1);
|
||||
let period_start = clock.utc_now();
|
||||
let period_data = event_coalescer.log_event(environment_1);
|
||||
|
||||
assert_eq!(period_data, None);
|
||||
assert_eq!(
|
||||
@@ -249,9 +262,10 @@ mod tests {
|
||||
);
|
||||
|
||||
let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap();
|
||||
let period_end = period_start + exceed_timeout_adjustment;
|
||||
clock.advance(exceed_timeout_adjustment);
|
||||
let period_end = clock.utc_now();
|
||||
let environment_2 = "environment_2";
|
||||
let period_data = event_coalescer.log_event_with_time(period_end, environment_2);
|
||||
let period_data = event_coalescer.log_event(environment_2);
|
||||
|
||||
assert_eq!(
|
||||
period_data,
|
||||
@@ -270,6 +284,7 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 0 20 40 60
|
||||
// |-------------------|-------------------|-------------------|-------------------
|
||||
// |--------|----------------------------------------env change
|
||||
|
||||
@@ -15,6 +15,15 @@ use util::TryFutureExt as _;
|
||||
|
||||
pub type UserId = u64;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub struct ChannelId(pub u64);
|
||||
|
||||
impl std::fmt::Display for ChannelId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ParticipantIndex(pub u32);
|
||||
|
||||
|
||||
@@ -9,5 +9,10 @@ license = "GPL-3.0-or-later"
|
||||
path = "src/clock.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = ["dep:parking_lot"]
|
||||
|
||||
[dependencies]
|
||||
chrono.workspace = true
|
||||
parking_lot = { workspace = true, optional = true }
|
||||
smallvec.workspace = true
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
mod system_clock;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
fmt, iter,
|
||||
};
|
||||
|
||||
pub use system_clock::*;
|
||||
|
||||
/// A unique identifier for each distributed node.
|
||||
pub type ReplicaId = u16;
|
||||
|
||||
/// A [Lamport sequence number](https://en.wikipedia.org/wiki/Lamport_timestamp).
|
||||
pub type Seq = u32;
|
||||
|
||||
/// A [Lamport timestamp](https://en.wikipedia.org/wiki/Lamport_timestamp),
|
||||
/// used to determine the ordering of events in the editor.
|
||||
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
|
||||
pub struct Lamport {
|
||||
pub replica_id: ReplicaId,
|
||||
pub value: Seq,
|
||||
}
|
||||
|
||||
/// A vector clock
|
||||
/// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock).
|
||||
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct Global(SmallVec<[u32; 8]>);
|
||||
|
||||
|
||||
59
crates/clock/src/system_clock.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub trait SystemClock: Send + Sync {
|
||||
/// Returns the current date and time in UTC.
|
||||
fn utc_now(&self) -> DateTime<Utc>;
|
||||
}
|
||||
|
||||
pub struct RealSystemClock;
|
||||
|
||||
impl SystemClock for RealSystemClock {
|
||||
fn utc_now(&self) -> DateTime<Utc> {
|
||||
Utc::now()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct FakeSystemClockState {
|
||||
now: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct FakeSystemClock {
|
||||
// Use an unfair lock to ensure tests are deterministic.
|
||||
state: parking_lot::Mutex<FakeSystemClockState>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl Default for FakeSystemClock {
|
||||
fn default() -> Self {
|
||||
Self::new(Utc::now())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl FakeSystemClock {
|
||||
pub fn new(now: DateTime<Utc>) -> Self {
|
||||
let state = FakeSystemClockState { now };
|
||||
|
||||
Self {
|
||||
state: parking_lot::Mutex::new(state),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_now(&self, now: DateTime<Utc>) {
|
||||
self.state.lock().now = now;
|
||||
}
|
||||
|
||||
/// Advances the [`FakeSystemClock`] by the specified [`Duration`](chrono::Duration).
|
||||
pub fn advance(&self, duration: chrono::Duration) {
|
||||
self.state.lock().now += duration;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl SystemClock for FakeSystemClock {
|
||||
fn utc_now(&self) -> DateTime<Utc> {
|
||||
self.state.lock().now
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,17 @@ ZED_ENVIRONMENT = "development"
|
||||
LIVE_KIT_SERVER = "http://localhost:7880"
|
||||
LIVE_KIT_KEY = "devkey"
|
||||
LIVE_KIT_SECRET = "secret"
|
||||
BLOB_STORE_ACCESS_KEY = "the-blob-store-access-key"
|
||||
BLOB_STORE_SECRET_KEY = "the-blob-store-secret-key"
|
||||
BLOB_STORE_BUCKET = "the-extensions-bucket"
|
||||
BLOB_STORE_URL = "http://127.0.0.1:9000"
|
||||
BLOB_STORE_REGION = "the-region"
|
||||
ZED_CLIENT_CHECKSUM_SEED = "development-checksum-seed"
|
||||
|
||||
# CLICKHOUSE_URL = ""
|
||||
# CLICKHOUSE_USER = "default"
|
||||
# CLICKHOUSE_PASSWORD = ""
|
||||
# CLICKHOUSE_DATABASE = "default"
|
||||
|
||||
# RUST_LOG=info
|
||||
# LOG_JSON=true
|
||||
|
||||
@@ -7,6 +7,9 @@ version = "0.44.0"
|
||||
publish = false
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
[features]
|
||||
seed-support = ["reqwest"]
|
||||
|
||||
[[bin]]
|
||||
name = "collab"
|
||||
|
||||
@@ -17,19 +20,20 @@ required-features = ["seed-support"]
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-tungstenite = "0.16"
|
||||
aws-config = { version = "1.1.5" }
|
||||
aws-sdk-s3 = { version = "1.15.0" }
|
||||
axum = { version = "0.5", features = ["json", "headers", "ws"] }
|
||||
axum-extra = { version = "0.3", features = ["erased-json"] }
|
||||
base64 = "0.13"
|
||||
chrono.workspace = true
|
||||
clap = { version = "3.1", features = ["derive"], optional = true }
|
||||
clock.workspace = true
|
||||
clickhouse.workspace = true
|
||||
collections.workspace = true
|
||||
dashmap = "5.4"
|
||||
envy = "0.4.2"
|
||||
futures.workspace = true
|
||||
hex.workspace = true
|
||||
hyper = "0.14"
|
||||
lazy_static.workspace = true
|
||||
lipsum = { version = "0.8", optional = true }
|
||||
live_kit_server.workspace = true
|
||||
log.workspace = true
|
||||
nanoid = "0.4"
|
||||
@@ -41,19 +45,19 @@ reqwest = { version = "0.11", features = ["json"], optional = true }
|
||||
rpc.workspace = true
|
||||
scrypt = "0.7"
|
||||
sea-orm = { version = "0.12.x", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha-1 = "0.9"
|
||||
smallvec.workspace = true
|
||||
sha2.workspace = true
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
|
||||
telemetry_events.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-tungstenite = "0.17"
|
||||
toml.workspace = true
|
||||
tonic = "0.6"
|
||||
tower = "0.4"
|
||||
tower-http = { workspace = true, features = ["trace"] }
|
||||
tracing = "0.1.34"
|
||||
tracing-log = "0.1.3"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
|
||||
@@ -61,7 +65,6 @@ util.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
release_channel.workspace = true
|
||||
async-trait.workspace = true
|
||||
audio.workspace = true
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
@@ -86,6 +89,7 @@ node_runtime.workspace = true
|
||||
notifications = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
release_channel.workspace = true
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
sea-orm = { version = "0.12.x", features = ["sqlx-sqlite"] }
|
||||
serde_json.workspace = true
|
||||
@@ -95,6 +99,3 @@ theme.workspace = true
|
||||
unindent.workspace = true
|
||||
util.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[features]
|
||||
seed-support = ["clap", "lipsum", "reqwest"]
|
||||
|
||||
@@ -9,7 +9,7 @@ kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
namespace: ${ZED_KUBE_NAMESPACE}
|
||||
name: collab
|
||||
name: ${ZED_SERVICE_NAME}
|
||||
annotations:
|
||||
service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443"
|
||||
service.beta.kubernetes.io/do-loadbalancer-certificate-id: ${ZED_DO_CERTIFICATE_ID}
|
||||
@@ -17,7 +17,7 @@ metadata:
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
app: collab
|
||||
app: ${ZED_SERVICE_NAME}
|
||||
ports:
|
||||
- name: web
|
||||
protocol: TCP
|
||||
@@ -29,17 +29,17 @@ apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
namespace: ${ZED_KUBE_NAMESPACE}
|
||||
name: collab
|
||||
name: ${ZED_SERVICE_NAME}
|
||||
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: collab
|
||||
app: ${ZED_SERVICE_NAME}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: collab
|
||||
app: ${ZED_SERVICE_NAME}
|
||||
annotations:
|
||||
ad.datadoghq.com/collab.check_names: |
|
||||
["openmetrics"]
|
||||
@@ -55,10 +55,11 @@ spec:
|
||||
]
|
||||
spec:
|
||||
containers:
|
||||
- name: collab
|
||||
- name: ${ZED_SERVICE_NAME}
|
||||
image: "${ZED_IMAGE_ID}"
|
||||
args:
|
||||
- serve
|
||||
- ${ZED_SERVICE_NAME}
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
@@ -90,6 +91,11 @@ spec:
|
||||
secretKeyRef:
|
||||
name: api
|
||||
key: token
|
||||
- name: ZED_CLIENT_CHECKSUM_SEED
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: zed-client
|
||||
key: checksum-seed
|
||||
- name: LIVE_KIT_SERVER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@@ -105,6 +111,51 @@ spec:
|
||||
secretKeyRef:
|
||||
name: livekit
|
||||
key: secret
|
||||
- name: BLOB_STORE_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: blob-store
|
||||
key: access_key
|
||||
- name: BLOB_STORE_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: blob-store
|
||||
key: secret_key
|
||||
- name: BLOB_STORE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: blob-store
|
||||
key: url
|
||||
- name: BLOB_STORE_REGION
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: blob-store
|
||||
key: region
|
||||
- name: BLOB_STORE_BUCKET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: blob-store
|
||||
key: bucket
|
||||
- name: CLICKHOUSE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: clickhouse
|
||||
key: url
|
||||
- name: CLICKHOUSE_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: clickhouse
|
||||
key: user
|
||||
- name: CLICKHOUSE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: clickhouse
|
||||
key: password
|
||||
- name: CLICKHOUSE_DATABASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: clickhouse
|
||||
key: database
|
||||
- name: INVITE_LINK_PREFIX
|
||||
value: ${INVITE_LINK_PREFIX}
|
||||
- name: RUST_BACKTRACE
|
||||
|
||||
@@ -353,3 +353,35 @@ CREATE TABLE contributors (
|
||||
signed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE extensions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
external_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
latest_version TEXT NOT NULL,
|
||||
total_download_count INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE extension_versions (
|
||||
extension_id INTEGER REFERENCES extensions(id),
|
||||
version TEXT NOT NULL,
|
||||
published_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
authors TEXT NOT NULL,
|
||||
repository TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
download_count INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (extension_id, version)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id");
|
||||
CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count");
|
||||
|
||||
CREATE TABLE hosted_projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
channel_id INTEGER NOT NULL REFERENCES channels(id),
|
||||
name TEXT NOT NULL,
|
||||
visibility TEXT NOT NULL,
|
||||
deleted_at TIMESTAMP NULL
|
||||
);
|
||||
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
|
||||
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add migration script here
|
||||
ALTER TABLE rooms DROP COLUMN enviroment;
|
||||
ALTER TABLE rooms DROP COLUMN environment;
|
||||
ALTER TABLE room_participants DROP COLUMN in_call;
|
||||
22
crates/collab/migrations/20240214102900_add_extensions.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE IF NOT EXISTS extensions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
external_id TEXT NOT NULL,
|
||||
latest_version TEXT NOT NULL,
|
||||
total_download_count BIGINT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS extension_versions (
|
||||
extension_id INTEGER REFERENCES extensions(id),
|
||||
version TEXT NOT NULL,
|
||||
published_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||
authors TEXT NOT NULL,
|
||||
repository TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
download_count BIGINT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(extension_id, version)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id");
|
||||
CREATE INDEX "trigram_index_extensions_name" ON "extensions" USING GIN(name gin_trgm_ops);
|
||||
CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count");
|
||||
11
crates/collab/migrations/20240226163408_hosted_projects.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Add migration script here
|
||||
|
||||
CREATE TABLE hosted_projects (
|
||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
channel_id INT NOT NULL REFERENCES channels(id),
|
||||
name TEXT NOT NULL,
|
||||
visibility TEXT NOT NULL,
|
||||
deleted_at TIMESTAMP NULL
|
||||
);
|
||||
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
|
||||
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add migration script here
|
||||
|
||||
CREATE UNIQUE INDEX uix_channels_parent_path_name ON channels(parent_path, name) WHERE (parent_path IS NOT NULL AND parent_path != '');
|
||||
@@ -1,3 +1,6 @@
|
||||
pub mod events;
|
||||
pub mod extensions;
|
||||
|
||||
use crate::{
|
||||
auth,
|
||||
db::{ContributorSelector, User, UserId},
|
||||
@@ -20,7 +23,9 @@ use std::sync::Arc;
|
||||
use tower::ServiceBuilder;
|
||||
use tracing::instrument;
|
||||
|
||||
pub fn routes(rpc_server: Arc<rpc::Server>, state: Arc<AppState>) -> Router<Body> {
|
||||
pub use extensions::fetch_extensions_from_blob_store_periodically;
|
||||
|
||||
pub fn routes(rpc_server: Option<Arc<rpc::Server>>, state: Arc<AppState>) -> Router<Body> {
|
||||
Router::new()
|
||||
.route("/user", get(get_authenticated_user))
|
||||
.route("/users/:id/access_tokens", post(create_access_token))
|
||||
@@ -130,8 +135,12 @@ async fn trace_panic(panic: Json<Panic>) -> Result<()> {
|
||||
}
|
||||
|
||||
async fn get_rpc_server_snapshot(
|
||||
Extension(rpc_server): Extension<Arc<rpc::Server>>,
|
||||
Extension(rpc_server): Extension<Option<Arc<rpc::Server>>>,
|
||||
) -> Result<ErasedJson> {
|
||||
let Some(rpc_server) = rpc_server else {
|
||||
return Err(Error::Internal(anyhow!("rpc server is not available")));
|
||||
};
|
||||
|
||||
Ok(ErasedJson::pretty(rpc_server.snapshot().await))
|
||||
}
|
||||
|
||||
|
||||
797
crates/collab/src/api/events.rs
Normal file
@@ -0,0 +1,797 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{
|
||||
body::Bytes, headers::Header, http::HeaderName, routing::post, Extension, Router, TypedHeader,
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Serialize, Serializer};
|
||||
use sha2::{Digest, Sha256};
|
||||
use telemetry_events::{
|
||||
ActionEvent, AppEvent, AssistantEvent, CallEvent, CopilotEvent, CpuEvent, EditEvent,
|
||||
EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
|
||||
};
|
||||
|
||||
use crate::{AppState, Error, Result};
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new().route("/telemetry/events", post(post_events))
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref ZED_CHECKSUM_HEADER: HeaderName = HeaderName::from_static("x-zed-checksum");
|
||||
static ref CLOUDFLARE_IP_COUNTRY_HEADER: HeaderName = HeaderName::from_static("cf-ipcountry");
|
||||
}
|
||||
|
||||
pub struct ZedChecksumHeader(Vec<u8>);
|
||||
|
||||
impl Header for ZedChecksumHeader {
|
||||
fn name() -> &'static HeaderName {
|
||||
&ZED_CHECKSUM_HEADER
|
||||
}
|
||||
|
||||
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
I: Iterator<Item = &'i axum::http::HeaderValue>,
|
||||
{
|
||||
let checksum = values
|
||||
.next()
|
||||
.ok_or_else(axum::headers::Error::invalid)?
|
||||
.to_str()
|
||||
.map_err(|_| axum::headers::Error::invalid())?;
|
||||
|
||||
let bytes = hex::decode(checksum).map_err(|_| axum::headers::Error::invalid())?;
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
|
||||
fn encode<E: Extend<axum::http::HeaderValue>>(&self, _values: &mut E) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CloudflareIpCountryHeader(String);
|
||||
|
||||
impl Header for CloudflareIpCountryHeader {
|
||||
fn name() -> &'static HeaderName {
|
||||
&CLOUDFLARE_IP_COUNTRY_HEADER
|
||||
}
|
||||
|
||||
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
I: Iterator<Item = &'i axum::http::HeaderValue>,
|
||||
{
|
||||
let country_code = values
|
||||
.next()
|
||||
.ok_or_else(axum::headers::Error::invalid)?
|
||||
.to_str()
|
||||
.map_err(|_| axum::headers::Error::invalid())?;
|
||||
|
||||
Ok(Self(country_code.to_string()))
|
||||
}
|
||||
|
||||
fn encode<E: Extend<axum::http::HeaderValue>>(&self, _values: &mut E) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn post_events(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
|
||||
country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
|
||||
body: Bytes,
|
||||
) -> Result<()> {
|
||||
let Some(clickhouse_client) = app.clickhouse_client.clone() else {
|
||||
Err(Error::Http(
|
||||
StatusCode::NOT_IMPLEMENTED,
|
||||
"not supported".into(),
|
||||
))?
|
||||
};
|
||||
|
||||
let Some(checksum_seed) = app.config.zed_client_checksum_seed.as_ref() else {
|
||||
return Err(Error::Http(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"events not enabled".into(),
|
||||
))?;
|
||||
};
|
||||
|
||||
let mut summer = Sha256::new();
|
||||
summer.update(checksum_seed);
|
||||
summer.update(&body);
|
||||
summer.update(checksum_seed);
|
||||
|
||||
if &checksum[..] != &summer.finalize()[..] {
|
||||
return Err(Error::Http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"invalid checksum".into(),
|
||||
))?;
|
||||
}
|
||||
|
||||
let request_body: telemetry_events::EventRequestBody =
|
||||
serde_json::from_slice(&body).map_err(|err| {
|
||||
log::error!("can't parse event json: {err}");
|
||||
Error::Internal(anyhow!(err))
|
||||
})?;
|
||||
|
||||
let mut to_upload = ToUpload::default();
|
||||
let Some(last_event) = request_body.events.last() else {
|
||||
return Err(Error::Http(StatusCode::BAD_REQUEST, "no events".into()))?;
|
||||
};
|
||||
let country_code = country_code_header.map(|h| h.0 .0);
|
||||
|
||||
let first_event_at = chrono::Utc::now()
|
||||
- chrono::Duration::milliseconds(last_event.milliseconds_since_first_event);
|
||||
|
||||
for wrapper in &request_body.events {
|
||||
match &wrapper.event {
|
||||
Event::Editor(event) => to_upload.editor_events.push(EditorEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
country_code.clone(),
|
||||
)),
|
||||
Event::Copilot(event) => to_upload.copilot_events.push(CopilotEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
country_code.clone(),
|
||||
)),
|
||||
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
)),
|
||||
Event::Assistant(event) => {
|
||||
to_upload
|
||||
.assistant_events
|
||||
.push(AssistantEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
))
|
||||
}
|
||||
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
)),
|
||||
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
)),
|
||||
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
)),
|
||||
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
)),
|
||||
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
)),
|
||||
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
|
||||
event.clone(),
|
||||
&wrapper,
|
||||
&request_body,
|
||||
first_event_at,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
to_upload
|
||||
.upload(&clickhouse_client)
|
||||
.await
|
||||
.map_err(|err| Error::Internal(anyhow!(err)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ToUpload {
|
||||
editor_events: Vec<EditorEventRow>,
|
||||
copilot_events: Vec<CopilotEventRow>,
|
||||
assistant_events: Vec<AssistantEventRow>,
|
||||
call_events: Vec<CallEventRow>,
|
||||
cpu_events: Vec<CpuEventRow>,
|
||||
memory_events: Vec<MemoryEventRow>,
|
||||
app_events: Vec<AppEventRow>,
|
||||
setting_events: Vec<SettingEventRow>,
|
||||
edit_events: Vec<EditEventRow>,
|
||||
action_events: Vec<ActionEventRow>,
|
||||
}
|
||||
|
||||
impl ToUpload {
|
||||
pub async fn upload(&self, clickhouse_client: &clickhouse::Client) -> anyhow::Result<()> {
|
||||
Self::upload_to_table("editor_events", &self.editor_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'editor_events'"))?;
|
||||
Self::upload_to_table("copilot_events", &self.copilot_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'copilot_events'"))?;
|
||||
Self::upload_to_table(
|
||||
"assistant_events",
|
||||
&self.assistant_events,
|
||||
clickhouse_client,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'assistant_events'"))?;
|
||||
Self::upload_to_table("call_events", &self.call_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'call_events'"))?;
|
||||
Self::upload_to_table("cpu_events", &self.cpu_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'cpu_events'"))?;
|
||||
Self::upload_to_table("memory_events", &self.memory_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'memory_events'"))?;
|
||||
Self::upload_to_table("app_events", &self.app_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'app_events'"))?;
|
||||
Self::upload_to_table("setting_events", &self.setting_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'setting_events'"))?;
|
||||
Self::upload_to_table("edit_events", &self.edit_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'edit_events'"))?;
|
||||
Self::upload_to_table("action_events", &self.action_events, clickhouse_client)
|
||||
.await
|
||||
.with_context(|| format!("failed to upload to table 'action_events'"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upload_to_table<T: clickhouse::Row + Serialize + std::fmt::Debug>(
|
||||
table: &str,
|
||||
rows: &[T],
|
||||
clickhouse_client: &clickhouse::Client,
|
||||
) -> anyhow::Result<()> {
|
||||
if !rows.is_empty() {
|
||||
let mut insert = clickhouse_client.insert(table)?;
|
||||
|
||||
for event in rows {
|
||||
insert.write(event).await?;
|
||||
}
|
||||
|
||||
insert.end().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_country_code<S>(country_code: &str, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if country_code.len() != 2 {
|
||||
use serde::ser::Error;
|
||||
return Err(S::Error::custom(
|
||||
"country_code must be exactly 2 characters",
|
||||
));
|
||||
}
|
||||
|
||||
let country_code = country_code.as_bytes();
|
||||
|
||||
serializer.serialize_u16(((country_code[0] as u16) << 8) + country_code[1] as u16)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct EditorEventRow {
|
||||
pub installation_id: String,
|
||||
pub operation: String,
|
||||
pub app_version: String,
|
||||
pub file_extension: String,
|
||||
pub os_name: String,
|
||||
pub os_version: String,
|
||||
pub release_channel: String,
|
||||
pub signed_in: bool,
|
||||
pub vim_mode: bool,
|
||||
#[serde(serialize_with = "serialize_country_code")]
|
||||
pub country_code: String,
|
||||
pub region_code: String,
|
||||
pub city: String,
|
||||
pub time: i64,
|
||||
pub copilot_enabled: bool,
|
||||
pub copilot_enabled_for_language: bool,
|
||||
pub historical_event: bool,
|
||||
pub architecture: String,
|
||||
pub is_staff: Option<bool>,
|
||||
pub session_id: Option<String>,
|
||||
pub major: Option<i32>,
|
||||
pub minor: Option<i32>,
|
||||
pub patch: Option<i32>,
|
||||
}
|
||||
|
||||
impl EditorEventRow {
|
||||
fn from_event(
|
||||
event: EditorEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
country_code: Option<String>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
architecture: body.architecture.clone(),
|
||||
installation_id: body.installation_id.clone().unwrap_or_default(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
operation: event.operation,
|
||||
file_extension: event.file_extension.unwrap_or_default(),
|
||||
signed_in: wrapper.signed_in,
|
||||
vim_mode: event.vim_mode,
|
||||
copilot_enabled: event.copilot_enabled,
|
||||
copilot_enabled_for_language: event.copilot_enabled_for_language,
|
||||
country_code: country_code.unwrap_or("XX".to_string()),
|
||||
region_code: "".to_string(),
|
||||
city: "".to_string(),
|
||||
historical_event: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct CopilotEventRow {
|
||||
pub installation_id: String,
|
||||
pub suggestion_id: String,
|
||||
pub suggestion_accepted: bool,
|
||||
pub app_version: String,
|
||||
pub file_extension: String,
|
||||
pub os_name: String,
|
||||
pub os_version: String,
|
||||
pub release_channel: String,
|
||||
pub signed_in: bool,
|
||||
#[serde(serialize_with = "serialize_country_code")]
|
||||
pub country_code: String,
|
||||
pub region_code: String,
|
||||
pub city: String,
|
||||
pub time: i64,
|
||||
pub is_staff: Option<bool>,
|
||||
pub session_id: Option<String>,
|
||||
pub major: Option<i32>,
|
||||
pub minor: Option<i32>,
|
||||
pub patch: Option<i32>,
|
||||
}
|
||||
|
||||
impl CopilotEventRow {
|
||||
fn from_event(
|
||||
event: CopilotEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
country_code: Option<String>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
os_name: body.os_name.clone(),
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone().unwrap_or_default(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
file_extension: event.file_extension.unwrap_or_default(),
|
||||
signed_in: wrapper.signed_in,
|
||||
country_code: country_code.unwrap_or("XX".to_string()),
|
||||
region_code: "".to_string(),
|
||||
city: "".to_string(),
|
||||
suggestion_id: event.suggestion_id.unwrap_or_default(),
|
||||
suggestion_accepted: event.suggestion_accepted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct CallEventRow {
|
||||
// AppInfoBase
|
||||
app_version: String,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
release_channel: String,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: String,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
time: i64,
|
||||
|
||||
// CallEventRow
|
||||
operation: String,
|
||||
room_id: Option<u64>,
|
||||
channel_id: Option<u64>,
|
||||
}
|
||||
|
||||
impl CallEventRow {
|
||||
fn from_event(
|
||||
event: CallEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone().unwrap_or_default(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
operation: event.operation,
|
||||
room_id: event.room_id,
|
||||
channel_id: event.channel_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct AssistantEventRow {
|
||||
// AppInfoBase
|
||||
app_version: String,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
release_channel: String,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
time: i64,
|
||||
|
||||
// AssistantEventRow
|
||||
conversation_id: String,
|
||||
kind: String,
|
||||
model: String,
|
||||
}
|
||||
|
||||
impl AssistantEventRow {
|
||||
fn from_event(
|
||||
event: AssistantEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
conversation_id: event.conversation_id.unwrap_or_default(),
|
||||
kind: event.kind.to_string(),
|
||||
model: event.model,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, clickhouse::Row, Serialize)]
|
||||
pub struct CpuEventRow {
|
||||
pub installation_id: Option<String>,
|
||||
pub is_staff: Option<bool>,
|
||||
pub usage_as_percentage: f32,
|
||||
pub core_count: u32,
|
||||
pub app_version: String,
|
||||
pub release_channel: String,
|
||||
pub time: i64,
|
||||
pub session_id: Option<String>,
|
||||
// pub normalized_cpu_usage: f64, MATERIALIZED
|
||||
pub major: Option<i32>,
|
||||
pub minor: Option<i32>,
|
||||
pub patch: Option<i32>,
|
||||
}
|
||||
|
||||
impl CpuEventRow {
|
||||
fn from_event(
|
||||
event: CpuEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
usage_as_percentage: event.usage_as_percentage,
|
||||
core_count: event.core_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct MemoryEventRow {
|
||||
// AppInfoBase
|
||||
app_version: String,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
release_channel: String,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
time: i64,
|
||||
|
||||
// MemoryEventRow
|
||||
memory_in_bytes: u64,
|
||||
virtual_memory_in_bytes: u64,
|
||||
}
|
||||
|
||||
impl MemoryEventRow {
|
||||
fn from_event(
|
||||
event: MemoryEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
memory_in_bytes: event.memory_in_bytes,
|
||||
virtual_memory_in_bytes: event.virtual_memory_in_bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct AppEventRow {
|
||||
// AppInfoBase
|
||||
app_version: String,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
release_channel: String,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
time: i64,
|
||||
|
||||
// AppEventRow
|
||||
operation: String,
|
||||
}
|
||||
|
||||
impl AppEventRow {
|
||||
fn from_event(
|
||||
event: AppEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
operation: event.operation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct SettingEventRow {
|
||||
// AppInfoBase
|
||||
app_version: String,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
release_channel: String,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: Option<String>,
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
time: i64,
|
||||
// SettingEventRow
|
||||
setting: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl SettingEventRow {
|
||||
fn from_event(
|
||||
event: SettingEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
setting: event.setting,
|
||||
value: event.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct EditEventRow {
|
||||
// AppInfoBase
|
||||
app_version: String,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
release_channel: String,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: Option<String>,
|
||||
// Note: This column name has a typo in the ClickHouse table.
|
||||
#[serde(rename = "sesssion_id")]
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
time: i64,
|
||||
|
||||
// EditEventRow
|
||||
period_start: i64,
|
||||
period_end: i64,
|
||||
environment: String,
|
||||
}
|
||||
|
||||
impl EditEventRow {
|
||||
fn from_event(
|
||||
event: EditEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
let period_start = time - chrono::Duration::milliseconds(event.duration);
|
||||
let period_end = time;
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
period_start: period_start.timestamp_millis(),
|
||||
period_end: period_end.timestamp_millis(),
|
||||
environment: event.environment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct ActionEventRow {
|
||||
// AppInfoBase
|
||||
app_version: String,
|
||||
major: Option<i32>,
|
||||
minor: Option<i32>,
|
||||
patch: Option<i32>,
|
||||
release_channel: String,
|
||||
|
||||
// ClientEventBase
|
||||
installation_id: Option<String>,
|
||||
// Note: This column name has a typo in the ClickHouse table.
|
||||
#[serde(rename = "sesssion_id")]
|
||||
session_id: Option<String>,
|
||||
is_staff: Option<bool>,
|
||||
time: i64,
|
||||
// ActionEventRow
|
||||
source: String,
|
||||
action: String,
|
||||
}
|
||||
|
||||
impl ActionEventRow {
|
||||
fn from_event(
|
||||
event: ActionEvent,
|
||||
wrapper: &EventWrapper,
|
||||
body: &EventRequestBody,
|
||||
first_event_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Self {
|
||||
let semver = body.semver();
|
||||
let time =
|
||||
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
|
||||
|
||||
Self {
|
||||
app_version: body.app_version.clone(),
|
||||
major: semver.map(|s| s.major as i32),
|
||||
minor: semver.map(|s| s.minor as i32),
|
||||
patch: semver.map(|s| s.patch as i32),
|
||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||
installation_id: body.installation_id.clone(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
source: event.source,
|
||||
action: event.action,
|
||||
}
|
||||
}
|
||||
}
|
||||
237
crates/collab/src/api/extensions.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
use crate::{
|
||||
db::{ExtensionMetadata, NewExtensionVersion},
|
||||
executor::Executor,
|
||||
AppState, Error, Result,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use aws_sdk_s3::presigning::PresigningConfig;
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
response::Redirect,
|
||||
routing::get,
|
||||
Extension, Json, Router,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use time::PrimitiveDateTime;
|
||||
use util::ResultExt;
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route("/extensions", get(get_extensions))
|
||||
.route(
|
||||
"/extensions/:extension_id/:version/download",
|
||||
get(download_extension),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GetExtensionsParams {
|
||||
filter: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DownloadExtensionParams {
|
||||
extension_id: String,
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct GetExtensionsResponse {
|
||||
pub data: Vec<ExtensionMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtensionManifest {
|
||||
name: String,
|
||||
version: String,
|
||||
description: Option<String>,
|
||||
authors: Vec<String>,
|
||||
repository: String,
|
||||
}
|
||||
|
||||
async fn get_extensions(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Query(params): Query<GetExtensionsParams>,
|
||||
) -> Result<Json<GetExtensionsResponse>> {
|
||||
let extensions = app.db.get_extensions(params.filter.as_deref(), 500).await?;
|
||||
Ok(Json(GetExtensionsResponse { data: extensions }))
|
||||
}
|
||||
|
||||
async fn download_extension(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Path(params): Path<DownloadExtensionParams>,
|
||||
) -> Result<Redirect> {
|
||||
let Some((blob_store_client, bucket)) = app
|
||||
.blob_store_client
|
||||
.clone()
|
||||
.zip(app.config.blob_store_bucket.clone())
|
||||
else {
|
||||
Err(Error::Http(
|
||||
StatusCode::NOT_IMPLEMENTED,
|
||||
"not supported".into(),
|
||||
))?
|
||||
};
|
||||
|
||||
let DownloadExtensionParams {
|
||||
extension_id,
|
||||
version,
|
||||
} = params;
|
||||
|
||||
let version_exists = app
|
||||
.db
|
||||
.record_extension_download(&extension_id, &version)
|
||||
.await?;
|
||||
|
||||
if !version_exists {
|
||||
Err(Error::Http(
|
||||
StatusCode::NOT_FOUND,
|
||||
"unknown extension version".into(),
|
||||
))?;
|
||||
}
|
||||
|
||||
let url = blob_store_client
|
||||
.get_object()
|
||||
.bucket(bucket)
|
||||
.key(format!(
|
||||
"extensions/{extension_id}/{version}/archive.tar.gz"
|
||||
))
|
||||
.presigned(PresigningConfig::expires_in(EXTENSION_DOWNLOAD_URL_LIFETIME).unwrap())
|
||||
.await
|
||||
.map_err(|e| anyhow!("failed to create presigned extension download url {e}"))?;
|
||||
|
||||
Ok(Redirect::temporary(url.uri()))
|
||||
}
|
||||
|
||||
const EXTENSION_FETCH_INTERVAL: Duration = Duration::from_secs(5 * 60);
|
||||
const EXTENSION_DOWNLOAD_URL_LIFETIME: Duration = Duration::from_secs(3 * 60);
|
||||
|
||||
pub fn fetch_extensions_from_blob_store_periodically(app_state: Arc<AppState>, executor: Executor) {
|
||||
let Some(blob_store_client) = app_state.blob_store_client.clone() else {
|
||||
log::info!("no blob store client");
|
||||
return;
|
||||
};
|
||||
let Some(blob_store_bucket) = app_state.config.blob_store_bucket.clone() else {
|
||||
log::info!("no blob store bucket");
|
||||
return;
|
||||
};
|
||||
|
||||
executor.spawn_detached({
|
||||
let executor = executor.clone();
|
||||
async move {
|
||||
loop {
|
||||
fetch_extensions_from_blob_store(
|
||||
&blob_store_client,
|
||||
&blob_store_bucket,
|
||||
&app_state,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
executor.sleep(EXTENSION_FETCH_INTERVAL).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn fetch_extensions_from_blob_store(
|
||||
blob_store_client: &aws_sdk_s3::Client,
|
||||
blob_store_bucket: &String,
|
||||
app_state: &Arc<AppState>,
|
||||
) -> anyhow::Result<()> {
|
||||
let list = blob_store_client
|
||||
.list_objects()
|
||||
.bucket(blob_store_bucket)
|
||||
.prefix("extensions/")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let objects = list
|
||||
.contents
|
||||
.ok_or_else(|| anyhow!("missing bucket contents"))?;
|
||||
|
||||
let mut published_versions = HashMap::<&str, Vec<&str>>::default();
|
||||
for object in &objects {
|
||||
let Some(key) = object.key.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let mut parts = key.split('/');
|
||||
let Some(_) = parts.next().filter(|part| *part == "extensions") else {
|
||||
continue;
|
||||
};
|
||||
let Some(extension_id) = parts.next() else {
|
||||
continue;
|
||||
};
|
||||
let Some(version) = parts.next() else {
|
||||
continue;
|
||||
};
|
||||
published_versions
|
||||
.entry(extension_id)
|
||||
.or_default()
|
||||
.push(version);
|
||||
}
|
||||
|
||||
let known_versions = app_state.db.get_known_extension_versions().await?;
|
||||
|
||||
let mut new_versions = HashMap::<&str, Vec<NewExtensionVersion>>::default();
|
||||
let empty = Vec::new();
|
||||
for (extension_id, published_versions) in published_versions {
|
||||
let known_versions = known_versions.get(extension_id).unwrap_or(&empty);
|
||||
|
||||
for published_version in published_versions {
|
||||
if known_versions
|
||||
.binary_search_by_key(&published_version, String::as_str)
|
||||
.is_err()
|
||||
{
|
||||
let object = blob_store_client
|
||||
.get_object()
|
||||
.bucket(blob_store_bucket)
|
||||
.key(format!(
|
||||
"extensions/{extension_id}/{published_version}/manifest.json"
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
let manifest_bytes = object
|
||||
.body
|
||||
.collect()
|
||||
.await
|
||||
.map(|data| data.into_bytes())
|
||||
.with_context(|| format!("failed to download manifest for extension {extension_id} version {published_version}"))?
|
||||
.to_vec();
|
||||
let manifest = serde_json::from_slice::<ExtensionManifest>(&manifest_bytes)
|
||||
.with_context(|| format!("invalid manifest for extension {extension_id} version {published_version}: {}", String::from_utf8_lossy(&manifest_bytes)))?;
|
||||
|
||||
let published_at = object.last_modified.ok_or_else(|| anyhow!("missing last modified timestamp for extension {extension_id} version {published_version}"))?;
|
||||
let published_at =
|
||||
time::OffsetDateTime::from_unix_timestamp_nanos(published_at.as_nanos())?;
|
||||
let published_at = PrimitiveDateTime::new(published_at.date(), published_at.time());
|
||||
|
||||
let version = semver::Version::parse(&manifest.version).with_context(|| {
|
||||
format!(
|
||||
"invalid version for extension {extension_id} version {published_version}"
|
||||
)
|
||||
})?;
|
||||
|
||||
new_versions
|
||||
.entry(extension_id)
|
||||
.or_default()
|
||||
.push(NewExtensionVersion {
|
||||
name: manifest.name,
|
||||
version,
|
||||
description: manifest.description.unwrap_or_default(),
|
||||
authors: manifest.authors,
|
||||
repository: manifest.repository,
|
||||
published_at,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app_state
|
||||
.db
|
||||
.insert_extension_versions(&new_versions)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -18,8 +18,8 @@ struct GitHubUser {
|
||||
async fn main() {
|
||||
load_dotenv().expect("failed to load .env.toml file");
|
||||
|
||||
let mut admin_logins =
|
||||
load_admins("./.admins.default.json").expect("failed to load default admins file");
|
||||
let mut admin_logins = load_admins("crates/collab/.admins.default.json")
|
||||
.expect("failed to load default admins file");
|
||||
if let Ok(other_admins) = load_admins("./.admins.json") {
|
||||
admin_logins.extend(other_admins);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use tests::TestDb;
|
||||
|
||||
mod ids;
|
||||
mod queries;
|
||||
mod tables;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use crate::{executor::Executor, Error, Result};
|
||||
use anyhow::anyhow;
|
||||
@@ -25,7 +21,7 @@ use sea_orm::{
|
||||
FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect, Statement,
|
||||
TransactionTrait,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{ser::Error as _, Deserialize, Serialize, Serializer};
|
||||
use sqlx::{
|
||||
migrate::{Migrate, Migration, MigrationSource},
|
||||
Connection,
|
||||
@@ -40,13 +36,17 @@ use std::{
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
pub use tables::*;
|
||||
use time::{format_description::well_known::iso8601, PrimitiveDateTime};
|
||||
use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
|
||||
#[cfg(test)]
|
||||
pub use tests::TestDb;
|
||||
|
||||
pub use ids::*;
|
||||
pub use queries::contributors::ContributorSelector;
|
||||
pub use sea_orm::ConnectOptions;
|
||||
pub use tables::user::Model as User;
|
||||
pub use tables::*;
|
||||
|
||||
/// Database gives you a handle that lets you access the database.
|
||||
/// It handles pooling internally.
|
||||
@@ -587,6 +587,7 @@ pub struct ChannelsForUser {
|
||||
pub channels: Vec<Channel>,
|
||||
pub channel_memberships: Vec<channel_member::Model>,
|
||||
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
||||
pub hosted_projects: Vec<proto::HostedProject>,
|
||||
|
||||
pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||
pub observed_channel_messages: Vec<proto::ChannelMessageId>,
|
||||
@@ -717,3 +718,43 @@ pub struct WorktreeSettingsFile {
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
pub struct NewExtensionVersion {
|
||||
pub name: String,
|
||||
pub version: semver::Version,
|
||||
pub description: String,
|
||||
pub authors: Vec<String>,
|
||||
pub repository: String,
|
||||
pub published_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq)]
|
||||
pub struct ExtensionMetadata {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub authors: Vec<String>,
|
||||
pub description: String,
|
||||
pub repository: String,
|
||||
#[serde(serialize_with = "serialize_iso8601")]
|
||||
pub published_at: PrimitiveDateTime,
|
||||
pub download_count: u64,
|
||||
}
|
||||
|
||||
pub fn serialize_iso8601<S: Serializer>(
|
||||
datetime: &PrimitiveDateTime,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
const SERDE_CONFIG: iso8601::EncodedConfig = iso8601::Config::DEFAULT
|
||||
.set_year_is_six_digits(false)
|
||||
.set_time_precision(iso8601::TimePrecision::Second {
|
||||
decimal_digits: None,
|
||||
})
|
||||
.encode();
|
||||
|
||||
datetime
|
||||
.assume_utc()
|
||||
.format(&time::format_description::well_known::Iso8601::<SERDE_CONFIG>)
|
||||
.map_err(S::Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
@@ -85,8 +85,10 @@ id_type!(SignupId);
|
||||
id_type!(UserId);
|
||||
id_type!(ChannelBufferCollaboratorId);
|
||||
id_type!(FlagId);
|
||||
id_type!(ExtensionId);
|
||||
id_type!(NotificationId);
|
||||
id_type!(NotificationKindId);
|
||||
id_type!(HostedProjectId);
|
||||
|
||||
/// ChannelRole gives you permissions for both channels and calls.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]
|
||||
@@ -99,8 +101,12 @@ pub enum ChannelRole {
|
||||
#[sea_orm(string_value = "member")]
|
||||
#[default]
|
||||
Member,
|
||||
/// Talker can read, but not write.
|
||||
/// They can use microphones and the channel chat
|
||||
#[sea_orm(string_value = "talker")]
|
||||
Talker,
|
||||
/// Guest can read, but not write.
|
||||
/// (thought they can use the channel chat)
|
||||
/// They can not use microphones but can use the chat.
|
||||
#[sea_orm(string_value = "guest")]
|
||||
Guest,
|
||||
/// Banned may not read.
|
||||
@@ -113,8 +119,9 @@ impl ChannelRole {
|
||||
pub fn should_override(&self, other: Self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin => matches!(other, Member | Banned | Guest),
|
||||
Member => matches!(other, Banned | Guest),
|
||||
Admin => matches!(other, Member | Banned | Talker | Guest),
|
||||
Member => matches!(other, Banned | Talker | Guest),
|
||||
Talker => matches!(other, Guest),
|
||||
Banned => matches!(other, Guest),
|
||||
Guest => false,
|
||||
}
|
||||
@@ -133,7 +140,7 @@ impl ChannelRole {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Guest => visibility == ChannelVisibility::Public,
|
||||
Guest | Talker => visibility == ChannelVisibility::Public,
|
||||
Banned => false,
|
||||
}
|
||||
}
|
||||
@@ -143,7 +150,7 @@ impl ChannelRole {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Guest | Banned => false,
|
||||
Guest | Talker | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,16 +158,16 @@ impl ChannelRole {
|
||||
pub fn can_only_see_public_descendants(&self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Guest => true,
|
||||
Guest | Talker => true,
|
||||
Admin | Member | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// True if the role can share screen/microphone/projects into rooms.
|
||||
pub fn can_publish_to_rooms(&self) -> bool {
|
||||
pub fn can_use_microphone(&self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Admin | Member | Talker => true,
|
||||
Guest | Banned => false,
|
||||
}
|
||||
}
|
||||
@@ -170,7 +177,7 @@ impl ChannelRole {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Guest | Banned => false,
|
||||
Talker | Guest | Banned => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +185,7 @@ impl ChannelRole {
|
||||
pub fn can_read_projects(&self) -> bool {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member | Guest => true,
|
||||
Admin | Member | Guest | Talker => true,
|
||||
Banned => false,
|
||||
}
|
||||
}
|
||||
@@ -187,7 +194,7 @@ impl ChannelRole {
|
||||
use ChannelRole::*;
|
||||
match self {
|
||||
Admin | Member => true,
|
||||
Banned | Guest => false,
|
||||
Banned | Guest | Talker => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,6 +204,7 @@ impl From<proto::ChannelRole> for ChannelRole {
|
||||
match value {
|
||||
proto::ChannelRole::Admin => ChannelRole::Admin,
|
||||
proto::ChannelRole::Member => ChannelRole::Member,
|
||||
proto::ChannelRole::Talker => ChannelRole::Talker,
|
||||
proto::ChannelRole::Guest => ChannelRole::Guest,
|
||||
proto::ChannelRole::Banned => ChannelRole::Banned,
|
||||
}
|
||||
@@ -208,6 +216,7 @@ impl Into<proto::ChannelRole> for ChannelRole {
|
||||
match self {
|
||||
ChannelRole::Admin => proto::ChannelRole::Admin,
|
||||
ChannelRole::Member => proto::ChannelRole::Member,
|
||||
ChannelRole::Talker => proto::ChannelRole::Talker,
|
||||
ChannelRole::Guest => proto::ChannelRole::Guest,
|
||||
ChannelRole::Banned => proto::ChannelRole::Banned,
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ pub mod buffers;
|
||||
pub mod channels;
|
||||
pub mod contacts;
|
||||
pub mod contributors;
|
||||
pub mod extensions;
|
||||
pub mod hosted_projects;
|
||||
pub mod messages;
|
||||
pub mod notifications;
|
||||
pub mod projects;
|
||||
|
||||
@@ -97,59 +97,12 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_in_channel_call(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
user_id: UserId,
|
||||
in_call: bool,
|
||||
) -> Result<(proto::Room, ChannelRole)> {
|
||||
self.transaction(move |tx| async move {
|
||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||
let role = self.channel_role_for_user(&channel, user_id, &*tx).await?;
|
||||
if role.is_none() || role == Some(ChannelRole::Banned) {
|
||||
Err(ErrorCode::Forbidden.anyhow())?
|
||||
}
|
||||
let role = role.unwrap();
|
||||
|
||||
let Some(room) = room::Entity::find()
|
||||
.filter(room::Column::ChannelId.eq(channel_id))
|
||||
.one(&*tx)
|
||||
.await?
|
||||
else {
|
||||
Err(anyhow!("no room exists"))?
|
||||
};
|
||||
|
||||
let result = room_participant::Entity::update_many()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(room_participant::Column::RoomId.eq(room.id))
|
||||
.add(room_participant::Column::UserId.eq(user_id)),
|
||||
)
|
||||
.set(room_participant::ActiveModel {
|
||||
in_call: ActiveValue::Set(in_call),
|
||||
..Default::default()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected != 1 {
|
||||
Err(anyhow!("not in channel"))?
|
||||
}
|
||||
|
||||
let room = self.get_room(room.id, &*tx).await?;
|
||||
Ok((room, role))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Adds a user to the specified channel.
|
||||
pub async fn join_channel(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
user_id: UserId,
|
||||
autojoin: bool,
|
||||
connection: ConnectionId,
|
||||
environment: &str,
|
||||
) -> Result<(JoinRoom, Option<MembershipUpdated>, ChannelRole)> {
|
||||
self.transaction(move |tx| async move {
|
||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||
@@ -209,10 +162,10 @@ impl Database {
|
||||
|
||||
let live_kit_room = format!("channel-{}", nanoid::nanoid!(30));
|
||||
let room_id = self
|
||||
.get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx)
|
||||
.get_or_create_channel_room(channel_id, &live_kit_room, &*tx)
|
||||
.await?;
|
||||
|
||||
self.join_channel_room_internal(room_id, user_id, autojoin, connection, role, &*tx)
|
||||
self.join_channel_room_internal(room_id, user_id, connection, role, &*tx)
|
||||
.await
|
||||
.map(|jr| (jr, accept_invite_result, role))
|
||||
})
|
||||
@@ -699,9 +652,14 @@ impl Database {
|
||||
.observed_channel_messages(&channel_ids, user_id, &*tx)
|
||||
.await?;
|
||||
|
||||
let hosted_projects = self
|
||||
.get_hosted_projects(&channel_ids, &roles_by_channel_id, &*tx)
|
||||
.await?;
|
||||
|
||||
Ok(ChannelsForUser {
|
||||
channel_memberships,
|
||||
channels,
|
||||
hosted_projects,
|
||||
channel_participants,
|
||||
latest_buffer_versions,
|
||||
latest_channel_messages,
|
||||
@@ -842,6 +800,7 @@ impl Database {
|
||||
match role {
|
||||
Some(ChannelRole::Admin) => Ok(role.unwrap()),
|
||||
Some(ChannelRole::Member)
|
||||
| Some(ChannelRole::Talker)
|
||||
| Some(ChannelRole::Banned)
|
||||
| Some(ChannelRole::Guest)
|
||||
| None => Err(anyhow!(
|
||||
@@ -860,7 +819,10 @@ impl Database {
|
||||
let channel_role = self.channel_role_for_user(channel, user_id, tx).await?;
|
||||
match channel_role {
|
||||
Some(ChannelRole::Admin) | Some(ChannelRole::Member) => Ok(channel_role.unwrap()),
|
||||
Some(ChannelRole::Banned) | Some(ChannelRole::Guest) | None => Err(anyhow!(
|
||||
Some(ChannelRole::Banned)
|
||||
| Some(ChannelRole::Guest)
|
||||
| Some(ChannelRole::Talker)
|
||||
| None => Err(anyhow!(
|
||||
"user is not a channel member or channel does not exist"
|
||||
))?,
|
||||
}
|
||||
@@ -875,9 +837,10 @@ impl Database {
|
||||
) -> Result<ChannelRole> {
|
||||
let role = self.channel_role_for_user(channel, user_id, tx).await?;
|
||||
match role {
|
||||
Some(ChannelRole::Admin) | Some(ChannelRole::Member) | Some(ChannelRole::Guest) => {
|
||||
Ok(role.unwrap())
|
||||
}
|
||||
Some(ChannelRole::Admin)
|
||||
| Some(ChannelRole::Member)
|
||||
| Some(ChannelRole::Guest)
|
||||
| Some(ChannelRole::Talker) => Ok(role.unwrap()),
|
||||
Some(ChannelRole::Banned) | None => Err(anyhow!(
|
||||
"user is not a channel participant or channel does not exist"
|
||||
))?,
|
||||
@@ -979,7 +942,6 @@ impl Database {
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
live_kit_room: &str,
|
||||
environment: &str,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<RoomId> {
|
||||
let room = room::Entity::find()
|
||||
@@ -988,19 +950,11 @@ impl Database {
|
||||
.await?;
|
||||
|
||||
let room_id = if let Some(room) = room {
|
||||
if let Some(env) = room.environment {
|
||||
if &env != environment {
|
||||
Err(ErrorCode::WrongReleaseChannel
|
||||
.with_tag("required", &env)
|
||||
.anyhow())?;
|
||||
}
|
||||
}
|
||||
room.id
|
||||
} else {
|
||||
let result = room::Entity::insert(room::ActiveModel {
|
||||
channel_id: ActiveValue::Set(Some(channel_id)),
|
||||
live_kit_room: ActiveValue::Set(live_kit_room.to_string()),
|
||||
environment: ActiveValue::Set(Some(environment.to_string())),
|
||||
..Default::default()
|
||||
})
|
||||
.exec(&*tx)
|
||||
|
||||
206
crates/collab/src/db/queries/extensions.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use super::*;
|
||||
|
||||
impl Database {
|
||||
pub async fn get_extensions(
|
||||
&self,
|
||||
filter: Option<&str>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<ExtensionMetadata>> {
|
||||
self.transaction(|tx| async move {
|
||||
let mut condition = Condition::all();
|
||||
if let Some(filter) = filter {
|
||||
let fuzzy_name_filter = Self::fuzzy_like_string(filter);
|
||||
condition = condition.add(Expr::cust_with_expr("name ILIKE $1", fuzzy_name_filter));
|
||||
}
|
||||
|
||||
let extensions = extension::Entity::find()
|
||||
.filter(condition)
|
||||
.order_by_desc(extension::Column::TotalDownloadCount)
|
||||
.order_by_asc(extension::Column::Name)
|
||||
.limit(Some(limit as u64))
|
||||
.filter(
|
||||
extension::Column::LatestVersion
|
||||
.into_expr()
|
||||
.eq(extension_version::Column::Version.into_expr()),
|
||||
)
|
||||
.inner_join(extension_version::Entity)
|
||||
.select_also(extension_version::Entity)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(extensions
|
||||
.into_iter()
|
||||
.filter_map(|(extension, latest_version)| {
|
||||
let version = latest_version?;
|
||||
Some(ExtensionMetadata {
|
||||
id: extension.external_id,
|
||||
name: extension.name,
|
||||
version: version.version,
|
||||
authors: version
|
||||
.authors
|
||||
.split(',')
|
||||
.map(|author| author.trim().to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
description: version.description,
|
||||
repository: version.repository,
|
||||
published_at: version.published_at,
|
||||
download_count: extension.total_download_count as u64,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_known_extension_versions<'a>(&self) -> Result<HashMap<String, Vec<String>>> {
|
||||
self.transaction(|tx| async move {
|
||||
let mut extension_external_ids_by_id = HashMap::default();
|
||||
|
||||
let mut rows = extension::Entity::find().stream(&*tx).await?;
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
extension_external_ids_by_id.insert(row.id, row.external_id);
|
||||
}
|
||||
drop(rows);
|
||||
|
||||
let mut known_versions_by_extension_id: HashMap<String, Vec<String>> =
|
||||
HashMap::default();
|
||||
let mut rows = extension_version::Entity::find().stream(&*tx).await?;
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
|
||||
let Some(extension_id) = extension_external_ids_by_id.get(&row.extension_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let versions = known_versions_by_extension_id
|
||||
.entry(extension_id.clone())
|
||||
.or_default();
|
||||
if let Err(ix) = versions.binary_search(&row.version) {
|
||||
versions.insert(ix, row.version);
|
||||
}
|
||||
}
|
||||
drop(rows);
|
||||
|
||||
Ok(known_versions_by_extension_id)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn insert_extension_versions(
|
||||
&self,
|
||||
versions_by_extension_id: &HashMap<&str, Vec<NewExtensionVersion>>,
|
||||
) -> Result<()> {
|
||||
self.transaction(|tx| async move {
|
||||
for (external_id, versions) in versions_by_extension_id {
|
||||
if versions.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let latest_version = versions
|
||||
.iter()
|
||||
.max_by_key(|version| &version.version)
|
||||
.unwrap();
|
||||
|
||||
let insert = extension::Entity::insert(extension::ActiveModel {
|
||||
name: ActiveValue::Set(latest_version.name.clone()),
|
||||
external_id: ActiveValue::Set(external_id.to_string()),
|
||||
id: ActiveValue::NotSet,
|
||||
latest_version: ActiveValue::Set(latest_version.version.to_string()),
|
||||
total_download_count: ActiveValue::NotSet,
|
||||
})
|
||||
.on_conflict(
|
||||
OnConflict::columns([extension::Column::ExternalId])
|
||||
.update_column(extension::Column::ExternalId)
|
||||
.to_owned(),
|
||||
);
|
||||
|
||||
let extension = if tx.support_returning() {
|
||||
insert.exec_with_returning(&*tx).await?
|
||||
} else {
|
||||
// Sqlite
|
||||
insert.exec_without_returning(&*tx).await?;
|
||||
extension::Entity::find()
|
||||
.filter(extension::Column::ExternalId.eq(*external_id))
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("failed to insert extension"))?
|
||||
};
|
||||
|
||||
extension_version::Entity::insert_many(versions.iter().map(|version| {
|
||||
extension_version::ActiveModel {
|
||||
extension_id: ActiveValue::Set(extension.id),
|
||||
published_at: ActiveValue::Set(version.published_at),
|
||||
version: ActiveValue::Set(version.version.to_string()),
|
||||
authors: ActiveValue::Set(version.authors.join(", ")),
|
||||
repository: ActiveValue::Set(version.repository.clone()),
|
||||
description: ActiveValue::Set(version.description.clone()),
|
||||
download_count: ActiveValue::NotSet,
|
||||
}
|
||||
}))
|
||||
.on_conflict(OnConflict::new().do_nothing().to_owned())
|
||||
.exec_without_returning(&*tx)
|
||||
.await?;
|
||||
|
||||
if let Ok(db_version) = semver::Version::parse(&extension.latest_version) {
|
||||
if db_version >= latest_version.version {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut extension = extension.into_active_model();
|
||||
extension.latest_version = ActiveValue::Set(latest_version.version.to_string());
|
||||
extension.name = ActiveValue::set(latest_version.name.clone());
|
||||
extension::Entity::update(extension).exec(&*tx).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn record_extension_download(&self, extension: &str, version: &str) -> Result<bool> {
|
||||
self.transaction(|tx| async move {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryId {
|
||||
Id,
|
||||
}
|
||||
|
||||
let extension_id: Option<ExtensionId> = extension::Entity::find()
|
||||
.filter(extension::Column::ExternalId.eq(extension))
|
||||
.select_only()
|
||||
.column(extension::Column::Id)
|
||||
.into_values::<_, QueryId>()
|
||||
.one(&*tx)
|
||||
.await?;
|
||||
let Some(extension_id) = extension_id else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
extension_version::Entity::update_many()
|
||||
.col_expr(
|
||||
extension_version::Column::DownloadCount,
|
||||
extension_version::Column::DownloadCount.into_expr().add(1),
|
||||
)
|
||||
.filter(
|
||||
extension_version::Column::ExtensionId
|
||||
.eq(extension_id)
|
||||
.and(extension_version::Column::Version.eq(version)),
|
||||
)
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
extension::Entity::update_many()
|
||||
.col_expr(
|
||||
extension::Column::TotalDownloadCount,
|
||||
extension::Column::TotalDownloadCount.into_expr().add(1),
|
||||
)
|
||||
.filter(extension::Column::Id.eq(extension_id))
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(true)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||