Compare commits

..

1 Commits

Author SHA1 Message Date
Mikayla
cc1de84610 WIP: Add item splitting into the editor itself
I initially attempted to add this at the workspace level, but abandoned the track
after I realized clone_on_split would make implementing it in the workspace difficult
After moving that effort into the editor itself, I ran into issues with rendering
and then further issues with non-re-usable scroll handles due to their implementation
in the pane group code. This is turning out to be several refactorings in one.
2024-01-27 19:55:22 -08:00
624 changed files with 10434 additions and 31528 deletions

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Prevent GitHub from displaying comments within JSON files as errors.
*.json linguist-language=JSON-with-Comments

View File

@@ -2,14 +2,23 @@ name: "Check formatting"
description: "Checks code formatting use cargo fmt"
runs:
using: "composite"
steps:
- name: cargo fmt
shell: bash -euxo pipefail {0}
run: cargo fmt --all -- --check
using: "composite"
steps:
- name: cargo fmt
shell: bash -euxo pipefail {0}
run: cargo fmt --all -- --check
- name: Find modified migrations
shell: bash -euxo pipefail {0}
run: |
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
. ./script/squawk
- name: 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

View File

@@ -10,7 +10,7 @@ runs:
cargo install cargo-nextest
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: "18"

View File

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

View File

@@ -1,210 +1,148 @@
name: CI
on:
push:
branches:
- main
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
pull_request:
branches:
- "**"
push:
branches:
- main
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
pull_request:
branches:
- "**"
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
jobs:
style:
name: Check formatting and spelling
runs-on:
- self-hosted
- test
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
submodules: "recursive"
fetch-depth: 0
style:
name: Check formatting, 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
- 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
- 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
tests:
name: Run tests
runs-on:
- self-hosted
- test
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
submodules: "recursive"
- 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: Run tests
uses: ./.github/actions/run_tests
macos_tests:
name: (macOS) Run Clippy and tests
runs-on:
- self-hosted
- test
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
submodules: "recursive"
- name: Build collab
run: cargo build -p collab
- name: cargo clippy
shell: bash -euxo pipefail {0}
run: script/clippy
- name: Build other binaries
run: cargo build --workspace --bins --all-features
- name: Run tests
uses: ./.github/actions/run_tests
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: Build collab
run: cargo build -p collab
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
submodules: "recursive"
- name: Build other binaries
run: cargo build --workspace --bins --all-features
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Build Blade GPUI
run: cargo check --features "macos-blade"
working-directory: "crates/gpui"
- 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: Build Zed
run: cargo build -p zed
bundle:
name: Bundle app
runs-on:
- self-hosted
- bundle
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
needs: [macos_tests, linux_tests]
- name: Upload app bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@v3
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- uses: softprops/action-gh-release@v1
name: Upload app bundle to release
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: target/release/Zed.dmg
body: ""
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
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 }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,35 +0,0 @@
name: Danger
on:
pull_request:
branches: [main]
types:
- opened
- synchronize
- reopened
- edited
jobs:
danger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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"
- run: pnpm install --dir script/danger
- name: Run Danger
run: pnpm run --dir script/danger danger ci
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@@ -1,111 +0,0 @@
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: 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}
- name: Prune Docker system
run: docker system prune --filter 'until=72h' -f
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

View File

@@ -0,0 +1,49 @@
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}

View File

@@ -3,35 +3,35 @@ name: Randomized Tests
concurrency: randomized-tests
on:
push:
branches:
- randomized-tests-runner
# schedule:
# - cron: '0 * * * *'
push:
branches:
- randomized-tests-runner
# schedule:
# - cron: '0 * * * *'
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
ZED_SERVER_URL: https://zed.dev
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
ZED_SERVER_URL: https://zed.dev
jobs:
tests:
name: Run randomized tests
runs-on:
- self-hosted
- randomized-tests
steps:
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "18"
tests:
name: Run randomized tests
runs-on:
- self-hosted
- randomized-tests
steps:
- name: Install Node
uses: actions/setup-node@v3
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

View File

@@ -16,7 +16,6 @@ env:
jobs:
style:
name: Check formatting and Clippy lints
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- test
@@ -31,12 +30,8 @@ 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
@@ -53,7 +48,6 @@ jobs:
bundle:
name: Bundle app
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- bundle
@@ -65,10 +59,9 @@ 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@v4
uses: actions/setup-node@v3
with:
node-version: "18"

View File

@@ -6,7 +6,6 @@ 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
@@ -15,4 +14,4 @@ jobs:
architecture: "x64"
cache: "pip"
- run: pip install -r script/update_top_ranking_issues/requirements.txt
- run: python script/update_top_ranking_issues/main.py 5393 --github-token ${{ secrets.GITHUB_TOKEN }} --prod
- run: python script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --prod

View File

@@ -1,18 +0,0 @@
on:
schedule:
- cron: "0 15 * * *"
workflow_dispatch:
jobs:
update_top_ranking_issues:
runs-on: ubuntu-latest
if: github.repository_owner == 'zed-industries'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: "3.10.5"
architecture: "x64"
cache: "pip"
- run: pip install -r script/update_top_ranking_issues/requirements.txt
- run: python script/update_top_ranking_issues/main.py 6952 --github-token ${{ secrets.GITHUB_TOKEN }} --prod --query-day-interval 7

View File

@@ -13,8 +13,6 @@ 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>
@@ -41,8 +39,6 @@ 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>

View File

@@ -1,6 +1,6 @@
{
"JSON": {
"tab_size": 4
},
"formatter": "auto"
"JSON": {
"tab_size": 4
},
"formatter": "auto"
}

View File

@@ -10,7 +10,7 @@ All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/d
If you're looking for ideas about what to work on, check out:
- Our [public roadmap](https://zed.dev/roadmap) contains a rough outline of our near-term priorities for Zed.
- Our public roadmap (link coming soon!) contains a rough outline of our near-term priorities for Zed.
- Our [top-ranking issues](https://github.com/zed-industries/zed/issues/5393) based on votes by the community.
Outside of a handful of extremely popular languages and themes, we are generally not looking to extend Zed's language or theme support by directly building them into Zed. We really want to build a plugin system to handle making the editor extensible going forward. If you are passionate about shipping new languages or themes we suggest contributing to the extension system to help us get there faster.
@@ -19,7 +19,7 @@ Outside of a handful of extremely popular languages and themes, we are generally
The best way to propose a change is to [start a discussion on our GitHub repository](https://github.com/zed-industries/zed/discussions).
First, write a short **problem statement**, which _clearly_ and _briefly_ describes the problem you want to solve independently from any specific solution. It doesn't need to be long or formal, but it's difficult to consider a solution in absence of a clear understanding of the problem.
First, write a short **problem statement**, which *clearly* and *briefly* describes the problem you want to solve independently from any specific solution. It doesn't need to be long or formal, but it's difficult to consider a solution in absence of a clear understanding of the problem.
Next, write a short **solution proposal**. How can the problem (or set of problems) you have stated above be addressed? What are the pros and cons of your approach? Again, keep it brief and informal. This isn't a specification, but rather a starting point for a conversation.
@@ -43,14 +43,14 @@ We plan to set aside time each week to pair program with contributors on promisi
Zed is made up of several smaller crates - let's go over those you're most likely to interact with:
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation**
- [`editor`](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions.
- [`project`](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP.
- [`workspace`](/crates/workspace) handles local state serialization and groups projects together.
- [`vim`](/crates/vim) is a thin implementation of Vim workflow over `editor`.
- [`lsp`](/crates/lsp) handles communication with external LSP server.
- [`language`](/crates/language) drives `editor`'s understanding of language - from providing a list of symbols to the syntax map.
- [`collab`](/crates/collab) is the collaboration server itself, driving the collaboration features such as project sharing.
- [`rpc`](/crates/rpc) defines messages to be exchanged with collaboration server.
- [`theme`](/crates/theme) defines the theme system and provides a default theme.
- [`ui`](/crates/ui) is a collection of UI components and common patterns used throughout Zed.
- [gpui](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation**
- [editor](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions.
- [project](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP.
- [workspace](/crates/workspace) handles local state serialization and groups projects together.
- [vim](/crates/vim) is a thin implementation of Vim workflow over `editor`.
- [lsp](/crates/lsp) handles communication with external LSP server.
- [language](/crates/language) drives `editor`'s understanding of language - from providing a list of symbols to the syntax map.
- [collab](/crates/collab) is the collaboration server itself, driving the collaboration features such as project sharing.
- [rpc](/crates/rpc) defines messages to be exchanged with collaboration server.
- [theme](/crates/theme) defines the theme system and provides a default theme.
- [ui](/crates/ui) is a collection of UI components and common patterns used throughout Zed.

1571
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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,15 +33,17 @@ 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",
@@ -57,10 +59,6 @@ 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",
@@ -69,7 +67,7 @@ members = [
"crates/snippet",
"crates/sqlez",
"crates/sqlez_macros",
"crates/story",
"crates/rich_text",
"crates/storybook",
"crates/sum_tree",
"crates/terminal",
@@ -80,10 +78,11 @@ members = [
"crates/theme_selector",
"crates/ui",
"crates/util",
"crates/vcs_menu",
"crates/story",
"crates/vim",
"crates/welcome",
"crates/vcs_menu",
"crates/workspace",
"crates/welcome",
"crates/zed",
"crates/zed_actions",
]
@@ -91,208 +90,89 @@ default-members = ["crates/zed"]
resolver = "2"
[workspace.dependencies]
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"
anyhow = { version = "1.0.57" }
async-trait = { version = "0.1" }
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-tar = "0.4.2"
async-trait = "0.1"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" }
blade-rwh = { package = "raw-window-handle", version = "0.5" }
chrono = { version = "0.4", features = ["serde"] }
ctor = "0.2.6"
derive_more = "0.99.17"
env_logger = "0.9"
futures = "0.3"
git2 = { version = "0.15", default-features = false }
globset = "0.4"
derive_more = { version = "0.99.17" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
globset = { version = "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 = "1.4.0"
isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
lazy_static = { version = "1.4.0" }
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
ordered-float = "2.1.1"
parking_lot = "0.11.1"
ordered-float = { version = "2.1.1" }
parking_lot = { version = "0.11.1" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = "1.3.0"
prost = "0.8"
pulldown-cmark = { version = "0.9.2", default-features = false }
rand = "0.8.5"
prost = { version = "0.8" }
rand = { version = "0.8.5" }
refineable = { path = "./crates/refineable" }
regex = "1.5"
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
regex = { version = "1.5" }
rust-embed = { version = "8.0", features = ["include-exclude"] }
schemars = "0.8"
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
schemars = { version = "0.8" }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
serde_json_lenient = { version = "0.1", features = [
"preserve_order",
"raw_value",
] }
serde_repr = "0.1"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
smol = { version = "1.2" }
strum = { version = "0.25.0", features = ["derive"] }
sysinfo = "0.29.10"
tempfile = "3.9.0"
thiserror = "1.0.29"
tiktoken-rs = "0.5.7"
tempfile = { version = "3.9.0" }
thiserror = { version = "1.0.29" }
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" }
toml = { version = "0.5" }
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"] }
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
tree-sitter-beancount = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "da1bf8c6eb0ae7a97588affde7227630bcd678b6" }
tree-sitter-c = "0.20.1"
tree-sitter-clojure = { git = "https://github.com/prcastro/tree-sitter-clojure", branch = "update-ts" }
tree-sitter-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-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-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-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" }
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
tree-sitter-gomod = { git = "https://github.com/camdencheek/tree-sitter-go-mod" }
tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work" }
tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "cf98de23e4285b8e6bcb57b050ef2326e2cc284b" }
tree-sitter-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-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-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-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
tree-sitter-php = "0.21.1"
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-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-ruby = "0.20.0"
tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "cf98de23e4285b8e6bcb57b050ef2326e2cc284b" }
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-lua = "0.0.14"
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-uiua = { git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2" }
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 = "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" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" }
# wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
[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

View File

@@ -1,17 +1,13 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.76-bullseye as builder
FROM rust:1.75-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=/usr/local/cargo/git \
--mount=type=cache,target=./target \
cargo build --release --package collab --bin collab

View File

@@ -14,11 +14,6 @@ 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)

View File

@@ -1,4 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.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>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,4 +0,0 @@
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M24.235 6.519l-16.47-0.004 0.266 3.277 12.653 0.002-0.319 3.394h-8.298l0.3 3.215h7.725l-0.457 4.403-3.636 1.005-3.694-1.012-0.235-2.637h-3.262l0.362 4.817 6.829 2.128 6.714-1.912 1.521-16.675zM2.879 1.004h26.242l-2.387 26.946-10.763 3.045-10.703-3.047z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 482 B

View File

@@ -1,9 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path 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>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,12 +1,7 @@
{
"suffixes": {
"astro": "astro",
"Emakefile": "erlang",
"aac": "audio",
"accdb": "storage",
"app.src": "erlang",
"avi": "video",
"avif": "image",
"bak": "backup",
"bash": "terminal",
"bash_aliases": "terminal",
@@ -18,7 +13,7 @@
"cc": "code",
"conf": "settings",
"cpp": "code",
"css": "css",
"css": "code",
"csv": "storage",
"dat": "storage",
"db": "storage",
@@ -27,9 +22,6 @@
"doc": "document",
"docx": "document",
"eex": "elixir",
"elm": "elm",
"erl": "erlang",
"escript": "erlang",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
@@ -44,46 +36,29 @@
"gif": "image",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitkeep": "vcs",
"gitmodules": "vcs",
"go": "go",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"heex": "elixir",
"heif": "image",
"heic": "image",
"hrl": "erlang",
"hs": "haskell",
"htm": "template",
"html": "template",
"ib": "storage",
"ico": "image",
"ini": "settings",
"j2k": "image",
"java": "code",
"jfif": "image",
"jp2": "image",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"jsonc": "storage",
"jxl": "image",
"ldf": "storage",
"lock": "lock",
"log": "log",
"m4a": "audio",
"m4v": "video",
"md": "document",
"mdb": "storage",
"mdf": "storage",
"mdx": "document",
"mkv": "video",
"mka": "audio",
"ml": "ocaml",
"mli": "ocaml",
"mov": "video",
"mp3": "audio",
"mp4": "video",
"myd": "storage",
@@ -91,24 +66,20 @@
"odp": "document",
"ods": "document",
"odt": "document",
"ogg": "audio",
"opus": "audio",
"ogg": "video",
"pdb": "storage",
"pdf": "document",
"php": "php",
"php": "code",
"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",
"rb": "code",
"rkt": "code",
"rs": "rust",
"rtf": "document",
@@ -126,20 +97,13 @@
"tsv": "storage",
"tsx": "code",
"txt": "document",
"vue": "vue",
"wav": "audio",
"webm": "video",
"webp": "image",
"wma": "audio",
"wmv": "video",
"wv": "audio",
"xls": "document",
"xlsx": "document",
"xml": "template",
"xrl": "erlang",
"yaml": "settings",
"yml": "settings",
"yrl": "erlang",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
@@ -149,9 +113,6 @@
"zshrc": "terminal"
},
"types": {
"astro": {
"icon": "icons/file_icons/astro.svg"
},
"audio": {
"icon": "icons/file_icons/audio.svg"
},
@@ -164,9 +125,6 @@
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"css": {
"icon": "icons/file_icons/css.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
@@ -176,12 +134,6 @@
"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"
},
@@ -191,12 +143,6 @@
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"haskell": {
"icon": "icons/file_icons/haskell.svg"
},
"go": {
"icon": "icons/file_icons/go.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
@@ -206,27 +152,15 @@
"log": {
"icon": "icons/file_icons/info.svg"
},
"ocaml": {
"icon": "icons/file_icons/ocaml.svg"
},
"phoenix": {
"icon": "icons/file_icons/phoenix.svg"
},
"php": {
"icon": "icons/file_icons/php.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"prisma": {
"icon": "icons/file_icons/prisma.svg"
},
"python": {
"icon": "icons/file_icons/python.svg"
},
"ruby": {
"icon": "icons/file_icons/ruby.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
@@ -253,9 +187,6 @@
},
"video": {
"icon": "icons/file_icons/video.svg"
},
"vue": {
"icon": "icons/file_icons/vue.svg"
}
}
}

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg977"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs981" />
<path
id="path973"
d="M 10.699219 8.9003906 L 10.699219 9 C 12.199219 11.3 13.800781 13.600391 15.300781 15.900391 L 15.300781 16 C 13.800781 18.3 12.199219 20.600391 10.699219 22.900391 L 10.699219 23 L 14.199219 23 L 14.300781 22.900391 C 15.200781 21.500391 16.199609 20.099219 17.099609 18.699219 C 17.199609 18.599219 17.099219 18.599219 17.199219 18.699219 C 18.099219 20.099219 19.1 21.500391 20 22.900391 L 20.099609 23 L 23.599609 23 C 21.699609 20 19.699219 17.099609 17.699219 14.099609 C 16.499219 12.399609 15.399219 10.600391 14.199219 8.9003906 L 10.699219 8.9003906 z M 6 9 C 7.6 11.3 9.0996094 13.6 10.599609 16 L 10.599609 16.099609 C 9.4996094 17.799609 8.4007813 19.399609 7.3007812 21.099609 C 6.8007813 21.699609 6.4 22.4 6 23 L 6 23.099609 L 9.5 23.099609 C 11.1 20.699609 12.699219 18.399609 14.199219 16.099609 L 14.199219 16 C 13.499219 14.8 12.700391 13.7 11.900391 12.5 C 11.100391 11.4 10.399609 10.199609 9.5996094 9.0996094 L 9.5 9 L 6 9 z M 18.199219 13 L 18.199219 13.099609 C 18.699219 13.899609 19.199219 14.600391 19.699219 15.400391 L 26 15.400391 L 26 13 L 18.199219 13 z M 20.5 16.599609 L 20.5 16.699219 C 21 17.499219 21.5 18.2 22 19 L 26 19 L 26 16.599609 L 20.5 16.599609 z " />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,5 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="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>

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,6 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 680 B

View File

@@ -1,7 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 7.9 4.8 12C6.1 12 8.5 10.5 9.5 9.5Z" fill="black" fill-opacity="0.6"/>
<path d="M12 4.8 7.9 5 9.5 9.5 12 12Z" fill="black" fill-opacity="0.6"/>
<path d="M12 4.8V12H4.8" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.3 7.2 2 10C2 11.8 3.6 12 4.8 12 6.5 12 8.5 10.5 9.5 9.5 10.5 8.5 12 6.5 12 4.8 12 3.6 11.8 2 10 2L7.2 2.3" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<ellipse rx="1.9" ry="3.8" cx="7.5" cy="0" transform="rotate(45)" stroke="black" stroke-width="1.25"/>
</svg>

Before

Width:  |  Height:  |  Size: 674 B

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
<path d="M7 8.135 3.578 2.212l-.18-.312H1l6 10.392L13 1.9h-2.4L7 8.135Z"/>
<path d="M7 3.675 5.972 1.9H1l.18.312h4.615L7 4.3l1.205-2.088h4.615L13 1.9H8.028L7 3.675Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 240 B

View File

@@ -415,15 +415,7 @@
"cmd-?": "assistant::ToggleFocus",
"cmd-alt-s": "workspace::SaveAll",
"cmd-k m": "language_selector::Toggle",
"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"]
"escape": "workspace::Unfollow"
}
},
// Bindings from Sublime Text
@@ -449,6 +441,18 @@
"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",
@@ -565,12 +569,6 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "ChatPanel > MessageEditor",
"bindings": {
"escape": "chat_panel::CloseReplyPreview"
}
},
{
"context": "Terminal",
"bindings": {

View File

@@ -42,7 +42,6 @@
"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",
@@ -81,18 +80,10 @@
"cmd-6": "diagnostics::Deploy"
}
},
{
"context": "Pane",
"bindings": {
"cmd-alt-left": "pane::GoBack",
"cmd-alt-right": "pane::GoForward"
}
},
{
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"shift-f6": "project_panel::Rename"
"enter": "project_panel::Open"
}
}
]

View File

@@ -31,7 +31,6 @@
"up": "vim::Up",
"l": "vim::Right",
"right": "vim::Right",
"space": "vim::Space",
"$": "vim::EndOfLine",
"^": "vim::FirstNonWhitespace",
"_": "vim::StartOfLineDownward",
@@ -96,8 +95,6 @@
}
}
],
";": "vim::RepeatFind",
",": "vim::RepeatFindReversed",
"ctrl-o": "pane::GoBack",
"ctrl-i": "pane::GoForward",
"ctrl-]": "editor::GoToDefinition",
@@ -209,9 +206,6 @@
"displayLines": true
}
],
"shift-h": "vim::WindowTop",
"shift-m": "vim::WindowMiddle",
"shift-l": "vim::WindowBottom",
// z commands
"z t": "editor::ScrollCursorTop",
"z z": "editor::ScrollCursorCenter",
@@ -284,8 +278,7 @@
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
"-": "pane::RevealInProjectPanel"
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"]
}
},
{
@@ -336,6 +329,13 @@
],
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
";": "vim::RepeatFind",
",": [
"vim::RepeatFind",
{
"backwards": true
}
],
"r": ["vim::PushOperator", "Replace"],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
@@ -400,8 +400,7 @@
{
"context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
"bindings": {
"u": "vim::ConvertToLowerCase",
"U": "vim::ConvertToUpperCase",
"u": "editor::Undo",
"o": "vim::OtherEnd",
"shift-o": "vim::OtherEnd",
"d": "vim::VisualDelete",
@@ -485,9 +484,7 @@
"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-w": "editor::DeleteToPreviousWordStart",
"ctrl-u": "editor::DeleteToBeginningOfLine"
"ctrl-x ctrl-z": "editor::Cancel"
}
},
{
@@ -500,38 +497,10 @@
}
},
{
"context": "BufferSearchBar && !in_replace",
"context": "BufferSearchBar && !in_replace > VimEnabled",
"bindings": {
"enter": "vim::SearchSubmit",
"escape": "buffer_search::Dismiss"
}
},
{
// Directory expansion
"context": "ProjectPanel && not_editing",
"bindings": {
"escape": "project_panel::ToggleFocus",
"enter": "project_panel::Open",
"o": "project_panel::Open",
"t": "project_panel::Open",
"v": "project_panel::Open",
"d": "project_panel::NewDirectory",
"%": "project_panel::NewFile",
"shift-r": "project_panel::Rename",
"m m": "project_panel::Cut",
"m c": "project_panel::Copy",
"m t": "project_panel::Paste",
"x": "project_panel::RevealInFinder",
"l": "project_panel::ExpandSelectedEntry",
"h": "project_panel::CollapseSelectedEntry",
// Move up and down
"j": "menu::SelectNext",
"k": "menu::SelectPrev",
"shift-d": "project_panel::Delete",
"/": "project_panel::NewSearchInDirectory",
// zed specific
"q p": "project_panel::CopyPath",
"q r": "project_panel::CopyRelativePath"
}
}
]

View File

@@ -62,9 +62,6 @@
// 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
@@ -72,17 +69,6 @@
"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,
@@ -104,17 +90,8 @@
"show_whitespaces": "selection",
// Settings related to calls in Zed
"calls": {
// 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
// Join calls with the microphone muted by default
"mute_on_join": false
},
// Scrollbar related settings
"scrollbar": {
@@ -134,14 +111,8 @@
// Whether to show git diff indicators in the scrollbar.
"git_diff": true,
// Whether to show selections in the scrollbar.
"selections": true,
// Whether to show symbols selections in the scrollbar.
"symbols_selections": true,
// Whether to show diagnostic indicators in the scrollbar.
"diagnostics": true
"selections": 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:
@@ -214,8 +185,6 @@
"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:
//
@@ -447,7 +416,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",
@@ -500,9 +469,6 @@
"JavaScript": {
"tab_size": 2
},
"Terraform": {
"tab_size": 2
},
"TypeScript": {
"tab_size": 2
},
@@ -514,12 +480,6 @@
},
"JSON": {
"tab_size": 2
},
"OCaml": {
"tab_size": 2
},
"OCaml Interface": {
"tab_size": 2
}
},
// Zed's Prettier integration settings.
@@ -543,28 +503,5 @@
// }
// }
// }
},
// The server to connect to. If the environment variable
// ZED_SERVER_URL is set, it will override this setting.
"server_url": "https://zed.dev",
// Settings overrides to use when using Zed Preview.
// Mostly useful for developers who are managing multiple instances of Zed.
"preview": {
// "theme": "Andromeda"
},
// Settings overrides to use when using Zed Nightly.
// Mostly useful for developers who are managing multiple instances of Zed.
"nightly": {
// "theme": "Andromeda"
},
// Settings overrides to use when using Zed Stable.
// Mostly useful for developers who are managing multiple instances of Zed.
"stable": {
// "theme": "Andromeda"
},
// Settings overrides to use when using Zed Dev.
// Mostly useful for developers who are managing multiple instances of Zed.
"dev": {
// "theme": "Andromeda"
}
}

View File

@@ -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://zed.dev/docs/configuring-zed#folder-specific-settings
// see the documentation: https://docs.zed.dev/configuration/configuring-zed#folder-specific-settings
{}

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#21242bff",
"tab.inactive_background": "#21242bff",
"tab.active_background": "#1e2025ff",
"search.match_background": "#11a79366",
"search.match_background": null,
"panel.background": "#21242bff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#221f26ff",
"tab.inactive_background": "#221f26ff",
"tab.active_background": "#19171cff",
"search.match_background": "#576dda66",
"search.match_background": null,
"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": "#586dda66",
"search.match_background": null,
"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": "#6684e066",
"search.match_background": null,
"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": "#6784e066",
"search.match_background": null,
"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": "#37a16666",
"search.match_background": null,
"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": "#38a16666",
"search.match_background": null,
"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": "#417ee666",
"search.match_background": null,
"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": "#417ee666",
"search.match_background": null,
"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": "#526aeb66",
"search.match_background": null,
"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": "#526aeb66",
"search.match_background": null,
"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": "#277fad66",
"search.match_background": null,
"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": "#277fad66",
"search.match_background": null,
"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": "#7272ca66",
"search.match_background": null,
"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": "#7372ca66",
"search.match_background": null,
"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": "#478c9066",
"search.match_background": null,
"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": "#488c9066",
"search.match_background": null,
"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": "#3e62f466",
"search.match_background": null,
"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": "#3f62f466",
"search.match_background": null,
"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": "#3e8fd066",
"search.match_background": null,
"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": "#3f8fd066",
"search.match_background": null,
"panel.background": "#e5e8f5ff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#1f2127ff",
"tab.inactive_background": "#1f2127ff",
"tab.active_background": "#0d1016ff",
"search.match_background": "#5ac2fe66",
"search.match_background": null,
"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": "#3b9ee566",
"search.match_background": null,
"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": "#73cffe66",
"search.match_background": null,
"panel.background": "#353944ff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#3a3735ff",
"tab.inactive_background": "#3a3735ff",
"tab.active_background": "#282828ff",
"search.match_background": "#83a59866",
"search.match_background": null,
"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": "#83a59866",
"search.match_background": null,
"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": "#83a59866",
"search.match_background": null,
"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": "#0b667866",
"search.match_background": null,
"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": "#0b667866",
"search.match_background": null,
"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": "#0b667866",
"search.match_background": null,
"panel.background": "#ecdcb3ff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#2f343eff",
"tab.inactive_background": "#2f343eff",
"tab.active_background": "#282c33ff",
"search.match_background": "#74ade866",
"search.match_background": null,
"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": "#5c79e266",
"search.match_background": null,
"panel.background": "#ebebecff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#1c1b2aff",
"tab.inactive_background": "#1c1b2aff",
"tab.active_background": "#191724ff",
"search.match_background": "#57949f66",
"search.match_background": null,
"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": "#9cced766",
"search.match_background": null,
"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": "#9cced766",
"search.match_background": null,
"panel.background": "#28253cff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#2b3038ff",
"tab.inactive_background": "#2b3038ff",
"tab.active_background": "#282c33ff",
"search.match_background": "#528b8b66",
"search.match_background": null,
"panel.background": "#2b3038ff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#04313bff",
"tab.inactive_background": "#04313bff",
"tab.active_background": "#002a35ff",
"search.match_background": "#288bd166",
"search.match_background": null,
"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": "#298bd166",
"search.match_background": null,
"panel.background": "#f3eddaff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -42,7 +42,7 @@
"tab_bar.background": "#231f16ff",
"tab.inactive_background": "#231f16ff",
"tab.active_background": "#1b1810ff",
"search.match_background": "#499bef66",
"search.match_background": null,
"panel.background": "#231f16ff",
"panel.focused_border": null,
"pane.focused_border": null,

View File

@@ -5,24 +5,26 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
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 = { workspace = true, features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }

View File

@@ -5,6 +5,7 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/ai.rs"
doctest = false
@@ -13,27 +14,27 @@ doctest = false
test-support = []
[dependencies]
anyhow.workspace = true
gpui = { path = "../gpui" }
util = { path = "../util" }
language = { path = "../language" }
async-trait.workspace = true
bincode = "1.3.3"
anyhow.workspace = true
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
parse_duration = "2.1.1"
postage.workspace = true
rand.workspace = true
isahc.workspace = true
regex.workspace = true
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
serde.workspace = true
serde_json.workspace = true
postage.workspace = true
rand.workspace = true
log.workspace = true
parse_duration = "2.1.1"
tiktoken-rs.workspace = true
util.workspace = true
matrixmultiply = "0.3.7"
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
bincode = "1.3.3"
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }

View File

@@ -1,9 +0,0 @@
pub mod completion;
pub mod embedding;
pub mod model;
pub use completion::*;
pub use embedding::*;
pub use model::OpenAiLanguageModel;
pub const OPEN_AI_API_URL: &'static str = "https://api.openai.com/v1";

View File

@@ -21,7 +21,7 @@ use crate::{
models::LanguageModel,
};
use crate::providers::open_ai::{OpenAiLanguageModel, OPEN_AI_API_URL};
use crate::providers::open_ai::{OpenAILanguageModel, OPENAI_API_URL};
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
@@ -58,7 +58,7 @@ pub struct RequestMessage {
}
#[derive(Debug, Default, Serialize)]
pub struct OpenAiRequest {
pub struct OpenAIRequest {
pub model: String,
pub messages: Vec<RequestMessage>,
pub stream: bool,
@@ -66,7 +66,7 @@ pub struct OpenAiRequest {
pub temperature: f32,
}
impl CompletionRequest for OpenAiRequest {
impl CompletionRequest for OpenAIRequest {
fn data(&self) -> serde_json::Result<String> {
serde_json::to_string(self)
}
@@ -79,7 +79,7 @@ pub struct ResponseMessage {
}
#[derive(Deserialize, Debug)]
pub struct OpenAiUsage {
pub struct OpenAIUsage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
@@ -93,21 +93,20 @@ pub struct ChatChoiceDelta {
}
#[derive(Deserialize, Debug)]
pub struct OpenAiResponseStreamEvent {
pub struct OpenAIResponseStreamEvent {
pub id: Option<String>,
pub object: String,
pub created: u32,
pub model: String,
pub choices: Vec<ChatChoiceDelta>,
pub usage: Option<OpenAiUsage>,
pub usage: Option<OpenAIUsage>,
}
pub async fn stream_completion(
api_url: String,
credential: ProviderCredential,
executor: BackgroundExecutor,
request: Box<dyn CompletionRequest>,
) -> Result<impl Stream<Item = Result<OpenAiResponseStreamEvent>>> {
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
let api_key = match credential {
ProviderCredential::Credentials { api_key } => api_key,
_ => {
@@ -115,10 +114,10 @@ pub async fn stream_completion(
}
};
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAiResponseStreamEvent>>();
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
let json_data = request.data()?;
let mut response = Request::post(format!("{api_url}/chat/completions"))
let mut response = Request::post(format!("{OPENAI_API_URL}/chat/completions"))
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
.body(json_data)?
@@ -133,7 +132,7 @@ pub async fn stream_completion(
fn parse_line(
line: Result<String, io::Error>,
) -> Result<Option<OpenAiResponseStreamEvent>> {
) -> Result<Option<OpenAIResponseStreamEvent>> {
if let Some(data) = line?.strip_prefix("data: ") {
let event = serde_json::from_str(data)?;
Ok(Some(event))
@@ -170,16 +169,16 @@ pub async fn stream_completion(
response.body_mut().read_to_string(&mut body).await?;
#[derive(Deserialize)]
struct OpenAiResponse {
error: OpenAiError,
struct OpenAIResponse {
error: OpenAIError,
}
#[derive(Deserialize)]
struct OpenAiError {
struct OpenAIError {
message: String,
}
match serde_json::from_str::<OpenAiResponse>(&body) {
match serde_json::from_str::<OpenAIResponse>(&body) {
Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
"Failed to connect to OpenAI API: {}",
response.error.message,
@@ -195,21 +194,19 @@ pub async fn stream_completion(
}
#[derive(Clone)]
pub struct OpenAiCompletionProvider {
api_url: String,
model: OpenAiLanguageModel,
pub struct OpenAICompletionProvider {
model: OpenAILanguageModel,
credential: Arc<RwLock<ProviderCredential>>,
executor: BackgroundExecutor,
}
impl OpenAiCompletionProvider {
pub async fn new(api_url: String, model_name: String, executor: BackgroundExecutor) -> Self {
impl OpenAICompletionProvider {
pub async fn new(model_name: String, executor: BackgroundExecutor) -> Self {
let model = executor
.spawn(async move { OpenAiLanguageModel::load(&model_name) })
.spawn(async move { OpenAILanguageModel::load(&model_name) })
.await;
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
Self {
api_url,
model,
credential,
executor,
@@ -217,7 +214,7 @@ impl OpenAiCompletionProvider {
}
}
impl CredentialProvider for OpenAiCompletionProvider {
impl CredentialProvider for OpenAICompletionProvider {
fn has_credentials(&self) -> bool {
match *self.credential.read() {
ProviderCredential::Credentials { .. } => true,
@@ -235,7 +232,7 @@ impl CredentialProvider for OpenAiCompletionProvider {
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
async move { ProviderCredential::Credentials { api_key } }.boxed()
} else {
let credentials = cx.read_credentials(OPEN_AI_API_URL);
let credentials = cx.read_credentials(OPENAI_API_URL);
async move {
if let Some(Some((_, api_key))) = credentials.await.log_err() {
if let Some(api_key) = String::from_utf8(api_key).log_err() {
@@ -269,7 +266,7 @@ impl CredentialProvider for OpenAiCompletionProvider {
let credential = credential.clone();
let write_credentials = match credential {
ProviderCredential::Credentials { api_key } => {
Some(cx.write_credentials(OPEN_AI_API_URL, "Bearer", api_key.as_bytes()))
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
}
_ => None,
};
@@ -284,7 +281,7 @@ impl CredentialProvider for OpenAiCompletionProvider {
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
*self.credential.write() = ProviderCredential::NoCredentials;
let delete_credentials = cx.delete_credentials(OPEN_AI_API_URL);
let delete_credentials = cx.delete_credentials(OPENAI_API_URL);
async move {
delete_credentials.await.log_err();
}
@@ -292,7 +289,7 @@ impl CredentialProvider for OpenAiCompletionProvider {
}
}
impl CompletionProvider for OpenAiCompletionProvider {
impl CompletionProvider for OpenAICompletionProvider {
fn base_model(&self) -> Box<dyn LanguageModel> {
let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
model
@@ -306,8 +303,7 @@ 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 api_url = self.api_url.clone();
let request = stream_completion(api_url, credential, self.executor.clone(), prompt);
let request = stream_completion(credential, self.executor.clone(), prompt);
async move {
let response = request.await?;
let stream = response

View File

@@ -25,18 +25,17 @@ use util::ResultExt;
use crate::auth::{CredentialProvider, ProviderCredential};
use crate::embedding::{Embedding, EmbeddingProvider};
use crate::models::LanguageModel;
use crate::providers::open_ai::OpenAiLanguageModel;
use crate::providers::open_ai::OpenAILanguageModel;
use crate::providers::open_ai::OPEN_AI_API_URL;
use crate::providers::open_ai::OPENAI_API_URL;
lazy_static! {
pub(crate) static ref OPEN_AI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap();
static ref OPENAI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap();
}
#[derive(Clone)]
pub struct OpenAiEmbeddingProvider {
api_url: String,
model: OpenAiLanguageModel,
pub struct OpenAIEmbeddingProvider {
model: OpenAILanguageModel,
credential: Arc<RwLock<ProviderCredential>>,
pub client: Arc<dyn HttpClient>,
pub executor: BackgroundExecutor,
@@ -45,47 +44,42 @@ pub struct OpenAiEmbeddingProvider {
}
#[derive(Serialize)]
struct OpenAiEmbeddingRequest<'a> {
struct OpenAIEmbeddingRequest<'a> {
model: &'static str,
input: Vec<&'a str>,
}
#[derive(Deserialize)]
struct OpenAiEmbeddingResponse {
data: Vec<OpenAiEmbedding>,
usage: OpenAiEmbeddingUsage,
struct OpenAIEmbeddingResponse {
data: Vec<OpenAIEmbedding>,
usage: OpenAIEmbeddingUsage,
}
#[derive(Debug, Deserialize)]
struct OpenAiEmbedding {
struct OpenAIEmbedding {
embedding: Vec<f32>,
index: usize,
object: String,
}
#[derive(Deserialize)]
struct OpenAiEmbeddingUsage {
struct OpenAIEmbeddingUsage {
prompt_tokens: usize,
total_tokens: usize,
}
impl OpenAiEmbeddingProvider {
pub async fn new(
api_url: String,
client: Arc<dyn HttpClient>,
executor: BackgroundExecutor,
) -> Self {
impl OpenAIEmbeddingProvider {
pub async fn new(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));
// Loading the model is expensive, so ensure this runs off the main thread.
let model = executor
.spawn(async move { OpenAiLanguageModel::load("text-embedding-ada-002") })
.spawn(async move { OpenAILanguageModel::load("text-embedding-ada-002") })
.await;
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
OpenAiEmbeddingProvider {
api_url,
OpenAIEmbeddingProvider {
model,
credential,
client,
@@ -136,18 +130,17 @@ 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(format!("{api_url}/embeddings"))
let request = Request::post("https://api.openai.com/v1/embeddings")
.redirect_policy(isahc::config::RedirectPolicy::Follow)
.timeout(Duration::from_secs(request_timeout))
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
.body(
serde_json::to_string(&OpenAiEmbeddingRequest {
serde_json::to_string(&OpenAIEmbeddingRequest {
input: spans.clone(),
model: "text-embedding-ada-002",
})
@@ -159,7 +152,7 @@ impl OpenAiEmbeddingProvider {
}
}
impl CredentialProvider for OpenAiEmbeddingProvider {
impl CredentialProvider for OpenAIEmbeddingProvider {
fn has_credentials(&self) -> bool {
match *self.credential.read() {
ProviderCredential::Credentials { .. } => true,
@@ -177,7 +170,7 @@ impl CredentialProvider for OpenAiEmbeddingProvider {
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
async move { ProviderCredential::Credentials { api_key } }.boxed()
} else {
let credentials = cx.read_credentials(OPEN_AI_API_URL);
let credentials = cx.read_credentials(OPENAI_API_URL);
async move {
if let Some(Some((_, api_key))) = credentials.await.log_err() {
if let Some(api_key) = String::from_utf8(api_key).log_err() {
@@ -211,7 +204,7 @@ impl CredentialProvider for OpenAiEmbeddingProvider {
let credential = credential.clone();
let write_credentials = match credential {
ProviderCredential::Credentials { api_key } => {
Some(cx.write_credentials(OPEN_AI_API_URL, "Bearer", api_key.as_bytes()))
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
}
_ => None,
};
@@ -226,7 +219,7 @@ impl CredentialProvider for OpenAiEmbeddingProvider {
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
*self.credential.write() = ProviderCredential::NoCredentials;
let delete_credentials = cx.delete_credentials(OPEN_AI_API_URL);
let delete_credentials = cx.delete_credentials(OPENAI_API_URL);
async move {
delete_credentials.await.log_err();
}
@@ -235,7 +228,7 @@ impl CredentialProvider for OpenAiEmbeddingProvider {
}
#[async_trait]
impl EmbeddingProvider for OpenAiEmbeddingProvider {
impl EmbeddingProvider for OpenAIEmbeddingProvider {
fn base_model(&self) -> Box<dyn LanguageModel> {
let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
model
@@ -253,7 +246,6 @@ 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;
@@ -263,7 +255,6 @@ 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,
@@ -279,7 +270,7 @@ impl EmbeddingProvider for OpenAiEmbeddingProvider {
StatusCode::OK => {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
let response: OpenAiEmbeddingResponse = serde_json::from_str(&body)?;
let response: OpenAIEmbeddingResponse = serde_json::from_str(&body)?;
log::trace!(
"openai embedding completed. tokens: {:?}",

View File

@@ -0,0 +1,9 @@
pub mod completion;
pub mod embedding;
pub mod model;
pub use completion::*;
pub use embedding::*;
pub use model::OpenAILanguageModel;
pub const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";

View File

@@ -1,28 +1,26 @@
use anyhow::anyhow;
use tiktoken_rs::CoreBPE;
use util::ResultExt;
use crate::models::{LanguageModel, TruncationDirection};
use super::OPEN_AI_BPE_TOKENIZER;
#[derive(Clone)]
pub struct OpenAiLanguageModel {
pub struct OpenAILanguageModel {
name: String,
bpe: Option<CoreBPE>,
}
impl OpenAiLanguageModel {
impl OpenAILanguageModel {
pub fn load(model_name: &str) -> Self {
let bpe =
tiktoken_rs::get_bpe_from_model(model_name).unwrap_or(OPEN_AI_BPE_TOKENIZER.to_owned());
OpenAiLanguageModel {
let bpe = tiktoken_rs::get_bpe_from_model(model_name).log_err();
OpenAILanguageModel {
name: model_name.to_string(),
bpe: Some(bpe),
bpe,
}
}
}
impl LanguageModel for OpenAiLanguageModel {
impl LanguageModel for OpenAILanguageModel {
fn name(&self) -> String {
self.name.clone()
}

View File

@@ -0,0 +1,11 @@
pub trait LanguageModel {
fn name(&self) -> String;
fn count_tokens(&self, content: &str) -> anyhow::Result<usize>;
fn truncate(
&self,
content: &str,
length: usize,
direction: TruncationDirection,
) -> anyhow::Result<String>;
fn capacity(&self) -> anyhow::Result<usize>;
}

View File

@@ -5,7 +5,10 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
gpui.workspace = true
gpui = {path = "../gpui"}
rust-embed.workspace = true
anyhow.workspace = true

View File

@@ -16,7 +16,7 @@ use rust_embed::RustEmbed;
pub struct Assets;
impl AssetSource for Assets {
fn load(&self, path: &str) -> Result<std::borrow::Cow<'static, [u8]>> {
fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
Self::get(path)
.map(|f| f.data)
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))

View File

@@ -5,49 +5,52 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/assistant.rs"
doctest = false
[dependencies]
ai.workspace = true
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
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 = { workspace = true, features = ["test-support"] }
ai = { path = "../ai", features = ["test-support"]}
editor = { path = "../editor", features = ["test-support"] }
project = { path = "../project", 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

View File

@@ -7,7 +7,7 @@ mod streaming_diff;
use ai::providers::open_ai::Role;
use anyhow::Result;
pub use assistant_panel::AssistantPanel;
use assistant_settings::OpenAiModel;
use assistant_settings::OpenAIModel;
use chrono::{DateTime, Local};
use collections::HashMap;
use fs::Fs;
@@ -68,8 +68,7 @@ struct SavedConversation {
messages: Vec<SavedMessage>,
message_metadata: HashMap<MessageId, MessageMetadata>,
summary: String,
api_url: Option<String>,
model: OpenAiModel,
model: OpenAIModel,
}
impl SavedConversation {

View File

@@ -1,5 +1,5 @@
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAiModel},
assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAIModel},
codegen::{self, Codegen, CodegenKind},
prompts::generate_content_prompt,
Assist, CycleMessageRole, InlineAssist, MessageId, MessageMetadata, MessageStatus,
@@ -7,11 +7,10 @@ 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},
providers::open_ai::{OpenAiCompletionProvider, OpenAiRequest, RequestMessage},
providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
};
use anyhow::{anyhow, Result};
use chrono::{DateTime, Local};
@@ -36,7 +35,7 @@ use gpui::{
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
};
use language::{language_settings::SoftWrap, Buffer, BufferId, LanguageRegistry, ToOffset as _};
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
use project::Project;
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use semantic_index::{SemanticIndex, SemanticIndexStatus};
@@ -122,22 +121,10 @@ impl AssistantPanel {
.await
.log_err()
.unwrap_or_default();
let (api_url, model_name) = cx
.update(|cx| {
let settings = AssistantSettings::get_global(cx);
(
settings.openai_api_url.clone(),
settings.default_open_ai_model.full_name().to_string(),
)
})
.log_err()
.unwrap();
let completion_provider = OpenAiCompletionProvider::new(
api_url,
model_name,
cx.background_executor().clone(),
)
.await;
// 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;
// TODO: deserialize state.
let workspace_handle = workspace.clone();
@@ -212,13 +199,9 @@ impl AssistantPanel {
.update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
cx.notify();
if self.focus_handle.is_focused(cx) {
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() {
if let Some(editor) = self.active_editor() {
cx.focus_view(editor);
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
cx.focus_view(api_key_editor);
}
}
@@ -734,7 +717,7 @@ impl AssistantPanel {
content: prompt,
});
let request = Box::new(OpenAiRequest {
let request = Box::new(OpenAIRequest {
model: model.full_name().into(),
messages,
stream: true,
@@ -794,10 +777,6 @@ 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(
@@ -891,7 +870,7 @@ impl AssistantPanel {
cx.update(|cx| completion_provider.delete_credentials(cx))?
.await;
this.update(&mut cx, |this, cx| {
this.build_api_key_editor(cx);
this.api_key_editor = Some(build_api_key_editor(cx));
this.focus_handle.focus(cx);
cx.notify();
})
@@ -975,7 +954,6 @@ impl AssistantPanel {
line_height: relative(1.3).into(),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(
@@ -1118,7 +1096,6 @@ impl AssistantPanel {
let conversation =
Conversation::deserialize(saved_conversation, path.clone(), languages, &mut cx)
.await?;
this.update(&mut cx, |this, cx| {
// If, by the time we've loaded the conversation, the user has already opened
// the same conversation, we don't want to open it again.
@@ -1158,7 +1135,7 @@ impl AssistantPanel {
}
}
fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
@@ -1169,10 +1146,9 @@ fn build_api_key_editor(cx: &mut WindowContext) -> 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; 6] = [
const INSTRUCTIONS: [&'static str; 5] = [
"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:"
@@ -1365,9 +1341,7 @@ impl Panel for AssistantPanel {
cx.spawn(|this, mut cx| async move {
load_credentials.await;
this.update(&mut cx, |this, cx| {
if !this.has_credentials() {
this.build_api_key_editor(cx);
} else if this.editors.is_empty() {
if this.editors.is_empty() {
this.new_conversation(cx);
}
})
@@ -1419,8 +1393,7 @@ struct Conversation {
pending_summary: Task<Option<()>>,
completion_count: usize,
pending_completions: Vec<PendingCompletion>,
model: OpenAiModel,
api_url: Option<String>,
model: OpenAIModel,
token_count: Option<usize>,
max_token_count: usize,
pending_token_count: Task<Option<()>>,
@@ -1440,7 +1413,7 @@ impl Conversation {
) -> Self {
let markdown = language_registry.language_for_name("Markdown");
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "");
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "");
buffer.set_language_registry(language_registry);
cx.spawn(|buffer, mut cx| async move {
let markdown = markdown.await?;
@@ -1455,7 +1428,6 @@ 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()),
@@ -1469,7 +1441,6 @@ 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(())),
@@ -1515,7 +1486,6 @@ impl Conversation {
.map(|summary| summary.text.clone())
.unwrap_or_default(),
model: self.model.clone(),
api_url: self.api_url.clone(),
}
}
@@ -1530,29 +1500,19 @@ 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()),
OpenAICompletionProvider::new(
model.full_name().into(),
cx.background_executor().clone(),
)
.await,
);
cx.update(|cx| completion_provider.retrieve_credentials(cx))?
.await;
cx.update(|cx| completion_provider.retrieve_credentials(cx))?;
let markdown = language_registry.language_for_name("Markdown");
let mut message_anchors = Vec::new();
let mut next_message_id = MessageId(0);
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
saved_conversation.text,
);
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), saved_conversation.text);
for message in saved_conversation.messages {
message_anchors.push(MessageAnchor {
id: message.id,
@@ -1588,7 +1548,6 @@ 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(())),
@@ -1667,7 +1626,7 @@ impl Conversation {
Some(self.max_token_count as isize - self.token_count? as isize)
}
fn set_model(&mut self, model: OpenAiModel, cx: &mut ModelContext<Self>) {
fn set_model(&mut self, model: OpenAIModel, cx: &mut ModelContext<Self>) {
self.model = model;
self.count_remaining_tokens(cx);
cx.notify();
@@ -1717,11 +1676,10 @@ impl Conversation {
if should_assist {
if !self.completion_provider.has_credentials() {
log::info!("completion provider has no credentials");
return Default::default();
}
let request: Box<dyn CompletionRequest> = Box::new(OpenAiRequest {
let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
model: self.model.full_name().to_string(),
messages: self
.messages(cx)
@@ -2004,7 +1962,7 @@ impl Conversation {
content: "Summarize the conversation into a short title without punctuation"
.into(),
}));
let request: Box<dyn CompletionRequest> = Box::new(OpenAiRequest {
let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
model: self.model.full_name().to_string(),
messages: messages.collect(),
stream: true,
@@ -3189,7 +3147,6 @@ impl InlineAssistant {
line_height: relative(1.3).into(),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(

View File

@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use settings::Settings;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum OpenAiModel {
pub enum OpenAIModel {
#[serde(rename = "gpt-3.5-turbo-0613")]
ThreePointFiveTurbo,
#[serde(rename = "gpt-4-0613")]
@@ -14,28 +14,28 @@ pub enum OpenAiModel {
FourTurbo,
}
impl OpenAiModel {
impl OpenAIModel {
pub fn full_name(&self) -> &'static str {
match self {
OpenAiModel::ThreePointFiveTurbo => "gpt-3.5-turbo-0613",
OpenAiModel::Four => "gpt-4-0613",
OpenAiModel::FourTurbo => "gpt-4-1106-preview",
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo-0613",
OpenAIModel::Four => "gpt-4-0613",
OpenAIModel::FourTurbo => "gpt-4-1106-preview",
}
}
pub fn short_name(&self) -> &'static str {
match self {
OpenAiModel::ThreePointFiveTurbo => "gpt-3.5-turbo",
OpenAiModel::Four => "gpt-4",
OpenAiModel::FourTurbo => "gpt-4-turbo",
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo",
OpenAIModel::Four => "gpt-4",
OpenAIModel::FourTurbo => "gpt-4-turbo",
}
}
pub fn cycle(&self) -> Self {
match self {
OpenAiModel::ThreePointFiveTurbo => OpenAiModel::Four,
OpenAiModel::Four => OpenAiModel::FourTurbo,
OpenAiModel::FourTurbo => OpenAiModel::ThreePointFiveTurbo,
OpenAIModel::ThreePointFiveTurbo => OpenAIModel::Four,
OpenAIModel::Four => OpenAIModel::FourTurbo,
OpenAIModel::FourTurbo => OpenAIModel::ThreePointFiveTurbo,
}
}
}
@@ -54,8 +54,7 @@ pub struct AssistantSettings {
pub dock: AssistantDockPosition,
pub default_width: Pixels,
pub default_height: Pixels,
pub default_open_ai_model: OpenAiModel,
pub openai_api_url: String,
pub default_open_ai_model: OpenAIModel,
}
/// Assistant panel settings
@@ -80,11 +79,7 @@ pub struct AssistantSettingsContent {
/// The default OpenAI model to use when starting new conversations.
///
/// 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>,
pub default_open_ai_model: Option<OpenAIModel>,
}
impl Settings for AssistantSettings {

View File

@@ -365,10 +365,7 @@ mod tests {
use futures::stream::{self};
use gpui::{Context, TestAppContext};
use indoc::indoc;
use language::{
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig,
LanguageMatcher, Point,
};
use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
use rand::prelude::*;
use serde::Serialize;
use settings::SettingsStore;
@@ -397,9 +394,8 @@ mod tests {
}
}
"};
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
@@ -464,9 +460,8 @@ mod tests {
le
}
"};
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let position = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
@@ -530,9 +525,8 @@ mod tests {
" \n",
"}\n" //
);
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let position = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
@@ -676,10 +670,7 @@ mod tests {
Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
Some(tree_sitter_rust::language()),

View File

@@ -4,7 +4,7 @@ use ai::prompts::file_context::FileContext;
use ai::prompts::generate::GenerateInlineContent;
use ai::prompts::preamble::EngineerPreamble;
use ai::prompts::repository_context::{PromptCodeSnippet, RepositoryContext};
use ai::providers::open_ai::OpenAiLanguageModel;
use ai::providers::open_ai::OpenAILanguageModel;
use language::{BufferSnapshot, OffsetRangeExt, ToOffset};
use std::cmp::{self, Reverse};
use std::ops::Range;
@@ -131,7 +131,7 @@ pub fn generate_content_prompt(
project_name: Option<String>,
) -> anyhow::Result<String> {
// Using new Prompt Templates
let openai_model: Arc<dyn LanguageModel> = Arc::new(OpenAiLanguageModel::load(model));
let openai_model: Arc<dyn LanguageModel> = Arc::new(OpenAILanguageModel::load(model));
let lang_name = if let Some(language_name) = language_name {
Some(language_name.to_string())
} else {
@@ -172,24 +172,20 @@ 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,
LanguageMatcher, Point,
};
use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
use settings::SettingsStore;
use std::sync::Arc;
pub(crate) fn rust_lang() -> Language {
Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
Some(tree_sitter_rust::language()),
@@ -257,9 +253,8 @@ pub(crate) mod tests {
}
}
"};
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
let snapshot = buffer.read(cx).snapshot();
assert_eq!(

View File

@@ -5,17 +5,20 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/audio.rs"
doctest = false
[dependencies]
anyhow.workspace = true
collections.workspace = true
derive_more.workspace = true
futures.workspace = true
gpui.workspace = true
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
parking_lot.workspace = true
rodio = { version = "0.17.1", default-features = false, features = ["wav"] }
util.workspace = true

View File

@@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc};
use anyhow::Result;
use collections::HashMap;
use gpui::{AppContext, AssetSource, Global};
use gpui::{AppContext, AssetSource};
use rodio::{
source::{Buffered, SamplesConverter},
Decoder, Source,
@@ -15,10 +15,6 @@ pub struct SoundRegistry {
assets: Box<dyn AssetSource>,
}
struct GlobalSoundRegistry(Arc<SoundRegistry>);
impl Global for GlobalSoundRegistry {}
impl SoundRegistry {
pub fn new(source: impl AssetSource) -> Arc<Self> {
Arc::new(Self {
@@ -28,11 +24,7 @@ impl SoundRegistry {
}
pub fn global(cx: &AppContext) -> Arc<Self> {
cx.global::<GlobalSoundRegistry>().0.clone()
}
pub(crate) fn set_global(source: impl AssetSource, cx: &mut AppContext) {
cx.set_global(GlobalSoundRegistry(SoundRegistry::new(source)));
cx.global::<Arc<Self>>().clone()
}
pub fn get(&self, name: &str) -> Result<impl Source<Item = f32>> {

View File

@@ -1,14 +1,13 @@
use assets::SoundRegistry;
use derive_more::{Deref, DerefMut};
use gpui::{AppContext, AssetSource, Global};
use gpui::{AppContext, AssetSource};
use rodio::{OutputStream, OutputStreamHandle};
use util::ResultExt;
mod assets;
pub fn init(source: impl AssetSource, cx: &mut AppContext) {
SoundRegistry::set_global(source, cx);
cx.set_global(GlobalAudio(Audio::new()));
cx.set_global(SoundRegistry::new(source));
cx.set_global(Audio::new());
}
pub enum Sound {
@@ -38,11 +37,6 @@ pub struct Audio {
output_handle: Option<OutputStreamHandle>,
}
#[derive(Deref, DerefMut)]
struct GlobalAudio(Audio);
impl Global for GlobalAudio {}
impl Audio {
pub fn new() -> Self {
Self {
@@ -62,11 +56,11 @@ impl Audio {
}
pub fn play_sound(sound: Sound, cx: &mut AppContext) {
if !cx.has_global::<GlobalAudio>() {
if !cx.has_global::<Self>() {
return;
}
cx.update_global::<GlobalAudio, _>(|this, cx| {
cx.update_global::<Self, _>(|this, cx| {
let output_handle = this.ensure_output_exists()?;
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
output_handle.play_raw(source).log_err()?;
@@ -75,11 +69,11 @@ impl Audio {
}
pub fn end_call(cx: &mut AppContext) {
if !cx.has_global::<GlobalAudio>() {
if !cx.has_global::<Self>() {
return;
}
cx.update_global::<GlobalAudio, _>(|this, _| {
cx.update_global::<Self, _>(|this, _| {
this._output_stream.take();
this.output_handle.take();
});

View File

@@ -5,28 +5,28 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/auto_update.rs"
doctest = false
[dependencies]
db = { path = "../db" }
client = { path = "../client" }
gpui = { path = "../gpui" }
menu = { path = "../menu" }
project = { path = "../project" }
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

View File

@@ -1,12 +1,12 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
use client::{Client, TelemetrySettings, ZED_APP_PATH};
use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION};
use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
SemanticVersion, Task, ViewContext, VisualContext, WindowContext,
actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
ViewContext, VisualContext, WindowContext,
};
use isahc::AsyncBody;
@@ -18,15 +18,10 @@ use smol::io::AsyncReadExt;
use settings::{Settings, SettingsStore};
use smol::{fs::File, process::Command};
use release_channel::{AppCommitSha, ReleaseChannel};
use std::{
env::consts::{ARCH, OS},
ffi::OsString,
sync::Arc,
time::Duration,
};
use std::{ffi::OsString, sync::Arc, time::Duration};
use update_notification::UpdateNotification;
use util::http::{HttpClient, ZedHttpClient};
use util::channel::{AppCommitSha, ReleaseChannel};
use util::http::HttpClient;
use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
@@ -54,8 +49,9 @@ pub enum AutoUpdateStatus {
pub struct AutoUpdater {
status: AutoUpdateStatus,
current_version: SemanticVersion,
http_client: Arc<ZedHttpClient>,
http_client: Arc<dyn HttpClient>,
pending_poll: Option<Task<Option<()>>>,
server_url: String,
}
#[derive(Deserialize)]
@@ -91,12 +87,7 @@ impl Settings for AutoUpdateSetting {
}
}
#[derive(Default)]
struct GlobalAutoUpdate(Option<Model<AutoUpdater>>);
impl Global for GlobalAutoUpdate {}
pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
@@ -108,28 +99,29 @@ pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
})
.detach();
let version = release_channel::AppVersion::global(cx);
let auto_updater = cx.new_model(|cx| {
let updater = AutoUpdater::new(version, http_client);
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, server_url);
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))
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();
}
} else {
update_subscription.take();
}
})
.detach();
})
.detach();
updater
});
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
updater
});
cx.set_global(Some(auto_updater));
}
}
pub fn check(_: &Check, cx: &mut WindowContext) {
@@ -147,18 +139,17 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<()> {
let auto_updater = AutoUpdater::get(cx)?;
let release_channel = ReleaseChannel::try_global(cx)?;
let release_channel = cx.try_global::<ReleaseChannel>()?;
if matches!(
release_channel,
ReleaseChannel::Stable | ReleaseChannel::Preview
) {
let auto_updater = auto_updater.read(cx);
let server_url = &auto_updater.server_url;
let release_channel = release_channel.dev_name();
let current_version = auto_updater.current_version;
let url = &auto_updater
.http_client
.zed_url(&format!("/releases/{release_channel}/{current_version}"));
let url = format!("{server_url}/releases/{release_channel}/{current_version}");
cx.open_url(&url);
}
@@ -192,14 +183,19 @@ pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
impl AutoUpdater {
pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
cx.default_global::<GlobalAutoUpdate>().0.clone()
cx.default_global::<Option<Model<Self>>>().clone()
}
fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self {
fn new(
current_version: SemanticVersion,
http_client: Arc<dyn HttpClient>,
server_url: String,
) -> Self {
Self {
status: AutoUpdateStatus::Idle,
current_version,
http_client,
server_url,
pending_poll: None,
}
}
@@ -245,16 +241,18 @@ impl AutoUpdater {
}
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
let (client, current_version) = this.read_with(&cx, |this, _| {
(this.http_client.clone(), this.current_version)
let (client, server_url, current_version) = this.read_with(&cx, |this, _| {
(
this.http_client.clone(),
this.server_url.clone(),
this.current_version,
)
})?;
let mut url_string = client.zed_url(&format!(
"/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
OS, ARCH
));
let mut url_string = format!("{server_url}/api/releases/latest?asset=Zed.dmg");
cx.update(|cx| {
if let Some(param) = ReleaseChannel::try_global(cx)
if let Some(param) = cx
.try_global::<ReleaseChannel>()
.map(|release_channel| release_channel.release_query_param())
.flatten()
{
@@ -276,9 +274,7 @@ impl AutoUpdater {
let should_download = match *RELEASE_CHANNEL {
ReleaseChannel::Nightly => cx
.update(|cx| AppCommitSha::try_global(cx).map(|sha| release.version != sha.0))
.ok()
.flatten()
.try_read_global::<AppCommitSha, _>(|sha, _| release.version != sha.0)
.unwrap_or(true),
_ => release.version.parse::<SemanticVersion>()? > current_version,
};
@@ -313,8 +309,9 @@ impl AutoUpdater {
let mut dmg_file = File::create(&dmg_path).await?;
let (installation_id, release_channel, telemetry) = cx.update(|cx| {
let installation_id = Client::global(cx).telemetry().installation_id();
let release_channel = ReleaseChannel::try_global(cx)
let installation_id = cx.global::<Arc<Client>>().telemetry().installation_id();
let release_channel = cx
.try_global::<ReleaseChannel>()
.map(|release_channel| release_channel.display_name());
let telemetry = TelemetrySettings::get_global(cx).metrics;

View File

@@ -3,7 +3,7 @@ use gpui::{
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
};
use menu::Cancel;
use release_channel::ReleaseChannel;
use util::channel::ReleaseChannel;
use workspace::ui::{h_flex, v_flex, Icon, IconName, Label, StyledExt};
pub struct UpdateNotification {
@@ -14,7 +14,7 @@ impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
let app_name = ReleaseChannel::global(cx).display_name();
let app_name = cx.global::<ReleaseChannel>().display_name();
v_flex()
.on_action(cx.listener(UpdateNotification::dismiss))
@@ -40,11 +40,10 @@ impl Render for UpdateNotification {
.id("notes")
.child(Label::new("View the release notes"))
.cursor_pointer()
.on_click(cx.listener(|this, _, cx| {
.on_click(|_, cx| {
crate::view_release_notes(&Default::default(), cx);
this.dismiss(&menu::Cancel, cx)
})),
);
}),
)
}
}

View File

@@ -5,25 +5,26 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/breadcrumbs.rs"
doctest = false
[dependencies]
collections.workspace = true
editor.workspace = true
gpui.workspace = true
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" }
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 = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
workspace = { workspace = true, features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }

View File

@@ -5,6 +5,7 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/call.rs"
doctest = false
@@ -20,35 +21,36 @@ 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_derive.workspace = true
serde_json.workspace = true
settings.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
util.workspace = true
[dev-dependencies]
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"] }
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"] }

View File

@@ -9,8 +9,8 @@ use client::{proto, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Subscription,
Task, WeakModel,
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
WeakModel,
};
use postage::watch;
use project::Project;
@@ -21,15 +21,11 @@ use std::sync::Arc;
pub use participant::ParticipantLocation;
pub use room::Room;
struct GlobalActiveCall(Model<ActiveCall>);
impl Global for GlobalActiveCall {}
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
CallSettings::register(cx);
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
cx.set_global(GlobalActiveCall(active_call));
cx.set_global(active_call);
}
pub struct OneAtATime {
@@ -158,12 +154,7 @@ impl ActiveCall {
}
pub fn global(cx: &AppContext) -> Model<Self> {
cx.global::<GlobalActiveCall>().0.clone()
}
pub fn try_global(cx: &AppContext) -> Option<Model<Self>> {
cx.try_global::<GlobalActiveCall>()
.map(|call| call.0.clone())
cx.global::<Model<Self>>().clone()
}
pub fn invite(

View File

@@ -7,7 +7,6 @@ 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.
@@ -17,11 +16,6 @@ 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 {

View File

@@ -617,10 +617,6 @@ 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,
@@ -1206,7 +1202,7 @@ impl Room {
})
}
pub fn share_project(
pub(crate) fn share_project(
&mut self,
project: Model<Project>,
cx: &mut ModelContext<Self>,

View File

@@ -5,6 +5,7 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/channel.rs"
doctest = false
@@ -13,43 +14,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" }
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 = { 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"] }
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"] }

View File

@@ -9,7 +9,6 @@ use rpc::{
TypedEnvelope,
};
use std::{sync::Arc, time::Duration};
use text::BufferId;
use util::ResultExt;
pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
@@ -54,7 +53,7 @@ impl ChannelBuffer {
channel_id: channel.id,
})
.await?;
let buffer_id = BufferId::new(response.buffer_id)?;
let base_text = response.base_text;
let operations = response
.operations
@@ -64,7 +63,12 @@ impl ChannelBuffer {
let buffer = cx.new_model(|cx| {
let capability = channel_store.read(cx).channel_capability(channel.id);
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
language::Buffer::remote(
response.buffer_id,
response.replica_id as u16,
capability,
base_text,
)
})?;
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
@@ -103,7 +107,7 @@ impl ChannelBuffer {
}
}
pub fn remote_id(&self, cx: &AppContext) -> BufferId {
pub fn remote_id(&self, cx: &AppContext) -> u64 {
self.buffer.read(cx).remote_id()
}
@@ -206,7 +210,7 @@ impl ChannelBuffer {
pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) {
let buffer = self.buffer.read(cx);
let version = buffer.version();
let buffer_id = buffer.remote_id().into();
let buffer_id = buffer.remote_id();
let client = self.client.clone();
let epoch = self.epoch();

View File

@@ -5,13 +5,12 @@ 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, WeakModel,
};
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
use rand::prelude::*;
use std::{
collections::HashSet,
mem,
ops::{ControlFlow, Range},
sync::Arc,
};
@@ -27,7 +26,6 @@ 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<()>>,
@@ -39,7 +37,6 @@ 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)]
@@ -50,7 +47,6 @@ 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)]
@@ -59,15 +55,6 @@ 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,
@@ -109,35 +96,28 @@ 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;
let handle = cx.new_model(|cx| {
Ok(cx.new_model(|cx| {
cx.on_release(Self::release).detach();
Self {
let mut this = Self {
channel_id: channel.id,
user_store: user_store.clone(),
user_store,
channel_store,
rpc: client.clone(),
rpc: client,
outgoing_messages_lock: Default::default(),
messages: Default::default(),
acknowledged_message_ids: Default::default(),
loaded_all_messages: false,
loaded_all_messages,
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()),
}
})?;
Self::handle_loaded_messages(
handle.downgrade(),
user_store,
client,
response.messages,
response.done,
&mut cx,
)
.await?;
Ok(handle)
};
this.insert_messages(messages, cx);
this
})?)
}
fn release(&mut self, _: &mut AppContext) {
@@ -186,7 +166,6 @@ impl ChannelChat {
timestamp: OffsetDateTime::now_utc(),
mentions: message.mentions.clone(),
nonce,
reply_to_message_id: message.reply_to_message_id,
},
&(),
),
@@ -204,7 +183,6 @@ 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);
@@ -249,16 +227,12 @@ impl ChannelChat {
before_message_id,
})
.await?;
Self::handle_loaded_messages(
this,
user_store,
rpc,
response.messages,
response.done,
&mut cx,
)
.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);
})?;
anyhow::Ok(())
}
.log_err()
@@ -266,14 +240,9 @@ impl ChannelChat {
}
pub fn first_loaded_message_id(&mut self) -> Option<u64> {
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,
self.messages.first().and_then(|message| match message.id {
ChannelMessageId::Saved(id) => Some(id),
ChannelMessageId::Pending(_) => None,
})
}
@@ -335,66 +304,6 @@ 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();
@@ -402,17 +311,28 @@ impl ChannelChat {
cx.spawn(move |this, mut cx| {
async move {
let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
Self::handle_loaded_messages(
this.clone(),
user_store.clone(),
rpc.clone(),
response.messages,
response.done,
&mut cx,
)
.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;
}
let pending_messages = this.update(&mut cx, |this, _| {
this.pending_messages().cloned().collect::<Vec<_>>()
})?;
@@ -422,7 +342,6 @@ 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(
@@ -634,7 +553,6 @@ impl ChannelMessage {
.nonce
.ok_or_else(|| anyhow!("nonce is required"))?
.into(),
reply_to_message_id: message.reply_to_message_id,
})
}
@@ -724,7 +642,6 @@ impl<'a> From<&'a str> for MessageParams {
Self {
text: value.into(),
mentions: Vec::new(),
reply_to_message_id: None,
}
}
}

View File

@@ -5,13 +5,13 @@ use anyhow::{anyhow, Result};
use channel_index::ChannelIndex;
use client::{Client, Subscription, User, UserId, UserStore};
use collections::{hash_map, HashMap, HashSet};
use db::RELEASE_CHANNEL;
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, SharedString,
Task, WeakModel,
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
WeakModel,
};
use language::Capability;
use release_channel::RELEASE_CHANNEL;
use rpc::{
proto::{self, ChannelRole, ChannelVisibility},
TypedEnvelope,
@@ -22,7 +22,7 @@ use util::{async_maybe, maybe, ResultExt};
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
let channel_store =
cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
cx.set_global(GlobalChannelStore(channel_store));
cx.set_global(channel_store);
}
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
@@ -74,19 +74,11 @@ impl Channel {
pub fn link(&self) -> String {
RELEASE_CHANNEL.link_prefix().to_owned()
+ "channel/"
+ &Self::slug(&self.name)
+ &self.slug()
+ "-"
+ &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()
}
@@ -98,8 +90,9 @@ impl Channel {
.unwrap_or(self.id)
}
pub fn slug(str: &str) -> String {
let slug: String = str
pub fn slug(&self) -> String {
let slug: String = self
.name
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '-' })
.collect();
@@ -150,13 +143,9 @@ enum OpenedModelHandle<E> {
Loading(Shared<Task<Result<Model<E>, Arc<anyhow::Error>>>>),
}
struct GlobalChannelStore(Model<ChannelStore>);
impl Global for GlobalChannelStore {}
impl ChannelStore {
pub fn global(cx: &AppContext) -> Model<Self> {
cx.global::<GlobalChannelStore>().0.clone()
cx.global::<Model<Self>>().clone()
}
pub fn new(
@@ -1131,10 +1120,9 @@ 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.epoch == observed_version.epoch
&& latest_version
.version
.changed_since(&observed_version.version))
|| latest_version
.version
.changed_since(&observed_version.version)
} else {
true
}

View File

@@ -184,7 +184,6 @@ 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,
@@ -193,7 +192,6 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
sender_id: 6,
mentions: vec![],
nonce: Some(2.into()),
reply_to_message_id: None,
},
],
done: false,
@@ -241,7 +239,6 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
sender_id: 7,
mentions: vec![],
nonce: Some(3.into()),
reply_to_message_id: None,
}),
});
@@ -295,7 +292,6 @@ 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,
@@ -304,7 +300,6 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
sender_id: 6,
nonce: Some(5.into()),
mentions: vec![],
reply_to_message_id: None,
},
],
},
@@ -334,8 +329,6 @@ 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();
let client = Client::new(http.clone(), cx);

View File

@@ -5,6 +5,7 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/cli.rs"
doctest = false
@@ -20,7 +21,7 @@ dirs = "3.0"
ipc-channel = "0.16"
serde.workspace = true
serde_derive.workspace = true
util.workspace = true
util = { path = "../util" }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9"

View File

@@ -1,14 +1,20 @@
#![cfg_attr(target_os = "linux", allow(dead_code))]
use anyhow::{anyhow, Context, Result};
use clap::Parser;
use cli::{CliRequest, CliResponse};
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 serde::Deserialize;
use std::{
ffi::OsStr,
fs::{self, OpenOptions},
io,
path::{Path, PathBuf},
ptr,
};
use util::paths::PathLikeWithPosition;
@@ -106,6 +112,136 @@ 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(()),
@@ -123,187 +259,3 @@ 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(),
)
}
}
}

View File

@@ -5,6 +5,7 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/client.rs"
doctest = false
@@ -14,25 +15,23 @@ test-support = ["collections/test-support", "gpui/test-support", "rpc/test-suppo
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
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
collections = { path = "../collections" }
db = { path = "../db" }
gpui = { path = "../gpui" }
util = { path = "../util" }
rpc = { path = "../rpc" }
text = { path = "../text" }
settings = { path = "../settings" }
feature_flags = { path = "../feature_flags" }
sum_tree = { path = "../sum_tree" }
anyhow.workspace = true
async-recursion = "0.3"
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
async-tungstenite = { version = "0.16", features = ["async-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
@@ -40,19 +39,18 @@ schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
sha2 = "0.10"
smol.workspace = true
sysinfo.workspace = true
tempfile.workspace = true
tempfile = "3"
thiserror.workspace = true
time.workspace = true
tiny_http = "0.8"
uuid.workspace = true
url.workspace = true
url = "2.2"
[dev-dependencies]
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"] }
collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }

View File

@@ -10,26 +10,26 @@ 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, Task, WeakModel,
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, SemanticVersion, Task,
WeakModel,
};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
use rand::prelude::*;
use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json;
use settings::{Settings, SettingsStore};
use settings::Settings;
use std::{
any::TypeId,
collections::HashMap,
convert::TryFrom,
fmt::Write as _,
future::Future,
@@ -41,7 +41,8 @@ use std::{
use telemetry::Telemetry;
use thiserror::Error;
use url::Url;
use util::http::{HttpClient, ZedHttpClient};
use util::channel::ReleaseChannel;
use util::http::HttpClient;
use util::{ResultExt, TryFutureExt};
pub use rpc::*;
@@ -49,14 +50,18 @@ pub use telemetry::Event;
pub use user::*;
lazy_static! {
static ref ZED_SERVER_URL: Option<String> = std::env::var("ZED_SERVER_URL").ok();
static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
pub static ref ZED_SERVER_URL: String =
std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string());
pub static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
.ok()
.and_then(|s| if s.is_empty() { None } else { Some(s) });
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 =
@@ -68,45 +73,13 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
actions!(client, [SignIn, SignOut, Reconnect]);
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct ClientSettingsContent {
server_url: Option<String>,
}
#[derive(Deserialize)]
pub struct ClientSettings {
pub server_url: String,
}
impl Settings for ClientSettings {
const KEY: Option<&'static str> = None;
type FileContent = ClientSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self>
where
Self: Sized,
{
let mut result = Self::load_via_json_merge(default_value, user_values)?;
if let Some(server_url) = &*ZED_SERVER_URL {
result.server_url = server_url.clone()
}
Ok(result)
}
}
pub fn init_settings(cx: &mut AppContext) {
TelemetrySettings::register(cx);
cx.update_global(|store: &mut SettingsStore, cx| {
store.register_setting::<ClientSettings>(cx);
});
}
pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
init_settings(cx);
let client = Arc::downgrade(client);
cx.on_action({
let client = client.clone();
@@ -145,14 +118,10 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
});
}
struct GlobalClient(Arc<Client>);
impl Global for GlobalClient {}
pub struct Client {
id: AtomicU64,
peer: Arc<Peer>,
http: Arc<ZedHttpClient>,
http: Arc<dyn HttpClient>,
telemetry: Arc<Telemetry>,
state: RwLock<ClientState>,
@@ -421,8 +390,8 @@ impl settings::Settings for TelemetrySettings {
}
impl Client {
pub fn new(http: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
let client = Arc::new(Self {
pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
Arc::new(Self {
id: AtomicU64::new(0),
peer: Peer::new(0),
telemetry: Telemetry::new(http.clone(), cx),
@@ -433,16 +402,14 @@ impl Client {
authenticate: Default::default(),
#[cfg(any(test, feature = "test-support"))]
establish_connection: Default::default(),
});
client
})
}
pub fn id(&self) -> u64 {
self.id.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn http_client(&self) -> Arc<ZedHttpClient> {
pub fn http_client(&self) -> Arc<dyn HttpClient> {
self.http.clone()
}
@@ -483,13 +450,6 @@ impl Client {
self
}
pub fn global(cx: &AppContext) -> Arc<Self> {
cx.global::<GlobalClient>().0.clone()
}
pub fn set_global(client: Arc<Client>, cx: &mut AppContext) {
cx.set_global(GlobalClient(client))
}
pub fn user_id(&self) -> Option<u64> {
self.state
.read()
@@ -965,14 +925,14 @@ impl Client {
}
async fn get_rpc_url(
http: Arc<ZedHttpClient>,
http: Arc<dyn HttpClient>,
release_channel: Option<ReleaseChannel>,
) -> Result<Url> {
if let Some(url) = &*ZED_RPC_URL {
return Url::parse(url).context("invalid rpc url");
}
let mut url = http.zed_url("/rpc");
let mut url = format!("{}/rpc", *ZED_SERVER_URL);
if let Some(preview_param) =
release_channel.and_then(|channel| channel.release_query_param())
{
@@ -1003,26 +963,14 @@ impl Client {
credentials: &Credentials,
cx: &AsyncAppContext,
) -> Task<Result<Connection, EstablishConnectionError>> {
let release_channel = cx
.update(|cx| ReleaseChannel::try_global(cx))
.ok()
.flatten();
let app_version = cx
.update(|cx| AppVersion::global(cx).to_string())
.ok()
.unwrap_or_default();
let release_channel = cx.try_read_global(|channel: &ReleaseChannel, _| *channel);
let request = Request::builder()
.header(
"Authorization",
format!("{} {}", credentials.user_id, credentials.access_token),
)
.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"),
);
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
let http = self.http.clone();
cx.background_executor().spawn(async move {
@@ -1040,7 +988,7 @@ impl Client {
rpc_url.set_scheme("wss").unwrap();
let request = request.uri(rpc_url.as_str()).body(())?;
let (stream, _) =
async_tungstenite::async_std::client_async_tls(request, stream).await?;
async_tungstenite::async_tls::client_async_tls(request, stream).await?;
Ok(Connection::new(
stream
.map_err(|error| anyhow!(error))
@@ -1105,10 +1053,10 @@ impl Client {
// Open the Zed sign-in page in the user's browser, with query parameters that indicate
// that the user is signing in from a Zed app running on the same device.
let mut url = http.zed_url(&format!(
"/native_app_signin?native_app_port={}&native_app_public_key={}",
port, public_key_string
));
let mut url = format!(
"{}/native_app_signin?native_app_port={}&native_app_public_key={}",
*ZED_SERVER_URL, port, public_key_string
);
if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
log::info!("impersonating user @{}", impersonate_login);
@@ -1140,7 +1088,7 @@ impl Client {
}
let post_auth_url =
http.zed_url("/native_app_signin_succeeded");
format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
req.respond(
tiny_http::Response::empty(302).with_header(
tiny_http::Header::from_bytes(
@@ -1182,7 +1130,7 @@ impl Client {
}
async fn authenticate_as_admin(
http: Arc<ZedHttpClient>,
http: Arc<dyn HttpClient>,
login: String,
mut api_token: String,
) -> Result<Credentials> {
@@ -1403,7 +1351,7 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credenti
}
let (user_id, access_token) = cx
.update(|cx| cx.read_credentials(&ClientSettings::get_global(cx).server_url))
.update(|cx| cx.read_credentials(&ZED_SERVER_URL))
.log_err()?
.await
.log_err()??;
@@ -1420,7 +1368,7 @@ async fn write_credentials_to_keychain(
) -> Result<()> {
cx.update(move |cx| {
cx.write_credentials(
&ClientSettings::get_global(cx).server_url,
&ZED_SERVER_URL,
&credentials.user_id.to_string(),
credentials.access_token.as_bytes(),
)
@@ -1429,7 +1377,7 @@ async fn write_credentials_to_keychain(
}
async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
cx.update(move |cx| cx.delete_credentials(&ClientSettings::get_global(cx).server_url))?
cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
.await
}
@@ -1736,7 +1684,6 @@ mod tests {
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
init_settings(cx);
});
}
}

View File

@@ -1,30 +1,27 @@
mod event_coalescer;
use crate::TelemetrySettings;
use crate::{TelemetrySettings, ZED_SERVER_URL};
use chrono::{DateTime, Utc};
use futures::Future;
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
use once_cell::sync::Lazy;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use release_channel::ReleaseChannel;
use serde::Serialize;
use settings::{Settings, SettingsStore};
use sha2::{Digest, Sha256};
use std::io::Write;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
};
use tempfile::NamedTempFile;
use util::http::{self, HttpClient, Method, ZedHttpClient};
use util::http::HttpClient;
#[cfg(not(debug_assertions))]
use util::ResultExt;
use util::TryFutureExt;
use util::{channel::ReleaseChannel, TryFutureExt};
use self::event_coalescer::EventCoalescer;
pub struct Telemetry {
http_client: Arc<ZedHttpClient>,
http_client: Arc<dyn HttpClient>,
executor: BackgroundExecutor,
state: Arc<Mutex<TelemetryState>>,
}
@@ -46,6 +43,12 @@ struct TelemetryState {
max_queue_size: usize,
}
const EVENTS_URL_PATH: &'static str = "/api/events";
lazy_static! {
static ref EVENTS_URL: String = format!("{}{}", *ZED_SERVER_URL, EVENTS_URL_PATH);
}
#[derive(Serialize, Debug)]
struct EventRequestBody {
installation_id: Option<Arc<str>>,
@@ -145,20 +148,11 @@ 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 =
ReleaseChannel::try_global(cx).map(|release_channel| release_channel.display_name());
pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
let release_channel = cx
.try_global::<ReleaseChannel>()
.map(|release_channel| release_channel.display_name());
TelemetrySettings::register(cx);
@@ -513,10 +507,6 @@ impl Telemetry {
return;
}
let Some(checksum_seed) = &*ZED_CLIENT_CHECKSUM_SEED else {
return;
};
let this = self.clone();
self.executor
.spawn(
@@ -557,27 +547,9 @@ impl Telemetry {
serde_json::to_writer(&mut json_bytes, &request_body)?;
}
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());
}
this.http_client
.post_json(EVENTS_URL.as_str(), json_bytes.into())
.await?;
anyhow::Ok(())
}
.log_err(),

View File

@@ -4,7 +4,7 @@ use collections::{hash_map::Entry, HashMap, HashSet};
use feature_flags::FeatureFlagAppExt;
use futures::{channel::mpsc, Future, StreamExt};
use gpui::{
AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, SharedUri, Task,
AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, SharedUrl, Task,
WeakModel,
};
use postage::{sink::Sink, watch};
@@ -22,7 +22,7 @@ pub struct ParticipantIndex(pub u32);
pub struct User {
pub id: UserId,
pub github_login: String,
pub avatar_uri: SharedUri,
pub avatar_uri: SharedUrl,
}
#[derive(Clone, Debug, PartialEq, Eq)]

View File

@@ -5,6 +5,7 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/clock.rs"
doctest = false

View File

@@ -3,10 +3,11 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.44.0"
version = "0.42.0"
publish = false
license = "AGPL-3.0-or-later"
[[bin]]
name = "collab"
@@ -15,22 +16,26 @@ 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"
chrono.workspace = true
clap = { version = "3.1", features = ["derive"], optional = true }
clock.workspace = true
collections.workspace = true
chrono.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
@@ -38,63 +43,62 @@ 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]
release_channel.workspace = true
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"] }
async-trait.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
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"] }
ctor.workspace = true
env_logger.workspace = true
indoc.workspace = true
util = { path = "../util" }
lazy_static.workspace = true
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"]

View File

@@ -3,35 +3,3 @@
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.

View File

@@ -0,0 +1,4 @@
ZED_ENVIRONMENT=nightly
RUST_LOG=info
INVITE_LINK_PREFIX=https://zed.dev/invites/
DATABASE_MAX_CONNECTIONS=10

View File

@@ -0,0 +1,4 @@
ZED_ENVIRONMENT=preview
RUST_LOG=info
INVITE_LINK_PREFIX=https://zed.dev/invites/
DATABASE_MAX_CONNECTIONS=10

View File

@@ -163,8 +163,7 @@ 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,
"in_call" BOOLEAN NOT NULL DEFAULT FALSE
"role" TEXT
);
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");
@@ -218,8 +217,7 @@ 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,
"reply_to_message_id" INTEGER DEFAULT NULL
"nonce" BLOB NOT 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");

View File

@@ -1,4 +0,0 @@
-- Add migration script here
DROP INDEX index_channels_on_parent_path;
CREATE INDEX index_channels_on_parent_path ON channels (parent_path text_pattern_ops);

View File

@@ -1 +0,0 @@
ALTER TABLE channel_messages ADD reply_to_message_id INTEGER DEFAULT NULL

View File

@@ -1,3 +0,0 @@
-- Add migration script here
ALTER TABLE room_participants ADD COLUMN in_call BOOL NOT NULL DEFAULT FALSE;

View File

@@ -1,4 +0,0 @@
-- Add migration script here
ALTER TABLE rooms DROP COLUMN enviroment;
ALTER TABLE rooms DROP COLUMN environment;
ALTER TABLE room_participants DROP COLUMN in_call;

View File

@@ -587,9 +587,6 @@ 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>,
}
@@ -695,7 +692,7 @@ impl ProjectCollaborator {
pub struct LeftProject {
pub id: ProjectId,
pub host_user_id: UserId,
pub host_connection_id: Option<ConnectionId>,
pub host_connection_id: ConnectionId,
pub connection_ids: Vec<ConnectionId>,
}

View File

@@ -561,6 +561,7 @@ 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),
@@ -670,7 +671,7 @@ impl Database {
buffer_id: row.buffer_id,
epoch: row.epoch,
lamport_timestamp: row.lamport_timestamp,
replica_id: row.replica_id,
replica_id: row.lamport_timestamp,
value: Default::default(),
});
operations.push(proto::Operation {
@@ -692,7 +693,7 @@ impl Database {
return Ok(());
}
let mut text_buffer = text::Buffer::new(0, text::BufferId::new(1).unwrap(), base_text);
let mut text_buffer = text::Buffer::new(0, 0, base_text);
text_buffer
.apply_ops(operations.into_iter().filter_map(operation_from_wire))
.unwrap();
@@ -749,9 +750,20 @@ impl Database {
pub async fn latest_channel_buffer_changes(
&self,
channel_ids_by_buffer_id: &HashMap<BufferId, ChannelId>,
channel_ids: &[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?;
@@ -771,36 +783,6 @@ 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,

View File

@@ -103,6 +103,7 @@ 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?;
@@ -162,7 +163,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, &*tx)
.get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx)
.await?;
self.join_channel_room_internal(room_id, user_id, connection, role, &*tx)
@@ -172,7 +173,7 @@ impl Database {
.await
}
/// Sets the visibility of the given channel.
/// Sets the visibiltity of the given channel.
pub async fn set_channel_visibility(
&self,
channel_id: ChannelId,
@@ -196,10 +197,12 @@ impl Database {
}
} else if visibility == ChannelVisibility::Members {
if self
.get_channel_descendants_excluding_self([&channel], &*tx)
.get_channel_descendants_including_self(vec![channel_id], &*tx)
.await?
.into_iter()
.any(|channel| channel.visibility == ChannelVisibility::Public)
.any(|channel| {
channel.id != channel_id && channel.visibility == ChannelVisibility::Public
})
{
Err(ErrorCode::BadPublicNesting
.with_tag("direction", "children")
@@ -258,11 +261,10 @@ impl Database {
.await?;
let channels_to_remove = self
.get_channel_descendants_excluding_self([&channel], &*tx)
.get_channel_descendants_including_self(vec![channel.id], &*tx)
.await?
.into_iter()
.map(|channel| channel.id)
.chain(Some(channel_id))
.collect::<Vec<_>>();
channel::Entity::delete_many()
@@ -443,12 +445,16 @@ impl Database {
) -> Result<MembershipUpdated> {
let new_channels = self.get_user_channels(user_id, Some(channel), &*tx).await?;
let removed_channels = self
.get_channel_descendants_excluding_self([channel], &*tx)
.get_channel_descendants_including_self(vec![channel.id], &*tx)
.await?
.into_iter()
.map(|channel| channel.id)
.chain([channel.id])
.filter(|channel_id| !new_channels.channels.iter().any(|c| c.id == *channel_id))
.filter_map(|channel| {
if !new_channels.channels.iter().any(|c| c.id == channel.id) {
Some(channel.id)
} else {
None
}
})
.collect::<Vec<_>>();
Ok(MembershipUpdated {
@@ -539,6 +545,26 @@ impl Database {
.await
}
pub async fn get_channel_memberships(
&self,
user_id: UserId,
) -> Result<(Vec<channel_member::Model>, Vec<channel::Model>)> {
self.transaction(|tx| async move {
let memberships = channel_member::Entity::find()
.filter(channel_member::Column::UserId.eq(user_id))
.all(&*tx)
.await?;
let channels = self
.get_channel_descendants_including_self(
memberships.iter().map(|m| m.channel_id),
&*tx,
)
.await?;
Ok((memberships, channels))
})
.await
}
/// Returns all channels for the user with the given ID.
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
self.transaction(|tx| async move {
@@ -570,21 +596,13 @@ impl Database {
.all(&*tx)
.await?;
let channels = channel::Entity::find()
.filter(channel::Column::Id.is_in(channel_memberships.iter().map(|m| m.channel_id)))
.all(&*tx)
let descendants = self
.get_channel_descendants_including_self(
channel_memberships.iter().map(|m| m.channel_id),
&*tx,
)
.await?;
let mut descendants = self
.get_channel_descendants_excluding_self(channels.iter(), &*tx)
.await?;
for channel in channels {
if let Err(ix) = descendants.binary_search_by_key(&channel.path(), |c| c.path()) {
descendants.insert(ix, channel);
}
}
let roles_by_channel_id = channel_memberships
.iter()
.map(|membership| (membership.channel_id, membership.role))
@@ -626,40 +644,18 @@ 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_by_buffer_id, &*tx)
.latest_channel_buffer_changes(&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?;
let latest_messages = self.latest_channel_messages(&channel_ids, &*tx).await?;
Ok(ChannelsForUser {
channel_memberships,
channels,
channel_participants,
latest_buffer_versions,
latest_channel_messages,
observed_buffer_versions,
observed_channel_messages,
latest_channel_messages: latest_messages,
})
}
@@ -884,23 +880,46 @@ impl Database {
// Get the descendants of the given set if channels, ordered by their
// path.
pub(crate) async fn get_channel_descendants_excluding_self(
async fn get_channel_descendants_including_self(
&self,
channels: impl IntoIterator<Item = &channel::Model>,
channel_ids: impl IntoIterator<Item = ChannelId>,
tx: &DatabaseTransaction,
) -> Result<Vec<channel::Model>> {
let mut filter = Condition::any();
for channel in channels.into_iter() {
filter = filter.add(channel::Column::ParentPath.like(channel.descendant_path_filter()));
let mut values = String::new();
for id in channel_ids {
if !values.is_empty() {
values.push_str(", ");
}
write!(&mut values, "({})", id).unwrap();
}
if filter.is_empty() {
if values.is_empty() {
return Ok(vec![]);
}
let sql = format!(
r#"
SELECT DISTINCT
descendant_channels.*,
descendant_channels.parent_path || descendant_channels.id as full_path
FROM
channels parent_channels, channels descendant_channels
WHERE
descendant_channels.id IN ({values}) OR
(
parent_channels.id IN ({values}) AND
descendant_channels.parent_path LIKE (parent_channels.parent_path || parent_channels.id || '/%')
)
ORDER BY
full_path ASC
"#
);
Ok(channel::Entity::find()
.filter(filter)
.order_by_asc(Expr::cust("parent_path || id || '/'"))
.from_raw_sql(Statement::from_string(
self.pool.get_database_backend(),
sql,
))
.all(tx)
.await?)
}
@@ -932,6 +951,7 @@ impl Database {
&self,
channel_id: ChannelId,
live_kit_room: &str,
environment: &str,
tx: &DatabaseTransaction,
) -> Result<RoomId> {
let room = room::Entity::find()
@@ -940,11 +960,19 @@ 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)

View File

@@ -161,7 +161,6 @@ 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<_>>();
@@ -208,7 +207,6 @@ 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?;
@@ -247,7 +245,6 @@ 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([
@@ -388,30 +385,6 @@ 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],

View File

@@ -778,7 +778,7 @@ impl Database {
let left_project = LeftProject {
id: project_id,
host_user_id: project.host_user_id,
host_connection_id: Some(project.host_connection()?),
host_connection_id: project.host_connection()?,
connection_ids,
};
Ok((room, left_project))

View File

@@ -110,10 +110,12 @@ 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)
@@ -300,21 +302,31 @@ 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 QueryChannelId {
enum QueryChannelIdAndEnvironment {
ChannelId,
Environment,
}
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"))?;
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))?;
}
}
if channel_id.is_some() {
Err(anyhow!("tried to join channel call directly"))?
@@ -850,7 +862,7 @@ impl Database {
id: collaborator.project_id,
host_user_id: Default::default(),
connection_ids: Default::default(),
host_connection_id: None,
host_connection_id: Default::default(),
});
let collaborator_connection_id = collaborator.connection();
@@ -860,7 +872,7 @@ impl Database {
if collaborator.is_host {
left_project.host_user_id = collaborator.user_id;
left_project.host_connection_id = Some(collaborator_connection_id);
left_project.host_connection_id = collaborator_connection_id;
}
}
drop(collaborators);

View File

@@ -39,10 +39,6 @@ impl Model {
pub fn path(&self) -> String {
format!("{}{}/", self.parent_path, self.id)
}
pub fn descendant_path_filter(&self) -> String {
format!("{}{}/%", self.parent_path, self.id)
}
}
impl ActiveModelBehavior for ActiveModel {}

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