Compare commits
345 Commits
collab-v0.
...
v0.123.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bad0ac6436 | ||
|
|
4ecfb2f816 | ||
|
|
fcd41af5d5 | ||
|
|
406d02174a | ||
|
|
5e5e3ecd08 | ||
|
|
2441bc5709 | ||
|
|
33900345b0 | ||
|
|
cd13d353c9 | ||
|
|
37298579b6 | ||
|
|
19823f2015 | ||
|
|
fd5ea30be3 | ||
|
|
a441d97cb1 | ||
|
|
734a293899 | ||
|
|
6e254d9b58 | ||
|
|
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 | ||
|
|
48a4cbad37 | ||
|
|
266988adea | ||
|
|
2e7db57e16 | ||
|
|
1c2081c10c | ||
|
|
a23313928b | ||
|
|
9e17018416 | ||
|
|
d959719f3e | ||
|
|
0fb6b32bc3 | ||
|
|
2880135037 | ||
|
|
664a195721 | ||
|
|
bd882c66d6 | ||
|
|
a159183f52 | ||
|
|
b4b59f8706 | ||
|
|
4f5fea5dba | ||
|
|
67839a967b | ||
|
|
2f3ad9da4c | ||
|
|
68893c2ae6 | ||
|
|
e2a3e89318 | ||
|
|
0304edd8ab | ||
|
|
36b89571e9 | ||
|
|
3635d2dced | ||
|
|
efe23ebfcd | ||
|
|
2b39a9512a | ||
|
|
2b383b854a | ||
|
|
862a9512b5 | ||
|
|
5175c8963a | ||
|
|
eee00c3fef | ||
|
|
2651037472 | ||
|
|
93ceb89c0c | ||
|
|
ad97e447f5 | ||
|
|
f3bfa11148 | ||
|
|
775bce3e1a | ||
|
|
25c4cfe1d0 | ||
|
|
702393af59 | ||
|
|
901279a044 | ||
|
|
0cebf68306 | ||
|
|
04e1641a29 | ||
|
|
f4d7b3e3b1 | ||
|
|
67d280b8cb | ||
|
|
0ab1094f0c | ||
|
|
91c699aeaa | ||
|
|
07891b4978 | ||
|
|
ed54665711 | ||
|
|
1da5241ef7 | ||
|
|
ad88e9754e | ||
|
|
e7fcddff69 | ||
|
|
bde509fa74 | ||
|
|
67b96b2b40 | ||
|
|
9e538e7916 | ||
|
|
d4be15b2b2 | ||
|
|
8f7d7863d6 | ||
|
|
1ba42f69ee | ||
|
|
b77d452b90 | ||
|
|
b25044393e | ||
|
|
17c203fef9 | ||
|
|
006e7a77d5 | ||
|
|
4048dbfafd | ||
|
|
9f4ce7fba5 | ||
|
|
bd390aabf4 | ||
|
|
a0b2614d57 | ||
|
|
89b1e76003 | ||
|
|
f734365b7b | ||
|
|
d457eef099 | ||
|
|
00024b791b | ||
|
|
73498f388a | ||
|
|
61b8d3639f | ||
|
|
cbe7a12e65 | ||
|
|
ccc6d76708 | ||
|
|
7b03e977e4 | ||
|
|
f2a4dbaf7f | ||
|
|
219ec91748 | ||
|
|
6cdd7796c3 | ||
|
|
f55aba51ec | ||
|
|
374c8a4c8c | ||
|
|
45cf36e870 | ||
|
|
e6dad23154 | ||
|
|
2f4bb79553 | ||
|
|
31d9edfaaa | ||
|
|
eaadf56db9 | ||
|
|
5ded86543b | ||
|
|
3a53db6502 | ||
|
|
6edeea7c8a | ||
|
|
ef8cab65b0 | ||
|
|
be455f7f28 | ||
|
|
f507698c62 | ||
|
|
67555ee5b4 | ||
|
|
6b598a07d9 | ||
|
|
3734a390b2 | ||
|
|
c1ada087b4 | ||
|
|
07fce7aae1 | ||
|
|
114889b8bb | ||
|
|
83cffdde1f | ||
|
|
c322179bb9 | ||
|
|
b59e110c59 | ||
|
|
42a5081aff | ||
|
|
c7b022144f | ||
|
|
ad3940c66f | ||
|
|
55129d4d6c | ||
|
|
5c8073d344 | ||
|
|
db39b9dadc | ||
|
|
2aa8ccd6b1 | ||
|
|
e3ae7c4fe0 | ||
|
|
eb236302c2 | ||
|
|
7939673a7d | ||
|
|
d3562d4c9c | ||
|
|
9fd221271a | ||
|
|
3aa4e0c90b | ||
|
|
90cd3b5e87 | ||
|
|
1264e36429 | ||
|
|
4e519e3af7 | ||
|
|
6c4b96ec76 | ||
|
|
743f9b345f | ||
|
|
70e7ea365c | ||
|
|
792c832205 | ||
|
|
0b2a9d2bea | ||
|
|
960eaf6245 | ||
|
|
068c141559 | ||
|
|
33d982b08a | ||
|
|
56f7f18033 | ||
|
|
b2ce515593 | ||
|
|
dad2df365c | ||
|
|
1446fb7632 | ||
|
|
c591681bad | ||
|
|
038d2a84e8 | ||
|
|
11964dc731 | ||
|
|
7509677003 | ||
|
|
78f32f30f5 | ||
|
|
7721b55808 | ||
|
|
c7c4166724 | ||
|
|
d04a286634 | ||
|
|
1a40c9f0f2 | ||
|
|
0c34bd8935 | ||
|
|
a80a3b8706 | ||
|
|
b59f925933 | ||
|
|
46464ebe87 | ||
|
|
28a62affe4 | ||
|
|
6863b9263e | ||
|
|
21797bad4d | ||
|
|
ce62404e24 | ||
|
|
8911e1b365 | ||
|
|
2ed45d72d8 | ||
|
|
91303a5021 | ||
|
|
e2e8e52ec4 | ||
|
|
4195f27964 | ||
|
|
583ce44359 | ||
|
|
47329f4489 | ||
|
|
223f45c65b | ||
|
|
d742b3bfac | ||
|
|
3bf412feff | ||
|
|
8030e8cf47 | ||
|
|
45429a4528 | ||
|
|
0755ce6486 | ||
|
|
87d3f59515 | ||
|
|
81bfa5fac4 | ||
|
|
fde159fea1 | ||
|
|
521b2b12e4 | ||
|
|
282cc71df9 | ||
|
|
8d339ede1c | ||
|
|
d03f3b9f18 | ||
|
|
1c410c1b99 | ||
|
|
f92be4b817 | ||
|
|
b13b5726c5 | ||
|
|
224fe13f9f | ||
|
|
0a5ebee9e5 | ||
|
|
26ca798707 | ||
|
|
61fa5e93a8 | ||
|
|
aae532987f | ||
|
|
13ba8b6b54 | ||
|
|
d6bbcf503b | ||
|
|
c5ff46e14f | ||
|
|
cf71fe8bf1 | ||
|
|
d0a0ce1885 | ||
|
|
ac74a72a9e | ||
|
|
ae2c23bd8e | ||
|
|
59642bf29a | ||
|
|
8da6e62914 | ||
|
|
55185c159b | ||
|
|
c9a53b63a7 | ||
|
|
1ab0af2fa3 | ||
|
|
06674a21f9 | ||
|
|
54aecd21ec | ||
|
|
c906fd232d | ||
|
|
d08d4174a5 | ||
|
|
2e32f5867e | ||
|
|
7c7aad5e76 | ||
|
|
04e49dc493 | ||
|
|
1a82470897 | ||
|
|
583273b6ee | ||
|
|
fcbc220408 | ||
|
|
f09da1a1c8 | ||
|
|
e1efa7298e | ||
|
|
430f5d5d53 | ||
|
|
15edc46827 | ||
|
|
f2ba969d5b | ||
|
|
2d58226a9b | ||
|
|
115f0672fb | ||
|
|
1f6bd6760f | ||
|
|
a0b52cc69a | ||
|
|
5360c0ea28 | ||
|
|
3995c22414 | ||
|
|
659423a4a1 | ||
|
|
074acacdf7 | ||
|
|
6f6cb53fad | ||
|
|
3d76ed96f5 | ||
|
|
cf4f3ed79a | ||
|
|
bacb2a266a | ||
|
|
33e5ba6278 | ||
|
|
11bd28870a | ||
|
|
01ddf840f5 | ||
|
|
ec9f44727e | ||
|
|
998f6cf80d | ||
|
|
980d4f1003 | ||
|
|
79c1003b34 | ||
|
|
d576cda789 | ||
|
|
10cd978e93 | ||
|
|
ce4c15dca6 | ||
|
|
2940a0ebd8 | ||
|
|
8fed9aaec2 | ||
|
|
c000d2e16b | ||
|
|
5ed3b44686 | ||
|
|
fdaffdbfff | ||
|
|
05c42211fe | ||
|
|
ed679c9347 | ||
|
|
ce84a2a671 | ||
|
|
c9ec337034 | ||
|
|
666b134d20 | ||
|
|
ecf4955899 | ||
|
|
8aa768765f | ||
|
|
503ac7a251 | ||
|
|
74fde5967b | ||
|
|
7f8c64aa6c | ||
|
|
aed363d3c7 | ||
|
|
cefc98258f | ||
|
|
e95bf24a1f | ||
|
|
b0376aaf8f | ||
|
|
ca62d22147 | ||
|
|
d675abf70c | ||
|
|
ef4ef5f0e8 | ||
|
|
69e0ea92e4 | ||
|
|
b35a7223b6 | ||
|
|
020c38a891 | ||
|
|
21f4da6bf2 | ||
|
|
da44f637ed | ||
|
|
0897c8eebd | ||
|
|
7b9d51929d | ||
|
|
5424c8bfd5 | ||
|
|
3521b50405 | ||
|
|
d4264cbe4e | ||
|
|
97be0a930c | ||
|
|
3107ed847a | ||
|
|
944a1f8fb0 | ||
|
|
47a1ff7df9 | ||
|
|
b9d5eb17a3 | ||
|
|
adc7cfb0d3 | ||
|
|
a853a80634 | ||
|
|
2d41a119b3 | ||
|
|
0102ffbfca | ||
|
|
0edffd9248 | ||
|
|
e65a76f0ec | ||
|
|
6c93c4bd35 | ||
|
|
8bafc61ef5 | ||
|
|
3b882918f7 | ||
|
|
5e64d45194 | ||
|
|
3df7da236d | ||
|
|
5e81d780bd | ||
|
|
cbc2746d70 | ||
|
|
aaba98d8ec | ||
|
|
2cc2a61c77 | ||
|
|
3025e5620d | ||
|
|
c4083c3cf6 | ||
|
|
2187513026 | ||
|
|
5b7b5bfea5 | ||
|
|
a588a7dd4d | ||
|
|
dcca48482b | ||
|
|
c983c9b6df | ||
|
|
f98d636203 | ||
|
|
5333eff0e4 | ||
|
|
135bca262c | ||
|
|
5d85801d1f | ||
|
|
ebdabb907a | ||
|
|
689d43047d | ||
|
|
59f77d316f | ||
|
|
b7ced3943e | ||
|
|
39200ec9f7 | ||
|
|
6e443ac298 | ||
|
|
5941102aac | ||
|
|
8c8a5ad275 | ||
|
|
7cb97e57f9 | ||
|
|
634fe99fa5 | ||
|
|
c3d4fa4336 | ||
|
|
ba91adf48a | ||
|
|
e5fe811d7a | ||
|
|
9459394ea0 | ||
|
|
db99d4fef1 | ||
|
|
aee0f65b26 | ||
|
|
dbb5fad147 | ||
|
|
28f875f5f9 | ||
|
|
e338f34097 | ||
|
|
d97e780135 | ||
|
|
176f63e86e | ||
|
|
cbcaca4153 | ||
|
|
871b8525b4 | ||
|
|
a5826e22f5 | ||
|
|
7f66e366b4 |
35
.github/actions/check_style/action.yml
vendored
@@ -2,29 +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: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
# clippy.toml is not currently supporting specifying allowed lints
|
||||
# so specify those here, and disable the rest until Zed's workspace
|
||||
# will have more fixes & suppression for the standard lint set
|
||||
run: |
|
||||
cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo
|
||||
cargo clippy -p gpui
|
||||
|
||||
- name: Find modified migrations
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
|
||||
. ./script/squawk
|
||||
|
||||
- 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=main,subdir=crates/rpc/proto/"
|
||||
- name: Find modified migrations
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
|
||||
. ./script/squawk
|
||||
|
||||
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
||||
cargo install cargo-nextest
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
6
.github/pull_request_template.md
vendored
@@ -2,4 +2,8 @@
|
||||
|
||||
Release Notes:
|
||||
|
||||
- (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/zed/issues/<public_issue_number_if_exists>)).
|
||||
- Added/Fixed/Improved ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/zed/issues/<public_issue_number_if_exists>)).
|
||||
|
||||
**or**
|
||||
|
||||
- N/A
|
||||
|
||||
304
.github/workflows/ci.yml
vendored
@@ -1,148 +1,206 @@
|
||||
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, Clippy lints, 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: Set up default .cargo/config.toml
|
||||
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
||||
|
||||
- name: Check spelling
|
||||
run: |
|
||||
if ! which typos > /dev/null; then
|
||||
cargo install typos-cli
|
||||
fi
|
||||
typos
|
||||
- name: Check spelling
|
||||
run: |
|
||||
if ! which typos > /dev/null; then
|
||||
cargo install typos-cli
|
||||
fi
|
||||
typos
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
tests:
|
||||
name: Run 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: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
- 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: Build collab
|
||||
run: cargo build -p collab
|
||||
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 other binaries
|
||||
run: cargo build --workspace --bins --all-features
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
|
||||
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: 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 }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18"
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
- name: Build collab
|
||||
run: cargo build -p collab
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
- name: Build other binaries
|
||||
run: cargo build --workspace --bins --all-features
|
||||
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
set -eu
|
||||
# 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"
|
||||
|
||||
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
|
||||
- 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') }}-
|
||||
|
||||
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: configure linux
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/linux
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
- name: cargo clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
|
||||
- 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: ""
|
||||
- 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:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
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:
|
||||
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@v2.2.4
|
||||
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 }}
|
||||
|
||||
111
.github/workflows/deploy_collab.yml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
name: Publish Collab Server Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- collab-production
|
||||
- collab-staging
|
||||
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
|
||||
jobs:
|
||||
style:
|
||||
name: Check formatting and Clippy lints
|
||||
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 clippy
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: script/clippy
|
||||
|
||||
tests:
|
||||
name: Run tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
needs: style
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
publish:
|
||||
name: Publish collab server image
|
||||
needs:
|
||||
- style
|
||||
- tests
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- 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:
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Build docker image
|
||||
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
|
||||
|
||||
- name: Publish docker image
|
||||
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}
|
||||
|
||||
deploy:
|
||||
name: Deploy new server image
|
||||
needs:
|
||||
- publish
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
|
||||
steps:
|
||||
- name: Sign into Kubernetes
|
||||
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 ${{ secrets.CLUSTER_NAME }}
|
||||
|
||||
- name: Determine namespace
|
||||
run: |
|
||||
set -eu
|
||||
if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
|
||||
echo "Deploying collab:$GITHUB_SHA to production"
|
||||
echo "KUBE_NAMESPACE=production" >> $GITHUB_ENV
|
||||
elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
|
||||
echo "Deploying collab:$GITHUB_SHA to staging"
|
||||
echo "KUBE_NAMESPACE=staging" >> $GITHUB_ENV
|
||||
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}
|
||||
|
||||
- name: Wait for rollout to finish
|
||||
run: kubectl -n "$KUBE_NAMESPACE" rollout status deployment/collab
|
||||
49
.github/workflows/publish_collab_image.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: Publish Collab Server Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- collab-v*
|
||||
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish collab server image
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Sign into DigitalOcean docker registry
|
||||
run: doctl registry login
|
||||
|
||||
- name: Prune Docker system
|
||||
run: docker system prune
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Determine version
|
||||
run: |
|
||||
set -eu
|
||||
version=$(script/get-crate-version collab)
|
||||
if [[ $GITHUB_REF_NAME != "collab-v${version}" ]]; then
|
||||
echo "release tag ${GITHUB_REF_NAME} does not match version ${version}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Publishing collab version: ${version}"
|
||||
echo "COLLAB_VERSION=${version}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build docker image
|
||||
run: docker build . --tag registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
|
||||
|
||||
- name: Publish docker image
|
||||
run: docker push registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
|
||||
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@v3
|
||||
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
|
||||
|
||||
9
.github/workflows/release_nightly.yml
vendored
@@ -16,6 +16,7 @@ env:
|
||||
jobs:
|
||||
style:
|
||||
name: Check formatting and Clippy lints
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
@@ -30,8 +31,12 @@ jobs:
|
||||
- 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
|
||||
@@ -48,6 +53,7 @@ jobs:
|
||||
|
||||
bundle:
|
||||
name: Bundle app
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
@@ -59,9 +65,10 @@ jobs:
|
||||
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@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
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
|
||||
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
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
|
||||
|
||||
4
.mailmap
@@ -13,6 +13,8 @@ Antonio Scandurra <me@as-cii.com>
|
||||
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
|
||||
Conrad Irwin <conrad@zed.dev>
|
||||
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
||||
Greg Morenz <greg-morenz@droid.cafe>
|
||||
Greg Morenz <greg-morenz@droid.cafe> <morenzg@gmail.com>
|
||||
Joseph T. Lyons <JosephTLyons@gmail.com>
|
||||
Joseph T. Lyons <JosephTLyons@gmail.com> <JosephTLyons@users.noreply.github.com>
|
||||
Julia <floc@unpromptedtirade.com>
|
||||
@@ -39,6 +41,8 @@ Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
|
||||
Nathan Sobo <nathan@zed.dev> <nathansobo@gmail.com>
|
||||
Piotr Osiewicz <piotr@zed.dev>
|
||||
Piotr Osiewicz <piotr@zed.dev> <24362066+osiewicz@users.noreply.github.com>
|
||||
Robert Clover <git@clo4.net>
|
||||
Robert Clover <git@clo4.net> <robert@clover.gdn>
|
||||
Thorsten Ball <thorsten@zed.dev>
|
||||
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.com>
|
||||
Thorsten Ball <thorsten@zed.dev> <mrnugget@gmail.com>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"JSON": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"formatter": "auto"
|
||||
"JSON": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"formatter": "auto"
|
||||
}
|
||||
|
||||
1217
Cargo.lock
generated
227
Cargo.toml
@@ -1,8 +1,8 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/assets",
|
||||
"crates/activity_indicator",
|
||||
"crates/ai",
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
"crates/audio",
|
||||
"crates/auto_update",
|
||||
@@ -19,10 +19,10 @@ members = [
|
||||
"crates/copilot",
|
||||
"crates/copilot_ui",
|
||||
"crates/db",
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/diagnostics",
|
||||
"crates/editor",
|
||||
"crates/extension",
|
||||
"crates/extensions_ui",
|
||||
"crates/feature_flags",
|
||||
"crates/feedback",
|
||||
"crates/file_finder",
|
||||
@@ -33,17 +33,15 @@ members = [
|
||||
"crates/go_to_line",
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/language_selector",
|
||||
"crates/language_tools",
|
||||
"crates/live_kit_client",
|
||||
"crates/live_kit_server",
|
||||
"crates/lsp",
|
||||
"crates/markdown_preview",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/multi_buffer",
|
||||
@@ -59,7 +57,10 @@ members = [
|
||||
"crates/project_symbols",
|
||||
"crates/quick_action_bar",
|
||||
"crates/recent_projects",
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/release_channel",
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/search",
|
||||
@@ -68,7 +69,7 @@ members = [
|
||||
"crates/snippet",
|
||||
"crates/sqlez",
|
||||
"crates/sqlez_macros",
|
||||
"crates/rich_text",
|
||||
"crates/story",
|
||||
"crates/storybook",
|
||||
"crates/sum_tree",
|
||||
"crates/terminal",
|
||||
@@ -79,11 +80,10 @@ members = [
|
||||
"crates/theme_selector",
|
||||
"crates/ui",
|
||||
"crates/util",
|
||||
"crates/story",
|
||||
"crates/vim",
|
||||
"crates/vcs_menu",
|
||||
"crates/workspace",
|
||||
"crates/vim",
|
||||
"crates/welcome",
|
||||
"crates/workspace",
|
||||
"crates/zed",
|
||||
"crates/zed_actions",
|
||||
]
|
||||
@@ -91,92 +91,205 @@ default-members = ["crates/zed"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.57" }
|
||||
async-trait = { version = "0.1" }
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
ai = { path = "crates/ai" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
audio = { path = "crates/audio" }
|
||||
auto_update = { path = "crates/auto_update" }
|
||||
breadcrumbs = { path = "crates/breadcrumbs" }
|
||||
call = { path = "crates/call" }
|
||||
channel = { path = "crates/channel" }
|
||||
cli = { path = "crates/cli" }
|
||||
client = { path = "crates/client" }
|
||||
clock = { path = "crates/clock" }
|
||||
collab = { path = "crates/collab" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
color = { path = "crates/color" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
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" }
|
||||
fs = { path = "crates/fs" }
|
||||
fsevent = { path = "crates/fsevent" }
|
||||
fuzzy = { path = "crates/fuzzy" }
|
||||
git = { path = "crates/git" }
|
||||
go_to_line = { path = "crates/go_to_line" }
|
||||
gpui = { path = "crates/gpui" }
|
||||
gpui_macros = { path = "crates/gpui_macros" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
language_tools = { path = "crates/language_tools" }
|
||||
live_kit_client = { path = "crates/live_kit_client" }
|
||||
live_kit_server = { path = "crates/live_kit_server" }
|
||||
lsp = { path = "crates/lsp" }
|
||||
markdown_preview = { path = "crates/markdown_preview" }
|
||||
media = { path = "crates/media" }
|
||||
menu = { path = "crates/menu" }
|
||||
multi_buffer = { path = "crates/multi_buffer" }
|
||||
node_runtime = { path = "crates/node_runtime" }
|
||||
notifications = { path = "crates/notifications" }
|
||||
outline = { path = "crates/outline" }
|
||||
picker = { path = "crates/picker" }
|
||||
plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
quick_action_bar = { path = "crates/quick_action_bar" }
|
||||
recent_projects = { path = "crates/recent_projects" }
|
||||
release_channel = { path = "crates/release_channel" }
|
||||
rich_text = { path = "crates/rich_text" }
|
||||
rope = { path = "crates/rope" }
|
||||
rpc = { path = "crates/rpc" }
|
||||
search = { path = "crates/search" }
|
||||
semantic_index = { path = "crates/semantic_index" }
|
||||
settings = { path = "crates/settings" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
sqlez = { path = "crates/sqlez" }
|
||||
sqlez_macros = { path = "crates/sqlez_macros" }
|
||||
story = { path = "crates/story" }
|
||||
storybook = { path = "crates/storybook" }
|
||||
sum_tree = { path = "crates/sum_tree" }
|
||||
terminal = { path = "crates/terminal" }
|
||||
terminal_view = { path = "crates/terminal_view" }
|
||||
text = { path = "crates/text" }
|
||||
theme = { path = "crates/theme" }
|
||||
theme_importer = { path = "crates/theme_importer" }
|
||||
theme_selector = { path = "crates/theme_selector" }
|
||||
ui = { path = "crates/ui" }
|
||||
util = { path = "crates/util" }
|
||||
vcs_menu = { path = "crates/vcs_menu" }
|
||||
vim = { path = "crates/vim" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
workspace = { path = "crates/workspace" }
|
||||
zed = { path = "crates/zed" }
|
||||
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"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
ctor = "0.2.6"
|
||||
derive_more = { version = "0.99.17" }
|
||||
env_logger = { version = "0.9" }
|
||||
futures = { version = "0.3" }
|
||||
globset = { version = "0.4" }
|
||||
derive_more = "0.99.17"
|
||||
env_logger = "0.9"
|
||||
futures = "0.3"
|
||||
git2 = { version = "0.15", default-features = false }
|
||||
globset = "0.4"
|
||||
indoc = "1"
|
||||
# We explicitly disable a http2 support in isahc.
|
||||
isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
|
||||
lazy_static = { version = "1.4.0" }
|
||||
isahc = { version = "1.7.2", default-features = false, features = [
|
||||
"static-curl",
|
||||
"text-decoding",
|
||||
] }
|
||||
lazy_static = "1.4.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
ordered-float = { version = "2.1.1" }
|
||||
parking_lot = { version = "0.11.1" }
|
||||
ordered-float = "2.1.1"
|
||||
parking_lot = "0.11.1"
|
||||
postage = { version = "0.5", features = ["futures-traits"] }
|
||||
prost = { version = "0.8" }
|
||||
rand = { version = "0.8.5" }
|
||||
pretty_assertions = "1.3.0"
|
||||
prost = "0.8"
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||
rand = "0.8.5"
|
||||
refineable = { path = "./crates/refineable" }
|
||||
regex = { version = "1.5" }
|
||||
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
||||
regex = "1.5"
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
schemars = { version = "0.8" }
|
||||
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||
serde_json_lenient = { version = "0.1", features = [
|
||||
"preserve_order",
|
||||
"raw_value",
|
||||
] }
|
||||
serde_repr = "0.1"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = { version = "1.2" }
|
||||
smol = "1.2"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
sysinfo = "0.29.10"
|
||||
tempfile = { version = "3.9.0" }
|
||||
thiserror = { version = "1.0.29" }
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
toml = { version = "0.5" }
|
||||
tempfile = "3.9.0"
|
||||
thiserror = "1.0.29"
|
||||
tiktoken-rs = "0.5.7"
|
||||
tree-sitter = { version = "0.20" }
|
||||
unindent = { version = "0.1.7" }
|
||||
pretty_assertions = "1.3.0"
|
||||
git2 = { version = "0.15", default-features = false}
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
toml = "0.5"
|
||||
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-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev="f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
|
||||
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-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-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
|
||||
tree-sitter-embedded-template = "0.20.0"
|
||||
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
|
||||
tree-sitter-erlang = "0.4.0"
|
||||
tree-sitter-gitcommit = { git = "https://github.com/gbprod/tree-sitter-gitcommit" }
|
||||
tree-sitter-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" }
|
||||
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
|
||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
|
||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||
tree-sitter-rust = "0.20.3"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-php = "0.21.1"
|
||||
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-python = "0.20.2"
|
||||
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-ruby = "0.20.0"
|
||||
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-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"
|
||||
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-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a" }
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
|
||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||
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-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42" }
|
||||
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-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-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-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"
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" }
|
||||
# wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1d8975319c2d5de1bf710e7e21db25b0eee4bc66" }
|
||||
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
|
||||
# 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"
|
||||
debug = "limited"
|
||||
|
||||
# todo!(linux) - Remove this
|
||||
[profile.dev.package.blade-graphics]
|
||||
split-debuginfo = "off"
|
||||
debug = "full"
|
||||
|
||||
[profile.dev.package.taffy]
|
||||
opt-level = 3
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.75-bullseye as builder
|
||||
FROM rust:1.76-bullseye as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
# Compile collab server
|
||||
ARG CARGO_PROFILE_RELEASE_PANIC=abort
|
||||
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=./target \
|
||||
|
||||
@@ -14,6 +14,11 @@ Support for additional platforms is on our [roadmap](https://zed.dev/roadmap):
|
||||
- Windows ([tracking issue](https://github.com/zed-industries/zed/issues/5394))
|
||||
- Web ([tracking issue](https://github.com/zed-industries/zed/issues/5396))
|
||||
|
||||
For macOS users, you can also install Zed from Homebrew:
|
||||
```sh
|
||||
brew install zed
|
||||
```
|
||||
|
||||
## Developing Zed
|
||||
|
||||
- [Building Zed](./docs/src/developing_zed__building_zed.md)
|
||||
|
||||
4
assets/icons/file_icons/astro.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.32668 11.5331C4.78215 10.9852 4.62318 9.834 4.85006 9C5.24343 9.52582 5.78848 9.69239 6.35304 9.78642C7.2246 9.93151 8.08055 9.87724 8.89019 9.43877C8.98281 9.38857 9.06841 9.32182 9.16962 9.25421C9.24559 9.49681 9.26536 9.74172 9.23882 9.99099C9.1743 10.5981 8.89982 11.067 8.46326 11.4225C8.28869 11.5647 8.10397 11.6918 7.92367 11.8259C7.36978 12.2379 7.21992 12.7211 7.42805 13.4239C7.433 13.4411 7.43742 13.4582 7.44861 13.5C7.1658 13.3606 6.95923 13.1578 6.80182 12.8911C6.63558 12.6097 6.55649 12.2983 6.55233 11.9614C6.55025 11.7974 6.55025 11.632 6.53022 11.4704C6.4813 11.0763 6.31323 10.8999 5.99661 10.8897C5.67166 10.8793 5.41462 11.1004 5.34646 11.4486C5.34125 11.4753 5.33371 11.5017 5.32616 11.5328L5.32668 11.5331Z" fill="#17191E" fill-opacity="0.6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.30826 8.62679L6.73279 4.5906C6.82176 4.33854 7.17823 4.33854 7.26719 4.5906L8.69173 8.62679C8.69652 8.64035 8.70626 8.65153 8.7189 8.65814C8.80645 8.6531 8.89466 8.65055 8.98347 8.65055C10.1418 8.65055 11.1986 9.08493 12 9.79967L8.83801 1.36772C8.75507 1.14653 8.54362 1 8.30739 1H5.6926C5.45637 1 5.24492 1.14653 5.16198 1.36772L1.99999 9.79968C2.80137 9.08493 3.85822 8.65055 5.01652 8.65055C5.10533 8.65055 5.19355 8.6531 5.28109 8.65814C5.29373 8.65153 5.30347 8.64035 5.30826 8.62679Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
9
assets/icons/file_icons/elm.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.7295 12.1981L7.17677 7.64544C7.07914 7.5478 6.92085 7.5478 6.82322 7.64544L2.27053 12.1981C2.11304 12.3556 2.22458 12.6249 2.4473 12.6249H11.5527C11.7754 12.6249 11.887 12.3556 11.7295 12.1981Z" fill="black" fill-opacity="0.9"/>
|
||||
<path d="M1.80178 11.7294L6.35447 7.17668C6.4521 7.07905 6.4521 6.92076 6.35447 6.82312L1.80178 2.27043C1.64429 2.11294 1.375 2.22448 1.375 2.44721L1.375 11.5526C1.375 11.7753 1.64428 11.8869 1.80178 11.7294Z" fill="black" fill-opacity="0.9"/>
|
||||
<path d="M9.98928 9.16694L11.9794 7.17751C12.0771 7.0799 12.0771 6.92161 11.9795 6.82396L9.98928 4.833C9.89165 4.73534 9.73333 4.73532 9.63569 4.83297L7.64553 6.82313C7.5479 6.92076 7.5479 7.07905 7.64553 7.17668L9.63575 9.16691C9.73337 9.26453 9.89164 9.26455 9.98928 9.16694Z" fill="black" fill-opacity="0.9"/>
|
||||
<path d="M7.89553 1.80168L12.1982 6.10438C12.3557 6.26187 12.625 6.15033 12.625 5.9276V2.37491C12.625 1.82262 12.1773 1.37491 11.625 1.37491H8.0723C7.84958 1.37491 7.73804 1.64419 7.89553 1.80168Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M8.73976 4.18772L5.25981 4.1895C5.03708 4.18962 4.92567 4.45896 5.08325 4.61637L6.82322 6.35456C6.92087 6.45211 7.07909 6.45207 7.17669 6.35447L8.91666 4.61449C9.0742 4.45696 8.96255 4.1876 8.73976 4.18772Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M8.1147 3.55936L4.13431 3.55936C4.06801 3.55936 4.00442 3.53302 3.95753 3.48614L2.27057 1.79918C2.11308 1.64169 2.22462 1.37241 2.44735 1.37241L6.42774 1.37241C6.49405 1.37241 6.55763 1.39874 6.60452 1.44563L8.29148 3.13258C8.44897 3.29007 8.33743 3.55936 8.1147 3.55936Z" fill="black" fill-opacity="0.6"/>
|
||||
<path d="M12.625 8.07221V11.5526C12.625 11.7753 12.3557 11.8869 12.1982 11.7294L10.458 9.98918C10.3604 9.89155 10.3604 9.73326 10.458 9.63563L12.1982 7.89544C12.3557 7.73794 12.625 7.84949 12.625 8.07221Z" fill="black" fill-opacity="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
1
assets/icons/file_icons/erlang.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="64" viewBox="0 0 128 128" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="0" x2="128" y1="128" y2="0"><stop offset="0" stop-color="#333"/><stop offset="1" stop-color="#5d5d5d"/></linearGradient><path d="m12.239265 30.664279h14.960911c-5.59432 5.460938-7.654216 10.692785-10.342106 18.023379-3.200764 8.729348-.549141 29.987457 3.815534 37.55289 2.943384 5.101853 6.282685 8.994876 8.233522 11.095173h-16.667861zm89.614855 0h13.90661v66.671442h-13.55518c1.31391-1.750328 3.43934-4.534454 5.12085-6.426163 2.32782-2.618784 4.97023-6.978412 4.97023-6.978412l-16.015202-8.133112s-5.48977 11.600331-15.964999 15.964998c-10.475214 4.364666-19.784679-.838179-25.604243-7.530659-5.819578-6.692502-5.82371-22.14014-5.82371-22.14014h60.797524c1.16391-14.839892-2.63216-21.249816-4.66901-25.90547-.91799-2.098266-1.89261-3.810819-3.16287-5.522484zm-38.356164 1.757154c.35429-.01632.731685-.0092 1.104497 0 11.930114.290977 13.053143 12.802122 13.053143 12.802122h-27.311192s2.170772-12.298638 13.153552-12.802122z" fill="url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"suffixes": {
|
||||
"astro": "astro",
|
||||
"Emakefile": "erlang",
|
||||
"aac": "audio",
|
||||
"accdb": "storage",
|
||||
"app.src": "erlang",
|
||||
"avi": "video",
|
||||
"avif": "image",
|
||||
"bak": "backup",
|
||||
"bash": "terminal",
|
||||
@@ -23,6 +27,9 @@
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"eex": "elixir",
|
||||
"elm": "elm",
|
||||
"erl": "erlang",
|
||||
"escript": "erlang",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
@@ -37,33 +44,46 @@
|
||||
"gif": "image",
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gitmodules": "vcs",
|
||||
"gitkeep": "vcs",
|
||||
"go": "code",
|
||||
"gitmodules": "vcs",
|
||||
"go": "go",
|
||||
"h": "code",
|
||||
"handlebars": "code",
|
||||
"hbs": "template",
|
||||
"heex": "elixir",
|
||||
"heif": "image",
|
||||
"heic": "image",
|
||||
"hrl": "erlang",
|
||||
"hs": "haskell",
|
||||
"htm": "template",
|
||||
"html": "template",
|
||||
"hs": "haskell",
|
||||
"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",
|
||||
@@ -71,20 +91,24 @@
|
||||
"odp": "document",
|
||||
"ods": "document",
|
||||
"odt": "document",
|
||||
"ogg": "video",
|
||||
"ogg": "audio",
|
||||
"opus": "audio",
|
||||
"pdb": "storage",
|
||||
"pdf": "document",
|
||||
"php": "code",
|
||||
"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",
|
||||
@@ -104,13 +128,18 @@
|
||||
"txt": "document",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webp": "image",
|
||||
"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",
|
||||
@@ -120,6 +149,9 @@
|
||||
"zshrc": "terminal"
|
||||
},
|
||||
"types": {
|
||||
"astro": {
|
||||
"icon": "icons/file_icons/astro.svg"
|
||||
},
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/audio.svg"
|
||||
},
|
||||
@@ -133,7 +165,7 @@
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
@@ -144,6 +176,12 @@
|
||||
"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"
|
||||
},
|
||||
@@ -156,6 +194,9 @@
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
"go": {
|
||||
"icon": "icons/file_icons/go.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
@@ -165,12 +206,21 @@
|
||||
"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"
|
||||
},
|
||||
|
||||
1
assets/icons/file_icons/go.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="14" viewBox="0 0 207 78" width="14" xmlns="http://www.w3.org/2000/svg"><g fill="#000000"><path d="m16.2 24.1c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h35.7c.4 0 .5.3.3.6l-1.7 2.6c-.2.3-.7.6-1 .6z"/><path d="m1.1 33.3c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h45.6c.4 0 .6.3.5.6l-.8 2.4c-.1.4-.5.6-.9.6z"/><path d="m25.3 42.5c-.4 0-.5-.3-.3-.6l1.4-2.5c.2-.3.6-.6 1-.6h20c.4 0 .6.3.6.7l-.2 2.4c0 .4-.4.7-.7.7z"/><g transform="translate(55)"><path d="m74.1 22.3c-6.3 1.6-10.6 2.8-16.8 4.4-1.5.4-1.6.5-2.9-1-1.5-1.7-2.6-2.8-4.7-3.8-6.3-3.1-12.4-2.2-18.1 1.5-6.8 4.4-10.3 10.9-10.2 19 .1 8 5.6 14.6 13.5 15.7 6.8.9 12.5-1.5 17-6.6.9-1.1 1.7-2.3 2.7-3.7-3.6 0-8.1 0-19.3 0-2.1 0-2.6-1.3-1.9-3 1.3-3.1 3.7-8.3 5.1-10.9.3-.6 1-1.6 2.5-1.6h36.4c-.2 2.7-.2 5.4-.6 8.1-1.1 7.2-3.8 13.8-8.2 19.6-7.2 9.5-16.6 15.4-28.5 17-9.8 1.3-18.9-.6-26.9-6.6-7.4-5.6-11.6-13-12.7-22.2-1.3-10.9 1.9-20.7 8.5-29.3 7.1-9.3 16.5-15.2 28-17.3 9.4-1.7 18.4-.6 26.5 4.9 5.3 3.5 9.1 8.3 11.6 14.1.6.9.2 1.4-1 1.7z"/><path d="m107.2 77.6c-9.1-.2-17.4-2.8-24.4-8.8-5.9-5.1-9.6-11.6-10.8-19.3-1.8-11.3 1.3-21.3 8.1-30.2 7.3-9.6 16.1-14.6 28-16.7 10.2-1.8 19.8-.8 28.5 5.1 7.9 5.4 12.8 12.7 14.1 22.3 1.7 13.5-2.2 24.5-11.5 33.9-6.6 6.7-14.7 10.9-24 12.8-2.7.5-5.4.6-8 .9zm23.8-40.4c-.1-1.3-.1-2.3-.3-3.3-1.8-9.9-10.9-15.5-20.4-13.3-9.3 2.1-15.3 8-17.5 17.4-1.8 7.8 2 15.7 9.2 18.9 5.5 2.4 11 2.1 16.3-.6 7.9-4.1 12.2-10.5 12.7-19.1z" fill-rule="nonzero"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
5
assets/icons/file_icons/ocaml.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.73843 11.709C6.70584 11.6334 6.60683 11.4367 6.55712 11.3736C6.44917 11.2362 6.42396 11.2258 6.39221 11.0523C6.33703 10.7501 6.19094 10.202 6.01879 9.82381C5.92987 9.62863 5.78201 9.46467 5.64665 9.32312C5.52847 9.19895 5.26214 8.99002 5.2157 9.00037C4.7807 9.09487 4.64576 9.55902 4.44115 9.92673C4.32795 10.1301 4.208 10.3031 4.1188 10.5195C4.03641 10.7184 4.04373 10.9387 3.90268 11.1095C3.75801 11.2849 3.66398 11.4715 3.59311 11.6981C3.57968 11.7412 3.54148 12.1939 3.5 12.3006L4.14649 12.2511C4.74896 12.2958 4.57496 12.547 5.51526 12.4922L7 12.4423C6.95398 12.2942 6.89056 12.1228 6.86613 12.067C6.82472 11.9732 6.77267 11.7897 6.73843 11.709Z" fill="black"/>
|
||||
<path d="M8.72454 8.42043C8.61775 8.50889 8.40904 8.72165 7.95506 8.8021C7.75133 8.83825 7.56076 8.84122 7.35158 8.82925C7.24918 8.8236 7.15263 8.81758 7.04997 8.81605C6.9895 8.81552 6.78663 8.80812 6.79667 8.83039L6.77408 8.89506C6.7776 8.91633 6.78497 8.96949 6.78703 8.98237C6.79534 9.03461 6.79766 9.07617 6.79939 9.12421C6.80252 9.22297 6.79228 9.32592 6.79667 9.42559C6.80577 9.63232 6.87262 9.82076 6.88106 10.0293C6.89029 10.2615 6.99037 10.5072 7.08718 10.6969C7.12393 10.7691 7.17988 10.7773 7.20426 10.8663C7.23284 10.9681 7.20579 11.0762 7.21968 11.1848C7.27417 11.6058 7.37982 12.0459 7.54501 12.4259C7.54621 12.4291 7.54747 12.4325 7.54893 12.4354C7.75293 12.3961 7.95732 12.3119 8.22239 12.2669C8.70839 12.1841 9.3843 12.2268 9.81848 12.1801C10.9171 12.0616 11.5133 12.6972 12.5 12.4367V3.09052C12.5 2.21217 11.8798 1.5 11.1142 1.5H2.88578C2.1205 1.5 1.5 2.21217 1.5 3.09052V6.5608C1.69828 6.47851 1.98348 5.99435 2.07285 5.87661C2.2292 5.67071 2.25758 5.40808 2.33546 5.24267C2.51281 4.86596 2.54331 4.60691 2.94645 4.60691C3.13436 4.60691 3.20899 4.65663 3.3361 4.85238C3.42454 4.98851 3.57731 5.24 3.64881 5.40815C3.73134 5.60215 3.86583 5.86464 3.92497 5.91763C3.96876 5.95698 4.01221 5.9865 4.05275 6.00396C4.11813 6.0321 4.17222 5.98047 4.21595 5.94051C4.27176 5.8895 4.29582 5.78556 4.34751 5.64692C4.422 5.44689 4.5032 5.20721 4.54938 5.12356C4.62932 4.97897 4.65656 4.80739 4.74288 4.72427C4.8702 4.60172 5.03632 4.59311 5.08203 4.58274C5.33779 4.52478 5.45408 4.72419 5.58006 4.85315C5.66253 4.93764 5.77522 5.10785 5.85523 5.33594C5.91776 5.51408 5.99736 5.67887 6.03065 5.78174C6.06281 5.88103 6.14222 6.04018 6.18926 6.23098C6.23199 6.40424 6.34635 6.537 6.3898 6.61936C6.3898 6.61936 6.45632 6.83319 6.86079 7.02864C6.9485 7.07104 7.12579 7.13998 7.23157 7.18413C7.40733 7.25741 7.57757 7.24788 7.79432 7.21807C7.94888 7.21807 8.03261 6.96123 8.10284 6.75556C8.14437 6.634 8.18418 6.28566 8.21129 6.18675C8.23754 6.09051 8.17614 6.01608 8.22843 5.93174C8.28956 5.83329 8.32591 5.82796 8.3612 5.69961C8.43701 5.42478 8.87524 5.4109 9.12156 5.4109C9.32689 5.4109 9.30078 5.63967 9.6491 5.56143C9.84858 5.51652 10.0408 5.59094 10.2526 5.65531C10.4309 5.7096 10.5986 5.77145 10.699 5.90642C10.764 5.99382 10.9254 6.43169 10.761 6.45037C10.7768 6.47257 10.7884 6.5126 10.8179 6.53456C10.7813 6.69974 10.622 6.58207 10.5335 6.56087C10.4142 6.5325 10.33 6.56514 10.2133 6.6244C10.0138 6.72635 9.72206 6.71446 9.5483 6.88055C9.40085 7.02132 9.40111 7.33558 9.33234 7.51166C9.33234 7.51166 9.14137 8.07551 8.72454 8.42043Z" fill="black"/>
|
||||
<path d="M3.6514 8.80413C3.57333 8.79137 3.50083 8.77702 3.425 8.75238C3.28339 8.70644 3.12867 8.66165 2.9892 8.60788C2.90451 8.57488 2.62239 8.41409 2.5611 8.36877C2.41737 8.26211 2.32191 7.97231 2.20955 8.00214C2.13782 8.02098 2.06795 8.06058 2.02333 8.17701C1.98692 8.27196 1.97457 8.43521 1.94936 8.54469C1.92011 8.67177 1.86959 8.7904 1.82536 8.91149C1.74401 9.13362 1.59759 9.33453 1.5345 9.55093C1.52181 9.59546 1.51055 9.64527 1.5 9.6972V10.5274V11.9637V12.1713C1.57359 12.1915 1.65057 12.2164 1.73674 12.2535C2.37264 12.5265 2.52781 12.5497 3.15152 12.4348L3.21002 12.4223C3.25775 12.2625 3.2946 11.718 3.32555 11.5494C3.34966 11.4202 3.38279 11.3173 3.39537 11.1853C3.40723 11.06 3.39427 10.9406 3.3876 10.8267C3.37011 10.5414 3.51669 10.4395 3.58667 10.1945C3.64976 9.97283 3.68617 9.7206 3.73839 9.49399C3.78847 9.27653 3.86665 8.96922 4 8.85975C3.98382 8.82938 3.72144 8.81539 3.6514 8.80413Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
1
assets/icons/file_icons/php.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512"><path d="M170.322 349.808c-2.4-15.66-9-28.38-25.020-34.531-6.27-2.4-11.7-6.78-17.88-9.54-7.020-3.15-14.16-6.15-21.57-8.1-5.61-1.5-10.83 1.020-14.16 5.94-3.15 4.62-0.87 8.97 1.77 12.84 2.97 4.35 6.27 8.49 9.6 12.57 5.52 6.78 11.37 13.29 16.74 20.161 5.13 6.57 9.51 13.86 8.76 22.56-1.65 19.080-10.29 34.891-24.21 47.76-1.53 1.38-4.23 2.37-6.21 2.19-8.88-0.96-16.95-4.32-23.46-10.53-7.47-7.11-6.33-15.48 2.61-20.67 2.13-1.23 4.35-2.37 6.3-3.87 5.46-4.11 7.29-11.13 4.32-17.22-1.41-2.94-3-6.12-5.34-8.25-11.43-10.41-22.651-21.151-34.891-30.63-29.671-23.041-44.91-53.52-47.251-90.421-2.64-40.981 6.87-79.231 28.5-114.242 8.19-13.29 17.73-25.951 32.37-32.52 9.96-4.47 20.88-6.99 31.531-9.78 29.311-7.71 58.89-13.5 89.401-8.34 26.28 4.41 45.511 17.94 54.331 43.77 5.79 16.89 7.17 34.35 5.37 52.231-3.54 35.131-29.49 66.541-63.331 75.841-14.67 4.020-22.68 1.77-31.5-10.44-6.33-8.79-11.58-18.36-17.25-27.631-0.84-1.38-1.44-2.97-2.16-4.44-0.69-1.47-1.44-2.88-2.16-4.35 2.13 15.24 5.67 29.911 13.98 42.99 4.5 7.11 10.5 12.36 19.29 13.14 32.34 2.91 59.641-7.71 79.021-33.721 21.69-29.101 26.461-62.581 20.19-97.831-1.23-6.96-3.3-13.77-4.77-20.7-0.99-4.47 0.78-7.77 5.19-9.33 2.040-0.69 4.14-1.26 6.18-1.68 26.461-5.7 53.221-7.59 80.191-4.86 30.601 3.060 59.551 11.46 85.441 28.471 40.531 26.67 65.641 64.621 79.291 110.522 1.98 6.66 2.28 13.95 2.46 20.971 0.12 4.68-2.88 5.91-6.45 2.97-3.93-3.21-7.53-6.87-10.92-10.65-3.15-3.57-5.67-7.65-8.73-11.4-2.37-2.94-4.44-2.49-5.58 1.17-0.72 2.22-1.35 4.41-1.98 6.63-7.080 25.26-18.24 48.3-36.33 67.711-2.52 2.73-4.77 6.78-5.070 10.38-0.78 9.96-1.35 20.13-0.39 30.060 1.98 21.331 5.070 42.57 7.47 63.871 1.35 12.030-2.52 19.11-13.83 23.281-7.95 2.91-16.47 5.040-24.87 5.64-13.38 0.93-26.88 0.27-40.32 0.27-0.36-15 0.93-29.731-13.17-37.771 2.73-11.13 5.88-21.69 7.77-32.49 1.56-8.97 0.24-17.79-6.060-25.14-5.91-6.93-13.32-8.82-20.101-4.86-20.43 11.91-41.671 11.97-63.301 4.17-9.93-3.6-16.86-1.56-22.351 7.5-5.91 9.75-8.4 20.7-7.74 31.771 0.84 13.95 3.27 27.75 5.13 41.64 1.020 7.77 0.15 9.78-7.56 11.76-17.13 4.35-34.56 4.83-52.081 3.42-0.93-0.090-1.86-0.48-2.46-0.63-0.87-14.55 0.66-29.671-16.68-37.411 7.68-16.29 6.63-33.18 3.99-50.070l-0.060-0.15zM66.761 292.718c2.55-2.4 4.59-6.15 5.31-9.6 1.8-8.64-4.68-20.22-12.18-23.43-3.99-1.74-7.47-1.11-10.29 2.070-6.87 7.77-13.65 15.63-20.401 23.521-1.14 1.35-2.16 2.94-2.97 4.53-2.7 5.19-1.11 8.97 4.65 10.38 3.48 0.87 7.080 1.050 10.65 1.56 9.3-0.9 18.3-2.46 25.23-9v-0.030zM67.541 206.347c-0.030-6.18-5.19-11.34-11.28-11.37-6.27-0.030-11.67 5.58-11.46 11.76 0.27 6.21 5.43 11.19 11.61 11.070 6.24-0.090 11.22-5.19 11.16-11.43l-0.030-0.030z"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
6
assets/icons/file_icons/prisma.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="
|
||||
0 0 425 512" xmlns="http://www.w3.org/2000/svg" fill="#000000
|
||||
">
|
||||
<path
|
||||
d="m381.38934 405.88714-229.67062 67.92744c-7.01651 2.07778-13.74132-3.99173-12.2669-11.07217l82.04834-392.9335c1.53436-7.352147 11.69152-8.514905 14.89609-1.710173l151.9177 322.59543c2.86494 6.08949-.40357 13.26702-6.92461 15.19297zm39.38512-16.02808-175.89887-373.53306c-11.59465-21.691431-39.0351-20.904032-49.75484-2.749064l-190.77231 308.99c-5.9096786 9.63371-5.7938027 21.50903.3356409 31.01887l93.252489 144.4589c9.615412 11.46292 18.506512 16.87006 33.692012 12.37878l270.68561-80.05849c18.03265-5.40039 26.72265-22.82202 18.46027-40.50593z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 680 B |
@@ -415,7 +415,15 @@
|
||||
"cmd-?": "assistant::ToggleFocus",
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
"cmd-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow"
|
||||
"escape": "workspace::Unfollow",
|
||||
"cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"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"]
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
@@ -441,18 +449,6 @@
|
||||
"ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"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"]
|
||||
}
|
||||
},
|
||||
// Bindings from Atom
|
||||
{
|
||||
"context": "Pane",
|
||||
@@ -569,6 +565,12 @@
|
||||
"tab": "channel_modal::ToggleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ChatPanel > MessageEditor",
|
||||
"bindings": {
|
||||
"escape": "chat_panel::CloseReplyPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Terminal",
|
||||
"bindings": {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"shift-alt-up": "editor::MoveLineUp",
|
||||
"shift-alt-down": "editor::MoveLineDown",
|
||||
"cmd-alt-l": "editor::Format",
|
||||
"shift-f6": "editor::Rename",
|
||||
"cmd-[": "pane::GoBack",
|
||||
"cmd-]": "pane::GoForward",
|
||||
"alt-f7": "editor::FindAllReferences",
|
||||
@@ -80,10 +81,18 @@
|
||||
"cmd-6": "diagnostics::Deploy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"cmd-alt-left": "pane::GoBack",
|
||||
"cmd-alt-right": "pane::GoForward"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"enter": "project_panel::Open"
|
||||
"enter": "project_panel::Open",
|
||||
"shift-f6": "project_panel::Rename"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
";": "vim::RepeatFind",
|
||||
",": "vim::RepeatFindReversed",
|
||||
"ctrl-o": "pane::GoBack",
|
||||
"ctrl-i": "pane::GoForward",
|
||||
"ctrl-]": "editor::GoToDefinition",
|
||||
@@ -207,6 +209,9 @@
|
||||
"displayLines": true
|
||||
}
|
||||
],
|
||||
"shift-h": "vim::WindowTop",
|
||||
"shift-m": "vim::WindowMiddle",
|
||||
"shift-l": "vim::WindowBottom",
|
||||
// z commands
|
||||
"z t": "editor::ScrollCursorTop",
|
||||
"z z": "editor::ScrollCursorCenter",
|
||||
@@ -279,7 +284,8 @@
|
||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
|
||||
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"]
|
||||
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
|
||||
"-": "pane::RevealInProjectPanel"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -330,16 +336,11 @@
|
||||
],
|
||||
"*": "vim::MoveToNext",
|
||||
"#": "vim::MoveToPrev",
|
||||
";": "vim::RepeatFind",
|
||||
",": "vim::RepeatFindReversed",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
"> >": "editor::Indent",
|
||||
"< <": "editor::Outdent",
|
||||
"shift-h": "vim::WindowTop",
|
||||
"shift-m": "vim::WindowMiddle",
|
||||
"shift-l": "vim::WindowBottom",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem"
|
||||
}
|
||||
@@ -484,7 +485,9 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -497,7 +500,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace > VimEnabled",
|
||||
"context": "BufferSearchBar && !in_replace",
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
|
||||
@@ -62,6 +62,9 @@
|
||||
// Whether to display inline and alongside documentation for items in the
|
||||
// completions menu
|
||||
"show_completion_documentation": true,
|
||||
// The debounce delay before re-querying the language server for completion
|
||||
// documentation when not included in original completion list.
|
||||
"completion_documentation_secondary_query_debounce": 300,
|
||||
// Whether to show wrap guides in the editor. Setting this to true will
|
||||
// show a guide at the 'preferred_line_length' value if softwrap is set to
|
||||
// 'preferred_line_length', and will show any additional guides as specified
|
||||
@@ -69,6 +72,17 @@
|
||||
"show_wrap_guides": true,
|
||||
// Character counts at which to show wrap guides in the editor.
|
||||
"wrap_guides": [],
|
||||
// Hide the values of in variables from visual display in private files
|
||||
"redact_private_values": false,
|
||||
// Globs to match against file paths to determine if a file is private.
|
||||
"private_files": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
"**/*.key",
|
||||
"**/*.cert",
|
||||
"**/*.crt",
|
||||
"**/secrets.yml"
|
||||
],
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
@@ -90,8 +104,17 @@
|
||||
"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": {
|
||||
// Whether to show breadcrumbs.
|
||||
"breadcrumbs": true,
|
||||
// Whether to show quick action buttons.
|
||||
"quick_actions": true
|
||||
},
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
@@ -111,8 +134,14 @@
|
||||
// Whether to show git diff indicators in the scrollbar.
|
||||
"git_diff": true,
|
||||
// Whether to show selections in the scrollbar.
|
||||
"selections": true
|
||||
"selections": true,
|
||||
// Whether to show symbols selections in the scrollbar.
|
||||
"symbols_selections": true,
|
||||
// Whether to show diagnostic indicators in the scrollbar.
|
||||
"diagnostics": true
|
||||
},
|
||||
// The number of lines to keep above/below the cursor when scrolling.
|
||||
"vertical_scroll_margin": 3,
|
||||
"relative_line_numbers": false,
|
||||
// When to populate a new search's query based on the text under the cursor.
|
||||
// This setting can take the following three values:
|
||||
@@ -185,6 +214,8 @@
|
||||
"default_width": 640,
|
||||
// Default height when the assistant is docked to the bottom.
|
||||
"default_height": 320,
|
||||
// The default OpenAI API endpoint to use when starting new conversations.
|
||||
"openai_api_url": "https://api.openai.com/v1",
|
||||
// The default OpenAI model to use when starting new conversations. This
|
||||
// setting can take three values:
|
||||
//
|
||||
@@ -416,7 +447,7 @@
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
// "font_size": "15",
|
||||
// "font_size": 15,
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono",
|
||||
@@ -469,6 +500,9 @@
|
||||
"JavaScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Terraform": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TypeScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
@@ -480,6 +514,12 @@
|
||||
},
|
||||
"JSON": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"OCaml": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"OCaml Interface": {
|
||||
"tab_size": 2
|
||||
}
|
||||
},
|
||||
// Zed's Prettier integration settings.
|
||||
@@ -522,7 +562,7 @@
|
||||
"stable": {
|
||||
// "theme": "Andromeda"
|
||||
},
|
||||
// Settings overrides to use when using Zed Stable.
|
||||
// Settings overrides to use when using Zed Dev.
|
||||
// Mostly useful for developers who are managing multiple instances of Zed.
|
||||
"dev": {
|
||||
// "theme": "Andromeda"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Folder-specific settings
|
||||
//
|
||||
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||
// see the documentation: https://docs.zed.dev/configuration/configuring-zed#folder-specific-settings
|
||||
// see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings
|
||||
{}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#21242bff",
|
||||
"tab.inactive_background": "#21242bff",
|
||||
"tab.active_background": "#1e2025ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#11a79366",
|
||||
"panel.background": "#21242bff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#221f26ff",
|
||||
"tab.inactive_background": "#221f26ff",
|
||||
"tab.active_background": "#19171cff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#576dda66",
|
||||
"panel.background": "#221f26ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -426,7 +426,7 @@
|
||||
"tab_bar.background": "#e6e3ebff",
|
||||
"tab.inactive_background": "#e6e3ebff",
|
||||
"tab.active_background": "#efecf4ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#586dda66",
|
||||
"panel.background": "#e6e3ebff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -810,7 +810,7 @@
|
||||
"tab_bar.background": "#262622ff",
|
||||
"tab.inactive_background": "#262622ff",
|
||||
"tab.active_background": "#20201dff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#6684e066",
|
||||
"panel.background": "#262622ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -1194,7 +1194,7 @@
|
||||
"tab_bar.background": "#eeebd7ff",
|
||||
"tab.inactive_background": "#eeebd7ff",
|
||||
"tab.active_background": "#fefbecff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#6784e066",
|
||||
"panel.background": "#eeebd7ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -1578,7 +1578,7 @@
|
||||
"tab_bar.background": "#2c2b23ff",
|
||||
"tab.inactive_background": "#2c2b23ff",
|
||||
"tab.active_background": "#22221bff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#37a16666",
|
||||
"panel.background": "#2c2b23ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -1962,7 +1962,7 @@
|
||||
"tab_bar.background": "#ebeae3ff",
|
||||
"tab.inactive_background": "#ebeae3ff",
|
||||
"tab.active_background": "#f4f3ecff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#38a16666",
|
||||
"panel.background": "#ebeae3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -2346,7 +2346,7 @@
|
||||
"tab_bar.background": "#27211eff",
|
||||
"tab.inactive_background": "#27211eff",
|
||||
"tab.active_background": "#1b1918ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#417ee666",
|
||||
"panel.background": "#27211eff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -2730,7 +2730,7 @@
|
||||
"tab_bar.background": "#e9e6e4ff",
|
||||
"tab.inactive_background": "#e9e6e4ff",
|
||||
"tab.active_background": "#f0eeedff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#417ee666",
|
||||
"panel.background": "#e9e6e4ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -3114,7 +3114,7 @@
|
||||
"tab_bar.background": "#252025ff",
|
||||
"tab.inactive_background": "#252025ff",
|
||||
"tab.active_background": "#1b181bff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#526aeb66",
|
||||
"panel.background": "#252025ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -3498,7 +3498,7 @@
|
||||
"tab_bar.background": "#e0d5e0ff",
|
||||
"tab.inactive_background": "#e0d5e0ff",
|
||||
"tab.active_background": "#f7f3f7ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#526aeb66",
|
||||
"panel.background": "#e0d5e0ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -3882,7 +3882,7 @@
|
||||
"tab_bar.background": "#1c2529ff",
|
||||
"tab.inactive_background": "#1c2529ff",
|
||||
"tab.active_background": "#161b1dff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#277fad66",
|
||||
"panel.background": "#1c2529ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -4266,7 +4266,7 @@
|
||||
"tab_bar.background": "#cdeaf9ff",
|
||||
"tab.inactive_background": "#cdeaf9ff",
|
||||
"tab.active_background": "#ebf8ffff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#277fad66",
|
||||
"panel.background": "#cdeaf9ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -4650,7 +4650,7 @@
|
||||
"tab_bar.background": "#252020ff",
|
||||
"tab.inactive_background": "#252020ff",
|
||||
"tab.active_background": "#1b1818ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#7272ca66",
|
||||
"panel.background": "#252020ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -5034,7 +5034,7 @@
|
||||
"tab_bar.background": "#ebe3e3ff",
|
||||
"tab.inactive_background": "#ebe3e3ff",
|
||||
"tab.active_background": "#f4ececff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#7372ca66",
|
||||
"panel.background": "#ebe3e3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -5418,7 +5418,7 @@
|
||||
"tab_bar.background": "#1f2621ff",
|
||||
"tab.inactive_background": "#1f2621ff",
|
||||
"tab.active_background": "#171c19ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#478c9066",
|
||||
"panel.background": "#1f2621ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -5802,7 +5802,7 @@
|
||||
"tab_bar.background": "#e3ebe6ff",
|
||||
"tab.inactive_background": "#e3ebe6ff",
|
||||
"tab.active_background": "#ecf4eeff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#488c9066",
|
||||
"panel.background": "#e3ebe6ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -6186,7 +6186,7 @@
|
||||
"tab_bar.background": "#1f231fff",
|
||||
"tab.inactive_background": "#1f231fff",
|
||||
"tab.active_background": "#131513ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#3e62f466",
|
||||
"panel.background": "#1f231fff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -6570,7 +6570,7 @@
|
||||
"tab_bar.background": "#daeedaff",
|
||||
"tab.inactive_background": "#daeedaff",
|
||||
"tab.active_background": "#f3faf3ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#3f62f466",
|
||||
"panel.background": "#daeedaff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -6954,7 +6954,7 @@
|
||||
"tab_bar.background": "#262f51ff",
|
||||
"tab.inactive_background": "#262f51ff",
|
||||
"tab.active_background": "#202646ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#3e8fd066",
|
||||
"panel.background": "#262f51ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -7338,7 +7338,7 @@
|
||||
"tab_bar.background": "#e5e8f5ff",
|
||||
"tab.inactive_background": "#e5e8f5ff",
|
||||
"tab.active_background": "#f5f7ffff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#3f8fd066",
|
||||
"panel.background": "#e5e8f5ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#1f2127ff",
|
||||
"tab.inactive_background": "#1f2127ff",
|
||||
"tab.active_background": "#0d1016ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#5ac2fe66",
|
||||
"panel.background": "#1f2127ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -411,7 +411,7 @@
|
||||
"tab_bar.background": "#ececedff",
|
||||
"tab.inactive_background": "#ececedff",
|
||||
"tab.active_background": "#fcfcfcff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#3b9ee566",
|
||||
"panel.background": "#ececedff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -780,7 +780,7 @@
|
||||
"tab_bar.background": "#353944ff",
|
||||
"tab.inactive_background": "#353944ff",
|
||||
"tab.active_background": "#242835ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#73cffe66",
|
||||
"panel.background": "#353944ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#3a3735ff",
|
||||
"tab.inactive_background": "#3a3735ff",
|
||||
"tab.active_background": "#282828ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#83a59866",
|
||||
"panel.background": "#3a3735ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -416,7 +416,7 @@
|
||||
"tab_bar.background": "#393634ff",
|
||||
"tab.inactive_background": "#393634ff",
|
||||
"tab.active_background": "#1d2021ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#83a59866",
|
||||
"panel.background": "#393634ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -790,7 +790,7 @@
|
||||
"tab_bar.background": "#3b3735ff",
|
||||
"tab.inactive_background": "#3b3735ff",
|
||||
"tab.active_background": "#32302fff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#83a59866",
|
||||
"panel.background": "#3b3735ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -1164,7 +1164,7 @@
|
||||
"tab_bar.background": "#ecddb4ff",
|
||||
"tab.inactive_background": "#ecddb4ff",
|
||||
"tab.active_background": "#fbf1c7ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#0b667866",
|
||||
"panel.background": "#ecddb4ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -1538,7 +1538,7 @@
|
||||
"tab_bar.background": "#ecddb5ff",
|
||||
"tab.inactive_background": "#ecddb5ff",
|
||||
"tab.active_background": "#f9f5d7ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#0b667866",
|
||||
"panel.background": "#ecddb5ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -1912,7 +1912,7 @@
|
||||
"tab_bar.background": "#ecdcb3ff",
|
||||
"tab.inactive_background": "#ecdcb3ff",
|
||||
"tab.active_background": "#f2e5bcff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#0b667866",
|
||||
"panel.background": "#ecdcb3ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#2f343eff",
|
||||
"tab.inactive_background": "#2f343eff",
|
||||
"tab.active_background": "#282c33ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#74ade866",
|
||||
"panel.background": "#2f343eff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -416,7 +416,7 @@
|
||||
"tab_bar.background": "#ebebecff",
|
||||
"tab.inactive_background": "#ebebecff",
|
||||
"tab.active_background": "#fafafaff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#5c79e266",
|
||||
"panel.background": "#ebebecff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#1c1b2aff",
|
||||
"tab.inactive_background": "#1c1b2aff",
|
||||
"tab.active_background": "#191724ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#57949f66",
|
||||
"panel.background": "#1c1b2aff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -421,7 +421,7 @@
|
||||
"tab_bar.background": "#fef9f2ff",
|
||||
"tab.inactive_background": "#fef9f2ff",
|
||||
"tab.active_background": "#faf4edff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#9cced766",
|
||||
"panel.background": "#fef9f2ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -800,7 +800,7 @@
|
||||
"tab_bar.background": "#28253cff",
|
||||
"tab.inactive_background": "#28253cff",
|
||||
"tab.active_background": "#232136ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#9cced766",
|
||||
"panel.background": "#28253cff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#2b3038ff",
|
||||
"tab.inactive_background": "#2b3038ff",
|
||||
"tab.active_background": "#282c33ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#528b8b66",
|
||||
"panel.background": "#2b3038ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#04313bff",
|
||||
"tab.inactive_background": "#04313bff",
|
||||
"tab.active_background": "#002a35ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#288bd166",
|
||||
"panel.background": "#04313bff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
@@ -411,7 +411,7 @@
|
||||
"tab_bar.background": "#f3eddaff",
|
||||
"tab.inactive_background": "#f3eddaff",
|
||||
"tab.active_background": "#fdf6e3ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#298bd166",
|
||||
"panel.background": "#f3eddaff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"tab_bar.background": "#231f16ff",
|
||||
"tab.inactive_background": "#231f16ff",
|
||||
"tab.active_background": "#1b1810ff",
|
||||
"search.match_background": null,
|
||||
"search.match_background": "#499bef66",
|
||||
"panel.background": "#231f16ff",
|
||||
"panel.focused_border": null,
|
||||
"pane.focused_border": null,
|
||||
|
||||
@@ -10,20 +10,19 @@ path = "src/activity_indicator.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
auto_update = { path = "../auto_update" }
|
||||
editor = { path = "../editor" }
|
||||
language = { path = "../language" }
|
||||
gpui = { path = "../gpui" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace", package = "workspace" }
|
||||
|
||||
anyhow.workspace = true
|
||||
auto_update.workspace = true
|
||||
editor.workspace = true
|
||||
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
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -13,27 +13,27 @@ doctest = false
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
language = { path = "../language" }
|
||||
async-trait.workspace = true
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
bincode = "1.3.3"
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
isahc.workspace = true
|
||||
language.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
matrixmultiply = "0.3.7"
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
isahc.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
parse_duration = "2.1.1"
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
log.workspace = true
|
||||
parse_duration = "2.1.1"
|
||||
tiktoken-rs.workspace = true
|
||||
matrixmultiply = "0.3.7"
|
||||
regex.workspace = true
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
bincode = "1.3.3"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tiktoken-rs.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -103,6 +103,7 @@ pub struct OpenAiResponseStreamEvent {
|
||||
}
|
||||
|
||||
pub async fn stream_completion(
|
||||
api_url: String,
|
||||
credential: ProviderCredential,
|
||||
executor: BackgroundExecutor,
|
||||
request: Box<dyn CompletionRequest>,
|
||||
@@ -117,7 +118,7 @@ pub async fn stream_completion(
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAiResponseStreamEvent>>();
|
||||
|
||||
let json_data = request.data()?;
|
||||
let mut response = Request::post(format!("{OPEN_AI_API_URL}/chat/completions"))
|
||||
let mut response = Request::post(format!("{api_url}/chat/completions"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.body(json_data)?
|
||||
@@ -195,18 +196,20 @@ pub async fn stream_completion(
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAiCompletionProvider {
|
||||
api_url: String,
|
||||
model: OpenAiLanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl OpenAiCompletionProvider {
|
||||
pub async fn new(model_name: String, executor: BackgroundExecutor) -> Self {
|
||||
pub async fn new(api_url: String, model_name: String, executor: BackgroundExecutor) -> Self {
|
||||
let model = executor
|
||||
.spawn(async move { OpenAiLanguageModel::load(&model_name) })
|
||||
.await;
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
Self {
|
||||
api_url,
|
||||
model,
|
||||
credential,
|
||||
executor,
|
||||
@@ -303,7 +306,8 @@ impl CompletionProvider for OpenAiCompletionProvider {
|
||||
// which is currently model based, due to the language model.
|
||||
// At some point in the future we should rectify this.
|
||||
let credential = self.credential.read().clone();
|
||||
let request = stream_completion(credential, self.executor.clone(), prompt);
|
||||
let api_url = self.api_url.clone();
|
||||
let request = stream_completion(api_url, credential, self.executor.clone(), prompt);
|
||||
async move {
|
||||
let response = request.await?;
|
||||
let stream = response
|
||||
|
||||
@@ -35,6 +35,7 @@ lazy_static! {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAiEmbeddingProvider {
|
||||
api_url: String,
|
||||
model: OpenAiLanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
pub client: Arc<dyn HttpClient>,
|
||||
@@ -69,7 +70,11 @@ struct OpenAiEmbeddingUsage {
|
||||
}
|
||||
|
||||
impl OpenAiEmbeddingProvider {
|
||||
pub async fn new(client: Arc<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
|
||||
pub async fn new(
|
||||
api_url: String,
|
||||
client: Arc<dyn HttpClient>,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Self {
|
||||
let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
|
||||
let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
|
||||
|
||||
@@ -80,6 +85,7 @@ impl OpenAiEmbeddingProvider {
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
|
||||
OpenAiEmbeddingProvider {
|
||||
api_url,
|
||||
model,
|
||||
credential,
|
||||
client,
|
||||
@@ -130,11 +136,12 @@ impl OpenAiEmbeddingProvider {
|
||||
}
|
||||
async fn send_request(
|
||||
&self,
|
||||
api_url: &str,
|
||||
api_key: &str,
|
||||
spans: Vec<&str>,
|
||||
request_timeout: u64,
|
||||
) -> Result<Response<AsyncBody>> {
|
||||
let request = Request::post("https://api.openai.com/v1/embeddings")
|
||||
let request = Request::post(format!("{api_url}/embeddings"))
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.timeout(Duration::from_secs(request_timeout))
|
||||
.header("Content-Type", "application/json")
|
||||
@@ -246,6 +253,7 @@ impl EmbeddingProvider for OpenAiEmbeddingProvider {
|
||||
const BACKOFF_SECONDS: [usize; 4] = [3, 5, 15, 45];
|
||||
const MAX_RETRIES: usize = 4;
|
||||
|
||||
let api_url = self.api_url.as_str();
|
||||
let api_key = self.get_api_key()?;
|
||||
|
||||
let mut request_number = 0;
|
||||
@@ -255,6 +263,7 @@ impl EmbeddingProvider for OpenAiEmbeddingProvider {
|
||||
while request_number < MAX_RETRIES {
|
||||
response = self
|
||||
.send_request(
|
||||
&api_url,
|
||||
&api_key,
|
||||
spans.iter().map(|x| &**x).collect(),
|
||||
request_timeout,
|
||||
|
||||
@@ -6,6 +6,6 @@ publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
gpui = {path = "../gpui"}
|
||||
rust-embed.workspace = true
|
||||
anyhow.workspace = true
|
||||
gpui.workspace = true
|
||||
rust-embed.workspace = true
|
||||
|
||||
@@ -10,46 +10,44 @@ path = "src/assistant.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
ai = { path = "../ai" }
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections"}
|
||||
editor = { path = "../editor" }
|
||||
fs = { path = "../fs" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
menu = { path = "../menu" }
|
||||
multi_buffer = { path = "../multi_buffer" }
|
||||
project = { path = "../project" }
|
||||
search = { path = "../search" }
|
||||
semantic_index = { path = "../semantic_index" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
||||
uuid.workspace = true
|
||||
log.workspace = true
|
||||
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
|
||||
search.workspace = true
|
||||
semantic_index.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
theme.workspace = true
|
||||
tiktoken-rs.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ai = { path = "../ai", features = ["test-support"]}
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
|
||||
ai = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
|
||||
@@ -68,6 +68,7 @@ struct SavedConversation {
|
||||
messages: Vec<SavedMessage>,
|
||||
message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: String,
|
||||
api_url: Option<String>,
|
||||
model: OpenAiModel,
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
|
||||
};
|
||||
use ai::prompts::repository_context::PromptCodeSnippet;
|
||||
use ai::providers::open_ai::OPEN_AI_API_URL;
|
||||
use ai::{
|
||||
auth::ProviderCredential,
|
||||
completion::{CompletionProvider, CompletionRequest},
|
||||
@@ -121,10 +122,22 @@ impl AssistantPanel {
|
||||
.await
|
||||
.log_err()
|
||||
.unwrap_or_default();
|
||||
// Defaulting currently to GPT4, allow for this to be set via config.
|
||||
let completion_provider =
|
||||
OpenAiCompletionProvider::new("gpt-4".into(), cx.background_executor().clone())
|
||||
.await;
|
||||
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 completion_provider = OpenAiCompletionProvider::new(
|
||||
api_url,
|
||||
model_name,
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// TODO: deserialize state.
|
||||
let workspace_handle = workspace.clone();
|
||||
@@ -199,9 +212,13 @@ impl AssistantPanel {
|
||||
.update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
|
||||
cx.notify();
|
||||
if self.focus_handle.is_focused(cx) {
|
||||
if let Some(editor) = self.active_editor() {
|
||||
cx.focus_view(editor);
|
||||
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
||||
if self.has_credentials() {
|
||||
if let Some(editor) = self.active_editor() {
|
||||
cx.focus_view(editor);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
||||
cx.focus_view(api_key_editor);
|
||||
}
|
||||
}
|
||||
@@ -777,6 +794,10 @@ impl AssistantPanel {
|
||||
});
|
||||
}
|
||||
|
||||
fn build_api_key_editor(&mut self, cx: &mut WindowContext<'_>) {
|
||||
self.api_key_editor = Some(build_api_key_editor(cx));
|
||||
}
|
||||
|
||||
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
|
||||
let editor = cx.new_view(|cx| {
|
||||
ConversationEditor::new(
|
||||
@@ -870,7 +891,7 @@ impl AssistantPanel {
|
||||
cx.update(|cx| completion_provider.delete_credentials(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.api_key_editor = Some(build_api_key_editor(cx));
|
||||
this.build_api_key_editor(cx);
|
||||
this.focus_handle.focus(cx);
|
||||
cx.notify();
|
||||
})
|
||||
@@ -954,6 +975,7 @@ impl AssistantPanel {
|
||||
line_height: relative(1.3).into(),
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
};
|
||||
EditorElement::new(
|
||||
@@ -1136,7 +1158,7 @@ impl AssistantPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
|
||||
fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
|
||||
cx.new_view(|cx| {
|
||||
let mut editor = Editor::single_line(cx);
|
||||
editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
|
||||
@@ -1147,9 +1169,10 @@ fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
|
||||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
if let Some(api_key_editor) = self.api_key_editor.clone() {
|
||||
const INSTRUCTIONS: [&'static str; 5] = [
|
||||
const INSTRUCTIONS: [&'static str; 6] = [
|
||||
"To use the assistant panel or inline assistant, you need to add your OpenAI API key.",
|
||||
" - You can create an API key at: platform.openai.com/api-keys",
|
||||
" - Make sure your OpenAI account has credits",
|
||||
" - Having a subscription for another service like GitHub Copilot won't work.",
|
||||
" ",
|
||||
"Paste your OpenAI API key and press Enter to use the assistant:"
|
||||
@@ -1342,7 +1365,9 @@ impl Panel for AssistantPanel {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
load_credentials.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.editors.is_empty() {
|
||||
if !this.has_credentials() {
|
||||
this.build_api_key_editor(cx);
|
||||
} else if this.editors.is_empty() {
|
||||
this.new_conversation(cx);
|
||||
}
|
||||
})
|
||||
@@ -1395,6 +1420,7 @@ struct Conversation {
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
model: OpenAiModel,
|
||||
api_url: Option<String>,
|
||||
token_count: Option<usize>,
|
||||
max_token_count: usize,
|
||||
pending_token_count: Task<Option<()>>,
|
||||
@@ -1429,6 +1455,7 @@ impl Conversation {
|
||||
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let model = settings.default_open_ai_model.clone();
|
||||
let api_url = settings.openai_api_url.clone();
|
||||
|
||||
let mut this = Self {
|
||||
id: Some(Uuid::new_v4().to_string()),
|
||||
@@ -1442,6 +1469,7 @@ impl Conversation {
|
||||
token_count: None,
|
||||
max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
|
||||
pending_token_count: Task::ready(None),
|
||||
api_url: Some(api_url),
|
||||
model: model.clone(),
|
||||
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
|
||||
pending_save: Task::ready(Ok(())),
|
||||
@@ -1487,6 +1515,7 @@ impl Conversation {
|
||||
.map(|summary| summary.text.clone())
|
||||
.unwrap_or_default(),
|
||||
model: self.model.clone(),
|
||||
api_url: self.api_url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1501,8 +1530,12 @@ impl Conversation {
|
||||
None => Some(Uuid::new_v4().to_string()),
|
||||
};
|
||||
let model = saved_conversation.model;
|
||||
let api_url = saved_conversation.api_url;
|
||||
let completion_provider: Arc<dyn CompletionProvider> = Arc::new(
|
||||
OpenAiCompletionProvider::new(
|
||||
api_url
|
||||
.clone()
|
||||
.unwrap_or_else(|| OPEN_AI_API_URL.to_string()),
|
||||
model.full_name().into(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
@@ -1555,6 +1588,7 @@ impl Conversation {
|
||||
token_count: None,
|
||||
max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
|
||||
pending_token_count: Task::ready(None),
|
||||
api_url,
|
||||
model,
|
||||
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
|
||||
pending_save: Task::ready(Ok(())),
|
||||
@@ -3155,6 +3189,7 @@ impl InlineAssistant {
|
||||
line_height: relative(1.3).into(),
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
};
|
||||
EditorElement::new(
|
||||
|
||||
@@ -55,6 +55,7 @@ pub struct AssistantSettings {
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_open_ai_model: OpenAiModel,
|
||||
pub openai_api_url: String,
|
||||
}
|
||||
|
||||
/// Assistant panel settings
|
||||
@@ -80,6 +81,10 @@ pub struct AssistantSettingsContent {
|
||||
///
|
||||
/// Default: gpt-4-1106-preview
|
||||
pub default_open_ai_model: Option<OpenAiModel>,
|
||||
/// OpenAI API base URL to use when starting new conversations.
|
||||
///
|
||||
/// Default: https://api.openai.com/v1
|
||||
pub openai_api_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Settings for AssistantSettings {
|
||||
|
||||
@@ -366,7 +366,8 @@ mod tests {
|
||||
use gpui::{Context, TestAppContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
|
||||
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig,
|
||||
LanguageMatcher, Point,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use serde::Serialize;
|
||||
@@ -675,7 +676,10 @@ mod tests {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
|
||||
@@ -172,22 +172,24 @@ pub fn generate_content_prompt(
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{AppContext, Context};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
|
||||
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig,
|
||||
LanguageMatcher, Point,
|
||||
};
|
||||
use settings::SettingsStore;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) fn rust_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
|
||||
@@ -10,14 +10,12 @@ path = "src/audio.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
collections = { path = "../collections" }
|
||||
util = { path = "../util" }
|
||||
|
||||
|
||||
rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
|
||||
|
||||
log.workspace = true
|
||||
futures.workspace = true
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use assets::SoundRegistry;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{AppContext, AssetSource, Global};
|
||||
use rodio::{OutputStream, OutputStreamHandle};
|
||||
use util::ResultExt;
|
||||
@@ -7,7 +8,7 @@ mod assets;
|
||||
|
||||
pub fn init(source: impl AssetSource, cx: &mut AppContext) {
|
||||
SoundRegistry::set_global(source, cx);
|
||||
cx.set_global(Audio::new());
|
||||
cx.set_global(GlobalAudio(Audio::new()));
|
||||
}
|
||||
|
||||
pub enum Sound {
|
||||
@@ -37,7 +38,10 @@ pub struct Audio {
|
||||
output_handle: Option<OutputStreamHandle>,
|
||||
}
|
||||
|
||||
impl Global for Audio {}
|
||||
#[derive(Deref, DerefMut)]
|
||||
struct GlobalAudio(Audio);
|
||||
|
||||
impl Global for GlobalAudio {}
|
||||
|
||||
impl Audio {
|
||||
pub fn new() -> Self {
|
||||
@@ -58,11 +62,11 @@ impl Audio {
|
||||
}
|
||||
|
||||
pub fn play_sound(sound: Sound, cx: &mut AppContext) {
|
||||
if !cx.has_global::<Self>() {
|
||||
if !cx.has_global::<GlobalAudio>() {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.update_global::<Self, _>(|this, cx| {
|
||||
cx.update_global::<GlobalAudio, _>(|this, cx| {
|
||||
let output_handle = this.ensure_output_exists()?;
|
||||
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
|
||||
output_handle.play_raw(source).log_err()?;
|
||||
@@ -71,11 +75,11 @@ impl Audio {
|
||||
}
|
||||
|
||||
pub fn end_call(cx: &mut AppContext) {
|
||||
if !cx.has_global::<Self>() {
|
||||
if !cx.has_global::<GlobalAudio>() {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.update_global::<Self, _>(|this, _| {
|
||||
cx.update_global::<GlobalAudio, _>(|this, _| {
|
||||
this._output_stream.take();
|
||||
this.output_handle.take();
|
||||
});
|
||||
|
||||
@@ -10,23 +10,23 @@ path = "src/auto_update.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
db = { path = "../db" }
|
||||
client = { path = "../client" }
|
||||
gpui = { path = "../gpui" }
|
||||
menu = { path = "../menu" }
|
||||
project = { path = "../project" }
|
||||
release_channel = { path = "../release_channel" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace" }
|
||||
util = { path = "../util" }
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
db.workspace = true
|
||||
gpui.workspace = true
|
||||
isahc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
theme.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod update_notification;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION};
|
||||
use client::{Client, TelemetrySettings, ZED_APP_PATH};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use db::RELEASE_CHANNEL;
|
||||
use gpui::{
|
||||
@@ -108,29 +108,28 @@ pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
|
||||
})
|
||||
.detach();
|
||||
|
||||
if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
|
||||
let auto_updater = cx.new_model(|cx| {
|
||||
let updater = AutoUpdater::new(version, http_client);
|
||||
let version = release_channel::AppVersion::global(cx);
|
||||
let auto_updater = cx.new_model(|cx| {
|
||||
let updater = AutoUpdater::new(version, http_client);
|
||||
|
||||
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
||||
.0
|
||||
.then(|| updater.start_polling(cx));
|
||||
let mut update_subscription = AutoUpdateSetting::get_global(cx)
|
||||
.0
|
||||
.then(|| updater.start_polling(cx));
|
||||
|
||||
cx.observe_global::<SettingsStore>(move |updater, cx| {
|
||||
if AutoUpdateSetting::get_global(cx).0 {
|
||||
if update_subscription.is_none() {
|
||||
update_subscription = Some(updater.start_polling(cx))
|
||||
}
|
||||
} else {
|
||||
update_subscription.take();
|
||||
cx.observe_global::<SettingsStore>(move |updater, cx| {
|
||||
if AutoUpdateSetting::get_global(cx).0 {
|
||||
if update_subscription.is_none() {
|
||||
update_subscription = Some(updater.start_polling(cx))
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
update_subscription.take();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
updater
|
||||
});
|
||||
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
|
||||
}
|
||||
updater
|
||||
});
|
||||
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
|
||||
}
|
||||
|
||||
pub fn check(_: &Check, cx: &mut WindowContext) {
|
||||
|
||||
@@ -40,10 +40,11 @@ impl Render for UpdateNotification {
|
||||
.id("notes")
|
||||
.child(Label::new("View the release notes"))
|
||||
.cursor_pointer()
|
||||
.on_click(|_, cx| {
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
crate::view_release_notes(&Default::default(), cx);
|
||||
}),
|
||||
)
|
||||
this.dismiss(&menu::Cancel, cx)
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,20 +10,20 @@ path = "src/breadcrumbs.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
editor = { path = "../editor" }
|
||||
gpui = { path = "../gpui" }
|
||||
ui = { path = "../ui" }
|
||||
language = { path = "../language" }
|
||||
project = { path = "../project" }
|
||||
search = { path = "../search" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace" }
|
||||
outline = { path = "../outline" }
|
||||
collections.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools = "0.10"
|
||||
language.workspace = true
|
||||
outline.workspace = true
|
||||
project.workspace = true
|
||||
search.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -20,36 +20,35 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
audio = { path = "../audio" }
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections" }
|
||||
gpui = { path = "../gpui" }
|
||||
log.workspace = true
|
||||
live_kit_client = { path = "../live_kit_client" }
|
||||
fs = { path = "../fs" }
|
||||
language = { path = "../language" }
|
||||
media = { path = "../media" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
util = { path = "../util" }
|
||||
|
||||
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_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
fs = { path = "../fs", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
live_kit_client = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -617,6 +617,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,
|
||||
@@ -1202,7 +1206,7 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn share_project(
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
|
||||
@@ -13,44 +13,43 @@ doctest = false
|
||||
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
|
||||
|
||||
[dependencies]
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections" }
|
||||
db = { path = "../db" }
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
rpc = { path = "../rpc" }
|
||||
text = { path = "../text" }
|
||||
language = { path = "../language" }
|
||||
release_channel = { path = "../release_channel" }
|
||||
settings = { path = "../settings" }
|
||||
feature_flags = { path = "../feature_flags" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
clock = { path = "../clock" }
|
||||
|
||||
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
|
||||
smallvec.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
|
||||
url = "2.2"
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
tempfile = "3"
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -5,12 +5,13 @@ use client::{
|
||||
user::{User, UserStore},
|
||||
Client, Subscription, TypedEnvelope, UserId,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use futures::lock::Mutex;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
mem,
|
||||
ops::{ControlFlow, Range},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -26,6 +27,7 @@ pub struct ChannelChat {
|
||||
loaded_all_messages: bool,
|
||||
last_acknowledged_id: Option<u64>,
|
||||
next_pending_message_id: usize,
|
||||
first_loaded_message_id: Option<u64>,
|
||||
user_store: Model<UserStore>,
|
||||
rpc: Arc<Client>,
|
||||
outgoing_messages_lock: Arc<Mutex<()>>,
|
||||
@@ -37,6 +39,7 @@ pub struct ChannelChat {
|
||||
pub struct MessageParams {
|
||||
pub text: String,
|
||||
pub mentions: Vec<(Range<usize>, UserId)>,
|
||||
pub reply_to_message_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -47,6 +50,7 @@ pub struct ChannelMessage {
|
||||
pub sender: Arc<User>,
|
||||
pub nonce: u128,
|
||||
pub mentions: Vec<(Range<usize>, UserId)>,
|
||||
pub reply_to_message_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -55,6 +59,15 @@ pub enum ChannelMessageId {
|
||||
Pending(usize),
|
||||
}
|
||||
|
||||
impl Into<Option<u64>> for ChannelMessageId {
|
||||
fn into(self) -> Option<u64> {
|
||||
match self {
|
||||
ChannelMessageId::Saved(id) => Some(id),
|
||||
ChannelMessageId::Pending(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ChannelMessageSummary {
|
||||
max_id: ChannelMessageId,
|
||||
@@ -96,28 +109,35 @@ impl ChannelChat {
|
||||
let response = client
|
||||
.request(proto::JoinChannelChat { channel_id })
|
||||
.await?;
|
||||
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
||||
let loaded_all_messages = response.done;
|
||||
|
||||
Ok(cx.new_model(|cx| {
|
||||
let handle = cx.new_model(|cx| {
|
||||
cx.on_release(Self::release).detach();
|
||||
let mut this = Self {
|
||||
Self {
|
||||
channel_id: channel.id,
|
||||
user_store,
|
||||
user_store: user_store.clone(),
|
||||
channel_store,
|
||||
rpc: client,
|
||||
rpc: client.clone(),
|
||||
outgoing_messages_lock: Default::default(),
|
||||
messages: Default::default(),
|
||||
acknowledged_message_ids: Default::default(),
|
||||
loaded_all_messages,
|
||||
loaded_all_messages: false,
|
||||
next_pending_message_id: 0,
|
||||
last_acknowledged_id: None,
|
||||
rng: StdRng::from_entropy(),
|
||||
first_loaded_message_id: None,
|
||||
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
|
||||
};
|
||||
this.insert_messages(messages, cx);
|
||||
this
|
||||
})?)
|
||||
}
|
||||
})?;
|
||||
Self::handle_loaded_messages(
|
||||
handle.downgrade(),
|
||||
user_store,
|
||||
client,
|
||||
response.messages,
|
||||
response.done,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
fn release(&mut self, _: &mut AppContext) {
|
||||
@@ -166,6 +186,7 @@ impl ChannelChat {
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
mentions: message.mentions.clone(),
|
||||
nonce,
|
||||
reply_to_message_id: message.reply_to_message_id,
|
||||
},
|
||||
&(),
|
||||
),
|
||||
@@ -183,6 +204,7 @@ impl ChannelChat {
|
||||
body: message.text,
|
||||
nonce: Some(nonce.into()),
|
||||
mentions: mentions_to_proto(&message.mentions),
|
||||
reply_to_message_id: message.reply_to_message_id,
|
||||
});
|
||||
let response = request.await?;
|
||||
drop(outgoing_message_guard);
|
||||
@@ -227,12 +249,16 @@ impl ChannelChat {
|
||||
before_message_id,
|
||||
})
|
||||
.await?;
|
||||
let loaded_all_messages = response.done;
|
||||
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.loaded_all_messages = loaded_all_messages;
|
||||
this.insert_messages(messages, cx);
|
||||
})?;
|
||||
Self::handle_loaded_messages(
|
||||
this,
|
||||
user_store,
|
||||
rpc,
|
||||
response.messages,
|
||||
response.done,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
@@ -240,9 +266,14 @@ impl ChannelChat {
|
||||
}
|
||||
|
||||
pub fn first_loaded_message_id(&mut self) -> Option<u64> {
|
||||
self.messages.first().and_then(|message| match message.id {
|
||||
ChannelMessageId::Saved(id) => Some(id),
|
||||
ChannelMessageId::Pending(_) => None,
|
||||
self.first_loaded_message_id
|
||||
}
|
||||
|
||||
/// Load a message by its id, if it's already stored locally.
|
||||
pub fn find_loaded_message(&self, id: u64) -> Option<&ChannelMessage> {
|
||||
self.messages.iter().find(|message| match message.id {
|
||||
ChannelMessageId::Saved(message_id) => message_id == id,
|
||||
ChannelMessageId::Pending(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -304,6 +335,66 @@ impl ChannelChat {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_loaded_messages(
|
||||
this: WeakModel<Self>,
|
||||
user_store: Model<UserStore>,
|
||||
rpc: Arc<Client>,
|
||||
proto_messages: Vec<proto::ChannelMessage>,
|
||||
loaded_all_messages: bool,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let loaded_messages = messages_from_proto(proto_messages, &user_store, cx).await?;
|
||||
|
||||
let first_loaded_message_id = loaded_messages.first().map(|m| m.id);
|
||||
let loaded_message_ids = this.update(cx, |this, _| {
|
||||
let mut loaded_message_ids: HashSet<u64> = HashSet::default();
|
||||
for message in loaded_messages.iter() {
|
||||
if let Some(saved_message_id) = message.id.into() {
|
||||
loaded_message_ids.insert(saved_message_id);
|
||||
}
|
||||
}
|
||||
for message in this.messages.iter() {
|
||||
if let Some(saved_message_id) = message.id.into() {
|
||||
loaded_message_ids.insert(saved_message_id);
|
||||
}
|
||||
}
|
||||
loaded_message_ids
|
||||
})?;
|
||||
|
||||
let missing_ancestors = loaded_messages
|
||||
.iter()
|
||||
.filter_map(|message| {
|
||||
if let Some(ancestor_id) = message.reply_to_message_id {
|
||||
if !loaded_message_ids.contains(&ancestor_id) {
|
||||
return Some(ancestor_id);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let loaded_ancestors = if missing_ancestors.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let response = rpc
|
||||
.request(proto::GetChannelMessagesById {
|
||||
message_ids: missing_ancestors,
|
||||
})
|
||||
.await?;
|
||||
Some(messages_from_proto(response.messages, &user_store, cx).await?)
|
||||
};
|
||||
this.update(cx, |this, cx| {
|
||||
this.first_loaded_message_id = first_loaded_message_id.and_then(|msg_id| msg_id.into());
|
||||
this.loaded_all_messages = loaded_all_messages;
|
||||
this.insert_messages(loaded_messages, cx);
|
||||
if let Some(loaded_ancestors) = loaded_ancestors {
|
||||
this.insert_messages(loaded_ancestors, cx);
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rejoin(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let user_store = self.user_store.clone();
|
||||
let rpc = self.rpc.clone();
|
||||
@@ -311,28 +402,17 @@ impl ChannelChat {
|
||||
cx.spawn(move |this, mut cx| {
|
||||
async move {
|
||||
let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
|
||||
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
||||
let loaded_all_messages = response.done;
|
||||
|
||||
let pending_messages = this.update(&mut cx, |this, cx| {
|
||||
if let Some((first_new_message, last_old_message)) =
|
||||
messages.first().zip(this.messages.last())
|
||||
{
|
||||
if first_new_message.id > last_old_message.id {
|
||||
let old_messages = mem::take(&mut this.messages);
|
||||
cx.emit(ChannelChatEvent::MessagesUpdated {
|
||||
old_range: 0..old_messages.summary().count,
|
||||
new_count: 0,
|
||||
});
|
||||
this.loaded_all_messages = loaded_all_messages;
|
||||
}
|
||||
}
|
||||
|
||||
this.insert_messages(messages, cx);
|
||||
if loaded_all_messages {
|
||||
this.loaded_all_messages = loaded_all_messages;
|
||||
}
|
||||
Self::handle_loaded_messages(
|
||||
this.clone(),
|
||||
user_store.clone(),
|
||||
rpc.clone(),
|
||||
response.messages,
|
||||
response.done,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let pending_messages = this.update(&mut cx, |this, _| {
|
||||
this.pending_messages().cloned().collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
@@ -342,6 +422,7 @@ impl ChannelChat {
|
||||
body: pending_message.body,
|
||||
mentions: mentions_to_proto(&pending_message.mentions),
|
||||
nonce: Some(pending_message.nonce.into()),
|
||||
reply_to_message_id: pending_message.reply_to_message_id,
|
||||
});
|
||||
let response = request.await?;
|
||||
let message = ChannelMessage::from_proto(
|
||||
@@ -553,6 +634,7 @@ impl ChannelMessage {
|
||||
.nonce
|
||||
.ok_or_else(|| anyhow!("nonce is required"))?
|
||||
.into(),
|
||||
reply_to_message_id: message.reply_to_message_id,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -642,6 +724,7 @@ impl<'a> From<&'a str> for MessageParams {
|
||||
Self {
|
||||
text: value.into(),
|
||||
mentions: Vec::new(),
|
||||
reply_to_message_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,11 +74,19 @@ impl Channel {
|
||||
pub fn link(&self) -> String {
|
||||
RELEASE_CHANNEL.link_prefix().to_owned()
|
||||
+ "channel/"
|
||||
+ &self.slug()
|
||||
+ &Self::slug(&self.name)
|
||||
+ "-"
|
||||
+ &self.id.to_string()
|
||||
}
|
||||
|
||||
pub fn notes_link(&self, heading: Option<String>) -> String {
|
||||
self.link()
|
||||
+ "/notes"
|
||||
+ &heading
|
||||
.map(|h| format!("#{}", Self::slug(&h)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_root_channel(&self) -> bool {
|
||||
self.parent_path.is_empty()
|
||||
}
|
||||
@@ -90,9 +98,8 @@ impl Channel {
|
||||
.unwrap_or(self.id)
|
||||
}
|
||||
|
||||
pub fn slug(&self) -> String {
|
||||
let slug: String = self
|
||||
.name
|
||||
pub fn slug(str: &str) -> String {
|
||||
let slug: String = str
|
||||
.chars()
|
||||
.map(|c| if c.is_alphanumeric() { c } else { '-' })
|
||||
.collect();
|
||||
@@ -1124,9 +1131,10 @@ impl ChannelState {
|
||||
if let Some(latest_version) = &self.latest_notes_versions {
|
||||
if let Some(observed_version) = &self.observed_notes_versions {
|
||||
latest_version.epoch > observed_version.epoch
|
||||
|| latest_version
|
||||
.version
|
||||
.changed_since(&observed_version.version)
|
||||
|| (latest_version.epoch == observed_version.epoch
|
||||
&& latest_version
|
||||
.version
|
||||
.changed_since(&observed_version.version))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -184,6 +184,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
sender_id: 5,
|
||||
mentions: vec![],
|
||||
nonce: Some(1.into()),
|
||||
reply_to_message_id: None,
|
||||
},
|
||||
proto::ChannelMessage {
|
||||
id: 11,
|
||||
@@ -192,6 +193,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
sender_id: 6,
|
||||
mentions: vec![],
|
||||
nonce: Some(2.into()),
|
||||
reply_to_message_id: None,
|
||||
},
|
||||
],
|
||||
done: false,
|
||||
@@ -239,6 +241,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
sender_id: 7,
|
||||
mentions: vec![],
|
||||
nonce: Some(3.into()),
|
||||
reply_to_message_id: None,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -292,6 +295,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
sender_id: 5,
|
||||
nonce: Some(4.into()),
|
||||
mentions: vec![],
|
||||
reply_to_message_id: None,
|
||||
},
|
||||
proto::ChannelMessage {
|
||||
id: 9,
|
||||
@@ -300,6 +304,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
sender_id: 6,
|
||||
nonce: Some(5.into()),
|
||||
mentions: vec![],
|
||||
reply_to_message_id: None,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -329,6 +334,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
|
||||
@@ -20,7 +20,7 @@ dirs = "3.0"
|
||||
ipc-channel = "0.16"
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
util = { path = "../util" }
|
||||
util.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
#![cfg_attr(target_os = "linux", allow(dead_code))]
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::Parser;
|
||||
use cli::{CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME};
|
||||
use core_foundation::{
|
||||
array::{CFArray, CFIndex},
|
||||
string::kCFStringEncodingUTF8,
|
||||
url::{CFURLCreateWithBytes, CFURL},
|
||||
};
|
||||
use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType};
|
||||
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
|
||||
use cli::{CliRequest, CliResponse};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs::{self, OpenOptions},
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
ptr,
|
||||
};
|
||||
use util::paths::PathLikeWithPosition;
|
||||
|
||||
@@ -112,136 +106,6 @@ enum Bundle {
|
||||
},
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
fn detect(args_bundle_path: Option<&Path>) -> anyhow::Result<Self> {
|
||||
let bundle_path = if let Some(bundle_path) = args_bundle_path {
|
||||
bundle_path
|
||||
.canonicalize()
|
||||
.with_context(|| format!("Args bundle path {bundle_path:?} canonicalization"))?
|
||||
} else {
|
||||
locate_bundle().context("bundle autodiscovery")?
|
||||
};
|
||||
|
||||
match bundle_path.extension().and_then(|ext| ext.to_str()) {
|
||||
Some("app") => {
|
||||
let plist_path = bundle_path.join("Contents/Info.plist");
|
||||
let plist = plist::from_file::<_, InfoPlist>(&plist_path).with_context(|| {
|
||||
format!("Reading *.app bundle plist file at {plist_path:?}")
|
||||
})?;
|
||||
Ok(Self::App {
|
||||
app_bundle: bundle_path,
|
||||
plist,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
println!("Bundle path {bundle_path:?} has no *.app extension, attempting to locate a dev build");
|
||||
let plist_path = bundle_path
|
||||
.parent()
|
||||
.with_context(|| format!("Bundle path {bundle_path:?} has no parent"))?
|
||||
.join("WebRTC.framework/Resources/Info.plist");
|
||||
let plist = plist::from_file::<_, InfoPlist>(&plist_path)
|
||||
.with_context(|| format!("Reading dev bundle plist file at {plist_path:?}"))?;
|
||||
Ok(Self::LocalPath {
|
||||
executable: bundle_path,
|
||||
plist,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn plist(&self) -> &InfoPlist {
|
||||
match self {
|
||||
Self::App { plist, .. } => plist,
|
||||
Self::LocalPath { plist, .. } => plist,
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
match self {
|
||||
Self::App { app_bundle, .. } => app_bundle,
|
||||
Self::LocalPath { executable, .. } => executable,
|
||||
}
|
||||
}
|
||||
|
||||
fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
|
||||
let (server, server_name) =
|
||||
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
|
||||
let url = format!("zed-cli://{server_name}");
|
||||
|
||||
match self {
|
||||
Self::App { app_bundle, .. } => {
|
||||
let app_path = app_bundle;
|
||||
|
||||
let status = unsafe {
|
||||
let app_url = CFURL::from_path(app_path, true)
|
||||
.with_context(|| format!("invalid app path {app_path:?}"))?;
|
||||
let url_to_open = CFURL::wrap_under_create_rule(CFURLCreateWithBytes(
|
||||
ptr::null(),
|
||||
url.as_ptr(),
|
||||
url.len() as CFIndex,
|
||||
kCFStringEncodingUTF8,
|
||||
ptr::null(),
|
||||
));
|
||||
// equivalent to: open zed-cli:... -a /Applications/Zed\ Preview.app
|
||||
let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]);
|
||||
LSOpenFromURLSpec(
|
||||
&LSLaunchURLSpec {
|
||||
appURL: app_url.as_concrete_TypeRef(),
|
||||
itemURLs: urls_to_open.as_concrete_TypeRef(),
|
||||
passThruParams: ptr::null(),
|
||||
launchFlags: kLSLaunchDefaults,
|
||||
asyncRefCon: ptr::null_mut(),
|
||||
},
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
anyhow::ensure!(
|
||||
status == 0,
|
||||
"cannot start app bundle {}",
|
||||
self.zed_version_string()
|
||||
);
|
||||
}
|
||||
|
||||
Self::LocalPath { executable, .. } => {
|
||||
let executable_parent = executable
|
||||
.parent()
|
||||
.with_context(|| format!("Executable {executable:?} path has no parent"))?;
|
||||
let subprocess_stdout_file =
|
||||
fs::File::create(executable_parent.join("zed_dev.log"))
|
||||
.with_context(|| format!("Log file creation in {executable_parent:?}"))?;
|
||||
let subprocess_stdin_file =
|
||||
subprocess_stdout_file.try_clone().with_context(|| {
|
||||
format!("Cloning descriptor for file {subprocess_stdout_file:?}")
|
||||
})?;
|
||||
let mut command = std::process::Command::new(executable);
|
||||
let command = command
|
||||
.env(FORCE_CLI_MODE_ENV_VAR_NAME, "")
|
||||
.stderr(subprocess_stdout_file)
|
||||
.stdout(subprocess_stdin_file)
|
||||
.arg(url);
|
||||
|
||||
command
|
||||
.spawn()
|
||||
.with_context(|| format!("Spawning {command:?}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
||||
Ok((handshake.requests, handshake.responses))
|
||||
}
|
||||
|
||||
fn zed_version_string(&self) -> String {
|
||||
let is_dev = matches!(self, Self::LocalPath { .. });
|
||||
format!(
|
||||
"Zed {}{} – {}",
|
||||
self.plist().bundle_short_version_string,
|
||||
if is_dev { " (dev)" } else { "" },
|
||||
self.path().display(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn touch(path: &Path) -> io::Result<()> {
|
||||
match OpenOptions::new().create(true).write(true).open(path) {
|
||||
Ok(_) => Ok(()),
|
||||
@@ -259,3 +123,187 @@ fn locate_bundle() -> Result<PathBuf> {
|
||||
}
|
||||
Ok(app_path)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux {
|
||||
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;
|
||||
use core_foundation::{
|
||||
array::{CFArray, CFIndex},
|
||||
string::kCFStringEncodingUTF8,
|
||||
url::{CFURLCreateWithBytes, CFURL},
|
||||
};
|
||||
use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType};
|
||||
use std::{fs, path::Path, ptr};
|
||||
|
||||
use cli::{CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME};
|
||||
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
|
||||
|
||||
use crate::{locate_bundle, Bundle, InfoPlist};
|
||||
|
||||
impl Bundle {
|
||||
pub fn detect(args_bundle_path: Option<&Path>) -> anyhow::Result<Self> {
|
||||
let bundle_path = if let Some(bundle_path) = args_bundle_path {
|
||||
bundle_path
|
||||
.canonicalize()
|
||||
.with_context(|| format!("Args bundle path {bundle_path:?} canonicalization"))?
|
||||
} else {
|
||||
locate_bundle().context("bundle autodiscovery")?
|
||||
};
|
||||
|
||||
match bundle_path.extension().and_then(|ext| ext.to_str()) {
|
||||
Some("app") => {
|
||||
let plist_path = bundle_path.join("Contents/Info.plist");
|
||||
let plist =
|
||||
plist::from_file::<_, InfoPlist>(&plist_path).with_context(|| {
|
||||
format!("Reading *.app bundle plist file at {plist_path:?}")
|
||||
})?;
|
||||
Ok(Self::App {
|
||||
app_bundle: bundle_path,
|
||||
plist,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
println!("Bundle path {bundle_path:?} has no *.app extension, attempting to locate a dev build");
|
||||
let plist_path = bundle_path
|
||||
.parent()
|
||||
.with_context(|| format!("Bundle path {bundle_path:?} has no parent"))?
|
||||
.join("WebRTC.framework/Resources/Info.plist");
|
||||
let plist =
|
||||
plist::from_file::<_, InfoPlist>(&plist_path).with_context(|| {
|
||||
format!("Reading dev bundle plist file at {plist_path:?}")
|
||||
})?;
|
||||
Ok(Self::LocalPath {
|
||||
executable: bundle_path,
|
||||
plist,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn plist(&self) -> &InfoPlist {
|
||||
match self {
|
||||
Self::App { plist, .. } => plist,
|
||||
Self::LocalPath { plist, .. } => plist,
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
match self {
|
||||
Self::App { app_bundle, .. } => app_bundle,
|
||||
Self::LocalPath { executable, .. } => executable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
|
||||
let (server, server_name) =
|
||||
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
|
||||
let url = format!("zed-cli://{server_name}");
|
||||
|
||||
match self {
|
||||
Self::App { app_bundle, .. } => {
|
||||
let app_path = app_bundle;
|
||||
|
||||
let status = unsafe {
|
||||
let app_url = CFURL::from_path(app_path, true)
|
||||
.with_context(|| format!("invalid app path {app_path:?}"))?;
|
||||
let url_to_open = CFURL::wrap_under_create_rule(CFURLCreateWithBytes(
|
||||
ptr::null(),
|
||||
url.as_ptr(),
|
||||
url.len() as CFIndex,
|
||||
kCFStringEncodingUTF8,
|
||||
ptr::null(),
|
||||
));
|
||||
// equivalent to: open zed-cli:... -a /Applications/Zed\ Preview.app
|
||||
let urls_to_open =
|
||||
CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]);
|
||||
LSOpenFromURLSpec(
|
||||
&LSLaunchURLSpec {
|
||||
appURL: app_url.as_concrete_TypeRef(),
|
||||
itemURLs: urls_to_open.as_concrete_TypeRef(),
|
||||
passThruParams: ptr::null(),
|
||||
launchFlags: kLSLaunchDefaults,
|
||||
asyncRefCon: ptr::null_mut(),
|
||||
},
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
anyhow::ensure!(
|
||||
status == 0,
|
||||
"cannot start app bundle {}",
|
||||
self.zed_version_string()
|
||||
);
|
||||
}
|
||||
|
||||
Self::LocalPath { executable, .. } => {
|
||||
let executable_parent = executable
|
||||
.parent()
|
||||
.with_context(|| format!("Executable {executable:?} path has no parent"))?;
|
||||
let subprocess_stdout_file = fs::File::create(
|
||||
executable_parent.join("zed_dev.log"),
|
||||
)
|
||||
.with_context(|| format!("Log file creation in {executable_parent:?}"))?;
|
||||
let subprocess_stdin_file =
|
||||
subprocess_stdout_file.try_clone().with_context(|| {
|
||||
format!("Cloning descriptor for file {subprocess_stdout_file:?}")
|
||||
})?;
|
||||
let mut command = std::process::Command::new(executable);
|
||||
let command = command
|
||||
.env(FORCE_CLI_MODE_ENV_VAR_NAME, "")
|
||||
.stderr(subprocess_stdout_file)
|
||||
.stdout(subprocess_stdin_file)
|
||||
.arg(url);
|
||||
|
||||
command
|
||||
.spawn()
|
||||
.with_context(|| format!("Spawning {command:?}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
||||
Ok((handshake.requests, handshake.responses))
|
||||
}
|
||||
|
||||
pub fn zed_version_string(&self) -> String {
|
||||
let is_dev = matches!(self, Self::LocalPath { .. });
|
||||
format!(
|
||||
"Zed {}{} – {}",
|
||||
self.plist().bundle_short_version_string,
|
||||
if is_dev { " (dev)" } else { "" },
|
||||
self.path().display(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,24 +14,25 @@ test-support = ["collections/test-support", "gpui/test-support", "rpc/test-suppo
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
collections = { path = "../collections" }
|
||||
db = { path = "../db" }
|
||||
gpui = { path = "../gpui" }
|
||||
util = { path = "../util" }
|
||||
release_channel = { path = "../release_channel" }
|
||||
rpc = { path = "../rpc" }
|
||||
text = { path = "../text" }
|
||||
settings = { path = "../settings" }
|
||||
feature_flags = { path = "../feature_flags" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
gpui.workspace = true
|
||||
util.workspace = true
|
||||
release_channel.workspace = true
|
||||
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-tls"] }
|
||||
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"
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand.workspace = true
|
||||
@@ -39,18 +40,19 @@ schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2 = "0.10"
|
||||
smol.workspace = true
|
||||
sysinfo.workspace = true
|
||||
tempfile = "3"
|
||||
tempfile.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
uuid.workspace = true
|
||||
url = "2.2"
|
||||
url.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -10,19 +10,19 @@ use async_tungstenite::tungstenite::{
|
||||
error::Error as WebsocketError,
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use collections::HashMap;
|
||||
use futures::{
|
||||
channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt,
|
||||
TryFutureExt as _, TryStreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, SemanticVersion,
|
||||
Task, WeakModel,
|
||||
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use rand::prelude::*;
|
||||
use release_channel::ReleaseChannel;
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -30,7 +30,6 @@ use serde_json;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::HashMap,
|
||||
convert::TryFrom,
|
||||
fmt::Write as _,
|
||||
future::Future,
|
||||
@@ -58,9 +57,6 @@ lazy_static! {
|
||||
pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
|
||||
.ok()
|
||||
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||
pub static ref ZED_APP_VERSION: Option<SemanticVersion> = std::env::var("ZED_APP_VERSION")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok());
|
||||
pub static ref ZED_APP_PATH: Option<PathBuf> =
|
||||
std::env::var("ZED_APP_PATH").ok().map(PathBuf::from);
|
||||
pub static ref ZED_ALWAYS_ACTIVE: bool =
|
||||
@@ -1011,13 +1007,22 @@ impl Client {
|
||||
.update(|cx| ReleaseChannel::try_global(cx))
|
||||
.ok()
|
||||
.flatten();
|
||||
let app_version = cx
|
||||
.update(|cx| AppVersion::global(cx).to_string())
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
let request = Request::builder()
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("{} {}", credentials.user_id, credentials.access_token),
|
||||
)
|
||||
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
|
||||
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION)
|
||||
.header("x-zed-app-version", app_version)
|
||||
.header(
|
||||
"x-zed-release-channel",
|
||||
release_channel.map(|r| r.dev_name()).unwrap_or("unknown"),
|
||||
);
|
||||
|
||||
let http = self.http.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
@@ -1035,7 +1040,7 @@ impl Client {
|
||||
rpc_url.set_scheme("wss").unwrap();
|
||||
let request = request.uri(rpc_url.as_str()).body(())?;
|
||||
let (stream, _) =
|
||||
async_tungstenite::async_tls::client_async_tls(request, stream).await?;
|
||||
async_tungstenite::async_std::client_async_tls(request, stream).await?;
|
||||
Ok(Connection::new(
|
||||
stream
|
||||
.map_err(|error| anyhow!(error))
|
||||
|
||||
@@ -4,16 +4,19 @@ use crate::TelemetrySettings;
|
||||
use chrono::{DateTime, Utc};
|
||||
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 std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::Write;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use sysinfo::{
|
||||
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use util::http::{HttpClient, ZedHttpClient};
|
||||
use util::http::{self, HttpClient, Method, ZedHttpClient};
|
||||
#[cfg(not(debug_assertions))]
|
||||
use util::ResultExt;
|
||||
use util::TryFutureExt;
|
||||
@@ -142,6 +145,16 @@ 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())
|
||||
.or_else(|| {
|
||||
env::var("ZED_CLIENT_CHECKSUM_SEED")
|
||||
.ok()
|
||||
.map(|s| s.as_bytes().into())
|
||||
})
|
||||
});
|
||||
|
||||
impl Telemetry {
|
||||
pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||
let release_channel =
|
||||
@@ -500,6 +513,10 @@ impl Telemetry {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(checksum_seed) = &*ZED_CLIENT_CHECKSUM_SEED else {
|
||||
return;
|
||||
};
|
||||
|
||||
let this = self.clone();
|
||||
self.executor
|
||||
.spawn(
|
||||
@@ -540,9 +557,27 @@ impl Telemetry {
|
||||
serde_json::to_writer(&mut json_bytes, &request_body)?;
|
||||
}
|
||||
|
||||
this.http_client
|
||||
.post_json(&this.http_client.zed_url("/api/events"), json_bytes.into())
|
||||
.await?;
|
||||
let mut summer = Sha256::new();
|
||||
summer.update(checksum_seed);
|
||||
summer.update(&json_bytes);
|
||||
summer.update(checksum_seed);
|
||||
let mut checksum = String::new();
|
||||
for byte in summer.finalize().as_slice() {
|
||||
use std::fmt::Write;
|
||||
write!(&mut checksum, "{:02x}", byte).unwrap();
|
||||
}
|
||||
|
||||
let request = http::Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(&this.http_client.zed_url("/api/events"))
|
||||
.header("Content-Type", "text/plain")
|
||||
.header("x-zed-checksum", checksum)
|
||||
.body(json_bytes.into());
|
||||
|
||||
let response = this.http_client.send(request?).await?;
|
||||
if response.status() != 200 {
|
||||
log::error!("Failed to send events: HTTP {:?}", response.status());
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err(),
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||
default-run = "collab"
|
||||
edition = "2021"
|
||||
name = "collab"
|
||||
version = "0.43.0"
|
||||
version = "0.44.0"
|
||||
publish = false
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
@@ -15,26 +15,22 @@ name = "seed"
|
||||
required-features = ["seed-support"]
|
||||
|
||||
[dependencies]
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
live_kit_server = { path = "../live_kit_server" }
|
||||
text = { path = "../text" }
|
||||
rpc = { path = "../rpc" }
|
||||
util = { path = "../util" }
|
||||
|
||||
anyhow.workspace = true
|
||||
async-tungstenite = "0.16"
|
||||
axum = { version = "0.5", features = ["json", "headers", "ws"] }
|
||||
axum-extra = { version = "0.3", features = ["erased-json"] }
|
||||
base64 = "0.13"
|
||||
clap = { version = "3.1", features = ["derive"], optional = true }
|
||||
chrono.workspace = true
|
||||
clap = { version = "3.1", features = ["derive"], optional = true }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
dashmap = "5.4"
|
||||
envy = "0.4.2"
|
||||
futures.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"
|
||||
parking_lot.workspace = true
|
||||
@@ -42,62 +38,63 @@ prometheus = "0.13"
|
||||
prost.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest = { version = "0.11", features = ["json"], optional = true }
|
||||
rpc.workspace = true
|
||||
scrypt = "0.7"
|
||||
smallvec.workspace = true
|
||||
sea-orm = { version = "0.12.x", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha-1 = "0.9"
|
||||
smallvec.workspace = true
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-tungstenite = "0.17"
|
||||
toml.workspace = true
|
||||
tonic = "0.6"
|
||||
tower = "0.4"
|
||||
toml.workspace = true
|
||||
tracing = "0.1.34"
|
||||
tracing-log = "0.1.3"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
audio = { path = "../audio" }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
call = { path = "../call", features = ["test-support"] }
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
channel = { path = "../channel" }
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
fs = { path = "../fs", features = ["test-support"] }
|
||||
git = { path = "../git", features = ["test-support"] }
|
||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
node_runtime = { path = "../node_runtime" }
|
||||
notifications = { path = "../notifications", features = ["test-support"] }
|
||||
file_finder = { path = "../file_finder"}
|
||||
menu = { path = "../menu"}
|
||||
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
theme = { path = "../theme" }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
collab_ui = { path = "../collab_ui", features = ["test-support"] }
|
||||
|
||||
release_channel.workspace = true
|
||||
async-trait.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
audio.workspace = true
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
channel.workspace = true
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
collab_ui = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
file_finder.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
git = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
util = { path = "../util" }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
lazy_static.workspace = true
|
||||
live_kit_client = { workspace = true, features = ["test-support"] }
|
||||
lsp = { workspace = true, features = ["test-support"] }
|
||||
menu.workspace = true
|
||||
node_runtime.workspace = true
|
||||
notifications = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
sea-orm = { version = "0.12.x", features = ["sqlx-sqlite"] }
|
||||
serde_json.workspace = true
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
sqlx = { version = "0.7", features = ["sqlite"] }
|
||||
theme.workspace = true
|
||||
unindent.workspace = true
|
||||
util.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[features]
|
||||
seed-support = ["clap", "lipsum", "reqwest"]
|
||||
|
||||
@@ -3,3 +3,35 @@
|
||||
This crate is what we run at https://collab.zed.dev.
|
||||
|
||||
It contains our back-end logic for collaboration, to which we connect from the Zed client via a websocket after authenticating via https://zed.dev, which is a separate repo running on Vercel.
|
||||
|
||||
# Local Development
|
||||
|
||||
Detailed instructions on getting started are [here](https://zed.dev/docs/local-collaboration).
|
||||
|
||||
# Deployment
|
||||
|
||||
We run two instances of collab:
|
||||
|
||||
* Staging (https://staging-collab.zed.dev)
|
||||
* Production (https://collab.zed.dev)
|
||||
|
||||
Both of these run on the Kubernetes cluster hosted in Digital Ocean.
|
||||
|
||||
Deployment is triggered by pushing to the `collab-staging` (or `collab-production`) tag in Github. The best way to do this is:
|
||||
|
||||
* `./script/deploy-collab staging`
|
||||
* `./script/deploy-collab production`
|
||||
|
||||
You can tell what is currently deployed with `./script/what-is-deployed`.
|
||||
|
||||
# Database Migrations
|
||||
|
||||
To create a new migration:
|
||||
|
||||
```
|
||||
./script/sqlx migrate add <name>
|
||||
```
|
||||
|
||||
Migrations are run automatically on service start, so run `foreman start` again. The service will crash if the migrations fail.
|
||||
|
||||
When you create a new migration, you also need to update the [SQLite schema](./migrations.sqlite/20221109000000_test_schema.sql) that is used for testing.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
ZED_ENVIRONMENT=nightly
|
||||
RUST_LOG=info
|
||||
INVITE_LINK_PREFIX=https://zed.dev/invites/
|
||||
DATABASE_MAX_CONNECTIONS=10
|
||||
@@ -1,4 +0,0 @@
|
||||
ZED_ENVIRONMENT=preview
|
||||
RUST_LOG=info
|
||||
INVITE_LINK_PREFIX=https://zed.dev/invites/
|
||||
DATABASE_MAX_CONNECTIONS=10
|
||||
@@ -163,7 +163,8 @@ CREATE TABLE "room_participants" (
|
||||
"calling_connection_id" INTEGER NOT NULL,
|
||||
"calling_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE SET NULL,
|
||||
"participant_index" INTEGER,
|
||||
"role" TEXT
|
||||
"role" TEXT,
|
||||
"in_call" BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
CREATE UNIQUE INDEX "index_room_participants_on_user_id" ON "room_participants" ("user_id");
|
||||
CREATE INDEX "index_room_participants_on_room_id" ON "room_participants" ("room_id");
|
||||
@@ -217,7 +218,8 @@ CREATE TABLE IF NOT EXISTS "channel_messages" (
|
||||
"sender_id" INTEGER NOT NULL REFERENCES users (id),
|
||||
"body" TEXT NOT NULL,
|
||||
"sent_at" TIMESTAMP,
|
||||
"nonce" BLOB NOT NULL
|
||||
"nonce" BLOB NOT NULL,
|
||||
"reply_to_message_id" INTEGER DEFAULT NULL
|
||||
);
|
||||
CREATE INDEX "index_channel_messages_on_channel_id" ON "channel_messages" ("channel_id");
|
||||
CREATE UNIQUE INDEX "index_channel_messages_on_sender_id_nonce" ON "channel_messages" ("sender_id", "nonce");
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE channel_messages ADD reply_to_message_id INTEGER DEFAULT NULL
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add migration script here
|
||||
|
||||
ALTER TABLE room_participants ADD COLUMN in_call BOOL NOT NULL DEFAULT FALSE;
|
||||
@@ -587,6 +587,9 @@ pub struct ChannelsForUser {
|
||||
pub channels: Vec<Channel>,
|
||||
pub channel_memberships: Vec<channel_member::Model>,
|
||||
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
||||
|
||||
pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||
pub observed_channel_messages: Vec<proto::ChannelMessageId>,
|
||||
pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||
pub latest_channel_messages: Vec<proto::ChannelMessageId>,
|
||||
}
|
||||
@@ -692,7 +695,7 @@ impl ProjectCollaborator {
|
||||
pub struct LeftProject {
|
||||
pub id: ProjectId,
|
||||
pub host_user_id: UserId,
|
||||
pub host_connection_id: ConnectionId,
|
||||
pub host_connection_id: Option<ConnectionId>,
|
||||
pub connection_ids: Vec<ConnectionId>,
|
||||
}
|
||||
|
||||
|
||||
@@ -561,7 +561,6 @@ impl Database {
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<()> {
|
||||
use observed_buffer_edits::Column;
|
||||
|
||||
observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
buffer_id: ActiveValue::Set(buffer_id),
|
||||
@@ -671,7 +670,7 @@ impl Database {
|
||||
buffer_id: row.buffer_id,
|
||||
epoch: row.epoch,
|
||||
lamport_timestamp: row.lamport_timestamp,
|
||||
replica_id: row.lamport_timestamp,
|
||||
replica_id: row.replica_id,
|
||||
value: Default::default(),
|
||||
});
|
||||
operations.push(proto::Operation {
|
||||
@@ -750,20 +749,9 @@ impl Database {
|
||||
|
||||
pub async fn latest_channel_buffer_changes(
|
||||
&self,
|
||||
channel_ids: &[ChannelId],
|
||||
channel_ids_by_buffer_id: &HashMap<BufferId, ChannelId>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::ChannelBufferVersion>> {
|
||||
let mut channel_ids_by_buffer_id = HashMap::default();
|
||||
let mut rows = buffer::Entity::find()
|
||||
.filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
channel_ids_by_buffer_id.insert(row.id, row.channel_id);
|
||||
}
|
||||
drop(rows);
|
||||
|
||||
let latest_operations = self
|
||||
.get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), &*tx)
|
||||
.await?;
|
||||
@@ -783,6 +771,36 @@ impl Database {
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn observed_channel_buffer_changes(
|
||||
&self,
|
||||
channel_ids_by_buffer_id: &HashMap<BufferId, ChannelId>,
|
||||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::ChannelBufferVersion>> {
|
||||
let observed_operations = observed_buffer_edits::Entity::find()
|
||||
.filter(observed_buffer_edits::Column::UserId.eq(user_id))
|
||||
.filter(
|
||||
observed_buffer_edits::Column::BufferId
|
||||
.is_in(channel_ids_by_buffer_id.keys().copied()),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(observed_operations
|
||||
.iter()
|
||||
.flat_map(|op| {
|
||||
Some(proto::ChannelBufferVersion {
|
||||
channel_id: channel_ids_by_buffer_id.get(&op.buffer_id)?.to_proto(),
|
||||
epoch: op.epoch as u64,
|
||||
version: vec![proto::VectorClockEntry {
|
||||
replica_id: op.replica_id as u32,
|
||||
timestamp: op.lamport_timestamp as u32,
|
||||
}],
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Returns the latest operations for the buffers with the specified IDs.
|
||||
pub async fn get_latest_operations_for_buffers(
|
||||
&self,
|
||||
|
||||
@@ -103,7 +103,6 @@ impl Database {
|
||||
channel_id: ChannelId,
|
||||
user_id: UserId,
|
||||
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?;
|
||||
@@ -163,7 +162,7 @@ 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, connection, role, &*tx)
|
||||
@@ -627,18 +626,40 @@ impl Database {
|
||||
}
|
||||
|
||||
let channel_ids = channels.iter().map(|c| c.id).collect::<Vec<_>>();
|
||||
|
||||
let mut channel_ids_by_buffer_id = HashMap::default();
|
||||
let mut rows = buffer::Entity::find()
|
||||
.filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
channel_ids_by_buffer_id.insert(row.id, row.channel_id);
|
||||
}
|
||||
drop(rows);
|
||||
|
||||
let latest_buffer_versions = self
|
||||
.latest_channel_buffer_changes(&channel_ids, &*tx)
|
||||
.latest_channel_buffer_changes(&channel_ids_by_buffer_id, &*tx)
|
||||
.await?;
|
||||
|
||||
let latest_messages = self.latest_channel_messages(&channel_ids, &*tx).await?;
|
||||
let latest_channel_messages = self.latest_channel_messages(&channel_ids, &*tx).await?;
|
||||
|
||||
let observed_buffer_versions = self
|
||||
.observed_channel_buffer_changes(&channel_ids_by_buffer_id, user_id, &*tx)
|
||||
.await?;
|
||||
|
||||
let observed_channel_messages = self
|
||||
.observed_channel_messages(&channel_ids, user_id, &*tx)
|
||||
.await?;
|
||||
|
||||
Ok(ChannelsForUser {
|
||||
channel_memberships,
|
||||
channels,
|
||||
channel_participants,
|
||||
latest_buffer_versions,
|
||||
latest_channel_messages: latest_messages,
|
||||
latest_channel_messages,
|
||||
observed_buffer_versions,
|
||||
observed_channel_messages,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -911,7 +932,6 @@ impl Database {
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
live_kit_room: &str,
|
||||
environment: &str,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<RoomId> {
|
||||
let room = room::Entity::find()
|
||||
@@ -920,19 +940,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)
|
||||
|
||||
@@ -161,6 +161,7 @@ impl Database {
|
||||
upper_half: nonce.0,
|
||||
lower_half: nonce.1,
|
||||
}),
|
||||
reply_to_message_id: row.reply_to_message_id.map(|id| id.to_proto()),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -207,6 +208,7 @@ impl Database {
|
||||
mentions: &[proto::ChatMention],
|
||||
timestamp: OffsetDateTime,
|
||||
nonce: u128,
|
||||
reply_to_message_id: Option<MessageId>,
|
||||
) -> Result<CreatedChannelMessage> {
|
||||
self.transaction(|tx| async move {
|
||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||
@@ -245,6 +247,7 @@ impl Database {
|
||||
sent_at: ActiveValue::Set(timestamp),
|
||||
nonce: ActiveValue::Set(Uuid::from_u128(nonce)),
|
||||
id: ActiveValue::NotSet,
|
||||
reply_to_message_id: ActiveValue::Set(reply_to_message_id),
|
||||
})
|
||||
.on_conflict(
|
||||
OnConflict::columns([
|
||||
@@ -385,6 +388,30 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn observed_channel_messages(
|
||||
&self,
|
||||
channel_ids: &[ChannelId],
|
||||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::ChannelMessageId>> {
|
||||
let rows = observed_channel_messages::Entity::find()
|
||||
.filter(observed_channel_messages::Column::UserId.eq(user_id))
|
||||
.filter(
|
||||
observed_channel_messages::Column::ChannelId
|
||||
.is_in(channel_ids.iter().map(|id| id.0)),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|message| proto::ChannelMessageId {
|
||||
channel_id: message.channel_id.to_proto(),
|
||||
message_id: message.channel_message_id.to_proto(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn latest_channel_messages(
|
||||
&self,
|
||||
channel_ids: &[ChannelId],
|
||||
|
||||
@@ -778,7 +778,7 @@ impl Database {
|
||||
let left_project = LeftProject {
|
||||
id: project_id,
|
||||
host_user_id: project.host_user_id,
|
||||
host_connection_id: project.host_connection()?,
|
||||
host_connection_id: Some(project.host_connection()?),
|
||||
connection_ids,
|
||||
};
|
||||
Ok((room, left_project))
|
||||
|
||||
@@ -110,12 +110,10 @@ impl Database {
|
||||
user_id: UserId,
|
||||
connection: ConnectionId,
|
||||
live_kit_room: &str,
|
||||
release_channel: &str,
|
||||
) -> Result<proto::Room> {
|
||||
self.transaction(|tx| async move {
|
||||
let room = room::ActiveModel {
|
||||
live_kit_room: ActiveValue::set(live_kit_room.into()),
|
||||
environment: ActiveValue::set(Some(release_channel.to_string())),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&*tx)
|
||||
@@ -302,31 +300,21 @@ impl Database {
|
||||
room_id: RoomId,
|
||||
user_id: UserId,
|
||||
connection: ConnectionId,
|
||||
environment: &str,
|
||||
) -> Result<RoomGuard<JoinRoom>> {
|
||||
self.room_transaction(room_id, |tx| async move {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryChannelIdAndEnvironment {
|
||||
enum QueryChannelId {
|
||||
ChannelId,
|
||||
Environment,
|
||||
}
|
||||
|
||||
let (channel_id, release_channel): (Option<ChannelId>, Option<String>) =
|
||||
room::Entity::find()
|
||||
.select_only()
|
||||
.column(room::Column::ChannelId)
|
||||
.column(room::Column::Environment)
|
||||
.filter(room::Column::Id.eq(room_id))
|
||||
.into_values::<_, QueryChannelIdAndEnvironment>()
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such room"))?;
|
||||
|
||||
if let Some(release_channel) = release_channel {
|
||||
if &release_channel != environment {
|
||||
Err(anyhow!("must join using the {} release", release_channel))?;
|
||||
}
|
||||
}
|
||||
let channel_id: Option<ChannelId> = room::Entity::find()
|
||||
.select_only()
|
||||
.column(room::Column::ChannelId)
|
||||
.filter(room::Column::Id.eq(room_id))
|
||||
.into_values::<_, QueryChannelId>()
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such room"))?;
|
||||
|
||||
if channel_id.is_some() {
|
||||
Err(anyhow!("tried to join channel call directly"))?
|
||||
@@ -862,7 +850,7 @@ impl Database {
|
||||
id: collaborator.project_id,
|
||||
host_user_id: Default::default(),
|
||||
connection_ids: Default::default(),
|
||||
host_connection_id: Default::default(),
|
||||
host_connection_id: None,
|
||||
});
|
||||
|
||||
let collaborator_connection_id = collaborator.connection();
|
||||
@@ -872,7 +860,7 @@ impl Database {
|
||||
|
||||
if collaborator.is_host {
|
||||
left_project.host_user_id = collaborator.user_id;
|
||||
left_project.host_connection_id = collaborator_connection_id;
|
||||
left_project.host_connection_id = Some(collaborator_connection_id);
|
||||
}
|
||||
}
|
||||
drop(collaborators);
|
||||
|
||||
@@ -12,6 +12,7 @@ pub struct Model {
|
||||
pub body: String,
|
||||
pub sent_at: PrimitiveDateTime,
|
||||
pub nonce: Uuid,
|
||||
pub reply_to_message_id: Option<MessageId>,
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
@@ -8,7 +8,6 @@ pub struct Model {
|
||||
pub id: RoomId,
|
||||
pub live_kit_room: String,
|
||||
pub channel_id: Option<ChannelId>,
|
||||
pub environment: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -15,8 +15,6 @@ use std::sync::{
|
||||
Arc,
|
||||
};
|
||||
|
||||
const TEST_RELEASE_CHANNEL: &'static str = "test";
|
||||
|
||||
pub struct TestDb {
|
||||
pub db: Option<Arc<Database>>,
|
||||
pub connection: Option<sqlx::AnyConnection>,
|
||||
|
||||
@@ -337,17 +337,12 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
let buffer_changes = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
async move {
|
||||
db.latest_channel_buffer_changes(
|
||||
&[
|
||||
buffers[0].channel_id,
|
||||
buffers[1].channel_id,
|
||||
buffers[2].channel_id,
|
||||
],
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
let mut hash = HashMap::default();
|
||||
hash.insert(buffers[0].id, buffers[0].channel_id);
|
||||
hash.insert(buffers[1].id, buffers[1].channel_id);
|
||||
hash.insert(buffers[2].id, buffers[2].channel_id);
|
||||
|
||||
async move { db.latest_channel_buffer_changes(&hash, &*tx).await }
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
db::{
|
||||
tests::{channel_tree, new_test_connection, new_test_user, TEST_RELEASE_CHANNEL},
|
||||
tests::{channel_tree, new_test_connection, new_test_user},
|
||||
Channel, ChannelId, ChannelRole, Database, NewUserParams, RoomId,
|
||||
},
|
||||
test_both_dbs,
|
||||
@@ -135,12 +135,7 @@ async fn test_joining_channels(db: &Arc<Database>) {
|
||||
|
||||
// can join a room with membership to its channel
|
||||
let (joined_room, _, _) = db
|
||||
.join_channel(
|
||||
channel_1,
|
||||
user_1,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
TEST_RELEASE_CHANNEL,
|
||||
)
|
||||
.join_channel(channel_1, user_1, ConnectionId { owner_id, id: 1 })
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(joined_room.room.participants.len(), 1);
|
||||
@@ -149,12 +144,7 @@ async fn test_joining_channels(db: &Arc<Database>) {
|
||||
drop(joined_room);
|
||||
// cannot join a room without membership to its channel
|
||||
assert!(db
|
||||
.join_room(
|
||||
room_id,
|
||||
user_2,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
TEST_RELEASE_CHANNEL
|
||||
)
|
||||
.join_room(room_id, user_2, ConnectionId { owner_id, id: 1 },)
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
@@ -732,7 +722,7 @@ async fn test_guest_access(db: &Arc<Database>) {
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
db.join_channel(zed_channel, guest, guest_connection, TEST_RELEASE_CHANNEL)
|
||||
db.join_channel(zed_channel, guest, guest_connection)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -517,7 +517,7 @@ async fn test_project_count(db: &Arc<Database>) {
|
||||
.unwrap();
|
||||
|
||||
let room_id = RoomId::from_proto(
|
||||
db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "", "test")
|
||||
db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
|
||||
.await
|
||||
.unwrap()
|
||||
.id,
|
||||
@@ -531,14 +531,9 @@ async fn test_project_count(db: &Arc<Database>) {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
db.join_room(
|
||||
room_id,
|
||||
user2.user_id,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
"test",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
|
||||
@@ -616,80 +611,3 @@ async fn test_fuzzy_search_users(cx: &mut TestAppContext) {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
test_both_dbs!(
|
||||
test_non_matching_release_channels,
|
||||
test_non_matching_release_channels_postgres,
|
||||
test_non_matching_release_channels_sqlite
|
||||
);
|
||||
|
||||
async fn test_non_matching_release_channels(db: &Arc<Database>) {
|
||||
let owner_id = db.create_server("test").await.unwrap().0 as u32;
|
||||
|
||||
let user1 = db
|
||||
.create_user(
|
||||
&format!("admin@example.com"),
|
||||
true,
|
||||
NewUserParams {
|
||||
github_login: "admin".into(),
|
||||
github_user_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let user2 = db
|
||||
.create_user(
|
||||
&format!("user@example.com"),
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user".into(),
|
||||
github_user_id: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let room = db
|
||||
.create_room(
|
||||
user1.user_id,
|
||||
ConnectionId { owner_id, id: 0 },
|
||||
"",
|
||||
"stable",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.call(
|
||||
RoomId::from_proto(room.id),
|
||||
user1.user_id,
|
||||
ConnectionId { owner_id, id: 0 },
|
||||
user2.user_id,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// User attempts to join from preview
|
||||
let result = db
|
||||
.join_room(
|
||||
RoomId::from_proto(room.id),
|
||||
user2.user_id,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
"preview",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
|
||||
// User switches to stable
|
||||
let result = db
|
||||
.join_room(
|
||||
RoomId::from_proto(room.id),
|
||||
user2.user_id,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
"stable",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok())
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ async fn test_channel_message_retrieval(db: &Arc<Database>) {
|
||||
&[],
|
||||
OffsetDateTime::now_utc(),
|
||||
i,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -106,6 +107,7 @@ async fn test_channel_message_nonces(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[(3..10, user_b.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
100,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -118,6 +120,7 @@ async fn test_channel_message_nonces(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[]),
|
||||
OffsetDateTime::now_utc(),
|
||||
200,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -130,6 +133,7 @@ async fn test_channel_message_nonces(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[(4..11, user_c.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
100,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -142,6 +146,7 @@ async fn test_channel_message_nonces(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[]),
|
||||
OffsetDateTime::now_utc(),
|
||||
200,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -157,6 +162,7 @@ async fn test_channel_message_nonces(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[(4..11, user_a.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
100,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -231,17 +237,41 @@ async fn test_unseen_channel_messages(db: &Arc<Database>) {
|
||||
.unwrap();
|
||||
|
||||
let _ = db
|
||||
.create_channel_message(channel_1, user, "1_1", &[], OffsetDateTime::now_utc(), 1)
|
||||
.create_channel_message(
|
||||
channel_1,
|
||||
user,
|
||||
"1_1",
|
||||
&[],
|
||||
OffsetDateTime::now_utc(),
|
||||
1,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _ = db
|
||||
.create_channel_message(channel_1, user, "1_2", &[], OffsetDateTime::now_utc(), 2)
|
||||
.create_channel_message(
|
||||
channel_1,
|
||||
user,
|
||||
"1_2",
|
||||
&[],
|
||||
OffsetDateTime::now_utc(),
|
||||
2,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let third_message = db
|
||||
.create_channel_message(channel_1, user, "1_3", &[], OffsetDateTime::now_utc(), 3)
|
||||
.create_channel_message(
|
||||
channel_1,
|
||||
user,
|
||||
"1_3",
|
||||
&[],
|
||||
OffsetDateTime::now_utc(),
|
||||
3,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
@@ -251,7 +281,15 @@ async fn test_unseen_channel_messages(db: &Arc<Database>) {
|
||||
.unwrap();
|
||||
|
||||
let fourth_message = db
|
||||
.create_channel_message(channel_2, user, "2_1", &[], OffsetDateTime::now_utc(), 4)
|
||||
.create_channel_message(
|
||||
channel_2,
|
||||
user,
|
||||
"2_1",
|
||||
&[],
|
||||
OffsetDateTime::now_utc(),
|
||||
4,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.message_id;
|
||||
@@ -317,6 +355,7 @@ async fn test_channel_message_mentions(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[(3..10, user_b.to_proto()), (15..22, user_c.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
1,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -327,6 +366,7 @@ async fn test_channel_message_mentions(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[(4..11, user_c.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
2,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -337,6 +377,7 @@ async fn test_channel_message_mentions(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[]),
|
||||
OffsetDateTime::now_utc(),
|
||||
3,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -347,6 +388,7 @@ async fn test_channel_message_mentions(db: &Arc<Database>) {
|
||||
&mentions_to_proto(&[(0..7, user_b.to_proto())]),
|
||||
OffsetDateTime::now_utc(),
|
||||
4,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -14,6 +14,7 @@ use tracing_subscriber::{filter::EnvFilter, fmt::format::JsonFields, Layer};
|
||||
use util::ResultExt;
|
||||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
const REVISION: Option<&'static str> = option_env!("GITHUB_SHA");
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
@@ -26,7 +27,10 @@ async fn main() -> Result<()> {
|
||||
|
||||
match args().skip(1).next().as_deref() {
|
||||
Some("version") => {
|
||||
println!("collab v{VERSION}");
|
||||
println!("collab v{} ({})", VERSION, REVISION.unwrap_or("unknown"));
|
||||
}
|
||||
Some("migrate") => {
|
||||
run_migrations().await?;
|
||||
}
|
||||
Some("serve") => {
|
||||
let config = envy::from_env::<Config>().expect("error loading config");
|
||||
@@ -102,7 +106,7 @@ async fn run_migrations() -> Result<()> {
|
||||
}
|
||||
|
||||
async fn handle_root() -> String {
|
||||
format!("collab v{VERSION}")
|
||||
format!("collab v{} ({})", VERSION, REVISION.unwrap_or("unknown"))
|
||||
}
|
||||
|
||||
async fn handle_liveness_probe(Extension(state): Extension<Arc<AppState>>) -> Result<String> {
|
||||
|
||||
@@ -64,6 +64,7 @@ use time::OffsetDateTime;
|
||||
use tokio::sync::{watch, Semaphore};
|
||||
use tower::ServiceBuilder;
|
||||
use tracing::{field, info_span, instrument, Instrument};
|
||||
use util::SemanticVersion;
|
||||
|
||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
@@ -101,7 +102,6 @@ impl<R: RequestMessage> Response<R> {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Session {
|
||||
zed_environment: Arc<str>,
|
||||
user_id: UserId,
|
||||
connection_id: ConnectionId,
|
||||
db: Arc<tokio::sync::Mutex<DbHandle>>,
|
||||
@@ -601,7 +601,7 @@ impl Server {
|
||||
let mut pool = this.connection_pool.lock();
|
||||
pool.add_connection(connection_id, user_id, user.admin);
|
||||
this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
|
||||
this.peer.send(connection_id, build_update_user_channels(&channels_for_user.channel_memberships))?;
|
||||
this.peer.send(connection_id, build_update_user_channels(&channels_for_user))?;
|
||||
this.peer.send(connection_id, build_channels_update(
|
||||
channels_for_user,
|
||||
channel_invites
|
||||
@@ -616,7 +616,6 @@ impl Server {
|
||||
user_id,
|
||||
connection_id,
|
||||
db: Arc::new(tokio::sync::Mutex::new(DbHandle(this.app_state.db.clone()))),
|
||||
zed_environment: this.app_state.config.zed_environment.clone(),
|
||||
peer: this.peer.clone(),
|
||||
connection_pool: this.connection_pool.clone(),
|
||||
live_kit_client: this.app_state.live_kit_client.clone(),
|
||||
@@ -795,6 +794,7 @@ fn broadcast<F>(
|
||||
|
||||
lazy_static! {
|
||||
static ref ZED_PROTOCOL_VERSION: HeaderName = HeaderName::from_static("x-zed-protocol-version");
|
||||
static ref ZED_APP_VERSION: HeaderName = HeaderName::from_static("x-zed-app-version");
|
||||
}
|
||||
|
||||
pub struct ProtocolVersion(u32);
|
||||
@@ -824,6 +824,32 @@ impl Header for ProtocolVersion {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppVersionHeader(SemanticVersion);
|
||||
impl Header for AppVersionHeader {
|
||||
fn name() -> &'static HeaderName {
|
||||
&ZED_APP_VERSION
|
||||
}
|
||||
|
||||
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
I: Iterator<Item = &'i axum::http::HeaderValue>,
|
||||
{
|
||||
let version = values
|
||||
.next()
|
||||
.ok_or_else(axum::headers::Error::invalid)?
|
||||
.to_str()
|
||||
.map_err(|_| axum::headers::Error::invalid())?
|
||||
.parse()
|
||||
.map_err(|_| axum::headers::Error::invalid())?;
|
||||
Ok(Self(version))
|
||||
}
|
||||
|
||||
fn encode<E: Extend<axum::http::HeaderValue>>(&self, values: &mut E) {
|
||||
values.extend([self.0.to_string().parse().unwrap()]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routes(server: Arc<Server>) -> Router<Body> {
|
||||
Router::new()
|
||||
.route("/rpc", get(handle_websocket_request))
|
||||
@@ -838,6 +864,7 @@ pub fn routes(server: Arc<Server>) -> Router<Body> {
|
||||
|
||||
pub async fn handle_websocket_request(
|
||||
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
|
||||
app_version_header: Option<TypedHeader<AppVersionHeader>>,
|
||||
ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
|
||||
Extension(server): Extension<Arc<Server>>,
|
||||
Extension(user): Extension<User>,
|
||||
@@ -851,6 +878,20 @@ pub async fn handle_websocket_request(
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
||||
// the first version of zed that sent this header was 0.121.x
|
||||
if let Some(version) = app_version_header.map(|header| header.0 .0) {
|
||||
// 0.123.0 was a nightly version with incompatible collab changes
|
||||
// that were reverted.
|
||||
if version == "0.123.0".parse().unwrap() {
|
||||
return (
|
||||
StatusCode::UPGRADE_REQUIRED,
|
||||
"client must be upgraded".to_string(),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
|
||||
let socket_address = socket_address.to_string();
|
||||
ws.on_upgrade(move |socket| {
|
||||
use util::ResultExt;
|
||||
@@ -979,12 +1020,7 @@ async fn create_room(
|
||||
let room = session
|
||||
.db()
|
||||
.await
|
||||
.create_room(
|
||||
session.user_id,
|
||||
session.connection_id,
|
||||
&live_kit_room,
|
||||
&session.zed_environment,
|
||||
)
|
||||
.create_room(session.user_id, session.connection_id, &live_kit_room)
|
||||
.await?;
|
||||
|
||||
response.send(proto::CreateRoomResponse {
|
||||
@@ -1014,12 +1050,7 @@ async fn join_room(
|
||||
let room = session
|
||||
.db()
|
||||
.await
|
||||
.join_room(
|
||||
room_id,
|
||||
session.user_id,
|
||||
session.connection_id,
|
||||
session.zed_environment.as_ref(),
|
||||
)
|
||||
.join_room(room_id, session.user_id, session.connection_id)
|
||||
.await?;
|
||||
room_updated(&room.room, &session.peer);
|
||||
room.into_inner()
|
||||
@@ -1661,7 +1692,7 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
|
||||
tracing::info!(
|
||||
%project_id,
|
||||
host_user_id = %project.host_user_id,
|
||||
host_connection_id = %project.host_connection_id,
|
||||
host_connection_id = ?project.host_connection_id,
|
||||
"leave project"
|
||||
);
|
||||
|
||||
@@ -2704,12 +2735,7 @@ async fn join_channel_internal(
|
||||
let db = session.db().await;
|
||||
|
||||
let (joined_room, membership_updated, role) = db
|
||||
.join_channel(
|
||||
channel_id,
|
||||
session.user_id,
|
||||
session.connection_id,
|
||||
session.zed_environment.as_ref(),
|
||||
)
|
||||
.join_channel(channel_id, session.user_id, session.connection_id)
|
||||
.await?;
|
||||
|
||||
let live_kit_connection_info = session.live_kit_client.as_ref().and_then(|live_kit| {
|
||||
@@ -2989,6 +3015,10 @@ async fn send_channel_message(
|
||||
&request.mentions,
|
||||
timestamp,
|
||||
nonce.clone().into(),
|
||||
match request.reply_to_message_id {
|
||||
Some(reply_to_message_id) => Some(MessageId::from_proto(reply_to_message_id)),
|
||||
None => None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let message = proto::ChannelMessage {
|
||||
@@ -2998,6 +3028,7 @@ async fn send_channel_message(
|
||||
mentions: request.mentions,
|
||||
timestamp: timestamp.unix_timestamp() as u64,
|
||||
nonce: Some(nonce),
|
||||
reply_to_message_id: request.reply_to_message_id,
|
||||
};
|
||||
broadcast(
|
||||
Some(session.connection_id),
|
||||
@@ -3308,17 +3339,18 @@ fn notify_membership_updated(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_update_user_channels(
|
||||
memberships: &Vec<db::channel_member::Model>,
|
||||
) -> proto::UpdateUserChannels {
|
||||
fn build_update_user_channels(channels: &ChannelsForUser) -> proto::UpdateUserChannels {
|
||||
proto::UpdateUserChannels {
|
||||
channel_memberships: memberships
|
||||
channel_memberships: channels
|
||||
.channel_memberships
|
||||
.iter()
|
||||
.map(|m| proto::ChannelMembership {
|
||||
channel_id: m.channel_id.to_proto(),
|
||||
role: m.role.into(),
|
||||
})
|
||||
.collect(),
|
||||
observed_channel_buffer_version: channels.observed_buffer_versions.clone(),
|
||||
observed_channel_message_id: channels.observed_channel_messages.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
tests::TestServer,
|
||||
tests::{test_server::open_channel_notes, TestServer},
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use channel::ACKNOWLEDGE_DEBOUNCE_INTERVAL;
|
||||
@@ -161,15 +161,15 @@ async fn test_channel_notes_participant_indices(
|
||||
|
||||
// Clients A, B, and C open the channel notes
|
||||
let channel_view_a = cx_a
|
||||
.update(|cx| ChannelView::open(channel_id, workspace_a.clone(), cx))
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel_view_b = cx_b
|
||||
.update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_b.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel_view_c = cx_c
|
||||
.update(|cx| ChannelView::open(channel_id, workspace_c.clone(), cx))
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_c.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -605,113 +605,75 @@ async fn test_channel_buffer_changes(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
let mut server = TestServer::start(deterministic.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
let (server, client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await;
|
||||
let (_, cx_a) = client_a.build_test_workspace(cx_a).await;
|
||||
let (workspace_b, cx_b) = client_b.build_test_workspace(cx_b).await;
|
||||
let channel_store_b = client_b.channel_store().clone();
|
||||
|
||||
let channel_id = server
|
||||
.make_channel(
|
||||
"the-channel",
|
||||
None,
|
||||
(&client_a, cx_a),
|
||||
&mut [(&client_b, cx_b)],
|
||||
)
|
||||
.await;
|
||||
|
||||
let channel_buffer_a = client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client A makes an edit, and client B should see that the note has changed.
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "1")], None, cx);
|
||||
})
|
||||
// Editing the channel notes should set them to dirty
|
||||
open_channel_notes(channel_id, cx_a).await.unwrap();
|
||||
cx_a.simulate_keystrokes("1");
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(channel_store.has_channel_buffer_changed(channel_id))
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
let has_buffer_changed = cx_b.update(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
});
|
||||
assert!(has_buffer_changed);
|
||||
|
||||
// Opening the buffer should clear the changed flag.
|
||||
let project_b = client_b.build_empty_local_project(cx_b);
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let channel_view_b = cx_b
|
||||
.update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
deterministic.run_until_parked();
|
||||
|
||||
let has_buffer_changed = cx_b.update(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
open_channel_notes(channel_id, cx_b).await.unwrap();
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(!channel_store.has_channel_buffer_changed(channel_id))
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
// Editing the channel while the buffer is open should not show that the buffer has changed.
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "2")], None, cx);
|
||||
})
|
||||
cx_a.simulate_keystrokes("2");
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(!channel_store.has_channel_buffer_changed(channel_id))
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
let has_buffer_changed = cx_b.read(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
deterministic.advance_clock(ACKNOWLEDGE_DEBOUNCE_INTERVAL);
|
||||
|
||||
// Test that the server is tracking things correctly, and we retain our 'not changed'
|
||||
// state across a disconnect
|
||||
deterministic.advance_clock(ACKNOWLEDGE_DEBOUNCE_INTERVAL);
|
||||
server
|
||||
.simulate_long_connection_interruption(client_b.peer_id().unwrap(), deterministic.clone());
|
||||
let has_buffer_changed = cx_b.read(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(!channel_store.has_channel_buffer_changed(channel_id))
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
// Closing the buffer should re-enable change tracking
|
||||
cx_b.update(|cx| {
|
||||
workspace_b.update(cx, |workspace, cx| {
|
||||
workspace.close_all_items_and_panes(&Default::default(), cx)
|
||||
});
|
||||
|
||||
drop(channel_view_b)
|
||||
});
|
||||
|
||||
deterministic.run_until_parked();
|
||||
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "3")], None, cx);
|
||||
})
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
let has_buffer_changed = cx_b.read(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
cx_a.simulate_keystrokes("3");
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(channel_store.has_channel_buffer_changed(channel_id))
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_channel_buffer_changes_persist(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
cx_b2: &mut TestAppContext,
|
||||
) {
|
||||
let (mut server, client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await;
|
||||
let (_, cx_a) = client_a.build_test_workspace(cx_a).await;
|
||||
let (_, cx_b) = client_b.build_test_workspace(cx_b).await;
|
||||
|
||||
// a) edits the notes
|
||||
open_channel_notes(channel_id, cx_a).await.unwrap();
|
||||
cx_a.simulate_keystrokes("1");
|
||||
// b) opens them to observe the current version
|
||||
open_channel_notes(channel_id, cx_b).await.unwrap();
|
||||
|
||||
// On boot the client should get the correct state.
|
||||
let client_b2 = server.create_client(cx_b2, "user_b").await;
|
||||
let channel_store_b2 = client_b2.channel_store().clone();
|
||||
channel_store_b2.read_with(cx_b2, |channel_store, _| {
|
||||
assert!(!channel_store.has_channel_buffer_changed(channel_id))
|
||||
});
|
||||
assert!(has_buffer_changed);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -43,6 +43,7 @@ async fn test_basic_channel_messages(
|
||||
MessageParams {
|
||||
text: "hi @user_c!".into(),
|
||||
mentions: vec![(3..10, client_c.id())],
|
||||
reply_to_message_id: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -402,3 +403,66 @@ async fn test_channel_message_changes(
|
||||
|
||||
assert!(b_has_messages);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_chat_replies(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
|
||||
let channel_id = server
|
||||
.make_channel(
|
||||
"the-channel",
|
||||
None,
|
||||
(&client_a, cx_a),
|
||||
&mut [(&client_b, cx_b)],
|
||||
)
|
||||
.await;
|
||||
|
||||
// Client A sends a message, client B should see that there is a new message.
|
||||
let channel_chat_a = client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let channel_chat_b = client_b
|
||||
.channel_store()
|
||||
.update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let msg_id = channel_chat_a
|
||||
.update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx_a.run_until_parked();
|
||||
|
||||
let reply_id = channel_chat_b
|
||||
.update(cx_b, |c, cx| {
|
||||
c.send_message(
|
||||
MessageParams {
|
||||
text: "reply".into(),
|
||||
reply_to_message_id: Some(msg_id),
|
||||
mentions: Vec::new(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx_a.run_until_parked();
|
||||
|
||||
channel_chat_a.update(cx_a, |channel_chat, _| {
|
||||
assert_eq!(
|
||||
channel_chat
|
||||
.find_loaded_message(reply_id)
|
||||
.unwrap()
|
||||
.reply_to_message_id,
|
||||
Some(msg_id),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use gpui::{TestAppContext, VisualContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
|
||||
tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher,
|
||||
};
|
||||
use rpc::RECEIVE_TIMEOUT;
|
||||
use serde_json::json;
|
||||
@@ -269,7 +269,10 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -455,7 +458,10 @@ async fn test_collaborating_with_code_actions(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -668,7 +674,10 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -853,7 +862,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -1144,7 +1156,10 @@ async fn test_on_input_format_from_host_to_guest(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -1272,7 +1287,10 @@ async fn test_on_input_format_from_guest_to_host(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -1431,7 +1449,10 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -1697,7 +1718,10 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
|
||||
@@ -22,6 +22,8 @@ use workspace::{
|
||||
SplitDirection, Workspace,
|
||||
};
|
||||
|
||||
use super::TestClient;
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_basic_following(
|
||||
cx_a: &mut TestAppContext,
|
||||
@@ -1905,7 +1907,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
|
||||
// Client A opens the notes for channel 1.
|
||||
let channel_notes_1_a = cx_a
|
||||
.update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx))
|
||||
.update(|cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
channel_notes_1_a.update(cx_a, |notes, cx| {
|
||||
@@ -1951,7 +1953,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
|
||||
// Client A opens the notes for channel 2.
|
||||
let channel_notes_2_a = cx_a
|
||||
.update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx))
|
||||
.update(|cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
channel_notes_2_a.update(cx_a, |notes, cx| {
|
||||
@@ -1996,3 +1998,82 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async fn join_channel(
|
||||
channel_id: u64,
|
||||
client: &TestClient,
|
||||
cx: &mut TestAppContext,
|
||||
) -> anyhow::Result<()> {
|
||||
cx.update(|cx| workspace::join_channel(channel_id, client.app_state.clone(), None, cx))
|
||||
.await
|
||||
}
|
||||
|
||||
async fn share_workspace(
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> anyhow::Result<u64> {
|
||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
|
||||
cx.read(ActiveCall::global)
|
||||
.update(cx, |call, cx| call.share_project(project, cx))
|
||||
.await
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_following_to_channel_notes_other_workspace(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
let (_, client_a, client_b, channel) = TestServer::start2(cx_a, cx_b).await;
|
||||
|
||||
let mut cx_a2 = cx_a.clone();
|
||||
let (workspace_a, cx_a) = client_a.build_test_workspace(cx_a).await;
|
||||
join_channel(channel, &client_a, cx_a).await.unwrap();
|
||||
share_workspace(&workspace_a, cx_a).await.unwrap();
|
||||
|
||||
// a opens 1.txt
|
||||
cx_a.simulate_keystrokes("cmd-p 1 enter");
|
||||
cx_a.run_until_parked();
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
let editor = workspace.active_item(cx).unwrap();
|
||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
||||
});
|
||||
|
||||
// b joins channel and is following a
|
||||
join_channel(channel, &client_b, cx_b).await.unwrap();
|
||||
cx_b.run_until_parked();
|
||||
let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
let editor = workspace.active_item(cx).unwrap();
|
||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
||||
});
|
||||
|
||||
// a opens a second workspace and the channel notes
|
||||
let (workspace_a2, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
|
||||
cx_a2.update(|cx| cx.activate_window());
|
||||
cx_a2
|
||||
.update(|cx| ChannelView::open(channel, None, workspace_a2, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a2.run_until_parked();
|
||||
|
||||
// b should follow a to the channel notes
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
let editor = workspace.active_item_as::<ChannelView>(cx).unwrap();
|
||||
assert_eq!(editor.read(cx).channel(cx).unwrap().id, channel);
|
||||
});
|
||||
|
||||
// a returns to the shared project
|
||||
cx_a.update(|cx| cx.activate_window());
|
||||
cx_a.run_until_parked();
|
||||
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
let editor = workspace.active_item(cx).unwrap();
|
||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
||||
});
|
||||
|
||||
// b should follow a back
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
let editor = workspace.active_item_as::<Editor>(cx).unwrap();
|
||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use gpui::{
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, Formatter},
|
||||
tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig,
|
||||
LineEnding, OffsetRangeExt, Point, Rope,
|
||||
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
|
||||
};
|
||||
use live_kit_client::MacOSDisplay;
|
||||
use lsp::LanguageServerId;
|
||||
@@ -2246,7 +2246,10 @@ async fn test_propagate_saves_and_fs_changes(
|
||||
let rust = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -2254,7 +2257,10 @@ async fn test_propagate_saves_and_fs_changes(
|
||||
let javascript = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "JavaScript".into(),
|
||||
path_suffixes: vec!["js".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["js".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -3783,7 +3789,10 @@ async fn test_collaborating_with_diagnostics(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -4061,7 +4070,10 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -4290,7 +4302,10 @@ async fn test_formatting_buffer(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -4395,7 +4410,10 @@ async fn test_prettier_formatting_buffer(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
prettier_parser_name: Some("test_parser".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -4511,7 +4529,10 @@ async fn test_definition(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -4655,7 +4676,10 @@ async fn test_references(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -4852,7 +4876,10 @@ async fn test_document_highlights(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -4955,7 +4982,10 @@ async fn test_lsp_hover(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -5051,7 +5081,10 @@ async fn test_project_symbols(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -5160,7 +5193,10 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -5967,6 +6003,6 @@ async fn test_cmd_k_left(cx: &mut TestAppContext) {
|
||||
cx.executor().advance_clock(Duration::from_secs(2));
|
||||
cx.simulate_keystrokes("left");
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 3);
|
||||
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ use editor::Bias;
|
||||
use fs::{repository::GitFileStatus, FakeFs, Fs as _};
|
||||
use futures::StreamExt;
|
||||
use gpui::{BackgroundExecutor, Model, TestAppContext};
|
||||
use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16};
|
||||
use language::{
|
||||
range_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, PointUtf16,
|
||||
};
|
||||
use lsp::FakeLanguageServer;
|
||||
use pretty_assertions::assert_eq;
|
||||
use project::{search::SearchQuery, Project, ProjectPath};
|
||||
@@ -1022,7 +1024,10 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
|
||||
@@ -10,10 +10,11 @@ use channel::{ChannelBuffer, ChannelStore};
|
||||
use client::{
|
||||
self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore,
|
||||
};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
use futures::{channel::oneshot, StreamExt as _};
|
||||
use gpui::{BackgroundExecutor, Context, Model, TestAppContext, View, VisualTestContext};
|
||||
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
|
||||
@@ -123,7 +124,12 @@ impl TestServer {
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
let channel_id = server
|
||||
.make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
||||
.make_channel(
|
||||
"test-channel",
|
||||
None,
|
||||
(&client_a, cx_a),
|
||||
&mut [(&client_b, cx_b)],
|
||||
)
|
||||
.await;
|
||||
cx_a.run_until_parked();
|
||||
|
||||
@@ -153,6 +159,7 @@ impl TestServer {
|
||||
}
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
});
|
||||
|
||||
@@ -754,6 +761,16 @@ impl TestClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_channel_notes(
|
||||
channel_id: u64,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Task<anyhow::Result<View<ChannelView>>> {
|
||||
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||
let view = window.root_view(cx).unwrap();
|
||||
|
||||
cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
|
||||
}
|
||||
|
||||
impl Drop for TestClient {
|
||||
fn drop(&mut self) {
|
||||
self.app_state.client.teardown();
|
||||
|
||||
@@ -25,63 +25,59 @@ test-support = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
auto_update = { path = "../auto_update" }
|
||||
db = { path = "../db" }
|
||||
call = { path = "../call" }
|
||||
client = { path = "../client" }
|
||||
channel = { path = "../channel" }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
# context_menu = { path = "../context_menu" }
|
||||
# drag_and_drop = { path = "../drag_and_drop" }
|
||||
editor = { path = "../editor" }
|
||||
feedback = { path = "../feedback" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
menu = { path = "../menu" }
|
||||
notifications = { path = "../notifications" }
|
||||
rich_text = { path = "../rich_text" }
|
||||
picker = { path = "../picker" }
|
||||
project = { path = "../project" }
|
||||
recent_projects = { path = "../recent_projects" }
|
||||
rpc = { path = "../rpc" }
|
||||
settings = { path = "../settings" }
|
||||
story = { path = "../story", optional = true }
|
||||
feature_flags = { path = "../feature_flags"}
|
||||
theme = { path = "../theme" }
|
||||
theme_selector = { path = "../theme_selector" }
|
||||
vcs_menu = { path = "../vcs_menu" }
|
||||
ui = { path = "../ui" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
zed_actions = { path = "../zed_actions"}
|
||||
|
||||
anyhow.workspace = true
|
||||
auto_update.workspace = true
|
||||
call.workspace = true
|
||||
channel.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
feedback.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
notifications.workspace = true
|
||||
parking_lot.workspace = true
|
||||
schemars.workspace = true
|
||||
picker.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
recent_projects.workspace = true
|
||||
rich_text.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
time.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
theme.workspace = true
|
||||
theme_selector.workspace = true
|
||||
time.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
vcs_menu.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
call = { path = "../call", features = ["test-support"] }
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
notifications = { path = "../notifications", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
notifications = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
tree-sitter-markdown.workspace = true
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -6,11 +6,14 @@ use client::{
|
||||
Collaborator, ParticipantIndex,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use editor::{CollaborationHub, Editor, EditorEvent};
|
||||
use editor::{
|
||||
display_map::ToDisplayPoint, scroll::Autoscroll, CollaborationHub, DisplayPoint, Editor,
|
||||
EditorEvent,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AnyElement, AnyView, AppContext, Entity as _, EventEmitter, FocusableView,
|
||||
IntoElement as _, Model, Pixels, Point, Render, Subscription, Task, View, ViewContext,
|
||||
VisualContext as _, WindowContext,
|
||||
actions, AnyElement, AnyView, AppContext, ClipboardItem, Entity as _, EventEmitter,
|
||||
FocusableView, IntoElement as _, Model, Pixels, Point, Render, Subscription, Task, View,
|
||||
ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||
};
|
||||
use project::Project;
|
||||
use std::{
|
||||
@@ -23,10 +26,10 @@ use workspace::{
|
||||
item::{FollowableItem, Item, ItemEvent, ItemHandle},
|
||||
register_followable_item,
|
||||
searchable::SearchableItemHandle,
|
||||
ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
|
||||
ItemNavHistory, Pane, SaveIntent, Toast, ViewId, Workspace, WorkspaceId,
|
||||
};
|
||||
|
||||
actions!(collab, [Deploy]);
|
||||
actions!(collab, [CopyLink]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
register_followable_item::<ChannelView>(cx)
|
||||
@@ -34,21 +37,30 @@ pub fn init(cx: &mut AppContext) {
|
||||
|
||||
pub struct ChannelView {
|
||||
pub editor: View<Editor>,
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_buffer: Model<ChannelBuffer>,
|
||||
remote_id: Option<ViewId>,
|
||||
_editor_event_subscription: Subscription,
|
||||
_reparse_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl ChannelView {
|
||||
pub fn open(
|
||||
channel_id: ChannelId,
|
||||
link_position: Option<String>,
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
let pane = workspace.read(cx).active_pane().clone();
|
||||
let channel_view = Self::open_in_pane(channel_id, pane.clone(), workspace.clone(), cx);
|
||||
let channel_view = Self::open_in_pane(
|
||||
channel_id,
|
||||
link_position,
|
||||
pane.clone(),
|
||||
workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|mut cx| async move {
|
||||
let channel_view = channel_view.await?;
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
@@ -66,10 +78,12 @@ impl ChannelView {
|
||||
|
||||
pub fn open_in_pane(
|
||||
channel_id: ChannelId,
|
||||
link_position: Option<String>,
|
||||
pane: View<Pane>,
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
let weak_workspace = workspace.downgrade();
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().to_owned();
|
||||
let channel_store = ChannelStore::global(cx);
|
||||
@@ -82,12 +96,13 @@ impl ChannelView {
|
||||
let channel_buffer = channel_buffer.await?;
|
||||
let markdown = markdown.await.log_err();
|
||||
|
||||
channel_buffer.update(&mut cx, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
channel_buffer.update(&mut cx, |channel_buffer, cx| {
|
||||
channel_buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.set_language_registry(language_registry);
|
||||
if let Some(markdown) = markdown {
|
||||
buffer.set_language(Some(markdown), cx);
|
||||
}
|
||||
let Some(markdown) = markdown else {
|
||||
return;
|
||||
};
|
||||
buffer.set_language(Some(markdown), cx);
|
||||
})
|
||||
})?;
|
||||
|
||||
@@ -101,12 +116,18 @@ impl ChannelView {
|
||||
// If this channel buffer is already open in this pane, just return it.
|
||||
if let Some(existing_view) = existing_view.clone() {
|
||||
if existing_view.read(cx).channel_buffer == channel_buffer {
|
||||
if let Some(link_position) = link_position {
|
||||
existing_view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
});
|
||||
}
|
||||
return existing_view;
|
||||
}
|
||||
}
|
||||
|
||||
let view = cx.new_view(|cx| {
|
||||
let mut this = Self::new(project, channel_store, channel_buffer, cx);
|
||||
let mut this =
|
||||
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
|
||||
this.acknowledge_buffer_version(cx);
|
||||
this
|
||||
});
|
||||
@@ -121,6 +142,12 @@ impl ChannelView {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(link_position) = link_position {
|
||||
view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
});
|
||||
}
|
||||
|
||||
view
|
||||
})
|
||||
})
|
||||
@@ -128,16 +155,29 @@ impl ChannelView {
|
||||
|
||||
pub fn new(
|
||||
project: Model<Project>,
|
||||
workspace: WeakView<Workspace>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_buffer: Model<ChannelBuffer>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let buffer = channel_buffer.read(cx).buffer();
|
||||
let this = cx.view().downgrade();
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, None, cx);
|
||||
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
|
||||
channel_buffer.clone(),
|
||||
)));
|
||||
editor.set_custom_context_menu(move |_, position, cx| {
|
||||
let this = this.clone();
|
||||
Some(ui::ContextMenu::build(cx, move |menu, _| {
|
||||
menu.entry("Copy link to section", None, move |cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.copy_link_for_position(position.clone(), cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}))
|
||||
});
|
||||
editor
|
||||
});
|
||||
let _editor_event_subscription =
|
||||
@@ -148,14 +188,94 @@ impl ChannelView {
|
||||
|
||||
Self {
|
||||
editor,
|
||||
workspace,
|
||||
project,
|
||||
channel_store,
|
||||
channel_buffer,
|
||||
remote_id: None,
|
||||
_editor_event_subscription,
|
||||
_reparse_subscription: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_position_from_link(
|
||||
&mut self,
|
||||
position: String,
|
||||
first_attempt: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let position = Channel::slug(&position).to_lowercase();
|
||||
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
|
||||
if let Some(outline) = snapshot.buffer_snapshot.outline(None) {
|
||||
if let Some(item) = outline
|
||||
.items
|
||||
.iter()
|
||||
.find(|item| &Channel::slug(&item.text).to_lowercase() == &position)
|
||||
{
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
|
||||
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(&map)])
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if !first_attempt {
|
||||
return;
|
||||
}
|
||||
self._reparse_subscription = Some(cx.subscribe(
|
||||
&self.editor,
|
||||
move |this, _, e: &EditorEvent, cx| {
|
||||
match e {
|
||||
EditorEvent::Reparsed => {
|
||||
this.focus_position_from_link(position.clone(), false, cx);
|
||||
this._reparse_subscription.take();
|
||||
}
|
||||
EditorEvent::Edited | EditorEvent::SelectionsChanged { local: true } => {
|
||||
this._reparse_subscription.take();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn copy_link(&mut self, _: &CopyLink, cx: &mut ViewContext<Self>) {
|
||||
let position = self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.selections.newest_display(cx).start);
|
||||
self.copy_link_for_position(position, cx)
|
||||
}
|
||||
|
||||
fn copy_link_for_position(&self, position: DisplayPoint, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
|
||||
let mut closest_heading = None;
|
||||
|
||||
if let Some(outline) = snapshot.buffer_snapshot.outline(None) {
|
||||
for item in outline.items {
|
||||
if item.range.start.to_display_point(&snapshot) > position {
|
||||
break;
|
||||
}
|
||||
closest_heading = Some(item);
|
||||
}
|
||||
}
|
||||
|
||||
let Some(channel) = self.channel(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let link = channel.notes_link(closest_heading.map(|heading| heading.text));
|
||||
cx.write_to_clipboard(ClipboardItem::new(link));
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.show_toast(Toast::new(0, "Link copied to clipboard"), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
|
||||
self.channel_buffer.read(cx).channel(cx)
|
||||
}
|
||||
@@ -215,8 +335,11 @@ impl ChannelView {
|
||||
impl EventEmitter<EditorEvent> for ChannelView {}
|
||||
|
||||
impl Render for ChannelView {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
self.editor.clone()
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::copy_link))
|
||||
.child(self.editor.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +397,7 @@ impl Item for ChannelView {
|
||||
Some(cx.new_view(|cx| {
|
||||
Self::new(
|
||||
self.project.clone(),
|
||||
self.workspace.clone(),
|
||||
self.channel_store.clone(),
|
||||
self.channel_buffer.clone(),
|
||||
cx,
|
||||
@@ -356,7 +480,7 @@ impl FollowableItem for ChannelView {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx);
|
||||
let open = ChannelView::open_in_pane(state.channel_id, None, pane, workspace, cx);
|
||||
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
let this = open.await?;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use crate::{collab_panel, ChatPanelSettings};
|
||||
use anyhow::Result;
|
||||
use call::{room, ActiveCall};
|
||||
use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore};
|
||||
use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore};
|
||||
use client::Client;
|
||||
use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, DismissEvent,
|
||||
ElementId, EventEmitter, Fill, FocusHandle, FocusableView, FontWeight, ListOffset,
|
||||
ListScrollEvent, ListState, Model, Render, Subscription, Task, View, ViewContext,
|
||||
VisualContext, WeakView,
|
||||
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, CursorStyle,
|
||||
DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
|
||||
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, StyledText,
|
||||
Subscription, Task, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use menu::Confirm;
|
||||
@@ -23,7 +23,7 @@ use std::{sync::Arc, time::Duration};
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{
|
||||
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
|
||||
TabBar,
|
||||
TabBar, Tooltip,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
@@ -62,6 +62,7 @@ pub struct ChatPanel {
|
||||
markdown_data: HashMap<ChannelMessageId, RichText>,
|
||||
focus_handle: FocusHandle,
|
||||
open_context_menu: Option<(u64, Subscription)>,
|
||||
highlighted_message: Option<(u64, Task<()>)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -69,7 +70,7 @@ struct SerializedChatPanel {
|
||||
width: Option<Pixels>,
|
||||
}
|
||||
|
||||
actions!(chat_panel, [ToggleFocus]);
|
||||
actions!(chat_panel, [ToggleFocus, CloseReplyPreview]);
|
||||
|
||||
impl ChatPanel {
|
||||
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||
@@ -124,6 +125,7 @@ impl ChatPanel {
|
||||
markdown_data: Default::default(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
open_context_menu: None,
|
||||
highlighted_message: None,
|
||||
};
|
||||
|
||||
if let Some(channel_id) = ActiveCall::global(cx)
|
||||
@@ -236,6 +238,7 @@ impl ChatPanel {
|
||||
let channel_name = chat.channel(cx).map(|channel| channel.name.clone());
|
||||
self.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_channel(channel_id, channel_name, cx);
|
||||
editor.clear_reply_to_message_id();
|
||||
});
|
||||
};
|
||||
let subscription = cx.subscribe(&chat, Self::channel_did_change);
|
||||
@@ -285,6 +288,99 @@ impl ChatPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_replied_to_message(
|
||||
&mut self,
|
||||
message_id: Option<ChannelMessageId>,
|
||||
reply_to_message: &ChannelMessage,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let body_element_id: ElementId = match message_id {
|
||||
Some(ChannelMessageId::Saved(id)) => ("reply-to-saved-message", id).into(),
|
||||
Some(ChannelMessageId::Pending(id)) => ("reply-to-pending-message", id).into(), // This should never happen
|
||||
None => ("composing-reply").into(),
|
||||
};
|
||||
|
||||
let message_element_id: ElementId = match message_id {
|
||||
Some(ChannelMessageId::Saved(id)) => ("reply-to-saved-message-container", id).into(),
|
||||
Some(ChannelMessageId::Pending(id)) => {
|
||||
("reply-to-pending-message-container", id).into()
|
||||
} // This should never happen
|
||||
None => ("composing-reply-container").into(),
|
||||
};
|
||||
|
||||
let current_channel_id = self.channel_id(cx);
|
||||
let reply_to_message_id = reply_to_message.id;
|
||||
|
||||
let reply_to_message_body = self
|
||||
.markdown_data
|
||||
.entry(reply_to_message.id)
|
||||
.or_insert_with(|| {
|
||||
Self::render_markdown_with_mentions(
|
||||
&self.languages,
|
||||
self.client.id(),
|
||||
reply_to_message,
|
||||
)
|
||||
});
|
||||
|
||||
const REPLY_TO_PREFIX: &str = "Reply to @";
|
||||
|
||||
div().flex_grow().child(
|
||||
v_flex()
|
||||
.id(message_element_id)
|
||||
.text_ui_xs()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_x_1()
|
||||
.items_center()
|
||||
.justify_start()
|
||||
.overflow_x_hidden()
|
||||
.whitespace_nowrap()
|
||||
.child(
|
||||
StyledText::new(format!(
|
||||
"{}{}",
|
||||
REPLY_TO_PREFIX,
|
||||
reply_to_message.sender.github_login.clone()
|
||||
))
|
||||
.with_highlights(
|
||||
&cx.text_style(),
|
||||
vec![(
|
||||
(REPLY_TO_PREFIX.len() - 1)
|
||||
..(reply_to_message.sender.github_login.len()
|
||||
+ REPLY_TO_PREFIX.len()),
|
||||
HighlightStyle {
|
||||
font_weight: Some(FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
)],
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.border_l_2()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.mb_1()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
div()
|
||||
.max_h_12()
|
||||
.child(reply_to_message_body.element(body_element_id, cx)),
|
||||
),
|
||||
)
|
||||
.cursor(CursorStyle::PointingHand)
|
||||
.tooltip(|cx| Tooltip::text("Go to message", cx))
|
||||
.on_click(cx.listener(move |chat_panel, _, cx| {
|
||||
if let Some(channel_id) = current_channel_id {
|
||||
chat_panel
|
||||
.select_channel(channel_id, reply_to_message_id.into(), cx)
|
||||
.detach_and_log_err(cx)
|
||||
}
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_chat = &self.active_chat.as_ref().unwrap().0;
|
||||
let (message, is_continuation_from_previous, is_admin) =
|
||||
@@ -317,18 +413,9 @@ impl ChatPanel {
|
||||
});
|
||||
|
||||
let _is_pending = message.is_pending();
|
||||
let text = self.markdown_data.entry(message.id).or_insert_with(|| {
|
||||
Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
|
||||
});
|
||||
|
||||
let belongs_to_user = Some(message.sender.id) == self.client.user_id();
|
||||
let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
|
||||
(message.id, belongs_to_user || is_admin)
|
||||
{
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let can_delete_message = belongs_to_user || is_admin;
|
||||
|
||||
let element_id: ElementId = match message.id {
|
||||
ChannelMessageId::Saved(id) => ("saved-message", id).into(),
|
||||
@@ -341,19 +428,41 @@ impl ChatPanel {
|
||||
.iter()
|
||||
.any(|m| Some(m.1) == self.client.user_id());
|
||||
|
||||
let message_id = match message.id {
|
||||
ChannelMessageId::Saved(id) => Some(id),
|
||||
ChannelMessageId::Pending(_) => None,
|
||||
};
|
||||
|
||||
let reply_to_message = message
|
||||
.reply_to_message_id
|
||||
.map(|id| active_chat.read(cx).find_loaded_message(id))
|
||||
.flatten()
|
||||
.cloned();
|
||||
|
||||
let replied_to_you =
|
||||
reply_to_message.as_ref().map(|m| m.sender.id) == self.client.user_id();
|
||||
|
||||
let is_highlighted_message = self
|
||||
.highlighted_message
|
||||
.as_ref()
|
||||
.is_some_and(|(id, _)| Some(id) == message_id.as_ref());
|
||||
let background = if is_highlighted_message {
|
||||
cx.theme().status().info_background
|
||||
} else if mentioning_you || replied_to_you {
|
||||
cx.theme().colors().background
|
||||
} else {
|
||||
cx.theme().colors().panel_background
|
||||
};
|
||||
|
||||
v_flex().w_full().relative().child(
|
||||
div()
|
||||
.bg(if mentioning_you {
|
||||
Fill::from(cx.theme().colors().background)
|
||||
} else {
|
||||
Fill::default()
|
||||
})
|
||||
.bg(background)
|
||||
.rounded_md()
|
||||
.overflow_hidden()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.when(!is_continuation_from_previous, |this| {
|
||||
this.mt_1().child(
|
||||
this.mt_2().child(
|
||||
h_flex()
|
||||
.text_ui_sm()
|
||||
.child(div().absolute().child(
|
||||
@@ -377,36 +486,86 @@ impl ChatPanel {
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(mentioning_you, |this| this.mt_1())
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.text_ui_sm()
|
||||
.id(element_id)
|
||||
.group("")
|
||||
.child(text.element("body".into(), cx))
|
||||
.child(
|
||||
.when(
|
||||
message.reply_to_message_id.is_some() && reply_to_message.is_none(),
|
||||
|this| {
|
||||
const MESSAGE_DELETED: &str = "Message has been deleted";
|
||||
|
||||
let body_text = StyledText::new(MESSAGE_DELETED).with_highlights(
|
||||
&cx.text_style(),
|
||||
vec![(
|
||||
0..MESSAGE_DELETED.len(),
|
||||
HighlightStyle {
|
||||
font_style: Some(FontStyle::Italic),
|
||||
..Default::default()
|
||||
},
|
||||
)],
|
||||
);
|
||||
|
||||
this.child(
|
||||
div()
|
||||
.absolute()
|
||||
.z_index(1)
|
||||
.right_0()
|
||||
.w_6()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.when(!self.has_open_menu(message_id_to_remove), |el| {
|
||||
el.visible_on_hover("")
|
||||
})
|
||||
.children(message_id_to_remove.map(|message_id| {
|
||||
popover_menu(("menu", message_id))
|
||||
.trigger(IconButton::new(
|
||||
("trigger", message_id),
|
||||
IconName::Ellipsis,
|
||||
))
|
||||
.menu(move |cx| {
|
||||
Some(Self::render_message_menu(&this, message_id, cx))
|
||||
})
|
||||
})),
|
||||
),
|
||||
),
|
||||
.border_l_2()
|
||||
.text_ui_xs()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.child(body_text),
|
||||
)
|
||||
},
|
||||
)
|
||||
.when_some(reply_to_message, |el, reply_to_message| {
|
||||
el.child(self.render_replied_to_message(
|
||||
Some(message.id),
|
||||
&reply_to_message,
|
||||
cx,
|
||||
))
|
||||
})
|
||||
.when(mentioning_you || replied_to_you, |this| this.my_0p5())
|
||||
.map(|el| {
|
||||
let text = self.markdown_data.entry(message.id).or_insert_with(|| {
|
||||
Self::render_markdown_with_mentions(
|
||||
&self.languages,
|
||||
self.client.id(),
|
||||
&message,
|
||||
)
|
||||
});
|
||||
el.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.text_ui_sm()
|
||||
.id(element_id)
|
||||
.group("")
|
||||
.child(text.element("body".into(), cx))
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.z_index(1)
|
||||
.right_0()
|
||||
.w_6()
|
||||
.bg(background)
|
||||
.when(!self.has_open_menu(message_id), |el| {
|
||||
el.visible_on_hover("")
|
||||
})
|
||||
.when_some(message_id, |el, message_id| {
|
||||
el.child(
|
||||
popover_menu(("menu", message_id))
|
||||
.trigger(IconButton::new(
|
||||
("trigger", message_id),
|
||||
IconName::Ellipsis,
|
||||
))
|
||||
.menu(move |cx| {
|
||||
Some(Self::render_message_menu(
|
||||
&this,
|
||||
message_id,
|
||||
can_delete_message,
|
||||
cx,
|
||||
))
|
||||
}),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -420,13 +579,27 @@ impl ChatPanel {
|
||||
fn render_message_menu(
|
||||
this: &View<Self>,
|
||||
message_id: u64,
|
||||
can_delete_message: bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> View<ContextMenu> {
|
||||
let menu = {
|
||||
let this = this.clone();
|
||||
ContextMenu::build(cx, move |menu, _| {
|
||||
menu.entry("Delete message", None, move |cx| {
|
||||
this.update(cx, |this, cx| this.remove_message(message_id, cx))
|
||||
ContextMenu::build(cx, move |menu, cx| {
|
||||
menu.entry(
|
||||
"Reply to message",
|
||||
None,
|
||||
cx.handler_for(&this, move |this, cx| {
|
||||
this.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_reply_to_message_id(message_id);
|
||||
editor.focus_handle(cx).focus(cx);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.when(can_delete_message, move |menu| {
|
||||
menu.entry(
|
||||
"Delete message",
|
||||
None,
|
||||
cx.handler_for(&this, move |this, cx| this.remove_message(message_id, cx)),
|
||||
)
|
||||
})
|
||||
})
|
||||
};
|
||||
@@ -453,7 +626,7 @@ impl ChatPanel {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None)
|
||||
rich_text::render_rich_text(message.body.clone(), &mentions, language_registry, None)
|
||||
}
|
||||
|
||||
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
@@ -517,7 +690,21 @@ impl ChatPanel {
|
||||
ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone())
|
||||
.await
|
||||
{
|
||||
let task = cx.spawn({
|
||||
let this = this.clone();
|
||||
|
||||
|mut cx| async move {
|
||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.highlighted_message.take();
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.highlighted_message = Some((message_id, task));
|
||||
if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) {
|
||||
this.message_list.scroll_to(ListOffset {
|
||||
item_ix,
|
||||
@@ -532,11 +719,19 @@ impl ChatPanel {
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn close_reply_preview(&mut self, _: &CloseReplyPreview, cx: &mut ViewContext<Self>) {
|
||||
self.message_editor
|
||||
.update(cx, |editor, _| editor.clear_reply_to_message_id());
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ChatPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let reply_to_message_id = self.message_editor.read(cx).reply_to_message_id();
|
||||
|
||||
v_flex()
|
||||
.key_context("ChatPanel")
|
||||
.track_focus(&self.focus_handle)
|
||||
.full()
|
||||
.on_action(cx.listener(Self::send))
|
||||
@@ -558,7 +753,7 @@ impl Render for ChatPanel {
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().flex_grow().px_2().pt_1().map(|this| {
|
||||
.child(div().flex_grow().px_2().map(|this| {
|
||||
if self.active_chat.is_some() {
|
||||
this.child(list(self.message_list.clone()).full())
|
||||
} else {
|
||||
@@ -589,25 +784,65 @@ impl Render for ChatPanel {
|
||||
)
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.when(!self.is_scrolled_to_bottom, |el| {
|
||||
el.border_t_1().border_color(cx.theme().colors().border)
|
||||
.when_some(reply_to_message_id, |el, reply_to_message_id| {
|
||||
let reply_message = self
|
||||
.active_chat()
|
||||
.map(|active_chat| {
|
||||
active_chat.read(cx).messages().iter().find_map(|m| {
|
||||
if m.id == ChannelMessageId::Saved(reply_to_message_id) {
|
||||
Some(m)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.p_2()
|
||||
.map(|el| {
|
||||
if self.active_chat.is_some() {
|
||||
el.child(self.message_editor.clone())
|
||||
} else {
|
||||
el.child(
|
||||
div()
|
||||
.rounded_md()
|
||||
.h_6()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().editor_background),
|
||||
)
|
||||
}
|
||||
}),
|
||||
.flatten()
|
||||
.cloned();
|
||||
|
||||
el.when_some(reply_message, |el, reply_message| {
|
||||
el.child(
|
||||
div()
|
||||
.when(!self.is_scrolled_to_bottom, |el| {
|
||||
el.border_t_1().border_color(cx.theme().colors().border)
|
||||
})
|
||||
.flex()
|
||||
.w_full()
|
||||
.items_start()
|
||||
.overflow_hidden()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.bg(cx.theme().colors().background)
|
||||
.child(self.render_replied_to_message(None, &reply_message, cx))
|
||||
.child(
|
||||
IconButton::new("close-reply-preview", IconName::Close)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Close reply preview",
|
||||
&CloseReplyPreview,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.dispatch_action(CloseReplyPreview.boxed_clone())
|
||||
})),
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
.children(
|
||||
Some(
|
||||
h_flex()
|
||||
.key_context("MessageEditor")
|
||||
.on_action(cx.listener(ChatPanel::close_reply_preview))
|
||||
.when(
|
||||
!self.is_scrolled_to_bottom && reply_to_message_id.is_none(),
|
||||
|el| el.border_t_1().border_color(cx.theme().colors().border),
|
||||
)
|
||||
.p_2()
|
||||
.map(|el| el.child(self.message_editor.clone())),
|
||||
)
|
||||
.filter(|_| self.active_chat.is_some()),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
@@ -747,6 +982,7 @@ mod tests {
|
||||
}),
|
||||
nonce: 5,
|
||||
mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)],
|
||||
reply_to_message_id: None,
|
||||
};
|
||||
|
||||
let message = ChatPanel::render_markdown_with_mentions(&language_registry, 102, &message);
|
||||
|
||||