Compare commits
2 Commits
wip-projec
...
context-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a533e622fa | ||
|
|
cd7073bd19 |
@@ -3,15 +3,6 @@ export default {
|
||||
const url = new URL(request.url);
|
||||
url.hostname = "docs-anw.pages.dev";
|
||||
|
||||
// These pages were removed, but may still be served due to Cloudflare's
|
||||
// [asset retention](https://developers.cloudflare.com/pages/configuration/serving-pages/#asset-retention).
|
||||
if (
|
||||
url.pathname === "/docs/assistant/context-servers" ||
|
||||
url.pathname === "/docs/assistant/model-context-protocol"
|
||||
) {
|
||||
return await fetch("https://zed.dev/404");
|
||||
}
|
||||
|
||||
let res = await fetch(url, request);
|
||||
|
||||
if (res.status === 404) {
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -363,7 +363,7 @@ jobs:
|
||||
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
|
||||
- uses: rui314/setup-mold@2e332a0b602c2fc65d2d3995941b1b29a5f554a0 # v1
|
||||
with:
|
||||
mold-version: 2.32.0
|
||||
|
||||
|
||||
33
.github/workflows/delete_comments.yml
vendored
33
.github/workflows/delete_comments.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Delete Mediafire Comments
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
delete_comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for specific strings in comment
|
||||
id: check_comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const comment = context.payload.comment.body;
|
||||
const triggerStrings = ['www.mediafire.com'];
|
||||
return triggerStrings.some(triggerString => comment.includes(triggerString));
|
||||
|
||||
- name: Delete comment if it contains any of the specific strings
|
||||
if: steps.check_comment.outputs.result == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const commentId = context.payload.comment.id;
|
||||
await github.rest.issues.deleteComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: commentId
|
||||
});
|
||||
8
.github/workflows/deploy_cloudflare.yml
vendored
8
.github/workflows/deploy_cloudflare.yml
vendored
@@ -21,14 +21,6 @@ jobs:
|
||||
with:
|
||||
mdbook-version: "0.4.37"
|
||||
|
||||
- name: Set up default .cargo/config.toml
|
||||
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libxkbcommon-dev libxkbcommon-x11-dev
|
||||
|
||||
- name: Build book
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
2
.github/workflows/release_nightly.yml
vendored
2
.github/workflows/release_nightly.yml
vendored
@@ -157,7 +157,7 @@ jobs:
|
||||
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
|
||||
- uses: rui314/setup-mold@2e332a0b602c2fc65d2d3995941b1b29a5f554a0 # v1
|
||||
with:
|
||||
mold-version: 2.32.0
|
||||
|
||||
|
||||
3
.mailmap
3
.mailmap
@@ -24,8 +24,7 @@ Conrad Irwin <conrad@zed.dev>
|
||||
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
|
||||
Danilo Leal <danilo@zed.dev>
|
||||
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
|
||||
Evren Sen <146845123+evrensen467@users.noreply.github.com>
|
||||
Evren Sen <146845123+evrensen467@users.noreply.github.com> <146845123+evrsen@users.noreply.github.com>
|
||||
Evren Sen <146845123+evrsen@users.noreply.github.com>
|
||||
Fernando Tagawa <tagawafernando@gmail.com>
|
||||
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
|
||||
Greg Morenz <greg-morenz@droid.cafe>
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
{
|
||||
"label": "clippy",
|
||||
"command": "./script/clippy",
|
||||
"args": [],
|
||||
"allow_concurrent_runs": true,
|
||||
"use_new_terminal": false
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"label": "cargo run --profile release-fast",
|
||||
"command": "cargo",
|
||||
"args": ["run", "--profile", "release-fast"],
|
||||
"allow_concurrent_runs": true,
|
||||
"use_new_terminal": false
|
||||
"args": ["run", "--profile", "release-fast"]
|
||||
}
|
||||
]
|
||||
|
||||
364
Cargo.lock
generated
364
Cargo.lock
generated
@@ -83,7 +83,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alacritty_terminal"
|
||||
version = "0.24.1-dev"
|
||||
source = "git+https://github.com/alacritty/alacritty?rev=91d034ff8b53867143c005acfaa14609147c9a2c#91d034ff8b53867143c005acfaa14609147c9a2c"
|
||||
source = "git+https://github.com/alacritty/alacritty?rev=cacdb5bb3b72bad2c729227537979d95af75978f#cacdb5bb3b72bad2c729227537979d95af75978f"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.6.0",
|
||||
@@ -148,19 +148,6 @@ version = "0.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b"
|
||||
|
||||
[[package]]
|
||||
name = "ammonia"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ab99eae5ee58501ab236beb6f20f6ca39be615267b014899c89b2f0bc18a459"
|
||||
dependencies = [
|
||||
"html5ever",
|
||||
"maplit",
|
||||
"once_cell",
|
||||
"tendril",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -295,9 +282,9 @@ checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "as-raw-xcb-connection"
|
||||
@@ -384,7 +371,7 @@ dependencies = [
|
||||
"fuzzy",
|
||||
"globset",
|
||||
"gpui",
|
||||
"handlebars 4.5.0",
|
||||
"handlebars",
|
||||
"heed",
|
||||
"html_to_markdown 0.1.0",
|
||||
"http_client",
|
||||
@@ -871,7 +858,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tungstenite 0.20.1",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1068,9 +1055,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.46.0"
|
||||
version = "1.43.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4abf69a87be33b6f125a93d5046b5f7395c26d1f449bf8d3927f5577463b6de0"
|
||||
checksum = "9ccda7e730ace3cb8bbd4071bc650c6d294364891f9564bd4e43adfc8dea3177"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"aws-credential-types",
|
||||
@@ -1282,9 +1269,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-runtime"
|
||||
version = "1.6.3"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0abbf454960d0db2ad12684a1640120e7557294b0ff8e2f11236290a1b293225"
|
||||
checksum = "ce87155eba55e11768b8c1afa607f3e864ae82f03caf63258b37455b0ad02537"
|
||||
dependencies = [
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-http",
|
||||
@@ -1326,9 +1313,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-types"
|
||||
version = "1.2.2"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cee7cadb433c781d3299b916fbf620fea813bf38f49db282fb6858141a05cc8"
|
||||
checksum = "cfe321a6b21f5d8eabd0ade9c55d3d0335f3c3157fc2b3e87f05f34b539e4df5"
|
||||
dependencies = [
|
||||
"base64-simd",
|
||||
"bytes 1.7.1",
|
||||
@@ -1403,7 +1390,7 @@ dependencies = [
|
||||
"sha1",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.20.1",
|
||||
"tokio-tungstenite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
@@ -1637,7 +1624,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=b37a9a994709d256f4634efd29281c78ba89071a#b37a9a994709d256f4634efd29281c78ba89071a"
|
||||
source = "git+https://github.com/kvark/blade?rev=ac25c77ed8d86c386a541c935ffe0a0f6024e701#ac25c77ed8d86c386a541c935ffe0a0f6024e701"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -1667,7 +1654,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/kvark/blade?rev=b37a9a994709d256f4634efd29281c78ba89071a#b37a9a994709d256f4634efd29281c78ba89071a"
|
||||
source = "git+https://github.com/kvark/blade?rev=ac25c77ed8d86c386a541c935ffe0a0f6024e701#ac25c77ed8d86c386a541c935ffe0a0f6024e701"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1677,7 +1664,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=b37a9a994709d256f4634efd29281c78ba89071a#b37a9a994709d256f4634efd29281c78ba89071a"
|
||||
source = "git+https://github.com/kvark/blade?rev=ac25c77ed8d86c386a541c935ffe0a0f6024e701#ac25c77ed8d86c386a541c935ffe0a0f6024e701"
|
||||
dependencies = [
|
||||
"blade-graphics",
|
||||
"bytemuck",
|
||||
@@ -2224,9 +2211,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.16"
|
||||
version = "4.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
|
||||
checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -2242,16 +2229,6 @@ dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531d7959c5bbb6e266cecdd0f20213639c3a5c3e4d615f97db87661745f781ff"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2289,7 +2266,6 @@ dependencies = [
|
||||
"plist",
|
||||
"release_channel",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"util",
|
||||
]
|
||||
|
||||
@@ -2353,6 +2329,7 @@ dependencies = [
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
@@ -2374,7 +2351,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time",
|
||||
"tiny_http",
|
||||
"tokio-socks",
|
||||
"url",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
@@ -2555,6 +2531,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"menu",
|
||||
"notifications",
|
||||
"parking_lot",
|
||||
@@ -3286,6 +3263,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"paths",
|
||||
"release_channel",
|
||||
@@ -3296,17 +3274,6 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.9"
|
||||
@@ -3500,20 +3467,6 @@ dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docs_preprocessor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"mdbook",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@@ -3590,6 +3543,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"itertools 0.11.0",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"linkify",
|
||||
"log",
|
||||
"lsp",
|
||||
@@ -3633,18 +3587,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elasticlunr-rs"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41e83863a500656dfa214fee6682de9c5b9f03de6860fec531235ed2ae9f6571"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.12.3"
|
||||
@@ -4364,6 +4306,7 @@ dependencies = [
|
||||
"git",
|
||||
"git2",
|
||||
"gpui",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"notify",
|
||||
"objc",
|
||||
@@ -4689,6 +4632,7 @@ dependencies = [
|
||||
"git2",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"pretty_assertions",
|
||||
@@ -4887,6 +4831,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"image",
|
||||
"itertools 0.11.0",
|
||||
"lazy_static",
|
||||
"linkme",
|
||||
"log",
|
||||
"media",
|
||||
@@ -5005,20 +4950,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "5.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -5950,9 +5881,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "khronos-egl"
|
||||
version = "6.0.0"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
|
||||
checksum = "d1382b16c04aeb821453d6215a3c80ba78f24c6595c5aa85653378aabe0c83e3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libloading",
|
||||
@@ -6016,6 +5947,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"indoc",
|
||||
"itertools 0.11.0",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"lsp",
|
||||
"parking_lot",
|
||||
@@ -6149,6 +6081,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
@@ -6216,19 +6149,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -6519,12 +6442,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "0.1.0"
|
||||
@@ -6631,42 +6548,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdbook"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b45a38e19bd200220ef07c892b0157ad3d2365e5b5a267ca01ad12182491eea5"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"elasticlunr-rs",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"handlebars 5.1.2",
|
||||
"ignore",
|
||||
"log",
|
||||
"memchr",
|
||||
"notify",
|
||||
"notify-debouncer-mini",
|
||||
"once_cell",
|
||||
"opener",
|
||||
"pathdiff",
|
||||
"pulldown-cmark",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"toml 0.5.11",
|
||||
"topological-sort",
|
||||
"walkdir",
|
||||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "media"
|
||||
version = "0.1.0"
|
||||
@@ -6750,16 +6631,6 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@@ -7004,15 +6875,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notifications"
|
||||
version = "0.1.0"
|
||||
@@ -7049,17 +6911,6 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-debouncer-mini"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"log",
|
||||
"notify",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
@@ -7390,18 +7241,6 @@ dependencies = [
|
||||
"strum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opener"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0812e5e4df08da354c851a3376fead46db31c2214f849d3de356d774d057681"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"dbus",
|
||||
"normpath",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.66"
|
||||
@@ -7555,11 +7394,9 @@ dependencies = [
|
||||
"menu",
|
||||
"project",
|
||||
"schemars",
|
||||
"search",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"theme",
|
||||
"util",
|
||||
"workspace",
|
||||
"worktree",
|
||||
@@ -8299,7 +8136,7 @@ dependencies = [
|
||||
"text",
|
||||
"unindent",
|
||||
"util",
|
||||
"which 6.0.3",
|
||||
"which 6.0.2",
|
||||
"worktree",
|
||||
]
|
||||
|
||||
@@ -8488,16 +8325,9 @@ checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"memchr",
|
||||
"pulldown-cmark-escape",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark-escape"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3"
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
@@ -9752,18 +9582,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.208"
|
||||
version = "1.0.207"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
|
||||
checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.208"
|
||||
version = "1.0.207"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
|
||||
checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9911,6 +9741,7 @@ dependencies = [
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
@@ -10322,6 +10153,7 @@ dependencies = [
|
||||
"collections",
|
||||
"futures 0.3.30",
|
||||
"indoc",
|
||||
"lazy_static",
|
||||
"libsqlite3-sys",
|
||||
"parking_lot",
|
||||
"smol",
|
||||
@@ -10334,6 +10166,7 @@ dependencies = [
|
||||
name = "sqlez_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"sqlez",
|
||||
"sqlformat",
|
||||
"syn 1.0.109",
|
||||
@@ -11139,16 +10972,6 @@ dependencies = [
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
||||
dependencies = [
|
||||
"rustix 0.38.34",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_view"
|
||||
version = "0.1.0"
|
||||
@@ -11178,7 +11001,6 @@ dependencies = [
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11192,6 +11014,7 @@ dependencies = [
|
||||
"env_logger",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
@@ -11480,9 +11303,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.39.3"
|
||||
version = "1.39.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
|
||||
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes 1.7.1",
|
||||
@@ -11538,18 +11361,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.15"
|
||||
@@ -11570,19 +11381,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite 0.20.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite 0.21.0",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11678,12 +11477,6 @@ dependencies = [
|
||||
"winnow 0.6.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "topological-sort"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
@@ -11877,9 +11670,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-css"
|
||||
version = "0.21.1"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e08e324b1cf60fd3291774b49724c66de2ce8fcf4d358d0b4b82e37b41b1c9b"
|
||||
checksum = "e2f806f96136762b0121f5fdd7172a3dcd8f42d37a2f23ed7f11b35895e20eb4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
@@ -11907,9 +11700,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-go"
|
||||
version = "0.21.2"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8d702a98d3c7e70e466456e58ff2b1ac550bf1e29b97e5770676d2fdabec00d"
|
||||
checksum = "55cb318be5ccf75f44e054acf6898a5c95d59b53443eed578e16be0cd7ec037f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
@@ -11944,9 +11737,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-html"
|
||||
version = "0.20.4"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8766b5ad3721517f8259e6394aefda9c686aebf7a8c74ab8624f2c3b46902fd5"
|
||||
checksum = "95b3492b08a786bf5cc79feb0ef2ff3b115d5174364e0ddfd7860e0b9b088b53"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
@@ -12078,25 +11871,6 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes 1.7.1",
|
||||
"data-encoding",
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.0"
|
||||
@@ -12438,6 +12212,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"itertools 0.11.0",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"lsp",
|
||||
"multi_buffer",
|
||||
@@ -12542,34 +12317,6 @@ dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "warp"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c"
|
||||
dependencies = [
|
||||
"bytes 1.7.1",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http 0.2.12",
|
||||
"hyper",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"scoped-tls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.21.0",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
@@ -13146,9 +12893,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "6.0.3"
|
||||
version = "6.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
|
||||
checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
@@ -13808,6 +13555,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"itertools 0.11.0",
|
||||
"language",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
@@ -13856,7 +13604,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"text",
|
||||
@@ -14181,7 +13928,6 @@ dependencies = [
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"time",
|
||||
"tree-sitter-md",
|
||||
"tree-sitter-rust",
|
||||
"ui",
|
||||
@@ -14242,7 +13988,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_elixir"
|
||||
version = "0.0.9"
|
||||
version = "0.0.8"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6",
|
||||
]
|
||||
@@ -14367,7 +14113,7 @@ dependencies = [
|
||||
name = "zed_ruby"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zed_extension_api 0.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14430,7 +14176,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_zig"
|
||||
version = "0.3.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -24,7 +24,6 @@ members = [
|
||||
"crates/db",
|
||||
"crates/dev_server_projects",
|
||||
"crates/diagnostics",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/editor",
|
||||
"crates/extension",
|
||||
"crates/extension_api",
|
||||
@@ -166,7 +165,7 @@ members = [
|
||||
# Tooling
|
||||
#
|
||||
|
||||
"tooling/xtask"
|
||||
"tooling/xtask",
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
|
||||
@@ -306,7 +305,7 @@ zed_actions = { path = "crates/zed_actions" }
|
||||
#
|
||||
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91d034ff8b53867143c005acfaa14609147c9a2c" }
|
||||
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "cacdb5bb3b72bad2c729227537979d95af75978f" }
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
ashpd = "0.9.1"
|
||||
@@ -322,9 +321,9 @@ async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b37a9a994709d256f4634efd29281c78ba89071a" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "b37a9a994709d256f4634efd29281c78ba89071a" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "b37a9a994709d256f4634efd29281c78ba89071a" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "ac25c77ed8d86c386a541c935ffe0a0f6024e701" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "ac25c77ed8d86c386a541c935ffe0a0f6024e701" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "ac25c77ed8d86c386a541c935ffe0a0f6024e701" }
|
||||
cargo_metadata = "0.18"
|
||||
cargo_toml = "0.20"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
@@ -360,6 +359,7 @@ isahc = { version = "1.7.2", default-features = false, features = [
|
||||
] }
|
||||
itertools = "0.11.0"
|
||||
jsonwebtoken = "9.3"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
linkify = "0.10.0"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
@@ -389,7 +389,7 @@ runtimelib = { version = "0.15", default-features = false, features = [
|
||||
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
|
||||
rustc-demangle = "0.1.23"
|
||||
rust-embed = { version = "8.4", features = ["include-exclude"] }
|
||||
schemars = { version = "0.8", features = ["impl_json_schema"] }
|
||||
schemars = {version = "0.8", features = ["impl_json_schema"]}
|
||||
semver = "1.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||
@@ -546,7 +546,6 @@ zed = { codegen-units = 16 }
|
||||
|
||||
[profile.release-fast]
|
||||
inherits = "release"
|
||||
debug = "full"
|
||||
lto = false
|
||||
codegen-units = 16
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pin"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>
|
||||
|
Before Width: | Height: | Size: 447 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pin-off"><path d="M12 17v5"/><path d="M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89"/><path d="m2 2 20 20"/><path d="M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11"/></svg>
|
||||
|
Before Width: | Height: | Size: 401 B |
@@ -523,7 +523,7 @@
|
||||
"ctrl-alt-c": "outline_panel::CopyPath",
|
||||
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-ctrl-r": "outline_panel::RevealInFileManager",
|
||||
"space": ["outline_panel::Open", { "change_selection": false }],
|
||||
"space": "outline_panel::Open",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev"
|
||||
}
|
||||
@@ -613,15 +613,11 @@
|
||||
"ctrl-alt-space": "terminal::ShowCharacterPalette",
|
||||
"ctrl-shift-c": "terminal::Copy",
|
||||
"ctrl-insert": "terminal::Copy",
|
||||
// "ctrl-a": "editor::SelectAll", // conflicts with readline
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
// Overrides for conflicting keybindings
|
||||
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
||||
"ctrl-shift-a": "editor::SelectAll",
|
||||
"ctrl-shift-f": "buffer_search::Deploy",
|
||||
"ctrl-shift-l": "terminal::Clear",
|
||||
"ctrl-shift-w": "pane::CloseActiveItem",
|
||||
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"],
|
||||
"up": ["terminal::SendKeystroke", "up"],
|
||||
"pageup": ["terminal::SendKeystroke", "pageup"],
|
||||
|
||||
@@ -536,7 +536,7 @@
|
||||
"cmd-alt-c": "outline_panel::CopyPath",
|
||||
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-cmd-r": "outline_panel::RevealInFileManager",
|
||||
"space": ["outline_panel::Open", { "change_selection": false }],
|
||||
"space": "outline_panel::Open",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev"
|
||||
}
|
||||
|
||||
@@ -92,7 +92,6 @@
|
||||
"g y": "editor::GoToTypeDefinition",
|
||||
"g shift-i": "editor::GoToImplementation",
|
||||
"g x": "editor::OpenUrl",
|
||||
"g f": "editor::OpenFile",
|
||||
"g n": "vim::SelectNextMatch",
|
||||
"g shift-n": "vim::SelectPreviousMatch",
|
||||
"g l": "vim::SelectNext",
|
||||
@@ -177,19 +176,19 @@
|
||||
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w v": "pane::SplitVertical",
|
||||
"ctrl-w ctrl-v": "pane::SplitVertical",
|
||||
"ctrl-w s": "pane::SplitHorizontal",
|
||||
"ctrl-w shift-s": "pane::SplitHorizontal",
|
||||
"ctrl-w ctrl-s": "pane::SplitHorizontal",
|
||||
"ctrl-w v": "pane::SplitLeft",
|
||||
"ctrl-w ctrl-v": "pane::SplitLeft",
|
||||
"ctrl-w s": "pane::SplitUp",
|
||||
"ctrl-w shift-s": "pane::SplitUp",
|
||||
"ctrl-w ctrl-s": "pane::SplitUp",
|
||||
"ctrl-w c": "pane::CloseAllItems",
|
||||
"ctrl-w ctrl-c": "pane::CloseAllItems",
|
||||
"ctrl-w q": "pane::CloseAllItems",
|
||||
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w n": "workspace::NewFileSplitHorizontal",
|
||||
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
|
||||
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
|
||||
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
|
||||
"ctrl-w d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w g d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
|
||||
@@ -64,15 +64,9 @@
|
||||
"ui_font_weight": 400,
|
||||
// The default font size for text in the UI
|
||||
"ui_font_size": 16,
|
||||
// How much to fade out unused code.
|
||||
"unnecessary_code_fade": 0.3,
|
||||
// The factor to grow the active pane by. Defaults to 1.0
|
||||
// which gives the same size as all other panes.
|
||||
"active_pane_magnification": 1.0,
|
||||
// The direction that you want to split panes horizontally. Defaults to "up"
|
||||
"pane_split_direction_horizontal": "up",
|
||||
// The direction that you want to split panes horizontally. Defaults to "left"
|
||||
"pane_split_direction_vertical": "left",
|
||||
// Centered layout related settings.
|
||||
"centered_layout": {
|
||||
// The relative width of the left padding of the central pane from the
|
||||
@@ -510,8 +504,6 @@
|
||||
// "soft_wrap": "editor_width",
|
||||
// 4. Soft wrap lines at the preferred line length.
|
||||
// "soft_wrap": "preferred_line_length",
|
||||
// 5. Soft wrap lines at the preferred line length or the editor width (whichever is smaller).
|
||||
// "soft_wrap": "bounded",
|
||||
"soft_wrap": "prefer_line",
|
||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||
// is enabled.
|
||||
@@ -844,7 +836,6 @@
|
||||
"language_servers": ["starpls", "!buck2-lsp", "..."]
|
||||
},
|
||||
"Svelte": {
|
||||
"language_servers": ["svelte-language-server", "..."],
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": ["prettier-plugin-svelte"]
|
||||
@@ -868,7 +859,6 @@
|
||||
}
|
||||
},
|
||||
"Vue.js": {
|
||||
"language_servers": ["vue-language-server", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
@@ -895,8 +885,7 @@
|
||||
"api_url": "https://generativelanguage.googleapis.com"
|
||||
},
|
||||
"ollama": {
|
||||
"api_url": "http://localhost:11434",
|
||||
"low_speed_timeout_in_seconds": 60
|
||||
"api_url": "http://localhost:11434"
|
||||
},
|
||||
"openai": {
|
||||
"version": "1",
|
||||
@@ -946,7 +935,6 @@
|
||||
},
|
||||
// Vim settings
|
||||
"vim": {
|
||||
"toggle_relative_line_numbers": false,
|
||||
"use_system_clipboard": "always",
|
||||
"use_multiline_find": false,
|
||||
"use_smartcase_find": false,
|
||||
|
||||
@@ -3,9 +3,10 @@ use editor::Editor;
|
||||
use extension::ExtensionStore;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
|
||||
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
|
||||
actions, anchored, deferred, percentage, Animation, AnimationExt as _, AppContext, CursorStyle,
|
||||
DismissEvent, EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render,
|
||||
SharedString, StatefulInteractiveElement, Styled, Transformation, View, ViewContext,
|
||||
VisualContext as _,
|
||||
};
|
||||
use language::{
|
||||
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
|
||||
@@ -13,7 +14,7 @@ use language::{
|
||||
use project::{LanguageServerProgress, Project};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
|
||||
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle};
|
||||
use ui::{prelude::*, ContextMenu};
|
||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||
|
||||
actions!(activity_indicator, [ShowErrorMessage]);
|
||||
@@ -26,7 +27,7 @@ pub struct ActivityIndicator {
|
||||
statuses: Vec<LspStatus>,
|
||||
project: Model<Project>,
|
||||
auto_updater: Option<Model<AutoUpdater>>,
|
||||
context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
context_menu: Option<View<ContextMenu>>,
|
||||
}
|
||||
|
||||
struct LspStatus {
|
||||
@@ -40,6 +41,7 @@ struct PendingWork<'a> {
|
||||
progress: &'a LanguageServerProgress,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Content {
|
||||
icon: Option<gpui::AnyElement>,
|
||||
message: String,
|
||||
@@ -77,7 +79,7 @@ impl ActivityIndicator {
|
||||
statuses: Default::default(),
|
||||
project: project.clone(),
|
||||
auto_updater,
|
||||
context_menu_handle: Default::default(),
|
||||
context_menu: None,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -172,7 +174,7 @@ impl ActivityIndicator {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Option<Content> {
|
||||
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Content {
|
||||
// Show any language server has pending activity.
|
||||
let mut pending_work = self.pending_language_server_work(cx);
|
||||
if let Some(PendingWork {
|
||||
@@ -201,7 +203,7 @@ impl ActivityIndicator {
|
||||
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||
}
|
||||
|
||||
return Some(Content {
|
||||
return Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
@@ -214,7 +216,7 @@ impl ActivityIndicator {
|
||||
),
|
||||
message,
|
||||
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Show any language server installation info.
|
||||
@@ -233,7 +235,7 @@ impl ActivityIndicator {
|
||||
}
|
||||
|
||||
if !downloading.is_empty() {
|
||||
return Some(Content {
|
||||
return Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
@@ -241,11 +243,11 @@ impl ActivityIndicator {
|
||||
),
|
||||
message: format!("Downloading {}...", downloading.join(", "),),
|
||||
on_click: None,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if !checking_for_update.is_empty() {
|
||||
return Some(Content {
|
||||
return Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
@@ -256,11 +258,11 @@ impl ActivityIndicator {
|
||||
checking_for_update.join(", "),
|
||||
),
|
||||
on_click: None,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if !failed.is_empty() {
|
||||
return Some(Content {
|
||||
return Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
.size(IconSize::Small)
|
||||
@@ -273,12 +275,12 @@ impl ActivityIndicator {
|
||||
on_click: Some(Arc::new(|this, cx| {
|
||||
this.show_error_message(&Default::default(), cx)
|
||||
})),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Show any formatting failure
|
||||
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
|
||||
return Some(Content {
|
||||
return Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
.size(IconSize::Small)
|
||||
@@ -288,13 +290,13 @@ impl ActivityIndicator {
|
||||
on_click: Some(Arc::new(|_, cx| {
|
||||
cx.dispatch_action(Box::new(workspace::OpenLog));
|
||||
})),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Show any application auto-update info.
|
||||
if let Some(updater) = &self.auto_updater {
|
||||
return match &updater.read(cx).status() {
|
||||
AutoUpdateStatus::Checking => Some(Content {
|
||||
AutoUpdateStatus::Checking => Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
@@ -302,8 +304,8 @@ impl ActivityIndicator {
|
||||
),
|
||||
message: "Checking for Zed updates…".to_string(),
|
||||
on_click: None,
|
||||
}),
|
||||
AutoUpdateStatus::Downloading => Some(Content {
|
||||
},
|
||||
AutoUpdateStatus::Downloading => Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
@@ -311,8 +313,8 @@ impl ActivityIndicator {
|
||||
),
|
||||
message: "Downloading Zed update…".to_string(),
|
||||
on_click: None,
|
||||
}),
|
||||
AutoUpdateStatus::Installing => Some(Content {
|
||||
},
|
||||
AutoUpdateStatus::Installing => Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
@@ -320,8 +322,8 @@ impl ActivityIndicator {
|
||||
),
|
||||
message: "Installing Zed update…".to_string(),
|
||||
on_click: None,
|
||||
}),
|
||||
AutoUpdateStatus::Updated { binary_path } => Some(Content {
|
||||
},
|
||||
AutoUpdateStatus::Updated { binary_path } => Content {
|
||||
icon: None,
|
||||
message: "Click to restart and update Zed".to_string(),
|
||||
on_click: Some(Arc::new({
|
||||
@@ -330,8 +332,8 @@ impl ActivityIndicator {
|
||||
};
|
||||
move |_, cx| workspace::reload(&reload, cx)
|
||||
})),
|
||||
}),
|
||||
AutoUpdateStatus::Errored => Some(Content {
|
||||
},
|
||||
AutoUpdateStatus::Errored => Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ExclamationTriangle)
|
||||
.size(IconSize::Small)
|
||||
@@ -341,8 +343,8 @@ impl ActivityIndicator {
|
||||
on_click: Some(Arc::new(|this, cx| {
|
||||
this.dismiss_error_message(&Default::default(), cx)
|
||||
})),
|
||||
}),
|
||||
AutoUpdateStatus::Idle => None,
|
||||
},
|
||||
AutoUpdateStatus::Idle => Default::default(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -350,7 +352,7 @@ impl ActivityIndicator {
|
||||
ExtensionStore::try_global(cx).map(|extension_store| extension_store.read(cx))
|
||||
{
|
||||
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
|
||||
return Some(Content {
|
||||
return Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::Download)
|
||||
.size(IconSize::Small)
|
||||
@@ -358,15 +360,80 @@ impl ActivityIndicator {
|
||||
),
|
||||
message: format!("Updating {extension_id} extension…"),
|
||||
on_click: None,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.context_menu_handle.toggle(cx);
|
||||
if self.context_menu.take().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.build_lsp_work_context_menu(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn build_lsp_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let mut has_work = false;
|
||||
let this = cx.view().downgrade();
|
||||
let context_menu = ContextMenu::build(cx, |mut menu, cx| {
|
||||
for work in self.pending_language_server_work(cx) {
|
||||
has_work = true;
|
||||
|
||||
let this = this.clone();
|
||||
let title = SharedString::from(
|
||||
work.progress
|
||||
.title
|
||||
.as_deref()
|
||||
.unwrap_or(work.progress_token)
|
||||
.to_string(),
|
||||
);
|
||||
if work.progress.is_cancellable {
|
||||
let language_server_id = work.language_server_id;
|
||||
let token = work.progress_token.to_string();
|
||||
menu = menu.custom_entry(
|
||||
move |_| {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new(title.clone()))
|
||||
.child(Icon::new(IconName::XCircle))
|
||||
.into_any_element()
|
||||
},
|
||||
move |cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.cancel_language_server_work(
|
||||
language_server_id,
|
||||
Some(token.clone()),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
this.context_menu.take();
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
menu = menu.label(title.clone());
|
||||
}
|
||||
}
|
||||
menu
|
||||
});
|
||||
|
||||
if has_work {
|
||||
cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
|
||||
this.context_menu.take();
|
||||
cx.notify();
|
||||
})
|
||||
.detach();
|
||||
cx.focus_view(&context_menu);
|
||||
self.context_menu = Some(context_menu);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,88 +441,33 @@ impl EventEmitter<Event> for ActivityIndicator {}
|
||||
|
||||
impl Render for ActivityIndicator {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let result = h_flex()
|
||||
let content = self.content_to_render(cx);
|
||||
|
||||
let mut result = h_flex()
|
||||
.id("activity-indicator")
|
||||
.on_action(cx.listener(Self::show_error_message))
|
||||
.on_action(cx.listener(Self::dismiss_error_message));
|
||||
let Some(content) = self.content_to_render(cx) else {
|
||||
return result;
|
||||
};
|
||||
let this = cx.view().downgrade();
|
||||
result.gap_2().child(
|
||||
PopoverMenu::new("activity-indicator-popover")
|
||||
.trigger(
|
||||
ButtonLike::new("activity-indicator-trigger").child(
|
||||
h_flex()
|
||||
.id("activity-indicator-status")
|
||||
.gap_2()
|
||||
.children(content.icon)
|
||||
.child(Label::new(content.message).size(LabelSize::Small))
|
||||
.when_some(content.on_click, |this, handler| {
|
||||
this.on_click(cx.listener(move |this, _, cx| {
|
||||
handler(this, cx);
|
||||
}))
|
||||
.cursor(CursorStyle::PointingHand)
|
||||
}),
|
||||
),
|
||||
|
||||
if let Some(on_click) = content.on_click {
|
||||
result = result
|
||||
.cursor(CursorStyle::PointingHand)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
on_click(this, cx);
|
||||
}))
|
||||
}
|
||||
|
||||
result
|
||||
.gap_2()
|
||||
.children(content.icon)
|
||||
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
|
||||
.children(self.context_menu.as_ref().map(|menu| {
|
||||
deferred(
|
||||
anchored()
|
||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||
.menu(move |cx| {
|
||||
let strong_this = this.upgrade()?;
|
||||
let mut has_work = false;
|
||||
let menu = ContextMenu::build(cx, |mut menu, cx| {
|
||||
for work in strong_this.read(cx).pending_language_server_work(cx) {
|
||||
has_work = true;
|
||||
let this = this.clone();
|
||||
let mut title = work
|
||||
.progress
|
||||
.title
|
||||
.as_deref()
|
||||
.unwrap_or(work.progress_token)
|
||||
.to_owned();
|
||||
|
||||
if work.progress.is_cancellable {
|
||||
let language_server_id = work.language_server_id;
|
||||
let token = work.progress_token.to_string();
|
||||
let title = SharedString::from(title);
|
||||
menu = menu.custom_entry(
|
||||
move |_| {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new(title.clone()))
|
||||
.child(Icon::new(IconName::XCircle))
|
||||
.into_any_element()
|
||||
},
|
||||
move |cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.cancel_language_server_work(
|
||||
language_server_id,
|
||||
Some(token.clone()),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
this.context_menu_handle.hide(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if let Some(progress_message) = work.progress.message.as_ref() {
|
||||
title.push_str(": ");
|
||||
title.push_str(progress_message);
|
||||
}
|
||||
|
||||
menu = menu.label(title);
|
||||
}
|
||||
}
|
||||
menu
|
||||
});
|
||||
has_work.then_some(menu)
|
||||
}),
|
||||
)
|
||||
.with_priority(1)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ pub use context_store::*;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::Context as _;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use gpui::{actions, impl_actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
pub(crate) use inline_assistant::*;
|
||||
use language_model::{
|
||||
@@ -69,6 +69,13 @@ actions!(
|
||||
|
||||
const DEFAULT_CONTEXT_LINES: usize = 50;
|
||||
|
||||
#[derive(Clone, Default, Deserialize, PartialEq)]
|
||||
pub struct InlineAssist {
|
||||
prompt: Option<String>,
|
||||
}
|
||||
|
||||
impl_actions!(assistant, [InlineAssist]);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct MessageId(clock::Lamport);
|
||||
|
||||
|
||||
@@ -12,11 +12,10 @@ use crate::{
|
||||
slash_command_picker,
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
Assist, CacheStatus, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore,
|
||||
CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssistId, InlineAssistant,
|
||||
InsertIntoEditor, Message, MessageId, MessageMetadata, MessageStatus, ModelSelector,
|
||||
PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
|
||||
SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
|
||||
WorkflowStepView,
|
||||
CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssist, InlineAssistId,
|
||||
InlineAssistant, InsertIntoEditor, MessageStatus, ModelSelector, PendingSlashCommand,
|
||||
PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split,
|
||||
ToggleFocus, ToggleModelSelector, WorkflowStepResolution, WorkflowStepView,
|
||||
};
|
||||
use crate::{ContextStoreEvent, ModelPickerDelegate};
|
||||
use anyhow::{anyhow, Result};
|
||||
@@ -37,10 +36,10 @@ use fs::Fs;
|
||||
use gpui::{
|
||||
canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
|
||||
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
|
||||
Context as _, Empty, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, FontWeight,
|
||||
InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render, RenderImage,
|
||||
SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
|
||||
UpdateGlobal, View, VisualContext, WeakView, WindowContext,
|
||||
Context as _, DismissEvent, Empty, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
|
||||
FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render,
|
||||
RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task,
|
||||
Transformation, UpdateGlobal, View, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{
|
||||
@@ -83,7 +82,6 @@ use workspace::{
|
||||
ToolbarItemView, Workspace,
|
||||
};
|
||||
use workspace::{searchable::SearchableItemHandle, NewFile};
|
||||
use zed_actions::InlineAssist;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
|
||||
@@ -109,12 +107,29 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
terminal_panel.asssistant_enabled(settings.enabled, cx);
|
||||
if !settings.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
terminal_panel.register_tab_bar_button(cx.new_view(|_| InlineAssistTabBarButton), cx);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
struct InlineAssistTabBarButton;
|
||||
|
||||
impl Render for InlineAssistTabBarButton {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
IconButton::new("terminal_inline_assistant", IconName::ZedAssistant)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.dispatch_action(InlineAssist::default().boxed_clone());
|
||||
}))
|
||||
.tooltip(move |cx| Tooltip::for_action("Inline Assist", &InlineAssist::default(), cx))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AssistantPanelEvent {
|
||||
ContextEdited,
|
||||
}
|
||||
@@ -334,7 +349,6 @@ impl AssistantPanel {
|
||||
model_summary_editor.clone(),
|
||||
)
|
||||
});
|
||||
|
||||
let pane = cx.new_view(|cx| {
|
||||
let mut pane = Pane::new(
|
||||
workspace.weak_handle(),
|
||||
@@ -371,7 +385,6 @@ impl AssistantPanel {
|
||||
pane.active_item()
|
||||
.map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
|
||||
);
|
||||
let _pane = cx.view().clone();
|
||||
let right_children = h_flex()
|
||||
.gap(Spacing::Small.rems(cx))
|
||||
.child(
|
||||
@@ -382,27 +395,32 @@ impl AssistantPanel {
|
||||
.tooltip(|cx| Tooltip::for_action("New Context", &NewFile, cx)),
|
||||
)
|
||||
.child(
|
||||
PopoverMenu::new("assistant-panel-popover-menu")
|
||||
.trigger(
|
||||
IconButton::new("menu", IconName::Menu).icon_size(IconSize::Small),
|
||||
)
|
||||
.menu(move |cx| {
|
||||
let zoom_label = if _pane.read(cx).is_zoomed() {
|
||||
IconButton::new("menu", IconName::Menu)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(cx.listener(|pane, _, cx| {
|
||||
let zoom_label = if pane.is_zoomed() {
|
||||
"Zoom Out"
|
||||
} else {
|
||||
"Zoom In"
|
||||
};
|
||||
let focus_handle = _pane.focus_handle(cx);
|
||||
Some(ContextMenu::build(cx, move |menu, _| {
|
||||
menu.context(focus_handle.clone())
|
||||
let menu = ContextMenu::build(cx, |menu, cx| {
|
||||
menu.context(pane.focus_handle(cx))
|
||||
.action("New Context", Box::new(NewFile))
|
||||
.action("History", Box::new(DeployHistory))
|
||||
.action("Prompt Library", Box::new(DeployPromptLibrary))
|
||||
.action("Configure", Box::new(ShowConfiguration))
|
||||
.action(zoom_label, Box::new(ToggleZoom))
|
||||
}))
|
||||
}),
|
||||
});
|
||||
cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
|
||||
pane.new_item_menu = None;
|
||||
})
|
||||
.detach();
|
||||
pane.new_item_menu = Some(menu);
|
||||
})),
|
||||
)
|
||||
.when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
|
||||
el.child(Pane::render_menu_overlay(new_item_menu))
|
||||
})
|
||||
.into_any_element()
|
||||
.into();
|
||||
|
||||
@@ -492,7 +510,7 @@ impl AssistantPanel {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let update_model_summary = match event {
|
||||
pane::Event::Remove { .. } => {
|
||||
pane::Event::Remove => {
|
||||
cx.emit(PanelEvent::Close);
|
||||
false
|
||||
}
|
||||
@@ -863,7 +881,7 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
||||
if self.project.read(cx).is_via_collab() {
|
||||
if self.project.read(cx).is_remote() {
|
||||
let task = self
|
||||
.context_store
|
||||
.update(cx, |store, cx| store.create_remote_context(cx));
|
||||
@@ -1703,8 +1721,6 @@ struct WorkflowAssist {
|
||||
assist_ids: Vec<InlineAssistId>,
|
||||
}
|
||||
|
||||
type MessageHeader = MessageMetadata;
|
||||
|
||||
pub struct ContextEditor {
|
||||
context: Model<Context>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -1712,7 +1728,7 @@ pub struct ContextEditor {
|
||||
project: Model<Project>,
|
||||
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
editor: View<Editor>,
|
||||
blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
|
||||
blocks: HashSet<CustomBlockId>,
|
||||
image_blocks: HashSet<CustomBlockId>,
|
||||
scroll_position: Option<ScrollPosition>,
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
@@ -3039,209 +3055,176 @@ impl ContextEditor {
|
||||
fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let excerpt_id = *buffer.as_singleton().unwrap().0;
|
||||
let mut old_blocks = std::mem::take(&mut self.blocks);
|
||||
let mut blocks_to_remove: HashMap<_, _> = old_blocks
|
||||
.iter()
|
||||
.map(|(message_id, (_, block_id))| (*message_id, *block_id))
|
||||
.collect();
|
||||
let mut blocks_to_replace: HashMap<_, RenderBlock> = Default::default();
|
||||
let old_blocks = std::mem::take(&mut self.blocks);
|
||||
let new_blocks = self
|
||||
.context
|
||||
.read(cx)
|
||||
.messages(cx)
|
||||
.map(|message| BlockProperties {
|
||||
position: buffer
|
||||
.anchor_in_excerpt(excerpt_id, message.anchor)
|
||||
.unwrap(),
|
||||
height: 2,
|
||||
style: BlockStyle::Sticky,
|
||||
render: Box::new({
|
||||
let context = self.context.clone();
|
||||
move |cx| {
|
||||
let message_id = message.id;
|
||||
let show_spinner = message.role == Role::Assistant
|
||||
&& message.status == MessageStatus::Pending;
|
||||
|
||||
let render_block = |message: MessageMetadata| -> RenderBlock {
|
||||
Box::new({
|
||||
let context = self.context.clone();
|
||||
move |cx| {
|
||||
let message_id = MessageId(message.timestamp);
|
||||
let show_spinner = message.role == Role::Assistant
|
||||
&& message.status == MessageStatus::Pending;
|
||||
|
||||
let label = match message.role {
|
||||
Role::User => {
|
||||
Label::new("You").color(Color::Default).into_any_element()
|
||||
}
|
||||
Role::Assistant => {
|
||||
let label = Label::new("Assistant").color(Color::Info);
|
||||
if show_spinner {
|
||||
label
|
||||
.with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.4, 0.8)),
|
||||
|label, delta| label.alpha(delta),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
label.into_any_element()
|
||||
let label = match message.role {
|
||||
Role::User => {
|
||||
Label::new("You").color(Color::Default).into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
Role::System => Label::new("System")
|
||||
.color(Color::Warning)
|
||||
.into_any_element(),
|
||||
};
|
||||
|
||||
let sender = ButtonLike::new("role")
|
||||
.style(ButtonStyle::Filled)
|
||||
.child(label)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta(
|
||||
"Toggle message role",
|
||||
None,
|
||||
"Available roles: You (User), Assistant, System",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let context = context.clone();
|
||||
move |_, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.cycle_message_roles(
|
||||
HashSet::from_iter(Some(message_id)),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.id(("message_header", message_id.as_u64()))
|
||||
.pl(cx.gutter_dimensions.full_width())
|
||||
.h_11()
|
||||
.w_full()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(sender)
|
||||
.children(match &message.cache {
|
||||
Some(cache) if cache.is_final_anchor => match cache.status {
|
||||
CacheStatus::Cached => Some(
|
||||
div()
|
||||
.id("cached")
|
||||
.child(
|
||||
Icon::new(IconName::DatabaseZap)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Hint),
|
||||
Role::Assistant => {
|
||||
let label = Label::new("Assistant").color(Color::Info);
|
||||
if show_spinner {
|
||||
label
|
||||
.with_animation(
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.4, 0.8)),
|
||||
|label, delta| label.alpha(delta),
|
||||
)
|
||||
.tooltip(|cx| {
|
||||
.into_any_element()
|
||||
} else {
|
||||
label.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
Role::System => Label::new("System")
|
||||
.color(Color::Warning)
|
||||
.into_any_element(),
|
||||
};
|
||||
|
||||
let sender = ButtonLike::new("role")
|
||||
.style(ButtonStyle::Filled)
|
||||
.child(label)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta(
|
||||
"Toggle message role",
|
||||
None,
|
||||
"Available roles: You (User), Assistant, System",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let context = context.clone();
|
||||
move |_, cx| {
|
||||
context.update(cx, |context, cx| {
|
||||
context.cycle_message_roles(
|
||||
HashSet::from_iter(Some(message_id)),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.id(("message_header", message_id.as_u64()))
|
||||
.pl(cx.gutter_dimensions.full_width())
|
||||
.h_11()
|
||||
.w_full()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(sender)
|
||||
.children(match &message.cache {
|
||||
Some(cache) if cache.is_final_anchor => match cache.status {
|
||||
CacheStatus::Cached => Some(
|
||||
div()
|
||||
.id("cached")
|
||||
.child(
|
||||
Icon::new(IconName::DatabaseZap)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Hint),
|
||||
)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta(
|
||||
"Context cached",
|
||||
None,
|
||||
"Large messages cached to optimize performance",
|
||||
cx,
|
||||
)
|
||||
}).into_any_element()
|
||||
),
|
||||
CacheStatus::Pending => Some(
|
||||
div()
|
||||
.child(
|
||||
Icon::new(IconName::Ellipsis)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Hint),
|
||||
).into_any_element()
|
||||
),
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.children(match &message.status {
|
||||
MessageStatus::Error(error) => Some(
|
||||
Button::new("show-error", "Error")
|
||||
.color(Color::Error)
|
||||
.selected_label_color(Color::Error)
|
||||
.selected_icon_color(Color::Error)
|
||||
.icon(IconName::XCircle)
|
||||
.icon_color(Color::Error)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
"Context cached",
|
||||
"Error interacting with language model",
|
||||
None,
|
||||
"Large messages cached to optimize performance",
|
||||
"Click for more details",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let context = context.clone();
|
||||
let error = error.clone();
|
||||
move |_, cx| {
|
||||
context.update(cx, |_, cx| {
|
||||
cx.emit(ContextEvent::ShowAssistError(
|
||||
error.clone(),
|
||||
));
|
||||
});
|
||||
}
|
||||
})
|
||||
.into_any_element(),
|
||||
),
|
||||
MessageStatus::Canceled => Some(
|
||||
ButtonLike::new("canceled")
|
||||
.child(
|
||||
Icon::new(IconName::XCircle).color(Color::Disabled),
|
||||
)
|
||||
.child(
|
||||
Label::new("Canceled")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Disabled),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
"Canceled",
|
||||
None,
|
||||
"Interaction with the assistant was canceled",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.into_any_element(),
|
||||
),
|
||||
CacheStatus::Pending => Some(
|
||||
div()
|
||||
.child(
|
||||
Icon::new(IconName::Ellipsis)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Hint),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.children(match &message.status {
|
||||
MessageStatus::Error(error) => Some(
|
||||
Button::new("show-error", "Error")
|
||||
.color(Color::Error)
|
||||
.selected_label_color(Color::Error)
|
||||
.selected_icon_color(Color::Error)
|
||||
.icon(IconName::XCircle)
|
||||
.icon_color(Color::Error)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
"Error interacting with language model",
|
||||
None,
|
||||
"Click for more details",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let context = context.clone();
|
||||
let error = error.clone();
|
||||
move |_, cx| {
|
||||
context.update(cx, |_, cx| {
|
||||
cx.emit(ContextEvent::ShowAssistError(
|
||||
error.clone(),
|
||||
));
|
||||
});
|
||||
}
|
||||
})
|
||||
.into_any_element(),
|
||||
),
|
||||
MessageStatus::Canceled => Some(
|
||||
ButtonLike::new("canceled")
|
||||
.child(Icon::new(IconName::XCircle).color(Color::Disabled))
|
||||
.child(
|
||||
Label::new("Canceled")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Disabled),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
"Canceled",
|
||||
None,
|
||||
"Interaction with the assistant was canceled",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.into_any_element(),
|
||||
),
|
||||
_ => None,
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}),
|
||||
disposition: BlockDisposition::Above,
|
||||
priority: usize::MAX,
|
||||
})
|
||||
};
|
||||
let create_block_properties = |message: &Message| BlockProperties {
|
||||
position: buffer
|
||||
.anchor_in_excerpt(excerpt_id, message.anchor)
|
||||
.unwrap(),
|
||||
height: 2,
|
||||
style: BlockStyle::Sticky,
|
||||
disposition: BlockDisposition::Above,
|
||||
priority: usize::MAX,
|
||||
render: render_block(MessageMetadata::from(message)),
|
||||
};
|
||||
let mut new_blocks = vec![];
|
||||
let mut block_index_to_message = vec![];
|
||||
for message in self.context.read(cx).messages(cx) {
|
||||
if let Some(_) = blocks_to_remove.remove(&message.id) {
|
||||
// This is an old message that we might modify.
|
||||
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
|
||||
debug_assert!(
|
||||
false,
|
||||
"old_blocks should contain a message_id we've just removed."
|
||||
);
|
||||
continue;
|
||||
};
|
||||
// Should we modify it?
|
||||
let message_meta = MessageMetadata::from(&message);
|
||||
if meta != &message_meta {
|
||||
blocks_to_replace.insert(*block_id, render_block(message_meta.clone()));
|
||||
*meta = message_meta;
|
||||
}
|
||||
} else {
|
||||
// This is a new message.
|
||||
new_blocks.push(create_block_properties(&message));
|
||||
block_index_to_message.push((message.id, MessageMetadata::from(&message)));
|
||||
}
|
||||
}
|
||||
editor.replace_blocks(blocks_to_replace, None, cx);
|
||||
editor.remove_blocks(blocks_to_remove.into_values().collect(), None, cx);
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
editor.remove_blocks(old_blocks, None, cx);
|
||||
let ids = editor.insert_blocks(new_blocks, None, cx);
|
||||
old_blocks.extend(ids.into_iter().zip(block_index_to_message).map(
|
||||
|(block_id, (message_id, message_meta))| (message_id, (message_meta, block_id)),
|
||||
));
|
||||
self.blocks = old_blocks;
|
||||
self.blocks = HashSet::from_iter(ids);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -153,8 +153,8 @@ impl AssistantSettingsContent {
|
||||
models
|
||||
.into_iter()
|
||||
.filter_map(|model| match model {
|
||||
open_ai::Model::Custom { name, max_tokens,max_output_tokens } => {
|
||||
Some(language_model::provider::open_ai::AvailableModel { name, max_tokens,max_output_tokens })
|
||||
open_ai::Model::Custom { name, max_tokens } => {
|
||||
Some(language_model::provider::open_ai::AvailableModel { name, max_tokens })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
||||
@@ -330,22 +330,11 @@ pub struct MessageCacheMetadata {
|
||||
pub struct MessageMetadata {
|
||||
pub role: Role,
|
||||
pub status: MessageStatus,
|
||||
pub(crate) timestamp: clock::Lamport,
|
||||
timestamp: clock::Lamport,
|
||||
#[serde(skip)]
|
||||
pub cache: Option<MessageCacheMetadata>,
|
||||
}
|
||||
|
||||
impl From<&Message> for MessageMetadata {
|
||||
fn from(message: &Message) -> Self {
|
||||
Self {
|
||||
role: message.role,
|
||||
status: message.status.clone(),
|
||||
timestamp: message.id.0,
|
||||
cache: message.cache.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageMetadata {
|
||||
pub fn is_cache_valid(&self, buffer: &BufferSnapshot, range: &Range<usize>) -> bool {
|
||||
let result = match &self.cache {
|
||||
|
||||
@@ -161,7 +161,7 @@ impl ContextStore {
|
||||
) -> Result<proto::OpenContextResponse> {
|
||||
let context_id = ContextId::from_proto(envelope.payload.context_id);
|
||||
let operations = this.update(&mut cx, |this, cx| {
|
||||
if this.project.read(cx).is_via_collab() {
|
||||
if this.project.read(cx).is_remote() {
|
||||
return Err(anyhow!("only the host contexts can be opened"));
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ impl ContextStore {
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::CreateContextResponse> {
|
||||
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
||||
if this.project.read(cx).is_via_collab() {
|
||||
if this.project.read(cx).is_remote() {
|
||||
return Err(anyhow!("can only create contexts as the host"));
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ impl ContextStore {
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::SynchronizeContextsResponse> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.project.read(cx).is_via_collab() {
|
||||
if this.project.read(cx).is_remote() {
|
||||
return Err(anyhow!("only the host can synchronize contexts"));
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ impl ContextStore {
|
||||
let Some(project_id) = project.remote_id() else {
|
||||
return Task::ready(Err(anyhow!("project was not remote")));
|
||||
};
|
||||
if project.is_local_or_ssh() {
|
||||
if project.is_local() {
|
||||
return Task::ready(Err(anyhow!("cannot create remote contexts as the host")));
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@ impl ContextStore {
|
||||
let Some(project_id) = project.remote_id() else {
|
||||
return Task::ready(Err(anyhow!("project was not remote")));
|
||||
};
|
||||
if project.is_local_or_ssh() {
|
||||
if project.is_local() {
|
||||
return Task::ready(Err(anyhow!("cannot open remote contexts as the host")));
|
||||
}
|
||||
|
||||
@@ -589,7 +589,7 @@ impl ContextStore {
|
||||
};
|
||||
|
||||
// For now, only the host can advertise their open contexts.
|
||||
if self.project.read(cx).is_via_collab() {
|
||||
if self.project.read(cx).is_remote() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
|
||||
AssistantPanel, AssistantPanelEvent, CharOperation, LineDiff, LineOperation, ModelSelector,
|
||||
StreamingDiff,
|
||||
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent,
|
||||
CharOperation, LineDiff, LineOperation, ModelSelector, StreamingDiff,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use client::{telemetry::Telemetry, ErrorExt};
|
||||
@@ -36,7 +35,7 @@ use language_model::{
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use rope::Rope;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use settings::Settings;
|
||||
use smol::future::FutureExt;
|
||||
use std::{
|
||||
cmp,
|
||||
@@ -48,7 +47,6 @@ use std::{
|
||||
task::{self, Poll},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
|
||||
use util::{RangeExt, ResultExt};
|
||||
@@ -133,18 +131,6 @@ impl InlineAssistant {
|
||||
Self::update_global(cx, |this, cx| this.handle_workspace_event(event, cx));
|
||||
})
|
||||
.detach();
|
||||
|
||||
let workspace = workspace.clone();
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let enabled = AssistantSettings::get_global(cx).enabled;
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.asssistant_enabled(enabled, cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_workspace_event(&mut self, event: &workspace::Event, cx: &mut WindowContext) {
|
||||
|
||||
@@ -190,15 +190,15 @@ impl PickerDelegate for ModelPickerDelegate {
|
||||
})
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.end_slot(div().when(model_info.is_selected, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Check)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
})),
|
||||
)
|
||||
.child(div().when(model_info.is_selected, |this| {
|
||||
this.child(
|
||||
Icon::new(IconName::Check)
|
||||
.color(Color::Accent)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::{slash_command::SlashCommandCompletionProvider, AssistantPanel, InlineAssistant};
|
||||
use crate::{
|
||||
slash_command::SlashCommandCompletionProvider, AssistantPanel, InlineAssist, InlineAssistant,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -42,7 +44,6 @@ use ui::{
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
use workspace::Workspace;
|
||||
use zed_actions::InlineAssist;
|
||||
|
||||
actions!(
|
||||
prompt_library,
|
||||
@@ -925,7 +926,6 @@ impl PromptLibrary {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
..EditorStyle::default()
|
||||
},
|
||||
)),
|
||||
),
|
||||
|
||||
@@ -8,7 +8,6 @@ use language::BufferSnapshot;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
||||
use text::LineEnding;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -192,8 +191,8 @@ impl PromptBuilder {
|
||||
if let Some(id) = path.split('/').last().and_then(|s| s.strip_suffix(".hbs")) {
|
||||
if let Some(prompt) = Assets.load(path.as_ref()).log_err().flatten() {
|
||||
log::info!("Registering built-in prompt template: {}", id);
|
||||
let prompt = String::from_utf8_lossy(prompt.as_ref());
|
||||
handlebars.register_template_string(id, LineEnding::normalize_cow(prompt))?
|
||||
handlebars
|
||||
.register_template_string(id, String::from_utf8_lossy(prompt.as_ref()))?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,6 +512,10 @@ mod custom_path_matcher {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sources(&self) -> &[String] {
|
||||
&self.sources
|
||||
}
|
||||
|
||||
pub fn is_match<P: AsRef<Path>>(&self, other: P) -> bool {
|
||||
let other_path = other.as_ref();
|
||||
self.sources
|
||||
|
||||
@@ -197,7 +197,6 @@ fn tab_items_for_queries(
|
||||
}
|
||||
|
||||
let mut timestamps_by_entity_id = HashMap::default();
|
||||
let mut visited_buffers = HashSet::default();
|
||||
let mut open_buffers = Vec::new();
|
||||
|
||||
for pane in workspace.panes() {
|
||||
@@ -212,11 +211,9 @@ fn tab_items_for_queries(
|
||||
if let Some(timestamp) =
|
||||
timestamps_by_entity_id.get(&editor.entity_id())
|
||||
{
|
||||
if visited_buffers.insert(buffer.read(cx).remote_id()) {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let full_path = snapshot.resolve_file_path(cx, true);
|
||||
open_buffers.push((full_path, snapshot, *timestamp));
|
||||
}
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let full_path = snapshot.resolve_file_path(cx, true);
|
||||
open_buffers.push((full_path, snapshot, *timestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
serde.workspace = true
|
||||
util.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
exec.workspace = true
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::{
|
||||
sync::Arc,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use util::paths::PathWithPosition;
|
||||
|
||||
struct Detect;
|
||||
@@ -23,11 +22,7 @@ trait InstalledApp {
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "zed",
|
||||
disable_version_flag = true,
|
||||
after_help = "To read from stdin, append '-' (e.g. 'ps axf | zed -')"
|
||||
)]
|
||||
#[command(name = "zed", disable_version_flag = true)]
|
||||
struct Args {
|
||||
/// Wait for all of the given paths to be opened/closed before exiting.
|
||||
#[arg(short, long)]
|
||||
@@ -125,7 +120,6 @@ fn main() -> Result<()> {
|
||||
let exit_status = Arc::new(Mutex::new(None));
|
||||
let mut paths = vec![];
|
||||
let mut urls = vec![];
|
||||
let mut stdin_tmp_file: Option<fs::File> = None;
|
||||
for path in args.paths_with_position.iter() {
|
||||
if path.starts_with("zed://")
|
||||
|| path.starts_with("http://")
|
||||
@@ -134,11 +128,6 @@ fn main() -> Result<()> {
|
||||
|| path.starts_with("ssh://")
|
||||
{
|
||||
urls.push(path.to_string());
|
||||
} else if path == "-" && args.paths_with_position.len() == 1 {
|
||||
let file = NamedTempFile::new()?;
|
||||
paths.push(file.path().to_string_lossy().to_string());
|
||||
let (file, _) = file.keep()?;
|
||||
stdin_tmp_file = Some(file);
|
||||
} else {
|
||||
paths.push(parse_path_with_position(path)?)
|
||||
}
|
||||
@@ -173,31 +162,11 @@ fn main() -> Result<()> {
|
||||
}
|
||||
});
|
||||
|
||||
let pipe_handle: JoinHandle<anyhow::Result<()>> = thread::spawn(move || {
|
||||
if let Some(mut tmp_file) = stdin_tmp_file {
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
if io::IsTerminal::is_terminal(&stdin) {
|
||||
return Ok(());
|
||||
}
|
||||
let mut buffer = [0; 8 * 1024];
|
||||
loop {
|
||||
let bytes_read = io::Read::read(&mut stdin, &mut buffer)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
io::Write::write(&mut tmp_file, &buffer[..bytes_read])?;
|
||||
}
|
||||
io::Write::flush(&mut tmp_file)?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if args.foreground {
|
||||
app.run_foreground(url)?;
|
||||
} else {
|
||||
app.launch(url)?;
|
||||
sender.join().unwrap()?;
|
||||
pipe_handle.join().unwrap()?;
|
||||
}
|
||||
|
||||
if let Some(exit_status) = exit_status.lock().take() {
|
||||
|
||||
@@ -27,6 +27,7 @@ fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
paths.workspace = true
|
||||
@@ -48,7 +49,6 @@ text.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io"] }
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
worktree.workspace = true
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
|
||||
mod socks;
|
||||
pub mod telemetry;
|
||||
pub mod user;
|
||||
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use async_recursion::async_recursion;
|
||||
use async_tungstenite::tungstenite::{
|
||||
client::IntoClientRequest,
|
||||
@@ -23,6 +22,7 @@ use gpui::{
|
||||
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use proto::ProtoClient;
|
||||
@@ -32,7 +32,6 @@ use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, Requ
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use socks::connect_socks_proxy_stream;
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::{
|
||||
@@ -44,7 +43,7 @@ use std::{
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc, LazyLock, Weak,
|
||||
Arc, Weak,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -66,35 +65,27 @@ impl fmt::Display for DevServerToken {
|
||||
}
|
||||
}
|
||||
|
||||
static ZED_SERVER_URL: LazyLock<Option<String>> =
|
||||
LazyLock::new(|| std::env::var("ZED_SERVER_URL").ok());
|
||||
static ZED_RPC_URL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("ZED_RPC_URL").ok());
|
||||
|
||||
/// An environment variable whose presence indicates that the development auth
|
||||
/// provider should be used.
|
||||
///
|
||||
/// Only works in development. Setting this environment variable in other release
|
||||
/// channels is a no-op.
|
||||
pub static ZED_DEVELOPMENT_AUTH: LazyLock<bool> = LazyLock::new(|| {
|
||||
std::env::var("ZED_DEVELOPMENT_AUTH").map_or(false, |value| !value.is_empty())
|
||||
});
|
||||
pub static IMPERSONATE_LOGIN: LazyLock<Option<String>> = LazyLock::new(|| {
|
||||
std::env::var("ZED_IMPERSONATE")
|
||||
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();
|
||||
/// An environment variable whose presence indicates that the development auth
|
||||
/// provider should be used.
|
||||
///
|
||||
/// Only works in development. Setting this environment variable in other release
|
||||
/// channels is a no-op.
|
||||
pub static ref ZED_DEVELOPMENT_AUTH: bool =
|
||||
std::env::var("ZED_DEVELOPMENT_AUTH").map_or(false, |value| !value.is_empty());
|
||||
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 ADMIN_API_TOKEN: LazyLock<Option<String>> = LazyLock::new(|| {
|
||||
std::env::var("ZED_ADMIN_API_TOKEN")
|
||||
.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 ZED_APP_PATH: LazyLock<Option<PathBuf>> =
|
||||
LazyLock::new(|| std::env::var("ZED_APP_PATH").ok().map(PathBuf::from));
|
||||
|
||||
pub static ZED_ALWAYS_ACTIVE: LazyLock<bool> =
|
||||
LazyLock::new(|| std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| !e.is_empty()));
|
||||
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||
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 =
|
||||
std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| !e.is_empty());
|
||||
}
|
||||
|
||||
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(500);
|
||||
pub const MAX_RECONNECTION_DELAY: Duration = Duration::from_secs(10);
|
||||
@@ -1179,7 +1170,6 @@ impl Client {
|
||||
.unwrap_or_default();
|
||||
|
||||
let http = self.http.clone();
|
||||
let proxy = http.proxy().cloned();
|
||||
let credentials = credentials.clone();
|
||||
let rpc_url = self.rpc_url(http, release_channel);
|
||||
cx.background_executor().spawn(async move {
|
||||
@@ -1201,7 +1191,7 @@ impl Client {
|
||||
.host_str()
|
||||
.zip(rpc_url.port_or_known_default())
|
||||
.ok_or_else(|| anyhow!("missing host in rpc url"))?;
|
||||
let stream = connect_socks_proxy_stream(proxy.as_ref(), rpc_host).await?;
|
||||
let stream = smol::net::TcpStream::connect(rpc_host).await?;
|
||||
|
||||
log::info!("connected to rpc endpoint {}", rpc_url);
|
||||
|
||||
@@ -1395,64 +1385,11 @@ impl Client {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
let github_user = {
|
||||
#[derive(Deserialize)]
|
||||
struct GithubUser {
|
||||
id: i32,
|
||||
login: String,
|
||||
}
|
||||
|
||||
let request = {
|
||||
let mut request_builder =
|
||||
Request::get(&format!("https://api.github.com/users/{login}"));
|
||||
if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
|
||||
request_builder =
|
||||
request_builder.header("Authorization", format!("Bearer {}", github_token));
|
||||
}
|
||||
|
||||
request_builder.body(AsyncBody::empty())?
|
||||
};
|
||||
|
||||
let mut response = http
|
||||
.send(request)
|
||||
.await
|
||||
.context("error fetching GitHub user")?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("error reading GitHub user")?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let text = String::from_utf8_lossy(body.as_slice());
|
||||
bail!(
|
||||
"status error {}, response: {text:?}",
|
||||
response.status().as_u16()
|
||||
);
|
||||
}
|
||||
|
||||
let user = serde_json::from_slice::<GithubUser>(body.as_slice()).map_err(|err| {
|
||||
log::error!("Error deserializing: {:?}", err);
|
||||
log::error!(
|
||||
"GitHub API response text: {:?}",
|
||||
String::from_utf8_lossy(body.as_slice())
|
||||
);
|
||||
anyhow!("error deserializing GitHub user")
|
||||
})?;
|
||||
|
||||
user
|
||||
};
|
||||
|
||||
// Use the collab server's admin API to retrieve the id
|
||||
// of the impersonated user.
|
||||
let mut url = self.rpc_url(http.clone(), None).await?;
|
||||
url.set_path("/user");
|
||||
url.set_query(Some(&format!(
|
||||
"github_login={}&github_user_id={}",
|
||||
github_user.login, github_user.id
|
||||
)));
|
||||
url.set_query(Some(&format!("github_login={login}")));
|
||||
let request: http_client::Request<AsyncBody> = Request::get(url.as_str())
|
||||
.header("Authorization", format!("token {api_token}"))
|
||||
.body("".into())?;
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
//! socks proxy
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::io::{AsyncRead, AsyncWrite};
|
||||
use http_client::Uri;
|
||||
use tokio_socks::{
|
||||
io::Compat,
|
||||
tcp::{Socks4Stream, Socks5Stream},
|
||||
};
|
||||
|
||||
pub(crate) async fn connect_socks_proxy_stream(
|
||||
proxy: Option<&Uri>,
|
||||
rpc_host: (&str, u16),
|
||||
) -> Result<Box<dyn AsyncReadWrite>> {
|
||||
let stream = match parse_socks_proxy(proxy) {
|
||||
Some((socks_proxy, SocksVersion::V4)) => {
|
||||
let stream = Socks4Stream::connect_with_socket(
|
||||
Compat::new(smol::net::TcpStream::connect(socks_proxy).await?),
|
||||
rpc_host,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error connecting to socks {}", err))?;
|
||||
Box::new(stream) as Box<dyn AsyncReadWrite>
|
||||
}
|
||||
Some((socks_proxy, SocksVersion::V5)) => Box::new(
|
||||
Socks5Stream::connect_with_socket(
|
||||
Compat::new(smol::net::TcpStream::connect(socks_proxy).await?),
|
||||
rpc_host,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error connecting to socks {}", err))?,
|
||||
) as Box<dyn AsyncReadWrite>,
|
||||
None => Box::new(smol::net::TcpStream::connect(rpc_host).await?) as Box<dyn AsyncReadWrite>,
|
||||
};
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
fn parse_socks_proxy(proxy: Option<&Uri>) -> Option<((String, u16), SocksVersion)> {
|
||||
let Some(proxy_uri) = proxy else {
|
||||
return None;
|
||||
};
|
||||
let Some(scheme) = proxy_uri.scheme_str() else {
|
||||
return None;
|
||||
};
|
||||
let socks_version = if scheme.starts_with("socks4") {
|
||||
// socks4
|
||||
SocksVersion::V4
|
||||
} else if scheme.starts_with("socks") {
|
||||
// socks, socks5
|
||||
SocksVersion::V5
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
if let (Some(host), Some(port)) = (proxy_uri.host(), proxy_uri.port_u16()) {
|
||||
Some(((host.to_string(), port), socks_version))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// private helper structs and traits
|
||||
|
||||
enum SocksVersion {
|
||||
V4,
|
||||
V5,
|
||||
}
|
||||
|
||||
pub(crate) trait AsyncReadWrite: AsyncRead + AsyncWrite + Unpin + Send + 'static {}
|
||||
impl<T: AsyncRead + AsyncWrite + Unpin + Send + 'static> AsyncReadWrite for T {}
|
||||
@@ -216,12 +216,6 @@ spec:
|
||||
secretKeyRef:
|
||||
name: supermaven
|
||||
key: api_key
|
||||
- name: USER_BACKFILLER_GITHUB_ACCESS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: user-backfiller
|
||||
key: github_access_token
|
||||
optional: true
|
||||
- name: INVITE_LINK_PREFIX
|
||||
value: ${INVITE_LINK_PREFIX}
|
||||
- name: RUST_BACKTRACE
|
||||
|
||||
@@ -9,14 +9,14 @@ CREATE TABLE "users" (
|
||||
"connected_once" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"metrics_id" TEXT,
|
||||
"github_user_id" INTEGER NOT NULL,
|
||||
"github_user_id" INTEGER,
|
||||
"accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE,
|
||||
"github_user_created_at" TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
|
||||
CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
|
||||
CREATE INDEX "index_users_on_email_address" ON "users" ("email_address");
|
||||
CREATE UNIQUE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id");
|
||||
CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id");
|
||||
|
||||
CREATE TABLE "access_tokens" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -86,7 +86,6 @@ CREATE TABLE "worktree_entries" (
|
||||
"is_ignored" BOOL NOT NULL,
|
||||
"is_deleted" BOOL NOT NULL,
|
||||
"git_status" INTEGER,
|
||||
"is_fifo" BOOL NOT NULL,
|
||||
PRIMARY KEY(project_id, worktree_id, id),
|
||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
alter table users alter column github_user_id set not null;
|
||||
|
||||
drop index index_users_on_github_user_id;
|
||||
create unique index uix_users_on_github_user_id on users (github_user_id);
|
||||
@@ -1,2 +0,0 @@
|
||||
ALTER TABLE "worktree_entries"
|
||||
ADD "is_fifo" BOOL NOT NULL DEFAULT FALSE;
|
||||
@@ -108,7 +108,7 @@ pub async fn validate_api_token<B>(req: Request<B>, next: Next<B>) -> impl IntoR
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AuthenticatedUserParams {
|
||||
github_user_id: i32,
|
||||
github_user_id: Option<i32>,
|
||||
github_login: String,
|
||||
github_email: Option<String>,
|
||||
github_user_created_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
|
||||
@@ -63,7 +63,7 @@ impl Database {
|
||||
pub async fn add_contributor(
|
||||
&self,
|
||||
github_login: &str,
|
||||
github_user_id: i32,
|
||||
github_user_id: Option<i32>,
|
||||
github_email: Option<&str>,
|
||||
github_user_created_at: Option<DateTimeUtc>,
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
|
||||
@@ -319,7 +319,6 @@ impl Database {
|
||||
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
|
||||
is_deleted: ActiveValue::set(false),
|
||||
scan_id: ActiveValue::set(update.scan_id as i64),
|
||||
is_fifo: ActiveValue::set(entry.is_fifo),
|
||||
}
|
||||
}))
|
||||
.on_conflict(
|
||||
@@ -728,7 +727,6 @@ impl Database {
|
||||
is_ignored: db_entry.is_ignored,
|
||||
is_external: db_entry.is_external,
|
||||
git_status: db_entry.git_status.map(|status| status as i32),
|
||||
is_fifo: db_entry.is_fifo,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +663,6 @@ impl Database {
|
||||
is_ignored: db_entry.is_ignored,
|
||||
is_external: db_entry.is_external,
|
||||
git_status: db_entry.git_status.map(|status| status as i32),
|
||||
is_fifo: db_entry.is_fifo,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,17 @@ impl Database {
|
||||
let user = user::Entity::insert(user::ActiveModel {
|
||||
email_address: ActiveValue::set(Some(email_address.into())),
|
||||
github_login: ActiveValue::set(params.github_login.clone()),
|
||||
github_user_id: ActiveValue::set(params.github_user_id),
|
||||
github_user_id: ActiveValue::set(Some(params.github_user_id)),
|
||||
admin: ActiveValue::set(admin),
|
||||
metrics_id: ActiveValue::set(Uuid::new_v4()),
|
||||
..Default::default()
|
||||
})
|
||||
.on_conflict(
|
||||
OnConflict::column(user::Column::GithubUserId)
|
||||
OnConflict::column(user::Column::GithubLogin)
|
||||
.update_columns([
|
||||
user::Column::Admin,
|
||||
user::Column::EmailAddress,
|
||||
user::Column::GithubLogin,
|
||||
user::Column::GithubUserId,
|
||||
])
|
||||
.to_owned(),
|
||||
)
|
||||
@@ -99,7 +99,7 @@ impl Database {
|
||||
pub async fn get_or_create_user_by_github_account(
|
||||
&self,
|
||||
github_login: &str,
|
||||
github_user_id: i32,
|
||||
github_user_id: Option<i32>,
|
||||
github_email: Option<&str>,
|
||||
github_user_created_at: Option<DateTimeUtc>,
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
@@ -121,61 +121,70 @@ impl Database {
|
||||
pub async fn get_or_create_user_by_github_account_tx(
|
||||
&self,
|
||||
github_login: &str,
|
||||
github_user_id: i32,
|
||||
github_user_id: Option<i32>,
|
||||
github_email: Option<&str>,
|
||||
github_user_created_at: Option<NaiveDateTime>,
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<User> {
|
||||
if let Some(user_by_github_user_id) = user::Entity::find()
|
||||
.filter(user::Column::GithubUserId.eq(github_user_id))
|
||||
.one(tx)
|
||||
.await?
|
||||
{
|
||||
let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
|
||||
user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
|
||||
if github_user_created_at.is_some() {
|
||||
user_by_github_user_id.github_user_created_at =
|
||||
ActiveValue::set(github_user_created_at);
|
||||
}
|
||||
Ok(user_by_github_user_id.update(tx).await?)
|
||||
} else if let Some(user_by_github_login) = user::Entity::find()
|
||||
.filter(user::Column::GithubLogin.eq(github_login))
|
||||
.one(tx)
|
||||
.await?
|
||||
{
|
||||
let mut user_by_github_login = user_by_github_login.into_active_model();
|
||||
user_by_github_login.github_user_id = ActiveValue::set(github_user_id);
|
||||
if github_user_created_at.is_some() {
|
||||
user_by_github_login.github_user_created_at =
|
||||
ActiveValue::set(github_user_created_at);
|
||||
}
|
||||
Ok(user_by_github_login.update(tx).await?)
|
||||
} else {
|
||||
let user = user::Entity::insert(user::ActiveModel {
|
||||
email_address: ActiveValue::set(github_email.map(|email| email.into())),
|
||||
github_login: ActiveValue::set(github_login.into()),
|
||||
github_user_id: ActiveValue::set(github_user_id),
|
||||
github_user_created_at: ActiveValue::set(github_user_created_at),
|
||||
admin: ActiveValue::set(false),
|
||||
invite_count: ActiveValue::set(0),
|
||||
invite_code: ActiveValue::set(None),
|
||||
metrics_id: ActiveValue::set(Uuid::new_v4()),
|
||||
..Default::default()
|
||||
})
|
||||
.exec_with_returning(tx)
|
||||
.await?;
|
||||
if let Some(channel_id) = initial_channel_id {
|
||||
channel_member::Entity::insert(channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel_id),
|
||||
user_id: ActiveValue::Set(user.id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Guest),
|
||||
if let Some(github_user_id) = github_user_id {
|
||||
if let Some(user_by_github_user_id) = user::Entity::find()
|
||||
.filter(user::Column::GithubUserId.eq(github_user_id))
|
||||
.one(tx)
|
||||
.await?
|
||||
{
|
||||
let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
|
||||
user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
|
||||
if github_user_created_at.is_some() {
|
||||
user_by_github_user_id.github_user_created_at =
|
||||
ActiveValue::set(github_user_created_at);
|
||||
}
|
||||
Ok(user_by_github_user_id.update(tx).await?)
|
||||
} else if let Some(user_by_github_login) = user::Entity::find()
|
||||
.filter(user::Column::GithubLogin.eq(github_login))
|
||||
.one(tx)
|
||||
.await?
|
||||
{
|
||||
let mut user_by_github_login = user_by_github_login.into_active_model();
|
||||
user_by_github_login.github_user_id = ActiveValue::set(Some(github_user_id));
|
||||
if github_user_created_at.is_some() {
|
||||
user_by_github_login.github_user_created_at =
|
||||
ActiveValue::set(github_user_created_at);
|
||||
}
|
||||
Ok(user_by_github_login.update(tx).await?)
|
||||
} else {
|
||||
let user = user::Entity::insert(user::ActiveModel {
|
||||
email_address: ActiveValue::set(github_email.map(|email| email.into())),
|
||||
github_login: ActiveValue::set(github_login.into()),
|
||||
github_user_id: ActiveValue::set(Some(github_user_id)),
|
||||
github_user_created_at: ActiveValue::set(github_user_created_at),
|
||||
admin: ActiveValue::set(false),
|
||||
invite_count: ActiveValue::set(0),
|
||||
invite_code: ActiveValue::set(None),
|
||||
metrics_id: ActiveValue::set(Uuid::new_v4()),
|
||||
..Default::default()
|
||||
})
|
||||
.exec(tx)
|
||||
.exec_with_returning(tx)
|
||||
.await?;
|
||||
if let Some(channel_id) = initial_channel_id {
|
||||
channel_member::Entity::insert(channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel_id),
|
||||
user_id: ActiveValue::Set(user.id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Guest),
|
||||
})
|
||||
.exec(tx)
|
||||
.await?;
|
||||
}
|
||||
Ok(user)
|
||||
}
|
||||
} else {
|
||||
let user = user::Entity::find()
|
||||
.filter(user::Column::GithubLogin.eq(github_login))
|
||||
.one(tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such user {}", github_login))?;
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
@@ -368,14 +377,4 @@ impl Database {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_users_missing_github_user_created_at(&self) -> Result<Vec<user::Model>> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(user::Entity::find()
|
||||
.filter(user::Column::GithubUserCreatedAt.is_null())
|
||||
.all(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: UserId,
|
||||
pub github_login: String,
|
||||
pub github_user_id: i32,
|
||||
pub github_user_id: Option<i32>,
|
||||
pub github_user_created_at: Option<NaiveDateTime>,
|
||||
pub email_address: Option<String>,
|
||||
pub admin: bool,
|
||||
|
||||
@@ -21,7 +21,6 @@ pub struct Model {
|
||||
pub is_external: bool,
|
||||
pub is_deleted: bool,
|
||||
pub scan_id: i64,
|
||||
pub is_fifo: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -42,7 +42,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user_c".into(),
|
||||
github_user_id: 103,
|
||||
github_user_id: 102,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -25,7 +25,7 @@ async fn test_contributors(db: &Arc<Database>) {
|
||||
assert_eq!(db.get_contributors().await.unwrap(), Vec::<String>::new());
|
||||
|
||||
let user1_created_at = Utc::now();
|
||||
db.add_contributor("user1", 1, None, Some(user1_created_at), None)
|
||||
db.add_contributor("user1", Some(1), None, Some(user1_created_at), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -34,7 +34,7 @@ async fn test_contributors(db: &Arc<Database>) {
|
||||
);
|
||||
|
||||
let user2_created_at = Utc::now();
|
||||
db.add_contributor("user2", 2, None, Some(user2_created_at), None)
|
||||
db.add_contributor("user2", Some(2), None, Some(user2_created_at), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
|
||||
@@ -45,25 +45,25 @@ async fn test_get_users(db: &Arc<Database>) {
|
||||
(
|
||||
user_ids[0],
|
||||
"user1".to_string(),
|
||||
1,
|
||||
Some(1),
|
||||
Some("user1@example.com".to_string()),
|
||||
),
|
||||
(
|
||||
user_ids[1],
|
||||
"user2".to_string(),
|
||||
2,
|
||||
Some(2),
|
||||
Some("user2@example.com".to_string()),
|
||||
),
|
||||
(
|
||||
user_ids[2],
|
||||
"user3".to_string(),
|
||||
3,
|
||||
Some(3),
|
||||
Some("user3@example.com".to_string()),
|
||||
),
|
||||
(
|
||||
user_ids[3],
|
||||
"user4".to_string(),
|
||||
4,
|
||||
Some(4),
|
||||
Some("user4@example.com".to_string()),
|
||||
)
|
||||
]
|
||||
@@ -101,17 +101,23 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
||||
.user_id;
|
||||
|
||||
let user = db
|
||||
.get_or_create_user_by_github_account("the-new-login2", 102, None, Some(Utc::now()), None)
|
||||
.get_or_create_user_by_github_account(
|
||||
"the-new-login2",
|
||||
Some(102),
|
||||
None,
|
||||
Some(Utc::now()),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(user.id, user_id2);
|
||||
assert_eq!(&user.github_login, "the-new-login2");
|
||||
assert_eq!(user.github_user_id, 102);
|
||||
assert_eq!(user.github_user_id, Some(102));
|
||||
|
||||
let user = db
|
||||
.get_or_create_user_by_github_account(
|
||||
"login3",
|
||||
103,
|
||||
Some(103),
|
||||
Some("user3@example.com"),
|
||||
Some(Utc::now()),
|
||||
None,
|
||||
@@ -119,7 +125,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(&user.github_login, "login3");
|
||||
assert_eq!(user.github_user_id, 103);
|
||||
assert_eq!(user.github_user_id, Some(103));
|
||||
assert_eq!(user.email_address, Some("user3@example.com".into()));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ pub mod migrations;
|
||||
mod rate_limiter;
|
||||
pub mod rpc;
|
||||
pub mod seed;
|
||||
pub mod user_backfiller;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -178,7 +177,6 @@ pub struct Config {
|
||||
pub stripe_api_key: Option<String>,
|
||||
pub stripe_price_id: Option<Arc<str>>,
|
||||
pub supermaven_admin_api_key: Option<Arc<str>>,
|
||||
pub user_backfiller_github_access_token: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -237,7 +235,6 @@ impl Config {
|
||||
supermaven_admin_api_key: None,
|
||||
qwen2_7b_api_key: None,
|
||||
qwen2_7b_api_url: None,
|
||||
user_backfiller_github_access_token: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ use axum::{
|
||||
Extension, Json, Router, TypedHeader,
|
||||
};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use collections::HashMap;
|
||||
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use http_client::IsahcHttpClient;
|
||||
@@ -30,7 +29,6 @@ use std::{
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
use telemetry::{report_llm_rate_limit, report_llm_usage, LlmRateLimitEventRow, LlmUsageEventRow};
|
||||
use tokio::sync::RwLock;
|
||||
use util::ResultExt;
|
||||
@@ -43,8 +41,7 @@ pub struct LlmState {
|
||||
pub db: Arc<LlmDatabase>,
|
||||
pub http_client: IsahcHttpClient,
|
||||
pub clickhouse_client: Option<clickhouse::Client>,
|
||||
active_user_count_by_model:
|
||||
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
|
||||
active_user_count: RwLock<Option<(DateTime<Utc>, ActiveUserCount)>>,
|
||||
}
|
||||
|
||||
const ACTIVE_USER_COUNT_CACHE_DURATION: Duration = Duration::seconds(30);
|
||||
@@ -72,6 +69,9 @@ impl LlmState {
|
||||
.build()
|
||||
.context("failed to construct http client")?;
|
||||
|
||||
let initial_active_user_count =
|
||||
Some((Utc::now(), db.get_active_user_count(Utc::now()).await?));
|
||||
|
||||
let this = Self {
|
||||
executor,
|
||||
db,
|
||||
@@ -80,34 +80,25 @@ impl LlmState {
|
||||
.clickhouse_url
|
||||
.as_ref()
|
||||
.and_then(|_| build_clickhouse_client(&config).log_err()),
|
||||
active_user_count_by_model: RwLock::new(HashMap::default()),
|
||||
active_user_count: RwLock::new(initial_active_user_count),
|
||||
config,
|
||||
};
|
||||
|
||||
Ok(Arc::new(this))
|
||||
}
|
||||
|
||||
pub async fn get_active_user_count(
|
||||
&self,
|
||||
provider: LanguageModelProvider,
|
||||
model: &str,
|
||||
) -> Result<ActiveUserCount> {
|
||||
pub async fn get_active_user_count(&self) -> Result<ActiveUserCount> {
|
||||
let now = Utc::now();
|
||||
|
||||
{
|
||||
let active_user_count_by_model = self.active_user_count_by_model.read().await;
|
||||
if let Some((last_updated, count)) =
|
||||
active_user_count_by_model.get(&(provider, model.to_string()))
|
||||
{
|
||||
if now - *last_updated < ACTIVE_USER_COUNT_CACHE_DURATION {
|
||||
return Ok(*count);
|
||||
}
|
||||
if let Some((last_updated, count)) = self.active_user_count.read().await.as_ref() {
|
||||
if now - *last_updated < ACTIVE_USER_COUNT_CACHE_DURATION {
|
||||
return Ok(*count);
|
||||
}
|
||||
}
|
||||
|
||||
let mut cache = self.active_user_count_by_model.write().await;
|
||||
let new_count = self.db.get_active_user_count(provider, model, now).await?;
|
||||
cache.insert((provider, model.to_string()), (now, new_count));
|
||||
let mut cache = self.active_user_count.write().await;
|
||||
let new_count = self.db.get_active_user_count(now).await?;
|
||||
*cache = Some((now, new_count));
|
||||
Ok(new_count)
|
||||
}
|
||||
}
|
||||
@@ -411,11 +402,6 @@ fn normalize_model_name(known_models: Vec<String>, name: String) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// The maximum lifetime spending an individual user can reach before being cut off.
|
||||
///
|
||||
/// Represented in cents.
|
||||
const LIFETIME_SPENDING_LIMIT_IN_CENTS: usize = 1_000 * 100;
|
||||
|
||||
async fn check_usage_limit(
|
||||
state: &Arc<LlmState>,
|
||||
provider: LanguageModelProvider,
|
||||
@@ -433,14 +419,7 @@ async fn check_usage_limit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
if usage.lifetime_spending >= LIFETIME_SPENDING_LIMIT_IN_CENTS {
|
||||
return Err(Error::http(
|
||||
StatusCode::FORBIDDEN,
|
||||
"Maximum spending limit reached.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let active_users = state.get_active_user_count(provider, model_name).await?;
|
||||
let active_users = state.get_active_user_count().await?;
|
||||
|
||||
let users_in_recent_minutes = active_users.users_in_recent_minutes.max(1);
|
||||
let users_in_recent_days = active_users.users_in_recent_days.max(1);
|
||||
@@ -484,24 +463,6 @@ async fn check_usage_limit(
|
||||
};
|
||||
|
||||
if let Some(client) = state.clickhouse_client.as_ref() {
|
||||
tracing::info!(
|
||||
target: "user rate limit",
|
||||
user_id = claims.user_id,
|
||||
login = claims.github_user_login,
|
||||
authn.jti = claims.jti,
|
||||
is_staff = claims.is_staff,
|
||||
provider = provider.to_string(),
|
||||
model = model.name,
|
||||
requests_this_minute = usage.requests_this_minute,
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
tokens_this_day = usage.tokens_this_day,
|
||||
users_in_recent_minutes = users_in_recent_minutes,
|
||||
users_in_recent_days = users_in_recent_days,
|
||||
max_requests_per_minute = per_user_max_requests_per_minute,
|
||||
max_tokens_per_minute = per_user_max_tokens_per_minute,
|
||||
max_tokens_per_day = per_user_max_tokens_per_day,
|
||||
);
|
||||
|
||||
report_llm_rate_limit(
|
||||
client,
|
||||
LlmRateLimitEventRow {
|
||||
@@ -644,39 +605,23 @@ pub fn log_usage_periodically(state: Arc<LlmState>) {
|
||||
.sleep(std::time::Duration::from_secs(30))
|
||||
.await;
|
||||
|
||||
for provider in LanguageModelProvider::iter() {
|
||||
for model in state.db.model_names_for_provider(provider) {
|
||||
if let Some(active_user_count) = state
|
||||
.get_active_user_count(provider, &model)
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
tracing::info!(
|
||||
target: "active user counts",
|
||||
provider = provider.to_string(),
|
||||
model = model,
|
||||
users_in_recent_minutes = active_user_count.users_in_recent_minutes,
|
||||
users_in_recent_days = active_user_count.users_in_recent_days,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(usages) = state
|
||||
let Some(usages) = state
|
||||
.db
|
||||
.get_application_wide_usages_by_model(Utc::now())
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
for usage in usages {
|
||||
tracing::info!(
|
||||
target: "computed usage",
|
||||
provider = usage.provider.to_string(),
|
||||
model = usage.model,
|
||||
requests_this_minute = usage.requests_this_minute,
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
);
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for usage in usages {
|
||||
tracing::info!(
|
||||
target: "computed usage",
|
||||
provider = usage.provider.to_string(),
|
||||
model = usage.model,
|
||||
requests_this_minute = usage.requests_this_minute,
|
||||
tokens_this_minute = usage.tokens_this_minute,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -343,30 +343,15 @@ impl LlmDatabase {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns the active user count for the specified model.
|
||||
pub async fn get_active_user_count(
|
||||
&self,
|
||||
provider: LanguageModelProvider,
|
||||
model_name: &str,
|
||||
now: DateTimeUtc,
|
||||
) -> Result<ActiveUserCount> {
|
||||
pub async fn get_active_user_count(&self, now: DateTimeUtc) -> Result<ActiveUserCount> {
|
||||
self.transaction(|tx| async move {
|
||||
let minute_since = now - Duration::minutes(5);
|
||||
let day_since = now - Duration::days(5);
|
||||
|
||||
let model = self
|
||||
.models
|
||||
.get(&(provider, model_name.to_string()))
|
||||
.ok_or_else(|| anyhow!("unknown model {provider}:{model_name}"))?;
|
||||
|
||||
let tokens_per_minute = self.usage_measure_ids[&UsageMeasure::TokensPerMinute];
|
||||
|
||||
let users_in_recent_minutes = usage::Entity::find()
|
||||
.filter(
|
||||
usage::Column::ModelId
|
||||
.eq(model.id)
|
||||
.and(usage::Column::MeasureId.eq(tokens_per_minute))
|
||||
.and(usage::Column::Timestamp.gte(minute_since.naive_utc()))
|
||||
usage::Column::Timestamp
|
||||
.gte(minute_since.naive_utc())
|
||||
.and(usage::Column::IsStaff.eq(false)),
|
||||
)
|
||||
.select_only()
|
||||
@@ -377,10 +362,8 @@ impl LlmDatabase {
|
||||
|
||||
let users_in_recent_days = usage::Entity::find()
|
||||
.filter(
|
||||
usage::Column::ModelId
|
||||
.eq(model.id)
|
||||
.and(usage::Column::MeasureId.eq(tokens_per_minute))
|
||||
.and(usage::Column::Timestamp.gte(day_since.naive_utc()))
|
||||
usage::Column::Timestamp
|
||||
.gte(day_since.naive_utc())
|
||||
.and(usage::Column::IsStaff.eq(false)),
|
||||
)
|
||||
.select_only()
|
||||
|
||||
@@ -7,7 +7,6 @@ use axum::{
|
||||
};
|
||||
use collab::llm::{db::LlmDatabase, log_usage_periodically};
|
||||
use collab::migrations::run_database_migrations;
|
||||
use collab::user_backfiller::spawn_user_backfiller;
|
||||
use collab::{api::billing::poll_stripe_events_periodically, llm::LlmState, ServiceMode};
|
||||
use collab::{
|
||||
api::fetch_extensions_from_blob_store_periodically, db, env, executor::Executor,
|
||||
@@ -132,7 +131,6 @@ async fn main() -> Result<()> {
|
||||
if mode.is_api() {
|
||||
poll_stripe_events_periodically(state.clone());
|
||||
fetch_extensions_from_blob_store_periodically(state.clone());
|
||||
spawn_user_backfiller(state.clone());
|
||||
|
||||
app = app
|
||||
.merge(collab::api::events::router())
|
||||
@@ -302,7 +300,10 @@ async fn handle_liveness_probe(
|
||||
}
|
||||
|
||||
if let Some(llm_state) = llm_state {
|
||||
llm_state.db.list_providers().await?;
|
||||
llm_state
|
||||
.db
|
||||
.get_active_user_count(chrono::Utc::now())
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok("ok".to_string())
|
||||
|
||||
@@ -478,7 +478,6 @@ impl Server {
|
||||
.add_request_handler(user_handler(
|
||||
forward_read_only_project_request::<proto::SearchProject>,
|
||||
))
|
||||
.add_request_handler(user_handler(forward_find_search_candidates_request))
|
||||
.add_request_handler(user_handler(
|
||||
forward_read_only_project_request::<proto::GetDocumentHighlights>,
|
||||
))
|
||||
@@ -2944,59 +2943,6 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn forward_find_search_candidates_request(
|
||||
request: proto::FindSearchCandidates,
|
||||
response: Response<proto::FindSearchCandidates>,
|
||||
session: UserSession,
|
||||
) -> Result<()> {
|
||||
let project_id = ProjectId::from_proto(request.remote_entity_id());
|
||||
let host_connection_id = session
|
||||
.db()
|
||||
.await
|
||||
.host_for_read_only_project_request(project_id, session.connection_id, session.user_id())
|
||||
.await?;
|
||||
|
||||
let host_version = session
|
||||
.connection_pool()
|
||||
.await
|
||||
.connection(host_connection_id)
|
||||
.map(|c| c.zed_version);
|
||||
|
||||
if host_version.is_some_and(|host_version| host_version < ZedVersion::with_search_candidates())
|
||||
{
|
||||
let query = request.query.ok_or_else(|| anyhow!("missing query"))?;
|
||||
let search = proto::SearchProject {
|
||||
project_id: project_id.to_proto(),
|
||||
query: query.query,
|
||||
regex: query.regex,
|
||||
whole_word: query.whole_word,
|
||||
case_sensitive: query.case_sensitive,
|
||||
files_to_include: query.files_to_include,
|
||||
files_to_exclude: query.files_to_exclude,
|
||||
include_ignored: query.include_ignored,
|
||||
};
|
||||
|
||||
let payload = session
|
||||
.peer
|
||||
.forward_request(session.connection_id, host_connection_id, search)
|
||||
.await?;
|
||||
return response.send(proto::FindSearchCandidatesResponse {
|
||||
buffer_ids: payload
|
||||
.locations
|
||||
.into_iter()
|
||||
.map(|loc| loc.buffer_id)
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
|
||||
let payload = session
|
||||
.peer
|
||||
.forward_request(session.connection_id, host_connection_id, request)
|
||||
.await?;
|
||||
response.send(payload)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// forward a project request to the dev server. Only allowed
|
||||
/// if it's your dev server.
|
||||
async fn forward_project_request_for_owner<T>(
|
||||
|
||||
@@ -42,10 +42,6 @@ impl ZedVersion {
|
||||
pub fn with_list_directory() -> ZedVersion {
|
||||
ZedVersion(SemanticVersion::new(0, 145, 0))
|
||||
}
|
||||
|
||||
pub fn with_search_candidates() -> ZedVersion {
|
||||
ZedVersion(SemanticVersion::new(0, 151, 0))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VersionedMessage {
|
||||
|
||||
@@ -127,7 +127,7 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
||||
let user = db
|
||||
.get_or_create_user_by_github_account(
|
||||
&github_user.login,
|
||||
github_user.id,
|
||||
Some(github_user.id),
|
||||
github_user.email.as_deref(),
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -168,7 +168,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
server
|
||||
.app_state
|
||||
.db
|
||||
.get_or_create_user_by_github_account("user_b", 100, None, Some(Utc::now()), None)
|
||||
.get_or_create_user_by_github_account("user_b", Some(100), None, Some(Utc::now()), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -266,7 +266,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||
server
|
||||
.app_state
|
||||
.db
|
||||
.add_contributor("user_b", 100, None, Some(Utc::now()), None)
|
||||
.add_contributor("user_b", Some(100), None, Some(Utc::now()), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ use live_kit_client::MacOSDisplay;
|
||||
use lsp::LanguageServerId;
|
||||
use parking_lot::Mutex;
|
||||
use project::{
|
||||
search::SearchQuery, search::SearchResult, DiagnosticSummary, FormatTrigger, HoverBlockKind,
|
||||
Project, ProjectPath,
|
||||
search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath,
|
||||
SearchResult,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -15,7 +15,7 @@ use language::{
|
||||
use lsp::FakeLanguageServer;
|
||||
use pretty_assertions::assert_eq;
|
||||
use project::{
|
||||
search::SearchQuery, search::SearchResult, Project, ProjectPath, DEFAULT_COMPLETION_CONTEXT,
|
||||
search::SearchQuery, Project, ProjectPath, SearchResult, DEFAULT_COMPLETION_CONTEXT,
|
||||
};
|
||||
use rand::{
|
||||
distributions::{Alphanumeric, DistString},
|
||||
@@ -298,8 +298,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
continue;
|
||||
};
|
||||
let project_root_name = root_name_for_project(&project, cx);
|
||||
let is_local =
|
||||
project.read_with(cx, |project, _| project.is_local_or_ssh());
|
||||
let is_local = project.read_with(cx, |project, _| project.is_local());
|
||||
let worktree = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.worktrees(cx)
|
||||
@@ -335,7 +334,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
continue;
|
||||
};
|
||||
let project_root_name = root_name_for_project(&project, cx);
|
||||
let is_local = project.read_with(cx, |project, _| project.is_local_or_ssh());
|
||||
let is_local = project.read_with(cx, |project, _| project.is_local());
|
||||
|
||||
match rng.gen_range(0..100_u32) {
|
||||
// Manipulate an existing buffer
|
||||
@@ -1255,7 +1254,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
let buffers = client.buffers().clone();
|
||||
for (guest_project, guest_buffers) in &buffers {
|
||||
let project_id = if guest_project.read_with(client_cx, |project, _| {
|
||||
project.is_local_or_ssh() || project.is_disconnected()
|
||||
project.is_local() || project.is_disconnected()
|
||||
}) {
|
||||
continue;
|
||||
} else {
|
||||
@@ -1559,9 +1558,7 @@ async fn ensure_project_shared(
|
||||
let first_root_name = root_name_for_project(project, cx);
|
||||
let active_call = cx.read(ActiveCall::global);
|
||||
if active_call.read_with(cx, |call, _| call.room().is_some())
|
||||
&& project.read_with(cx, |project, _| {
|
||||
project.is_local_or_ssh() && !project.is_shared()
|
||||
})
|
||||
&& project.read_with(cx, |project, _| project.is_local() && !project.is_shared())
|
||||
{
|
||||
match active_call
|
||||
.update(cx, |call, cx| call.share_project(project.clone(), cx))
|
||||
|
||||
@@ -682,7 +682,6 @@ impl TestServer {
|
||||
supermaven_admin_api_key: None,
|
||||
qwen2_7b_api_key: None,
|
||||
qwen2_7b_api_url: None,
|
||||
user_backfiller_github_access_token: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::db::Database;
|
||||
use crate::executor::Executor;
|
||||
use crate::{AppState, Config};
|
||||
|
||||
pub fn spawn_user_backfiller(app_state: Arc<AppState>) {
|
||||
let Some(user_backfiller_github_access_token) =
|
||||
app_state.config.user_backfiller_github_access_token.clone()
|
||||
else {
|
||||
log::info!("no USER_BACKFILLER_GITHUB_ACCESS_TOKEN set; not spawning user backfiller");
|
||||
return;
|
||||
};
|
||||
|
||||
let executor = app_state.executor.clone();
|
||||
executor.spawn_detached({
|
||||
let executor = executor.clone();
|
||||
async move {
|
||||
let user_backfiller = UserBackfiller::new(
|
||||
app_state.config.clone(),
|
||||
user_backfiller_github_access_token,
|
||||
app_state.db.clone(),
|
||||
executor,
|
||||
);
|
||||
|
||||
log::info!("backfilling users");
|
||||
|
||||
user_backfiller
|
||||
.backfill_github_user_created_at()
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const GITHUB_REQUESTS_PER_HOUR_LIMIT: usize = 5_000;
|
||||
const SLEEP_DURATION_BETWEEN_USERS: std::time::Duration = std::time::Duration::from_millis(
|
||||
(GITHUB_REQUESTS_PER_HOUR_LIMIT as f64 / 60. / 60. * 1000.) as u64,
|
||||
);
|
||||
|
||||
struct UserBackfiller {
|
||||
config: Config,
|
||||
github_access_token: Arc<str>,
|
||||
db: Arc<Database>,
|
||||
http_client: reqwest::Client,
|
||||
executor: Executor,
|
||||
}
|
||||
|
||||
impl UserBackfiller {
|
||||
fn new(
|
||||
config: Config,
|
||||
github_access_token: Arc<str>,
|
||||
db: Arc<Database>,
|
||||
executor: Executor,
|
||||
) -> Self {
|
||||
Self {
|
||||
config,
|
||||
github_access_token,
|
||||
db,
|
||||
http_client: reqwest::Client::new(),
|
||||
executor,
|
||||
}
|
||||
}
|
||||
|
||||
async fn backfill_github_user_created_at(&self) -> Result<()> {
|
||||
let initial_channel_id = self.config.auto_join_channel_id;
|
||||
|
||||
let users_missing_github_user_created_at =
|
||||
self.db.get_users_missing_github_user_created_at().await?;
|
||||
|
||||
for user in users_missing_github_user_created_at {
|
||||
match self
|
||||
.fetch_github_user(&format!(
|
||||
"https://api.github.com/user/{}",
|
||||
user.github_user_id
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(github_user) => {
|
||||
self.db
|
||||
.get_or_create_user_by_github_account(
|
||||
&user.github_login,
|
||||
github_user.id,
|
||||
user.email_address.as_deref(),
|
||||
Some(github_user.created_at),
|
||||
initial_channel_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::info!("backfilled user: {}", user.github_login);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to fetch GitHub user {}: {err}", user.github_login);
|
||||
}
|
||||
}
|
||||
|
||||
self.executor.sleep(SLEEP_DURATION_BETWEEN_USERS).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_github_user(&self, url: &str) -> Result<GithubUser> {
|
||||
let response = self
|
||||
.http_client
|
||||
.get(url)
|
||||
.header(
|
||||
"authorization",
|
||||
format!("Bearer {}", self.github_access_token),
|
||||
)
|
||||
.header("user-agent", "zed")
|
||||
.send()
|
||||
.await
|
||||
.with_context(|| format!("failed to fetch '{url}'"))?;
|
||||
|
||||
let rate_limit_remaining = response
|
||||
.headers()
|
||||
.get("x-ratelimit-remaining")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<i32>().ok());
|
||||
let rate_limit_reset = response
|
||||
.headers()
|
||||
.get("x-ratelimit-reset")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<i64>().ok())
|
||||
.and_then(|value| DateTime::from_timestamp(value, 0));
|
||||
|
||||
if rate_limit_remaining == Some(0) {
|
||||
if let Some(reset_at) = rate_limit_reset {
|
||||
let now = Utc::now();
|
||||
if reset_at > now {
|
||||
let sleep_duration = reset_at - now;
|
||||
log::info!(
|
||||
"rate limit reached. Sleeping for {} seconds",
|
||||
sleep_duration.num_seconds()
|
||||
);
|
||||
self.executor.sleep(sleep_duration.to_std().unwrap()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let response = match response.error_for_status() {
|
||||
Ok(response) => response,
|
||||
Err(err) => return Err(anyhow!("failed to fetch GitHub user: {err}")),
|
||||
};
|
||||
|
||||
response
|
||||
.json()
|
||||
.await
|
||||
.with_context(|| format!("failed to deserialize GitHub user from '{url}'"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct GithubUser {
|
||||
id: i32,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -42,6 +42,7 @@ futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
lazy_static.workspace = true
|
||||
menu.workspace = true
|
||||
notifications.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
@@ -12,10 +12,11 @@ use language::{
|
||||
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
|
||||
LanguageServerId, ToOffset,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use project::{search::SearchQuery, Completion};
|
||||
use settings::Settings;
|
||||
use std::{ops::Range, sync::Arc, sync::LazyLock, time::Duration};
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, TextSize};
|
||||
|
||||
@@ -23,17 +24,17 @@ use crate::panel_settings::MessageEditorSettings;
|
||||
|
||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
static MENTIONS_SEARCH: LazyLock<SearchQuery> = LazyLock::new(|| {
|
||||
SearchQuery::regex(
|
||||
lazy_static! {
|
||||
static ref MENTIONS_SEARCH: SearchQuery = SearchQuery::regex(
|
||||
"@[-_\\w]+",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default()
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub struct MessageEditor {
|
||||
pub editor: View<Editor>,
|
||||
@@ -398,8 +399,8 @@ impl MessageEditor {
|
||||
end_anchor: Anchor,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<(Anchor, String, &'static [StringMatchCandidate])> {
|
||||
static EMOJI_FUZZY_MATCH_CANDIDATES: LazyLock<Vec<StringMatchCandidate>> =
|
||||
LazyLock::new(|| {
|
||||
lazy_static! {
|
||||
static ref EMOJI_FUZZY_MATCH_CANDIDATES: Vec<StringMatchCandidate> = {
|
||||
let emojis = emojis::iter()
|
||||
.flat_map(|s| s.shortcodes())
|
||||
.map(|emoji| StringMatchCandidate {
|
||||
@@ -409,7 +410,8 @@ impl MessageEditor {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
emojis
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
|
||||
@@ -1395,22 +1395,15 @@ impl CollabPanel {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn reset_filter_editor_text(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
self.filter_editor.update(cx, |editor, cx| {
|
||||
if editor.buffer().read(cx).len(cx) > 0 {
|
||||
editor.set_text("", cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
if self.take_editing_state(cx) {
|
||||
cx.focus_view(&self.filter_editor);
|
||||
} else if !self.reset_filter_editor_text(cx) {
|
||||
self.focus_handle.focus(cx);
|
||||
} else {
|
||||
self.filter_editor.update(cx, |editor, cx| {
|
||||
if editor.buffer().read(cx).len(cx) > 0 {
|
||||
editor.set_text("", cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if self.context_menu.is_some() {
|
||||
|
||||
@@ -1060,7 +1060,7 @@ mod tests {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
|
||||
});
|
||||
editor.refresh_inline_completion(true, false, cx);
|
||||
editor.next_inline_completion(&Default::default(), cx);
|
||||
});
|
||||
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
@@ -1070,7 +1070,7 @@ mod tests {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
|
||||
});
|
||||
editor.refresh_inline_completion(true, false, cx);
|
||||
editor.next_inline_completion(&Default::default(), cx);
|
||||
});
|
||||
|
||||
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||
|
||||
@@ -19,6 +19,7 @@ test-support = []
|
||||
anyhow.workspace = true
|
||||
gpui.workspace = true
|
||||
indoc.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
|
||||
@@ -6,6 +6,7 @@ pub use anyhow;
|
||||
use anyhow::Context;
|
||||
use gpui::AppContext;
|
||||
pub use indoc::indoc;
|
||||
pub use lazy_static;
|
||||
pub use paths::database_dir;
|
||||
pub use smol;
|
||||
pub use sqlez;
|
||||
@@ -16,11 +17,9 @@ pub use release_channel::RELEASE_CHANNEL;
|
||||
use sqlez::domain::Migrator;
|
||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||
use sqlez_macros::sql;
|
||||
use std::env;
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::LazyLock;
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const CONNECTION_INITIALIZE_QUERY: &str = sql!(
|
||||
@@ -38,10 +37,10 @@ const FALLBACK_DB_NAME: &str = "FALLBACK_MEMORY_DB";
|
||||
|
||||
const DB_FILE_NAME: &str = "db.sqlite";
|
||||
|
||||
pub static ZED_STATELESS: LazyLock<bool> =
|
||||
LazyLock::new(|| env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()));
|
||||
|
||||
pub static ALL_FILE_DB_FAILED: LazyLock<AtomicBool> = LazyLock::new(|| AtomicBool::new(false));
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty());
|
||||
pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
/// Open or create a database at the given directory path.
|
||||
/// This will retry a couple times if there are failures. If opening fails once, the db directory
|
||||
@@ -139,16 +138,15 @@ macro_rules! define_connection {
|
||||
}
|
||||
}
|
||||
|
||||
use std::sync::LazyLock;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub static $id: LazyLock<$t> = LazyLock::new(|| {
|
||||
$t($crate::smol::block_on($crate::open_test_db(stringify!($id))))
|
||||
});
|
||||
$crate::lazy_static::lazy_static! {
|
||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_test_db(stringify!($id))));
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
pub static $id: LazyLock<$t> = LazyLock::new(|| {
|
||||
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
|
||||
});
|
||||
$crate::lazy_static::lazy_static! {
|
||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
|
||||
}
|
||||
};
|
||||
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
|
||||
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
|
||||
@@ -172,14 +170,14 @@ macro_rules! define_connection {
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||
$t($crate::smol::block_on($crate::open_test_db(stringify!($id))))
|
||||
});
|
||||
$crate::lazy_static::lazy_static! {
|
||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_test_db(stringify!($id))));
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
|
||||
});
|
||||
$crate::lazy_static::lazy_static! {
|
||||
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "docs_preprocessor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
mdbook = "0.4.40"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
regex.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/docs_preprocessor.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "docs_preprocessor"
|
||||
path = "src/main.rs"
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,93 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use mdbook::book::{Book, BookItem};
|
||||
use mdbook::errors::Error;
|
||||
use mdbook::preprocess::{Preprocessor, PreprocessorContext as MdBookContext};
|
||||
use settings::KeymapFile;
|
||||
use std::sync::Arc;
|
||||
use util::asset_str;
|
||||
|
||||
mod templates;
|
||||
|
||||
use templates::{ActionTemplate, KeybindingTemplate, Template};
|
||||
|
||||
pub struct PreprocessorContext {
|
||||
macos_keymap: Arc<KeymapFile>,
|
||||
linux_keymap: Arc<KeymapFile>,
|
||||
}
|
||||
|
||||
impl PreprocessorContext {
|
||||
pub fn new() -> Result<Self> {
|
||||
let macos_keymap = Arc::new(load_keymap("keymaps/default-macos.json")?);
|
||||
let linux_keymap = Arc::new(load_keymap("keymaps/default-linux.json")?);
|
||||
Ok(Self {
|
||||
macos_keymap,
|
||||
linux_keymap,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_binding(&self, os: &str, action: &str) -> Option<String> {
|
||||
let keymap = match os {
|
||||
"macos" => &self.macos_keymap,
|
||||
"linux" => &self.linux_keymap,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
keymap.blocks().iter().find_map(|block| {
|
||||
block.bindings().iter().find_map(|(keystroke, a)| {
|
||||
if a.to_string() == action {
|
||||
Some(keystroke.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn load_keymap(asset_path: &str) -> Result<KeymapFile> {
|
||||
let content = asset_str::<settings::SettingsAssets>(asset_path);
|
||||
KeymapFile::parse(content.as_ref())
|
||||
}
|
||||
|
||||
pub struct ZedDocsPreprocessor {
|
||||
context: PreprocessorContext,
|
||||
templates: Vec<Box<dyn Template>>,
|
||||
}
|
||||
|
||||
impl ZedDocsPreprocessor {
|
||||
pub fn new() -> Result<Self> {
|
||||
let context = PreprocessorContext::new()?;
|
||||
let templates: Vec<Box<dyn Template>> = vec![
|
||||
Box::new(KeybindingTemplate::new()),
|
||||
Box::new(ActionTemplate::new()),
|
||||
];
|
||||
Ok(Self { context, templates })
|
||||
}
|
||||
|
||||
fn process_content(&self, content: &str) -> String {
|
||||
let mut processed = content.to_string();
|
||||
for template in &self.templates {
|
||||
processed = template.process(&self.context, &processed);
|
||||
}
|
||||
processed
|
||||
}
|
||||
}
|
||||
|
||||
impl Preprocessor for ZedDocsPreprocessor {
|
||||
fn name(&self) -> &str {
|
||||
"zed-docs-preprocessor"
|
||||
}
|
||||
|
||||
fn run(&self, _ctx: &MdBookContext, mut book: Book) -> Result<Book, Error> {
|
||||
book.for_each_mut(|item| {
|
||||
if let BookItem::Chapter(chapter) = item {
|
||||
chapter.content = self.process_content(&chapter.content);
|
||||
}
|
||||
});
|
||||
Ok(book)
|
||||
}
|
||||
|
||||
fn supports_renderer(&self, renderer: &str) -> bool {
|
||||
renderer != "not-supported"
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use docs_preprocessor::ZedDocsPreprocessor;
|
||||
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
|
||||
use std::io::{self, Read};
|
||||
use std::process;
|
||||
|
||||
pub fn make_app() -> Command {
|
||||
Command::new("zed-docs-preprocessor")
|
||||
.about("Preprocesses Zed Docs content to provide rich action & keybinding support and more")
|
||||
.subcommand(
|
||||
Command::new("supports")
|
||||
.arg(Arg::new("renderer").required(true))
|
||||
.about("Check whether a renderer is supported by this preprocessor"),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let matches = make_app().get_matches();
|
||||
|
||||
let preprocessor =
|
||||
ZedDocsPreprocessor::new().context("Failed to create ZedDocsPreprocessor")?;
|
||||
|
||||
if let Some(sub_args) = matches.subcommand_matches("supports") {
|
||||
handle_supports(&preprocessor, sub_args);
|
||||
} else {
|
||||
handle_preprocessing(&preprocessor)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<()> {
|
||||
let mut stdin = io::stdin();
|
||||
let mut input = String::new();
|
||||
stdin.read_to_string(&mut input)?;
|
||||
|
||||
let (ctx, book) = CmdPreprocessor::parse_input(input.as_bytes())?;
|
||||
|
||||
let processed_book = pre.run(&ctx, book)?;
|
||||
|
||||
serde_json::to_writer(io::stdout(), &processed_book)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
|
||||
let renderer = sub_args
|
||||
.get_one::<String>("renderer")
|
||||
.expect("Required argument");
|
||||
let supported = pre.supports_renderer(renderer);
|
||||
|
||||
if supported {
|
||||
process::exit(0);
|
||||
} else {
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
use crate::PreprocessorContext;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod action;
|
||||
mod keybinding;
|
||||
|
||||
pub use action::*;
|
||||
pub use keybinding::*;
|
||||
|
||||
pub trait Template {
|
||||
fn key(&self) -> &'static str;
|
||||
fn regex(&self) -> Regex;
|
||||
fn parse_args(&self, args: &str) -> HashMap<String, String>;
|
||||
fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> String;
|
||||
|
||||
fn process(&self, context: &PreprocessorContext, content: &str) -> String {
|
||||
self.regex()
|
||||
.replace_all(content, |caps: ®ex::Captures| {
|
||||
let args = self.parse_args(&caps[1]);
|
||||
self.render(context, &args)
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
use crate::PreprocessorContext;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::Template;
|
||||
|
||||
pub struct ActionTemplate;
|
||||
|
||||
impl ActionTemplate {
|
||||
pub fn new() -> Self {
|
||||
ActionTemplate
|
||||
}
|
||||
}
|
||||
|
||||
impl Template for ActionTemplate {
|
||||
fn key(&self) -> &'static str {
|
||||
"action"
|
||||
}
|
||||
|
||||
fn regex(&self) -> Regex {
|
||||
Regex::new(&format!(r"\{{#{}(.*?)\}}", self.key())).unwrap()
|
||||
}
|
||||
|
||||
fn parse_args(&self, args: &str) -> HashMap<String, String> {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("name".to_string(), args.trim().to_string());
|
||||
map
|
||||
}
|
||||
|
||||
fn render(&self, _context: &PreprocessorContext, args: &HashMap<String, String>) -> String {
|
||||
let name = args.get("name").map(String::as_str).unwrap_or_default();
|
||||
|
||||
let formatted_name = name
|
||||
.chars()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
if i > 0 && c.is_uppercase() {
|
||||
format!(" {}", c.to_lowercase())
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<String>()
|
||||
.trim()
|
||||
.to_string()
|
||||
.replace("::", ":");
|
||||
|
||||
format!("<code class=\"hljs\">{}</code>", formatted_name)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use crate::PreprocessorContext;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::Template;
|
||||
|
||||
pub struct KeybindingTemplate;
|
||||
|
||||
impl KeybindingTemplate {
|
||||
pub fn new() -> Self {
|
||||
KeybindingTemplate
|
||||
}
|
||||
}
|
||||
|
||||
impl Template for KeybindingTemplate {
|
||||
fn key(&self) -> &'static str {
|
||||
"kb"
|
||||
}
|
||||
|
||||
fn regex(&self) -> Regex {
|
||||
Regex::new(&format!(r"\{{#{}(.*?)\}}", self.key())).unwrap()
|
||||
}
|
||||
|
||||
fn parse_args(&self, args: &str) -> HashMap<String, String> {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("action".to_string(), args.trim().to_string());
|
||||
map
|
||||
}
|
||||
|
||||
fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> String {
|
||||
let action = args.get("action").map(String::as_str).unwrap_or("");
|
||||
let macos_binding = context.find_binding("macos", action).unwrap_or_default();
|
||||
let linux_binding = context.find_binding("linux", action).unwrap_or_default();
|
||||
format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ http_client.workspace = true
|
||||
indoc.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
lazy_static.workspace = true
|
||||
linkify.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
|
||||
@@ -199,7 +199,6 @@ gpui::actions!(
|
||||
CopyHighlightJson,
|
||||
CopyPath,
|
||||
CopyPermalinkToLine,
|
||||
CopyFileLocation,
|
||||
CopyRelativePath,
|
||||
Cut,
|
||||
CutToEndOfLine,
|
||||
@@ -263,7 +262,6 @@ gpui::actions!(
|
||||
OpenExcerptsSplit,
|
||||
OpenPermalinkToLine,
|
||||
OpenUrl,
|
||||
OpenFile,
|
||||
Outdent,
|
||||
PageDown,
|
||||
PageUp,
|
||||
@@ -308,7 +306,6 @@ gpui::actions!(
|
||||
SortLinesCaseInsensitive,
|
||||
SortLinesCaseSensitive,
|
||||
SplitSelectionIntoLines,
|
||||
SwitchSourceHeader,
|
||||
Tab,
|
||||
TabPrev,
|
||||
ToggleAutoSignatureHelp,
|
||||
@@ -318,7 +315,6 @@ gpui::actions!(
|
||||
ToggleHunkDiff,
|
||||
ToggleInlayHints,
|
||||
ToggleLineNumbers,
|
||||
ToggleRelativeLineNumbers,
|
||||
ToggleIndentGuides,
|
||||
ToggleSoftWrap,
|
||||
ToggleTabBar,
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use gpui::{View, ViewContext, WindowContext};
|
||||
use language::Language;
|
||||
use url::Url;
|
||||
|
||||
use crate::lsp_ext::find_specific_language_server_in_selection;
|
||||
|
||||
use crate::{element::register_action, Editor, SwitchSourceHeader};
|
||||
|
||||
static CLANGD_SERVER_NAME: &str = "clangd";
|
||||
|
||||
fn is_c_language(language: &Language) -> bool {
|
||||
return language.name().as_ref() == "C++" || language.name().as_ref() == "C";
|
||||
}
|
||||
|
||||
pub fn switch_source_header(
|
||||
editor: &mut Editor,
|
||||
_: &SwitchSourceHeader,
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) {
|
||||
let Some(project) = &editor.project else {
|
||||
return;
|
||||
};
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some((_, _, server_to_query, buffer)) =
|
||||
find_specific_language_server_in_selection(&editor, cx, &is_c_language, CLANGD_SERVER_NAME)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let project = project.clone();
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let source_file = buffer_snapshot
|
||||
.file()
|
||||
.unwrap()
|
||||
.file_name(cx)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
let switch_source_header_task = project.update(cx, |project, cx| {
|
||||
project.request_lsp(
|
||||
buffer,
|
||||
project::LanguageServerToQuery::Other(server_to_query),
|
||||
project::lsp_ext_command::SwitchSourceHeader,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.spawn(|_editor, mut cx| async move {
|
||||
let switch_source_header = switch_source_header_task
|
||||
.await
|
||||
.with_context(|| format!("Switch source/header LSP request for path \"{}\" failed", source_file))?;
|
||||
if switch_source_header.0.is_empty() {
|
||||
log::info!("Clangd returned an empty string when requesting to switch source/header from \"{}\"", source_file);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let goto = Url::parse(&switch_source_header.0).with_context(|| {
|
||||
format!(
|
||||
"Parsing URL \"{}\" returned from switch source/header failed",
|
||||
switch_source_header.0
|
||||
)
|
||||
})?;
|
||||
|
||||
workspace
|
||||
.update(&mut cx, |workspace, view_cx| {
|
||||
workspace.open_abs_path(PathBuf::from(goto.path()), false, view_cx)
|
||||
})
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Switch source/header could not open \"{}\" in workspace",
|
||||
goto.path()
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.map(|_| ())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
||||
if editor.update(cx, |e, cx| {
|
||||
find_specific_language_server_in_selection(e, cx, &is_c_language, CLANGD_SERVER_NAME)
|
||||
.is_some()
|
||||
}) {
|
||||
register_action(editor, cx, switch_source_header);
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,8 @@ pub enum FoldStatus {
|
||||
|
||||
pub type RenderFoldToggle = Arc<dyn Fn(FoldStatus, &mut WindowContext) -> AnyElement>;
|
||||
|
||||
const UNNECESSARY_CODE_FADE: f32 = 0.3;
|
||||
|
||||
pub trait ToDisplayPoint {
|
||||
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
|
||||
}
|
||||
@@ -689,7 +691,7 @@ impl DisplaySnapshot {
|
||||
let mut diagnostic_highlight = HighlightStyle::default();
|
||||
|
||||
if chunk.is_unnecessary {
|
||||
diagnostic_highlight.fade_out = Some(editor_style.unnecessary_code_fade);
|
||||
diagnostic_highlight.fade_out = Some(UNNECESSARY_CODE_FADE);
|
||||
}
|
||||
|
||||
if let Some(severity) = chunk.diagnostic_severity {
|
||||
|
||||
@@ -783,13 +783,11 @@ impl<'a> BlockMapWriter<'a> {
|
||||
&mut self,
|
||||
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
|
||||
) -> Vec<CustomBlockId> {
|
||||
let blocks = blocks.into_iter();
|
||||
let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
|
||||
let mut ids = Vec::new();
|
||||
let mut edits = Patch::default();
|
||||
let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
|
||||
let buffer = wrap_snapshot.buffer_snapshot();
|
||||
|
||||
let mut previous_wrap_row_range: Option<Range<u32>> = None;
|
||||
for block in blocks {
|
||||
let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
|
||||
ids.push(id);
|
||||
@@ -799,18 +797,11 @@ impl<'a> BlockMapWriter<'a> {
|
||||
let wrap_row = wrap_snapshot
|
||||
.make_wrap_point(Point::new(point.row, 0), Bias::Left)
|
||||
.row();
|
||||
let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
|
||||
let end_row = wrap_snapshot
|
||||
.next_row_boundary(WrapPoint::new(wrap_row, 0))
|
||||
.unwrap_or(wrap_snapshot.max_point().row() + 1);
|
||||
|
||||
let (start_row, end_row) = {
|
||||
previous_wrap_row_range.take_if(|range| !range.contains(&wrap_row));
|
||||
let range = previous_wrap_row_range.get_or_insert_with(|| {
|
||||
let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
|
||||
let end_row = wrap_snapshot
|
||||
.next_row_boundary(WrapPoint::new(wrap_row, 0))
|
||||
.unwrap_or(wrap_snapshot.max_point().row() + 1);
|
||||
start_row..end_row
|
||||
});
|
||||
(range.start, range.end)
|
||||
};
|
||||
let block_ix = match self
|
||||
.0
|
||||
.custom_blocks
|
||||
@@ -890,7 +881,6 @@ impl<'a> BlockMapWriter<'a> {
|
||||
let buffer = wrap_snapshot.buffer_snapshot();
|
||||
let mut edits = Patch::default();
|
||||
let mut last_block_buffer_row = None;
|
||||
let mut previous_wrap_row_range: Option<Range<u32>> = None;
|
||||
self.0.custom_blocks.retain(|block| {
|
||||
if block_ids.contains(&block.id) {
|
||||
let buffer_row = block.position.to_point(buffer).row;
|
||||
@@ -899,32 +889,21 @@ impl<'a> BlockMapWriter<'a> {
|
||||
let wrap_row = wrap_snapshot
|
||||
.make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
|
||||
.row();
|
||||
let (start_row, end_row) = {
|
||||
previous_wrap_row_range.take_if(|range| !range.contains(&wrap_row));
|
||||
let range = previous_wrap_row_range.get_or_insert_with(|| {
|
||||
let start_row =
|
||||
wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
|
||||
let end_row = wrap_snapshot
|
||||
.next_row_boundary(WrapPoint::new(wrap_row, 0))
|
||||
.unwrap_or(wrap_snapshot.max_point().row() + 1);
|
||||
start_row..end_row
|
||||
});
|
||||
(range.start, range.end)
|
||||
};
|
||||
|
||||
let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
|
||||
let end_row = wrap_snapshot
|
||||
.next_row_boundary(WrapPoint::new(wrap_row, 0))
|
||||
.unwrap_or(wrap_snapshot.max_point().row() + 1);
|
||||
edits.push(Edit {
|
||||
old: start_row..end_row,
|
||||
new: start_row..end_row,
|
||||
})
|
||||
}
|
||||
self.0.custom_blocks_by_id.remove(&block.id);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
self.0
|
||||
.custom_blocks_by_id
|
||||
.retain(|id, _| !block_ids.contains(id));
|
||||
self.0.sync(wrap_snapshot, edits);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ use super::{
|
||||
};
|
||||
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
|
||||
use language::{Chunk, Point};
|
||||
use lazy_static::lazy_static;
|
||||
use multi_buffer::MultiBufferSnapshot;
|
||||
use smol::future::yield_now;
|
||||
use std::sync::LazyLock;
|
||||
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
|
||||
use sum_tree::{Bias, Cursor, SumTree};
|
||||
use text::Patch;
|
||||
@@ -887,12 +887,14 @@ impl Transform {
|
||||
}
|
||||
|
||||
fn wrap(indent: u32) -> Self {
|
||||
static WRAP_TEXT: LazyLock<String> = LazyLock::new(|| {
|
||||
let mut wrap_text = String::new();
|
||||
wrap_text.push('\n');
|
||||
wrap_text.extend((0..LineWrapper::MAX_INDENT as usize).map(|_| ' '));
|
||||
wrap_text
|
||||
});
|
||||
lazy_static! {
|
||||
static ref WRAP_TEXT: String = {
|
||||
let mut wrap_text = String::new();
|
||||
wrap_text.push('\n');
|
||||
wrap_text.extend((0..LineWrapper::MAX_INDENT as usize).map(|_| ' '));
|
||||
wrap_text
|
||||
};
|
||||
}
|
||||
|
||||
Self {
|
||||
summary: TransformSummary {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
pub mod actions;
|
||||
mod blame_entry_tooltip;
|
||||
mod blink_manager;
|
||||
mod clangd_ext;
|
||||
mod debounced_delay;
|
||||
pub mod display_map;
|
||||
mod editor_settings;
|
||||
@@ -31,7 +30,6 @@ mod inlay_hint_cache;
|
||||
mod inline_completion_provider;
|
||||
pub mod items;
|
||||
mod linked_editing_ranges;
|
||||
mod lsp_ext;
|
||||
mod mouse_context_menu;
|
||||
pub mod movement;
|
||||
mod persistence;
|
||||
@@ -59,7 +57,7 @@ use convert_case::{Case, Casing};
|
||||
use debounced_delay::DebouncedDelay;
|
||||
use display_map::*;
|
||||
pub use display_map::{DisplayPoint, FoldPlaceholder};
|
||||
pub use editor_settings::{CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine};
|
||||
pub use editor_settings::{CurrentLineHighlight, EditorSettings};
|
||||
pub use editor_settings_controls::*;
|
||||
use element::LineWithInvisibles;
|
||||
pub use element::{
|
||||
@@ -99,7 +97,7 @@ use language::{point_to_lsp, BufferRow, Runnable, RunnableRange};
|
||||
use linked_editing_ranges::refresh_linked_ranges;
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
|
||||
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
|
||||
pub use lsp::CompletionContext;
|
||||
use lsp::{
|
||||
CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity, InsertTextFormat,
|
||||
@@ -298,8 +296,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace.register_action(Editor::new_file);
|
||||
workspace.register_action(Editor::new_file_vertical);
|
||||
workspace.register_action(Editor::new_file_horizontal);
|
||||
workspace.register_action(Editor::new_file_in_direction);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
@@ -375,7 +372,6 @@ pub enum SoftWrap {
|
||||
PreferLine,
|
||||
EditorWidth,
|
||||
Column(u32),
|
||||
Bounded(u32),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -388,7 +384,6 @@ pub struct EditorStyle {
|
||||
pub status: StatusColors,
|
||||
pub inlay_hints_style: HighlightStyle,
|
||||
pub suggestions_style: HighlightStyle,
|
||||
pub unnecessary_code_fade: f32,
|
||||
}
|
||||
|
||||
impl Default for EditorStyle {
|
||||
@@ -405,7 +400,6 @@ impl Default for EditorStyle {
|
||||
status: StatusColors::dark(),
|
||||
inlay_hints_style: HighlightStyle::default(),
|
||||
suggestions_style: HighlightStyle::default(),
|
||||
unnecessary_code_fade: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,14 +460,6 @@ struct ResolvedTasks {
|
||||
struct MultiBufferOffset(usize);
|
||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||
struct BufferOffset(usize);
|
||||
|
||||
// Addons allow storing per-editor state in other crates (e.g. Vim)
|
||||
pub trait Addon: 'static {
|
||||
fn extend_key_context(&self, _: &mut KeyContext, _: &AppContext) {}
|
||||
|
||||
fn to_any(&self) -> &dyn std::any::Any;
|
||||
}
|
||||
|
||||
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
|
||||
///
|
||||
/// See the [module level documentation](self) for more information.
|
||||
@@ -512,7 +498,6 @@ pub struct Editor {
|
||||
show_breadcrumbs: bool,
|
||||
show_gutter: bool,
|
||||
show_line_numbers: Option<bool>,
|
||||
use_relative_line_numbers: Option<bool>,
|
||||
show_git_diff_gutter: Option<bool>,
|
||||
show_code_actions: Option<bool>,
|
||||
show_runnables: Option<bool>,
|
||||
@@ -546,6 +531,7 @@ pub struct Editor {
|
||||
collapse_matches: bool,
|
||||
autoindent_mode: Option<AutoindentMode>,
|
||||
workspace: Option<(WeakView<Workspace>, Option<WorkspaceId>)>,
|
||||
keymap_context_layers: BTreeMap<TypeId, KeyContext>,
|
||||
input_enabled: bool,
|
||||
use_modal_editing: bool,
|
||||
read_only: bool,
|
||||
@@ -563,6 +549,7 @@ pub struct Editor {
|
||||
_subscriptions: Vec<Subscription>,
|
||||
pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
|
||||
gutter_dimensions: GutterDimensions,
|
||||
pub vim_replace_map: HashMap<Range<usize>, String>,
|
||||
style: Option<EditorStyle>,
|
||||
next_editor_action_id: EditorActionId,
|
||||
editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
|
||||
@@ -592,7 +579,6 @@ pub struct Editor {
|
||||
breadcrumb_header: Option<String>,
|
||||
focused_block: Option<FocusedBlock>,
|
||||
next_scroll_position: NextScrollCursorCenterTopBottom,
|
||||
addons: HashMap<TypeId, Box<dyn Addon>>,
|
||||
_scroll_cursor_center_top_bottom_task: Task<()>,
|
||||
}
|
||||
|
||||
@@ -1854,7 +1840,6 @@ impl Editor {
|
||||
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
|
||||
show_gutter: mode == EditorMode::Full,
|
||||
show_line_numbers: None,
|
||||
use_relative_line_numbers: None,
|
||||
show_git_diff_gutter: None,
|
||||
show_code_actions: None,
|
||||
show_runnables: None,
|
||||
@@ -1888,6 +1873,7 @@ impl Editor {
|
||||
autoindent_mode: Some(AutoindentMode::EachLine),
|
||||
collapse_matches: false,
|
||||
workspace: None,
|
||||
keymap_context_layers: Default::default(),
|
||||
input_enabled: true,
|
||||
use_modal_editing: mode == EditorMode::Full,
|
||||
read_only: false,
|
||||
@@ -1912,6 +1898,7 @@ impl Editor {
|
||||
hovered_cursors: Default::default(),
|
||||
next_editor_action_id: EditorActionId::default(),
|
||||
editor_actions: Rc::default(),
|
||||
vim_replace_map: Default::default(),
|
||||
show_inline_completions: mode == EditorMode::Full,
|
||||
custom_context_menu: None,
|
||||
show_git_blame_gutter: false,
|
||||
@@ -1950,7 +1937,6 @@ impl Editor {
|
||||
breadcrumb_header: None,
|
||||
focused_block: None,
|
||||
next_scroll_position: NextScrollCursorCenterTopBottom::default(),
|
||||
addons: HashMap::default(),
|
||||
_scroll_cursor_center_top_bottom_task: Task::ready(()),
|
||||
};
|
||||
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
||||
@@ -1973,13 +1959,13 @@ impl Editor {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn mouse_menu_is_focused(&self, cx: &WindowContext) -> bool {
|
||||
pub fn mouse_menu_is_focused(&self, cx: &mut WindowContext) -> bool {
|
||||
self.mouse_context_menu
|
||||
.as_ref()
|
||||
.is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(cx))
|
||||
}
|
||||
|
||||
fn key_context(&self, cx: &ViewContext<Self>) -> KeyContext {
|
||||
fn key_context(&self, cx: &AppContext) -> KeyContext {
|
||||
let mut key_context = KeyContext::new_with_defaults();
|
||||
key_context.add("Editor");
|
||||
let mode = match self.mode {
|
||||
@@ -2010,13 +1996,8 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
// Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
|
||||
if !self.focus_handle(cx).contains_focused(cx)
|
||||
|| (self.is_focused(cx) || self.mouse_menu_is_focused(cx))
|
||||
{
|
||||
for addon in self.addons.values() {
|
||||
addon.extend_key_context(&mut key_context, cx)
|
||||
}
|
||||
for layer in self.keymap_context_layers.values() {
|
||||
key_context.extend(layer);
|
||||
}
|
||||
|
||||
if let Some(extension) = self
|
||||
@@ -2072,29 +2053,14 @@ impl Editor {
|
||||
})
|
||||
}
|
||||
|
||||
fn new_file_vertical(
|
||||
pub fn new_file_in_direction(
|
||||
workspace: &mut Workspace,
|
||||
_: &workspace::NewFileSplitVertical,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), cx)
|
||||
}
|
||||
|
||||
fn new_file_horizontal(
|
||||
workspace: &mut Workspace,
|
||||
_: &workspace::NewFileSplitHorizontal,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), cx)
|
||||
}
|
||||
|
||||
fn new_file_in_direction(
|
||||
workspace: &mut Workspace,
|
||||
direction: SplitDirection,
|
||||
action: &workspace::NewFileInDirection,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let project = workspace.project().clone();
|
||||
let create = project.update(cx, |project, cx| project.create_buffer(cx));
|
||||
let direction = action.0;
|
||||
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let buffer = create.await?;
|
||||
@@ -2220,7 +2186,7 @@ impl Editor {
|
||||
}),
|
||||
provider: Arc::new(provider),
|
||||
});
|
||||
self.refresh_inline_completion(false, false, cx);
|
||||
self.refresh_inline_completion(false, cx);
|
||||
}
|
||||
|
||||
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
|
||||
@@ -2273,6 +2239,21 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_keymap_context_layer<Tag: 'static>(
|
||||
&mut self,
|
||||
context: KeyContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.keymap_context_layers
|
||||
.insert(TypeId::of::<Tag>(), context);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.keymap_context_layers.remove(&TypeId::of::<Tag>());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_input_enabled(&mut self, input_enabled: bool) {
|
||||
self.input_enabled = input_enabled;
|
||||
}
|
||||
@@ -3018,17 +2999,6 @@ impl Editor {
|
||||
if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
|
||||
continue;
|
||||
}
|
||||
if self.selections.disjoint_anchor_ranges().iter().any(|s| {
|
||||
if s.start.buffer_id != selection.start.buffer_id
|
||||
|| s.end.buffer_id != selection.end.buffer_id
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
|
||||
&& TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
let start = buffer_snapshot.anchor_after(start_offset);
|
||||
let end = buffer_snapshot.anchor_after(end_offset);
|
||||
linked_edits
|
||||
@@ -3349,7 +3319,7 @@ impl Editor {
|
||||
let trigger_in_words = !had_active_inline_completion;
|
||||
this.trigger_completion_on_input(&text, trigger_in_words, cx);
|
||||
linked_editing_ranges::refresh_linked_ranges(this, cx);
|
||||
this.refresh_inline_completion(true, false, cx);
|
||||
this.refresh_inline_completion(true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3535,7 +3505,7 @@ impl Editor {
|
||||
.collect();
|
||||
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
|
||||
this.refresh_inline_completion(true, false, cx);
|
||||
this.refresh_inline_completion(true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4083,7 +4053,7 @@ impl Editor {
|
||||
// hence we do LSP request & edit on host side only — add formats to host's history.
|
||||
let push_to_lsp_host_history = true;
|
||||
// If this is not the host, append its history with new edits.
|
||||
let push_to_client_history = project.read(cx).is_via_collab();
|
||||
let push_to_client_history = project.read(cx).is_remote();
|
||||
|
||||
let on_type_formatting = project.update(cx, |project, cx| {
|
||||
project.on_type_format(
|
||||
@@ -4423,7 +4393,7 @@ impl Editor {
|
||||
})
|
||||
}
|
||||
|
||||
this.refresh_inline_completion(true, false, cx);
|
||||
this.refresh_inline_completion(true, cx);
|
||||
});
|
||||
|
||||
let show_new_completions_on_confirm = completion
|
||||
@@ -4923,19 +4893,17 @@ impl Editor {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn refresh_inline_completion(
|
||||
fn refresh_inline_completion(
|
||||
&mut self,
|
||||
debounce: bool,
|
||||
user_requested: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<()> {
|
||||
let provider = self.inline_completion_provider()?;
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let (buffer, cursor_buffer_position) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
|
||||
if !user_requested
|
||||
&& (!self.show_inline_completions
|
||||
|| !provider.is_enabled(&buffer, cursor_buffer_position, cx))
|
||||
if !self.show_inline_completions
|
||||
|| !provider.is_enabled(&buffer, cursor_buffer_position, cx)
|
||||
{
|
||||
self.discard_inline_completion(false, cx);
|
||||
return None;
|
||||
@@ -4969,7 +4937,7 @@ impl Editor {
|
||||
|
||||
pub fn show_inline_completion(&mut self, _: &ShowInlineCompletion, cx: &mut ViewContext<Self>) {
|
||||
if !self.has_active_inline_completion(cx) {
|
||||
self.refresh_inline_completion(false, true, cx);
|
||||
self.refresh_inline_completion(false, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4998,7 +4966,7 @@ impl Editor {
|
||||
if self.has_active_inline_completion(cx) {
|
||||
self.cycle_inline_completion(Direction::Next, cx);
|
||||
} else {
|
||||
let is_copilot_disabled = self.refresh_inline_completion(false, true, cx).is_none();
|
||||
let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
|
||||
if is_copilot_disabled {
|
||||
cx.propagate();
|
||||
}
|
||||
@@ -5013,7 +4981,7 @@ impl Editor {
|
||||
if self.has_active_inline_completion(cx) {
|
||||
self.cycle_inline_completion(Direction::Prev, cx);
|
||||
} else {
|
||||
let is_copilot_disabled = self.refresh_inline_completion(false, true, cx).is_none();
|
||||
let is_copilot_disabled = self.refresh_inline_completion(false, cx).is_none();
|
||||
if is_copilot_disabled {
|
||||
cx.propagate();
|
||||
}
|
||||
@@ -5041,7 +5009,7 @@ impl Editor {
|
||||
self.change_selections(None, cx, |s| s.select_ranges([range]))
|
||||
}
|
||||
self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
|
||||
self.refresh_inline_completion(true, true, cx);
|
||||
self.refresh_inline_completion(true, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -5077,7 +5045,7 @@ impl Editor {
|
||||
}
|
||||
self.insert_with_autoindent_mode(&partial_completion, None, cx);
|
||||
|
||||
self.refresh_inline_completion(true, true, cx);
|
||||
self.refresh_inline_completion(true, cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -5543,7 +5511,7 @@ impl Editor {
|
||||
this.edit(edits, None, cx);
|
||||
})
|
||||
}
|
||||
this.refresh_inline_completion(true, false, cx);
|
||||
this.refresh_inline_completion(true, cx);
|
||||
linked_editing_ranges::refresh_linked_ranges(this, cx);
|
||||
});
|
||||
}
|
||||
@@ -5562,7 +5530,7 @@ impl Editor {
|
||||
})
|
||||
});
|
||||
this.insert("", cx);
|
||||
this.refresh_inline_completion(true, false, cx);
|
||||
this.refresh_inline_completion(true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5649,7 +5617,7 @@ impl Editor {
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
this.refresh_inline_completion(true, false, cx);
|
||||
this.refresh_inline_completion(true, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6817,7 +6785,7 @@ impl Editor {
|
||||
}
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
self.unmark_text(cx);
|
||||
self.refresh_inline_completion(true, false, cx);
|
||||
self.refresh_inline_completion(true, cx);
|
||||
cx.emit(EditorEvent::Edited { transaction_id });
|
||||
cx.emit(EditorEvent::TransactionUndone { transaction_id });
|
||||
}
|
||||
@@ -6838,7 +6806,7 @@ impl Editor {
|
||||
}
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
self.unmark_text(cx);
|
||||
self.refresh_inline_completion(true, false, cx);
|
||||
self.refresh_inline_completion(true, cx);
|
||||
cx.emit(EditorEvent::Edited { transaction_id });
|
||||
}
|
||||
}
|
||||
@@ -8607,7 +8575,7 @@ impl Editor {
|
||||
let hide_runnables = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
// Do not display any test indicators in non-dev server remote projects.
|
||||
project.is_via_collab() && project.ssh_connection_string(cx).is_none()
|
||||
project.is_remote() && project.ssh_connection_string(cx).is_none()
|
||||
})
|
||||
.unwrap_or(true);
|
||||
if hide_runnables {
|
||||
@@ -9209,38 +9177,6 @@ impl Editor {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
|
||||
let Some(workspace) = self.workspace() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let position = self.selections.newest_anchor().head();
|
||||
|
||||
let Some((buffer, buffer_position)) =
|
||||
self.buffer.read(cx).text_anchor_for_position(position, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(project) = self.project.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let result = find_file(&buffer, project, buffer_position, &mut cx).await;
|
||||
|
||||
if let Some((_, path)) = result {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_resolved_path(path, cx)
|
||||
})?
|
||||
.await?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub(crate) fn navigate_to_hover_links(
|
||||
&mut self,
|
||||
kind: Option<GotoDefinitionKind>,
|
||||
@@ -9251,49 +9187,21 @@ impl Editor {
|
||||
// If there is one definition, just open it directly
|
||||
if definitions.len() == 1 {
|
||||
let definition = definitions.pop().unwrap();
|
||||
|
||||
enum TargetTaskResult {
|
||||
Location(Option<Location>),
|
||||
AlreadyNavigated,
|
||||
}
|
||||
|
||||
let target_task = match definition {
|
||||
HoverLink::Text(link) => {
|
||||
Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
|
||||
}
|
||||
HoverLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
|
||||
HoverLink::InlayHint(lsp_location, server_id) => {
|
||||
let computation = self.compute_target_location(lsp_location, server_id, cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
let location = computation.await?;
|
||||
Ok(TargetTaskResult::Location(location))
|
||||
})
|
||||
self.compute_target_location(lsp_location, server_id, cx)
|
||||
}
|
||||
HoverLink::Url(url) => {
|
||||
cx.open_url(&url);
|
||||
Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
|
||||
}
|
||||
HoverLink::File(path) => {
|
||||
if let Some(workspace) = self.workspace() {
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_resolved_path(path, cx)
|
||||
})?
|
||||
.await
|
||||
.map(|_| TargetTaskResult::AlreadyNavigated)
|
||||
})
|
||||
} else {
|
||||
Task::ready(Ok(TargetTaskResult::Location(None)))
|
||||
}
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
};
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
let target = match target_task.await.context("target resolution task")? {
|
||||
TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
|
||||
TargetTaskResult::Location(None) => return Ok(Navigated::No),
|
||||
TargetTaskResult::Location(Some(target)) => target,
|
||||
let target = target_task.await.context("target resolution task")?;
|
||||
let Some(target) = target else {
|
||||
return Ok(Navigated::No);
|
||||
};
|
||||
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return Navigated::No;
|
||||
@@ -9371,7 +9279,6 @@ impl Editor {
|
||||
}),
|
||||
HoverLink::InlayHint(_, _) => None,
|
||||
HoverLink::Url(_) => None,
|
||||
HoverLink::File(_) => None,
|
||||
})
|
||||
.unwrap_or(tab_kind.to_string());
|
||||
let location_tasks = definitions
|
||||
@@ -9382,7 +9289,6 @@ impl Editor {
|
||||
editor.compute_target_location(lsp_location, server_id, cx)
|
||||
}
|
||||
HoverLink::Url(_) => Task::ready(Ok(None)),
|
||||
HoverLink::File(_) => Task::ready(Ok(None)),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(title, location_tasks, editor.workspace().clone())
|
||||
@@ -9792,7 +9698,6 @@ impl Editor {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
..EditorStyle::default()
|
||||
},
|
||||
))
|
||||
.into_any_element()
|
||||
@@ -10505,8 +10410,6 @@ impl Editor {
|
||||
if settings.show_wrap_guides {
|
||||
if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
|
||||
wrap_guides.push((soft_wrap as usize, true));
|
||||
} else if let SoftWrap::Bounded(soft_wrap) = self.soft_wrap_mode(cx) {
|
||||
wrap_guides.push((soft_wrap as usize, true));
|
||||
}
|
||||
wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
|
||||
}
|
||||
@@ -10526,9 +10429,6 @@ impl Editor {
|
||||
language_settings::SoftWrap::PreferredLineLength => {
|
||||
SoftWrap::Column(settings.preferred_line_length)
|
||||
}
|
||||
language_settings::SoftWrap::Bounded => {
|
||||
SoftWrap::Bounded(settings.preferred_line_length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10570,7 +10470,7 @@ impl Editor {
|
||||
} else {
|
||||
let soft_wrap = match self.soft_wrap_mode(cx) {
|
||||
SoftWrap::None | SoftWrap::PreferLine => language_settings::SoftWrap::EditorWidth,
|
||||
SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
|
||||
SoftWrap::EditorWidth | SoftWrap::Column(_) => {
|
||||
language_settings::SoftWrap::PreferLine
|
||||
}
|
||||
};
|
||||
@@ -10612,29 +10512,6 @@ impl Editor {
|
||||
EditorSettings::override_global(editor_settings, cx);
|
||||
}
|
||||
|
||||
pub fn should_use_relative_line_numbers(&self, cx: &WindowContext) -> bool {
|
||||
self.use_relative_line_numbers
|
||||
.unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
|
||||
}
|
||||
|
||||
pub fn toggle_relative_line_numbers(
|
||||
&mut self,
|
||||
_: &ToggleRelativeLineNumbers,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let is_relative = self.should_use_relative_line_numbers(cx);
|
||||
self.set_relative_line_number(Some(!is_relative), cx)
|
||||
}
|
||||
|
||||
pub fn set_relative_line_number(
|
||||
&mut self,
|
||||
is_relative: Option<bool>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.use_relative_line_numbers = is_relative;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut ViewContext<Self>) {
|
||||
self.show_gutter = show_gutter;
|
||||
cx.notify();
|
||||
@@ -10930,17 +10807,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_file_location(&mut self, _: &CopyFileLocation, cx: &mut ViewContext<Self>) {
|
||||
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
|
||||
if let Some(path) = file.path().to_str() {
|
||||
let selection = self.selections.newest::<Point>(cx).start.row + 1;
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_permalink_to_line(&mut self, _: &OpenPermalinkToLine, cx: &mut ViewContext<Self>) {
|
||||
let permalink = self.get_permalink_to_line(cx);
|
||||
|
||||
@@ -11466,7 +11332,7 @@ impl Editor {
|
||||
.filter_map(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let language = buffer.language()?;
|
||||
if project.is_local_or_ssh()
|
||||
if project.is_local()
|
||||
&& project.language_servers_for_buffer(buffer, cx).count() == 0
|
||||
{
|
||||
None
|
||||
@@ -11553,7 +11419,7 @@ impl Editor {
|
||||
|
||||
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.tasks_update_task = Some(self.refresh_runnables(cx));
|
||||
self.refresh_inline_completion(true, false, cx);
|
||||
self.refresh_inline_completion(true, cx);
|
||||
self.refresh_inlay_hints(
|
||||
InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
|
||||
self.selections.newest_anchor().head(),
|
||||
@@ -11995,6 +11861,7 @@ impl Editor {
|
||||
self.editor_actions.borrow_mut().insert(
|
||||
id,
|
||||
Box::new(move |cx| {
|
||||
let _view = cx.view().clone();
|
||||
let cx = cx.window_context();
|
||||
let listener = listener.clone();
|
||||
cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
|
||||
@@ -12080,22 +11947,6 @@ impl Editor {
|
||||
menu.visible() && matches!(menu, ContextMenu::Completions(_))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register_addon<T: Addon>(&mut self, instance: T) {
|
||||
self.addons
|
||||
.insert(std::any::TypeId::of::<T>(), Box::new(instance));
|
||||
}
|
||||
|
||||
pub fn unregister_addon<T: Addon>(&mut self) {
|
||||
self.addons.remove(&std::any::TypeId::of::<T>());
|
||||
}
|
||||
|
||||
pub fn addon<T: Addon>(&self) -> Option<&T> {
|
||||
let type_id = std::any::TypeId::of::<T>();
|
||||
self.addons
|
||||
.get(&type_id)
|
||||
.and_then(|item| item.to_any().downcast_ref::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
fn hunks_for_selections(
|
||||
@@ -12737,7 +12588,6 @@ impl Render for Editor {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9090,43 +9090,6 @@ async fn go_to_prev_overlapping_diagnostic(
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn func(abˇc def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
|
||||
|
||||
cx.update(|cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
project.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path("/root/file").unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
|
||||
..Default::default()
|
||||
}],
|
||||
},
|
||||
&[],
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}).unwrap();
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
@@ -165,7 +165,6 @@ impl EditorElement {
|
||||
});
|
||||
|
||||
crate::rust_analyzer_ext::apply_related_actions(view, cx);
|
||||
crate::clangd_ext::apply_related_actions(view, cx);
|
||||
register_action(view, cx, Editor::move_left);
|
||||
register_action(view, cx, Editor::move_right);
|
||||
register_action(view, cx, Editor::move_down);
|
||||
@@ -331,7 +330,6 @@ impl EditorElement {
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
register_action(view, cx, Editor::open_url);
|
||||
register_action(view, cx, Editor::open_file);
|
||||
register_action(view, cx, Editor::fold);
|
||||
register_action(view, cx, Editor::fold_at);
|
||||
register_action(view, cx, Editor::unfold_lines);
|
||||
@@ -344,7 +342,6 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::toggle_soft_wrap);
|
||||
register_action(view, cx, Editor::toggle_tab_bar);
|
||||
register_action(view, cx, Editor::toggle_line_numbers);
|
||||
register_action(view, cx, Editor::toggle_relative_line_numbers);
|
||||
register_action(view, cx, Editor::toggle_indent_guides);
|
||||
register_action(view, cx, Editor::toggle_inlay_hints);
|
||||
register_action(view, cx, hover_popover::hover);
|
||||
@@ -354,7 +351,6 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::copy_highlight_json);
|
||||
register_action(view, cx, Editor::copy_permalink_to_line);
|
||||
register_action(view, cx, Editor::open_permalink_to_line);
|
||||
register_action(view, cx, Editor::copy_file_location);
|
||||
register_action(view, cx, Editor::toggle_git_blame);
|
||||
register_action(view, cx, Editor::toggle_git_blame_inline);
|
||||
register_action(view, cx, Editor::toggle_hunk_diff);
|
||||
@@ -1771,7 +1767,7 @@ impl EditorElement {
|
||||
});
|
||||
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
|
||||
|
||||
let is_relative = editor.should_use_relative_line_numbers(cx);
|
||||
let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
|
||||
let relative_to = if is_relative {
|
||||
Some(newest_selection_head.row())
|
||||
} else {
|
||||
@@ -4998,8 +4994,7 @@ impl Element for EditorElement {
|
||||
Some((MAX_LINE_LEN / 2) as f32 * em_advance)
|
||||
}
|
||||
SoftWrap::EditorWidth => Some(editor_width),
|
||||
SoftWrap::Column(column) => Some(column as f32 * em_advance),
|
||||
SoftWrap::Bounded(column) => {
|
||||
SoftWrap::Column(column) => {
|
||||
Some(editor_width.min(column as f32 * em_advance))
|
||||
}
|
||||
};
|
||||
@@ -5618,7 +5613,7 @@ impl Element for EditorElement {
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let key_context = self.editor.update(cx, |editor, cx| editor.key_context(cx));
|
||||
let key_context = self.editor.read(cx).key_context(cx);
|
||||
cx.set_key_context(key_context);
|
||||
cx.handle_input(
|
||||
&focus_handle,
|
||||
|
||||
@@ -9,8 +9,8 @@ use language::{Bias, ToOffset};
|
||||
use linkify::{LinkFinder, LinkKind};
|
||||
use lsp::LanguageServerId;
|
||||
use project::{
|
||||
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, Project,
|
||||
ResolveState, ResolvedPath,
|
||||
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
|
||||
ResolveState,
|
||||
};
|
||||
use std::ops::Range;
|
||||
use theme::ActiveTheme as _;
|
||||
@@ -63,7 +63,6 @@ impl RangeInEditor {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HoverLink {
|
||||
Url(String),
|
||||
File(ResolvedPath),
|
||||
Text(LocationLink),
|
||||
InlayHint(lsp::Location, LanguageServerId),
|
||||
}
|
||||
@@ -523,54 +522,35 @@ pub fn show_link_definition(
|
||||
})
|
||||
.ok()
|
||||
} else if let Some(project) = project {
|
||||
if let Some((filename_range, filename)) =
|
||||
find_file(&buffer, project.clone(), buffer_position, &mut cx).await
|
||||
{
|
||||
let range = maybe!({
|
||||
let start =
|
||||
snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?;
|
||||
let end =
|
||||
snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?;
|
||||
Some(RangeInEditor::Text(start..end))
|
||||
});
|
||||
// query the LSP for definition info
|
||||
project
|
||||
.update(&mut cx, |project, cx| match preferred_kind {
|
||||
LinkDefinitionKind::Symbol => {
|
||||
project.definition(&buffer, buffer_position, cx)
|
||||
}
|
||||
|
||||
Some((range, vec![HoverLink::File(filename)]))
|
||||
} else {
|
||||
// query the LSP for definition info
|
||||
project
|
||||
.update(&mut cx, |project, cx| match preferred_kind {
|
||||
LinkDefinitionKind::Symbol => {
|
||||
project.definition(&buffer, buffer_position, cx)
|
||||
}
|
||||
|
||||
LinkDefinitionKind::Type => {
|
||||
project.type_definition(&buffer, buffer_position, cx)
|
||||
}
|
||||
})?
|
||||
.await
|
||||
.ok()
|
||||
.map(|definition_result| {
|
||||
(
|
||||
definition_result.iter().find_map(|link| {
|
||||
link.origin.as_ref().and_then(|origin| {
|
||||
let start = snapshot.anchor_in_excerpt(
|
||||
excerpt_id,
|
||||
origin.range.start,
|
||||
)?;
|
||||
let end = snapshot.anchor_in_excerpt(
|
||||
excerpt_id,
|
||||
origin.range.end,
|
||||
)?;
|
||||
Some(RangeInEditor::Text(start..end))
|
||||
})
|
||||
}),
|
||||
definition_result
|
||||
.into_iter()
|
||||
.map(HoverLink::Text)
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
}
|
||||
LinkDefinitionKind::Type => {
|
||||
project.type_definition(&buffer, buffer_position, cx)
|
||||
}
|
||||
})?
|
||||
.await
|
||||
.ok()
|
||||
.map(|definition_result| {
|
||||
(
|
||||
definition_result.iter().find_map(|link| {
|
||||
link.origin.as_ref().and_then(|origin| {
|
||||
let start = snapshot.anchor_in_excerpt(
|
||||
excerpt_id,
|
||||
origin.range.start,
|
||||
)?;
|
||||
let end = snapshot
|
||||
.anchor_in_excerpt(excerpt_id, origin.range.end)?;
|
||||
Some(RangeInEditor::Text(start..end))
|
||||
})
|
||||
}),
|
||||
definition_result.into_iter().map(HoverLink::Text).collect(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -706,116 +686,6 @@ pub(crate) fn find_url(
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) async fn find_file(
|
||||
buffer: &Model<language::Buffer>,
|
||||
project: Model<Project>,
|
||||
position: text::Anchor,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Option<(Range<text::Anchor>, ResolvedPath)> {
|
||||
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()).ok()?;
|
||||
|
||||
let (range, candidate_file_path) = surrounding_filename(snapshot, position)?;
|
||||
|
||||
let existing_path = project
|
||||
.update(cx, |project, cx| {
|
||||
project.resolve_existing_file_path(&candidate_file_path, &buffer, cx)
|
||||
})
|
||||
.ok()?
|
||||
.await?;
|
||||
|
||||
Some((range, existing_path))
|
||||
}
|
||||
|
||||
fn surrounding_filename(
|
||||
snapshot: language::BufferSnapshot,
|
||||
position: text::Anchor,
|
||||
) -> Option<(Range<text::Anchor>, String)> {
|
||||
const LIMIT: usize = 2048;
|
||||
|
||||
let offset = position.to_offset(&snapshot);
|
||||
let mut token_start = offset;
|
||||
let mut token_end = offset;
|
||||
let mut found_start = false;
|
||||
let mut found_end = false;
|
||||
let mut inside_quotes = false;
|
||||
|
||||
let mut filename = String::new();
|
||||
|
||||
let mut backwards = snapshot.reversed_chars_at(offset).take(LIMIT).peekable();
|
||||
while let Some(ch) = backwards.next() {
|
||||
// Escaped whitespace
|
||||
if ch.is_whitespace() && backwards.peek() == Some(&'\\') {
|
||||
filename.push(ch);
|
||||
token_start -= ch.len_utf8();
|
||||
backwards.next();
|
||||
token_start -= '\\'.len_utf8();
|
||||
continue;
|
||||
}
|
||||
if ch.is_whitespace() {
|
||||
found_start = true;
|
||||
break;
|
||||
}
|
||||
if (ch == '"' || ch == '\'') && !inside_quotes {
|
||||
found_start = true;
|
||||
inside_quotes = true;
|
||||
break;
|
||||
}
|
||||
|
||||
filename.push(ch);
|
||||
token_start -= ch.len_utf8();
|
||||
}
|
||||
if !found_start && token_start != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
filename = filename.chars().rev().collect();
|
||||
|
||||
let mut forwards = snapshot
|
||||
.chars_at(offset)
|
||||
.take(LIMIT - (offset - token_start))
|
||||
.peekable();
|
||||
while let Some(ch) = forwards.next() {
|
||||
// Skip escaped whitespace
|
||||
if ch == '\\' && forwards.peek().map_or(false, |ch| ch.is_whitespace()) {
|
||||
token_end += ch.len_utf8();
|
||||
let whitespace = forwards.next().unwrap();
|
||||
token_end += whitespace.len_utf8();
|
||||
filename.push(whitespace);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ch.is_whitespace() {
|
||||
found_end = true;
|
||||
break;
|
||||
}
|
||||
if ch == '"' || ch == '\'' {
|
||||
// If we're inside quotes, we stop when we come across the next quote
|
||||
if inside_quotes {
|
||||
found_end = true;
|
||||
break;
|
||||
} else {
|
||||
// Otherwise, we skip the quote
|
||||
inside_quotes = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
filename.push(ch);
|
||||
token_end += ch.len_utf8();
|
||||
}
|
||||
|
||||
if !found_end && (token_end - token_start >= LIMIT) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if filename.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let range = snapshot.anchor_before(token_start)..snapshot.anchor_after(token_end);
|
||||
|
||||
Some((range, filename))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1398,184 +1268,4 @@ mod tests {
|
||||
cx.simulate_click(screen_coord, Modifiers::secondary_key());
|
||||
assert_eq!(cx.opened_url(), Some("https://zed.dev/releases".into()));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_surrounding_filename(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
let test_cases = [
|
||||
("file ˇ name", None),
|
||||
("ˇfile name", Some("file")),
|
||||
("file ˇname", Some("name")),
|
||||
("fiˇle name", Some("file")),
|
||||
("filenˇame", Some("filename")),
|
||||
// Absolute path
|
||||
("foobar ˇ/home/user/f.txt", Some("/home/user/f.txt")),
|
||||
("foobar /home/useˇr/f.txt", Some("/home/user/f.txt")),
|
||||
// Windows
|
||||
("C:\\Useˇrs\\user\\f.txt", Some("C:\\Users\\user\\f.txt")),
|
||||
// Whitespace
|
||||
("ˇfile\\ -\\ name.txt", Some("file - name.txt")),
|
||||
("file\\ -\\ naˇme.txt", Some("file - name.txt")),
|
||||
// Tilde
|
||||
("ˇ~/file.txt", Some("~/file.txt")),
|
||||
("~/fiˇle.txt", Some("~/file.txt")),
|
||||
// Double quotes
|
||||
("\"fˇile.txt\"", Some("file.txt")),
|
||||
("ˇ\"file.txt\"", Some("file.txt")),
|
||||
("ˇ\"fi\\ le.txt\"", Some("fi le.txt")),
|
||||
// Single quotes
|
||||
("'fˇile.txt'", Some("file.txt")),
|
||||
("ˇ'file.txt'", Some("file.txt")),
|
||||
("ˇ'fi\\ le.txt'", Some("fi le.txt")),
|
||||
];
|
||||
|
||||
for (input, expected) in test_cases {
|
||||
cx.set_state(input);
|
||||
|
||||
let (position, snapshot) = cx.editor(|editor, cx| {
|
||||
let positions = editor.selections.newest_anchor().head().text_anchor;
|
||||
let snapshot = editor
|
||||
.buffer()
|
||||
.clone()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.snapshot();
|
||||
(positions, snapshot)
|
||||
});
|
||||
|
||||
let result = surrounding_filename(snapshot, position);
|
||||
|
||||
if let Some(expected) = expected {
|
||||
assert!(result.is_some(), "Failed to find file path: {}", input);
|
||||
let (_, path) = result.unwrap();
|
||||
assert_eq!(&path, expected, "Incorrect file path for input: {}", input);
|
||||
} else {
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Expected no result, but got one: {:?}",
|
||||
result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_hover_filenames(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Insert a new file
|
||||
let fs = cx.update_workspace(|workspace, cx| workspace.project().read(cx).fs().clone());
|
||||
fs.as_fake()
|
||||
.insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec())
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
You can't go to a file that does_not_exist.txt.
|
||||
Go to file2.rs if you want.
|
||||
Or go to ../dir/file2.rs if you want.
|
||||
Or go to /root/dir/file2.rs if project is local.ˇ
|
||||
"});
|
||||
|
||||
// File does not exist
|
||||
let screen_coord = cx.pixel_position(indoc! {"
|
||||
You can't go to a file that dˇoes_not_exist.txt.
|
||||
Go to file2.rs if you want.
|
||||
Or go to ../dir/file2.rs if you want.
|
||||
Or go to /root/dir/file2.rs if project is local.
|
||||
"});
|
||||
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
|
||||
// No highlight
|
||||
cx.update_editor(|editor, cx| {
|
||||
assert!(editor
|
||||
.snapshot(cx)
|
||||
.text_highlight_ranges::<HoveredLinkState>()
|
||||
.unwrap_or_default()
|
||||
.1
|
||||
.is_empty());
|
||||
});
|
||||
|
||||
// Moving the mouse over a file that does exist should highlight it.
|
||||
let screen_coord = cx.pixel_position(indoc! {"
|
||||
You can't go to a file that does_not_exist.txt.
|
||||
Go to fˇile2.rs if you want.
|
||||
Or go to ../dir/file2.rs if you want.
|
||||
Or go to /root/dir/file2.rs if project is local.
|
||||
"});
|
||||
|
||||
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
|
||||
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
|
||||
You can't go to a file that does_not_exist.txt.
|
||||
Go to «file2.rsˇ» if you want.
|
||||
Or go to ../dir/file2.rs if you want.
|
||||
Or go to /root/dir/file2.rs if project is local.
|
||||
"});
|
||||
|
||||
// Moving the mouse over a relative path that does exist should highlight it
|
||||
let screen_coord = cx.pixel_position(indoc! {"
|
||||
You can't go to a file that does_not_exist.txt.
|
||||
Go to file2.rs if you want.
|
||||
Or go to ../dir/fˇile2.rs if you want.
|
||||
Or go to /root/dir/file2.rs if project is local.
|
||||
"});
|
||||
|
||||
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
|
||||
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
|
||||
You can't go to a file that does_not_exist.txt.
|
||||
Go to file2.rs if you want.
|
||||
Or go to «../dir/file2.rsˇ» if you want.
|
||||
Or go to /root/dir/file2.rs if project is local.
|
||||
"});
|
||||
|
||||
// Moving the mouse over an absolute path that does exist should highlight it
|
||||
let screen_coord = cx.pixel_position(indoc! {"
|
||||
You can't go to a file that does_not_exist.txt.
|
||||
Go to file2.rs if you want.
|
||||
Or go to ../dir/file2.rs if you want.
|
||||
Or go to /root/diˇr/file2.rs if project is local.
|
||||
"});
|
||||
|
||||
cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
|
||||
cx.assert_editor_text_highlights::<HoveredLinkState>(indoc! {"
|
||||
You can't go to a file that does_not_exist.txt.
|
||||
Go to file2.rs if you want.
|
||||
Or go to ../dir/file2.rs if you want.
|
||||
Or go to «/root/dir/file2.rsˇ» if project is local.
|
||||
"});
|
||||
|
||||
cx.simulate_click(screen_coord, Modifiers::secondary_key());
|
||||
|
||||
cx.update_workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
|
||||
cx.update_workspace(|workspace, cx| {
|
||||
let active_editor = workspace.active_item_as::<Editor>(cx).unwrap();
|
||||
|
||||
let buffer = active_editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap();
|
||||
|
||||
let file = buffer.read(cx).file().unwrap();
|
||||
let file_path = file.as_local().unwrap().abs_path(cx);
|
||||
|
||||
assert_eq!(file_path.to_str().unwrap(), "/root/dir/file2.rs");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1144,37 +1144,16 @@ pub(crate) enum BufferSearchHighlights {}
|
||||
impl SearchableItem for Editor {
|
||||
type Match = Range<Anchor>;
|
||||
|
||||
fn get_matches(&self, _: &mut WindowContext) -> Vec<Range<Anchor>> {
|
||||
self.background_highlights
|
||||
.get(&TypeId::of::<BufferSearchHighlights>())
|
||||
.map_or(Vec::new(), |(_color, ranges)| {
|
||||
ranges.iter().map(|range| range.clone()).collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if self
|
||||
.clear_background_highlights::<BufferSearchHighlights>(cx)
|
||||
.is_some()
|
||||
{
|
||||
cx.emit(SearchEvent::MatchesInvalidated);
|
||||
}
|
||||
self.clear_background_highlights::<BufferSearchHighlights>(cx);
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, matches: &[Range<Anchor>], cx: &mut ViewContext<Self>) {
|
||||
let existing_range = self
|
||||
.background_highlights
|
||||
.get(&TypeId::of::<BufferSearchHighlights>())
|
||||
.map(|(_, range)| range.as_ref());
|
||||
let updated = existing_range != Some(matches);
|
||||
self.highlight_background::<BufferSearchHighlights>(
|
||||
matches,
|
||||
|theme| theme.search_match_background,
|
||||
cx,
|
||||
);
|
||||
if updated {
|
||||
cx.emit(SearchEvent::MatchesInvalidated);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_filtered_search_ranges(&mut self) -> bool {
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Editor;
|
||||
use gpui::{Model, WindowContext};
|
||||
use language::Buffer;
|
||||
use language::Language;
|
||||
use lsp::LanguageServerId;
|
||||
use multi_buffer::Anchor;
|
||||
|
||||
pub(crate) fn find_specific_language_server_in_selection<F>(
|
||||
editor: &Editor,
|
||||
cx: &WindowContext,
|
||||
filter_language: F,
|
||||
language_server_name: &str,
|
||||
) -> Option<(Anchor, Arc<Language>, LanguageServerId, Model<Buffer>)>
|
||||
where
|
||||
F: Fn(&Language) -> bool,
|
||||
{
|
||||
let Some(project) = &editor.project else {
|
||||
return None;
|
||||
};
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.filter(|selection| selection.start == selection.end)
|
||||
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
|
||||
.filter_map(|(buffer_id, trigger_anchor)| {
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
|
||||
if !filter_language(&language) {
|
||||
return None;
|
||||
}
|
||||
Some((trigger_anchor, language, buffer))
|
||||
})
|
||||
.find_map(|(trigger_anchor, language, buffer)| {
|
||||
project
|
||||
.read(cx)
|
||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||
.find_map(|(adapter, server)| {
|
||||
if adapter.name.0.as_ref() == language_server_name {
|
||||
Some((
|
||||
trigger_anchor,
|
||||
Arc::clone(&language),
|
||||
server.server_id(),
|
||||
buffer.clone(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::GoToDeclaration;
|
||||
use crate::{
|
||||
actions::Format, selections_collection::SelectionsCollection, Copy, CopyPermalinkToLine, Cut,
|
||||
DisplayPoint, DisplaySnapshot, Editor, EditorMode, FindAllReferences, GoToDeclaration,
|
||||
GoToDefinition, GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFileManager,
|
||||
SelectMode, ToDisplayPoint, ToggleCodeActions,
|
||||
selections_collection::SelectionsCollection, Copy, CopyPermalinkToLine, Cut, DisplayPoint,
|
||||
DisplaySnapshot, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToImplementation,
|
||||
GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, ToDisplayPoint,
|
||||
ToggleCodeActions,
|
||||
};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
|
||||
@@ -161,14 +162,12 @@ pub fn deploy_context_menu(
|
||||
ui::ContextMenu::build(cx, |menu, _cx| {
|
||||
let builder = menu
|
||||
.on_blur_subscription(Subscription::new(|| {}))
|
||||
.action("Rename Symbol", Box::new(Rename))
|
||||
.action("Go to Definition", Box::new(GoToDefinition))
|
||||
.action("Go to Declaration", Box::new(GoToDeclaration))
|
||||
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
|
||||
.action("Go to Implementation", Box::new(GoToImplementation))
|
||||
.action("Find All References", Box::new(FindAllReferences))
|
||||
.separator()
|
||||
.action("Rename Symbol", Box::new(Rename))
|
||||
.action("Format Buffer", Box::new(Format))
|
||||
.action(
|
||||
"Code Actions",
|
||||
Box::new(ToggleCodeActions {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
|
||||
use language::Language;
|
||||
@@ -5,24 +7,22 @@ use multi_buffer::MultiBuffer;
|
||||
use project::lsp_ext_command::ExpandMacro;
|
||||
use text::ToPointUtf16;
|
||||
|
||||
use crate::{
|
||||
element::register_action, lsp_ext::find_specific_language_server_in_selection, Editor,
|
||||
ExpandMacroRecursively,
|
||||
};
|
||||
|
||||
static RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||
|
||||
fn is_rust_language(language: &Language) -> bool {
|
||||
language.name().as_ref() == "Rust"
|
||||
}
|
||||
use crate::{element::register_action, Editor, ExpandMacroRecursively};
|
||||
|
||||
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
||||
if editor
|
||||
.update(cx, |e, cx| {
|
||||
find_specific_language_server_in_selection(e, cx, &is_rust_language, RUST_ANALYZER_NAME)
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
let is_rust_related = editor.update(cx, |editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.all_buffers()
|
||||
.iter()
|
||||
.any(|b| match b.read(cx).language() {
|
||||
Some(l) => is_rust_language(l),
|
||||
None => false,
|
||||
})
|
||||
});
|
||||
|
||||
if is_rust_related {
|
||||
register_action(editor, cx, expand_macro_recursively);
|
||||
}
|
||||
}
|
||||
@@ -42,13 +42,39 @@ pub fn expand_macro_recursively(
|
||||
return;
|
||||
};
|
||||
|
||||
let Some((trigger_anchor, rust_language, server_to_query, buffer)) =
|
||||
find_specific_language_server_in_selection(
|
||||
&editor,
|
||||
cx,
|
||||
&is_rust_language,
|
||||
RUST_ANALYZER_NAME,
|
||||
)
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
|
||||
let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.into_iter()
|
||||
.filter(|selection| selection.start == selection.end)
|
||||
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
|
||||
.filter_map(|(buffer_id, trigger_anchor)| {
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
|
||||
if !is_rust_language(&rust_language) {
|
||||
return None;
|
||||
}
|
||||
Some((trigger_anchor, rust_language, buffer))
|
||||
})
|
||||
.find_map(|(trigger_anchor, rust_language, buffer)| {
|
||||
project
|
||||
.read(cx)
|
||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||
.find_map(|(adapter, server)| {
|
||||
if adapter.name.0.as_ref() == "rust-analyzer" {
|
||||
Some((
|
||||
trigger_anchor,
|
||||
Arc::clone(&rust_language),
|
||||
server.server_id(),
|
||||
buffer.clone(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -94,3 +120,7 @@ pub fn expand_macro_recursively(
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn is_rust_language(language: &Language) -> bool {
|
||||
language.name().as_ref() == "Rust"
|
||||
}
|
||||
|
||||
@@ -505,7 +505,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
if let Some(visible_lines) = self.visible_line_count() {
|
||||
if newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32) {
|
||||
if newest_head.row() < DisplayRow(screen_top.row().0 + visible_lines as u32) {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,21 +93,12 @@ impl ExtensionBuilder {
|
||||
self.compile_rust_extension(extension_dir, extension_manifest, options)
|
||||
.await
|
||||
.context("failed to compile Rust extension")?;
|
||||
log::info!("compiled Rust extension {}", extension_dir.display());
|
||||
}
|
||||
|
||||
for (grammar_name, grammar_metadata) in &extension_manifest.grammars {
|
||||
log::info!(
|
||||
"compiling grammar {grammar_name} for extension {}",
|
||||
extension_dir.display()
|
||||
);
|
||||
self.compile_grammar(extension_dir, grammar_name.as_ref(), grammar_metadata)
|
||||
.await
|
||||
.with_context(|| format!("failed to compile grammar '{grammar_name}'"))?;
|
||||
log::info!(
|
||||
"compiled grammar {grammar_name} for extension {}",
|
||||
extension_dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
log::info!("finished compiling extension {}", extension_dir.display());
|
||||
@@ -126,10 +117,7 @@ impl ExtensionBuilder {
|
||||
let cargo_toml_content = fs::read_to_string(&extension_dir.join("Cargo.toml"))?;
|
||||
let cargo_toml: CargoToml = toml::from_str(&cargo_toml_content)?;
|
||||
|
||||
log::info!(
|
||||
"compiling Rust crate for extension {}",
|
||||
extension_dir.display()
|
||||
);
|
||||
log::info!("compiling rust extension {}", extension_dir.display());
|
||||
let output = Command::new("cargo")
|
||||
.args(["build", "--target", RUST_TARGET])
|
||||
.args(options.release.then_some("--release"))
|
||||
@@ -145,11 +133,6 @@ impl ExtensionBuilder {
|
||||
);
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"compiled Rust crate for extension {}",
|
||||
extension_dir.display()
|
||||
);
|
||||
|
||||
let mut wasm_path = PathBuf::from(extension_dir);
|
||||
wasm_path.extend([
|
||||
"target",
|
||||
@@ -172,11 +155,6 @@ impl ExtensionBuilder {
|
||||
.context("failed to load adapter module")?
|
||||
.validate(true);
|
||||
|
||||
log::info!(
|
||||
"encoding wasm component for extension {}",
|
||||
extension_dir.display()
|
||||
);
|
||||
|
||||
let component_bytes = encoder
|
||||
.encode()
|
||||
.context("failed to encode wasm component")?;
|
||||
@@ -190,16 +168,9 @@ impl ExtensionBuilder {
|
||||
.context("compiled wasm did not contain a valid zed extension api version")?;
|
||||
manifest.lib.version = Some(wasm_extension_api_version);
|
||||
|
||||
let extension_file = extension_dir.join("extension.wasm");
|
||||
fs::write(extension_file.clone(), &component_bytes)
|
||||
fs::write(extension_dir.join("extension.wasm"), &component_bytes)
|
||||
.context("failed to write extension.wasm")?;
|
||||
|
||||
log::info!(
|
||||
"extension {} written to {}",
|
||||
extension_dir.display(),
|
||||
extension_file.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ impl FeedbackModal {
|
||||
.language_for_name("Markdown");
|
||||
|
||||
let project = workspace.project().clone();
|
||||
let is_local_project = project.read(cx).is_local_or_ssh();
|
||||
let is_local_project = project.read(cx).is_local();
|
||||
|
||||
if !is_local_project {
|
||||
struct FeedbackInRemoteProject;
|
||||
|
||||
@@ -788,7 +788,7 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
project
|
||||
.worktree_for_id(history_item.project.worktree_id, cx)
|
||||
.is_some()
|
||||
|| (project.is_local_or_ssh() && history_item.absolute.is_some())
|
||||
|| (project.is_local() && history_item.absolute.is_some())
|
||||
}),
|
||||
self.currently_opened_path.as_ref(),
|
||||
None,
|
||||
|
||||
@@ -20,6 +20,7 @@ futures.workspace = true
|
||||
git.workspace = true
|
||||
git2.workspace = true
|
||||
gpui.workspace = true
|
||||
lazy_static.workspace = true
|
||||
libc.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
|
||||
@@ -9,9 +9,6 @@ use std::{fs::File, os::fd::AsFd};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
|
||||
use async_tar::Archive;
|
||||
use futures::{future::BoxFuture, AsyncRead, Stream, StreamExt};
|
||||
use git::repository::{GitRepository, RealGitRepository};
|
||||
@@ -152,7 +149,6 @@ pub struct Metadata {
|
||||
pub mtime: SystemTime,
|
||||
pub is_symlink: bool,
|
||||
pub is_dir: bool,
|
||||
pub is_fifo: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -355,16 +351,6 @@ impl Fs for RealFs {
|
||||
// invalid cross-device link error, and XDG_CACHE_DIR for fallback.
|
||||
// See https://github.com/zed-industries/zed/pull/8437 for more details.
|
||||
NamedTempFile::new_in(path.parent().unwrap_or(&paths::temp_dir()))
|
||||
} else if cfg!(target_os = "windows") {
|
||||
// If temp dir is set to a different drive than the destination,
|
||||
// we receive error:
|
||||
//
|
||||
// failed to persist temporary file:
|
||||
// The system cannot move the file to a different disk drive. (os error 17)
|
||||
//
|
||||
// So we use the directory of the destination as a temp dir to avoid it.
|
||||
// https://github.com/zed-industries/zed/issues/16571
|
||||
NamedTempFile::new_in(path.parent().unwrap_or(&paths::temp_dir()))
|
||||
} else {
|
||||
NamedTempFile::new()
|
||||
}?;
|
||||
@@ -432,18 +418,11 @@ impl Fs for RealFs {
|
||||
#[cfg(windows)]
|
||||
let inode = file_id(path).await?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let is_fifo = false;
|
||||
|
||||
#[cfg(unix)]
|
||||
let is_fifo = metadata.file_type().is_fifo();
|
||||
|
||||
Ok(Some(Metadata {
|
||||
inode,
|
||||
mtime: metadata.modified().unwrap(),
|
||||
is_symlink,
|
||||
is_dir: metadata.file_type().is_dir(),
|
||||
is_fifo,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -815,8 +794,9 @@ impl FakeFsState {
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub static FS_DOT_GIT: std::sync::LazyLock<&'static OsStr> =
|
||||
std::sync::LazyLock::new(|| OsStr::new(".git"));
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref FS_DOT_GIT: &'static OsStr = OsStr::new(".git");
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl FakeFs {
|
||||
@@ -846,35 +826,6 @@ impl FakeFs {
|
||||
state.next_mtime = next_mtime;
|
||||
}
|
||||
|
||||
pub async fn touch_path(&self, path: impl AsRef<Path>) {
|
||||
let mut state = self.state.lock();
|
||||
let path = path.as_ref();
|
||||
let new_mtime = state.next_mtime;
|
||||
let new_inode = state.next_inode;
|
||||
state.next_inode += 1;
|
||||
state.next_mtime += Duration::from_nanos(1);
|
||||
state
|
||||
.write_path(path, move |entry| {
|
||||
match entry {
|
||||
btree_map::Entry::Vacant(e) => {
|
||||
e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
|
||||
inode: new_inode,
|
||||
mtime: new_mtime,
|
||||
content: Vec::new(),
|
||||
})));
|
||||
}
|
||||
btree_map::Entry::Occupied(mut e) => match &mut *e.get_mut().lock() {
|
||||
FakeFsEntry::File { mtime, .. } => *mtime = new_mtime,
|
||||
FakeFsEntry::Dir { mtime, .. } => *mtime = new_mtime,
|
||||
FakeFsEntry::Symlink { .. } => {}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
state.emit_event([path.to_path_buf()]);
|
||||
}
|
||||
|
||||
pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
|
||||
self.write_file_internal(path, content).unwrap()
|
||||
}
|
||||
@@ -1577,14 +1528,12 @@ impl Fs for FakeFs {
|
||||
mtime: *mtime,
|
||||
is_dir: false,
|
||||
is_symlink,
|
||||
is_fifo: false,
|
||||
},
|
||||
FakeFsEntry::Dir { inode, mtime, .. } => Metadata {
|
||||
inode: *inode,
|
||||
mtime: *mtime,
|
||||
is_dir: true,
|
||||
is_symlink,
|
||||
is_fifo: false,
|
||||
},
|
||||
FakeFsEntry::Symlink { .. } => unreachable!(),
|
||||
}))
|
||||
|
||||
@@ -20,6 +20,7 @@ derive_more.workspace = true
|
||||
git2.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rope.workspace = true
|
||||
|
||||
@@ -5,9 +5,9 @@ use serde::{Deserialize, Serialize};
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub use git2 as libgit;
|
||||
pub use lazy_static::lazy_static;
|
||||
|
||||
pub use crate::hosting_provider::*;
|
||||
|
||||
@@ -17,8 +17,10 @@ pub mod diff;
|
||||
pub mod repository;
|
||||
pub mod status;
|
||||
|
||||
pub static DOT_GIT: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".git"));
|
||||
pub static GITIGNORE: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".gitignore"));
|
||||
lazy_static! {
|
||||
pub static ref DOT_GIT: &'static OsStr = OsStr::new(".git");
|
||||
pub static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub struct Oid(libgit::Oid);
|
||||
|
||||
@@ -36,7 +36,11 @@ pub trait GitRepository: Send + Sync {
|
||||
/// Returns the SHA of the current HEAD.
|
||||
fn head_sha(&self) -> Option<String>;
|
||||
|
||||
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus>;
|
||||
fn statuses(&self, path_prefix: &Path) -> Result<GitStatus>;
|
||||
|
||||
fn status(&self, path: &Path) -> Option<GitFileStatus> {
|
||||
Some(self.statuses(path).ok()?.entries.first()?.1)
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>>;
|
||||
fn change_branch(&self, _: &str) -> Result<()>;
|
||||
@@ -122,14 +126,14 @@ impl GitRepository for RealGitRepository {
|
||||
Some(self.repository.lock().head().ok()?.target()?.to_string())
|
||||
}
|
||||
|
||||
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus> {
|
||||
fn statuses(&self, path_prefix: &Path) -> Result<GitStatus> {
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.context("failed to read git work directory")?
|
||||
.to_path_buf();
|
||||
GitStatus::new(&self.git_binary_path, &working_directory, path_prefixes)
|
||||
GitStatus::new(&self.git_binary_path, &working_directory, path_prefix)
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
@@ -241,16 +245,13 @@ impl GitRepository for FakeGitRepository {
|
||||
None
|
||||
}
|
||||
|
||||
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus> {
|
||||
fn statuses(&self, path_prefix: &Path) -> Result<GitStatus> {
|
||||
let state = self.state.lock();
|
||||
let mut entries = state
|
||||
.worktree_statuses
|
||||
.iter()
|
||||
.filter_map(|(repo_path, status)| {
|
||||
if path_prefixes
|
||||
.iter()
|
||||
.any(|path_prefix| repo_path.0.starts_with(path_prefix))
|
||||
{
|
||||
if repo_path.0.starts_with(path_prefix) {
|
||||
Some((repo_path.to_owned(), *status))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -15,10 +15,14 @@ impl GitStatus {
|
||||
pub(crate) fn new(
|
||||
git_binary: &Path,
|
||||
working_directory: &Path,
|
||||
path_prefixes: &[PathBuf],
|
||||
mut path_prefix: &Path,
|
||||
) -> Result<Self> {
|
||||
let mut child = Command::new(git_binary);
|
||||
|
||||
if path_prefix == Path::new("") {
|
||||
path_prefix = Path::new(".");
|
||||
}
|
||||
|
||||
child
|
||||
.current_dir(working_directory)
|
||||
.args([
|
||||
@@ -28,13 +32,7 @@ impl GitStatus {
|
||||
"--untracked-files=all",
|
||||
"-z",
|
||||
])
|
||||
.args(path_prefixes.iter().map(|path_prefix| {
|
||||
if *path_prefix == Path::new("") {
|
||||
Path::new(".")
|
||||
} else {
|
||||
path_prefix
|
||||
}
|
||||
}))
|
||||
.arg(path_prefix)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
@@ -43,6 +43,7 @@ gpui_macros.workspace = true
|
||||
http_client.workspace = true
|
||||
image = "0.25.1"
|
||||
itertools.workspace = true
|
||||
lazy_static.workspace = true
|
||||
linkme = "0.3"
|
||||
log.workspace = true
|
||||
num_cpus = "1.13"
|
||||
@@ -178,7 +179,3 @@ path = "examples/input.rs"
|
||||
[[example]]
|
||||
name = "shadow"
|
||||
path = "examples/shadow.rs"
|
||||
|
||||
[[example]]
|
||||
name = "text_wrapper"
|
||||
path = "examples/text_wrapper.rs"
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
use gpui::*;
|
||||
|
||||
struct HelloWorld {}
|
||||
|
||||
impl Render for HelloWorld {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let text = "The longest word in any of the major English language 以及中文的测试 dictionaries is pneumonoultramicroscopicsilicovolcanoconiosis, a word that refers to a lung disease contracted from the inhalation of very fine silica particles, specifically from a volcano; medically, it is the same as silicosis.";
|
||||
div()
|
||||
.id("page")
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.bg(gpui::white())
|
||||
.child(
|
||||
div()
|
||||
.text_xl()
|
||||
.overflow_hidden()
|
||||
.text_ellipsis()
|
||||
.border_1()
|
||||
.border_color(gpui::red())
|
||||
.child("ELLIPSIS: ".to_owned() + text),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xl()
|
||||
.overflow_hidden()
|
||||
.truncate()
|
||||
.border_1()
|
||||
.border_color(gpui::green())
|
||||
.child("TRUNCATE: ".to_owned() + text),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xl()
|
||||
.whitespace_nowrap()
|
||||
.overflow_hidden()
|
||||
.border_1()
|
||||
.border_color(gpui::blue())
|
||||
.child("NOWRAP: ".to_owned() + text),
|
||||
)
|
||||
.child(div().text_xl().w_full().child(text))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new().run(|cx: &mut AppContext| {
|
||||
let bounds = Bounds::centered(None, size(px(600.0), px(480.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| cx.new_view(|_cx| HelloWorld {}),
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
@@ -642,8 +642,10 @@ impl<T> PartialEq<Model<T>> for WeakModel<T> {
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
static LEAK_BACKTRACE: std::sync::LazyLock<bool> =
|
||||
std::sync::LazyLock::new(|| std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty()));
|
||||
lazy_static::lazy_static! {
|
||||
static ref LEAK_BACKTRACE: bool =
|
||||
std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
|
||||
|
||||
@@ -30,7 +30,7 @@ impl AssetSource for () {
|
||||
|
||||
/// A unique identifier for the image cache
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct ImageId(pub usize);
|
||||
pub struct ImageId(usize);
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub(crate) struct RenderImageParams {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//!
|
||||
//! # Element Basics
|
||||
//!
|
||||
//! Elements are constructed by calling [`Render::render()`] on the root view of the window,
|
||||
//! Elements are constructed by calling [`Render::render()`] on the root view of the window, which
|
||||
//! which recursively constructs the element tree from the current state of the application,.
|
||||
//! These elements are then laid out by Taffy, and painted to the screen according to their own
|
||||
//! implementation of [`Element::paint()`]. Before the start of the next frame, the entire element
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user