Compare commits

..

2 Commits

Author SHA1 Message Date
Jason Mancuso
a533e622fa clean up documentation, fix links, add to summary 2024-08-20 13:59:41 -04:00
Jason Mancuso
cd7073bd19 docs: add documentation about context servers 2024-08-20 12:35:15 -04:00
275 changed files with 7772 additions and 12083 deletions

View File

@@ -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) {

View File

@@ -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

View File

@@ -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
});

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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
View File

@@ -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)",
]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"],

View File

@@ -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"
}

View File

@@ -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",

View File

@@ -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,

View File

@@ -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)
}))
}
}

View File

@@ -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);

View File

@@ -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);
});
}

View File

@@ -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,
})

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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),
)
})),
),
)
}

View File

@@ -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()
},
)),
),

View File

@@ -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()))?
}
}
}

View File

@@ -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

View File

@@ -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));
}
}
}

View File

@@ -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

View File

@@ -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() {

View File

@@ -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

View File

@@ -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())?;

View File

@@ -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 {}

View File

@@ -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

View File

@@ -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
);

View File

@@ -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);

View File

@@ -1,2 +0,0 @@
ALTER TABLE "worktree_entries"
ADD "is_fifo" BOOL NOT NULL DEFAULT FALSE;

View File

@@ -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>>,

View File

@@ -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>,

View File

@@ -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,
});
}
}

View File

@@ -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,
});
}
}

View File

@@ -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
}
}

View File

@@ -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,

View File

@@ -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)]

View File

@@ -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

View File

@@ -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!(

View File

@@ -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()));
}

View File

@@ -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,
}
}
}

View File

@@ -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,
);
}
}
})

View File

@@ -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()

View File

@@ -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())

View File

@@ -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>(

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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();

View File

@@ -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;

View File

@@ -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))

View File

@@ -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,
},
})
}

View File

@@ -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>,
}

View File

@@ -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

View File

@@ -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));

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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

View File

@@ -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)));
}
};
}

View File

@@ -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"

View File

@@ -1 +0,0 @@
../../LICENSE-GPL

View File

@@ -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"
}
}

View File

@@ -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);
}
}

View File

@@ -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: &regex::Captures| {
let args = self.parse_args(&caps[1]);
self.render(context, &args)
})
.into_owned()
}
}

View File

@@ -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)
}
}

View File

@@ -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>")
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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,
},
)
}

View File

@@ -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, |_| {});

View File

@@ -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,

View File

@@ -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");
});
}
}

View File

@@ -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 {

View File

@@ -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
}
})
})
}

View File

@@ -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 {

View File

@@ -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"
}

View File

@@ -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;
}
}

View File

@@ -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(())
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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

View File

@@ -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!(),
}))

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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());

View File

@@ -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"

View File

@@ -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();
});
}

View File

@@ -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)]

View File

@@ -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 {

View File

@@ -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