Compare commits

..

1 Commits

Author SHA1 Message Date
Mikayla Maki
7c2dffc792 Wire through IPC mechanism for GIT_ASKPASS
co-authored-by: julia@zed.dev
2025-02-28 20:56:11 -08:00
214 changed files with 2433 additions and 7962 deletions

View File

@@ -1,51 +0,0 @@
name: Git Beta
description: There is a bug related to new Git features in Zed
type: "Bug"
labels: [git]
title: "Git Beta: <a short description of the Git bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one line summary of the issue below -->
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
Steps to trigger the problem:
1.
2.
3.
Actual Behavior:
Expected Behavior:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
placeholder: |
Output of "zed: Copy System Specs Into Clipboard"
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
macOS: `~/Library/Logs/Zed/Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary>
<!-- Click below this line and paste or drag-and-drop your log-->
```
```
<!-- Click above this line and paste or drag-and-drop your log--></details>
validations:
required: false

266
Cargo.lock generated
View File

@@ -405,7 +405,6 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"project", "project",
"prompt_library", "prompt_library",
"prompt_store",
"proto", "proto",
"rand 0.8.5", "rand 0.8.5",
"rope", "rope",
@@ -473,7 +472,6 @@ dependencies = [
"picker", "picker",
"project", "project",
"prompt_library", "prompt_library",
"prompt_store",
"proto", "proto",
"rand 0.8.5", "rand 0.8.5",
"rope", "rope",
@@ -528,7 +526,7 @@ dependencies = [
"picker", "picker",
"pretty_assertions", "pretty_assertions",
"project", "project",
"prompt_store", "prompt_library",
"rand 0.8.5", "rand 0.8.5",
"regex", "regex",
"rope", "rope",
@@ -619,7 +617,7 @@ dependencies = [
"log", "log",
"pretty_assertions", "pretty_assertions",
"project", "project",
"prompt_store", "prompt_library",
"rope", "rope",
"schemars", "schemars",
"semantic_index", "semantic_index",
@@ -1178,9 +1176,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-config" name = "aws-config"
version = "1.5.16" version = "1.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50236e4d60fe8458de90a71c0922c761e41755adf091b1b03de1cef537179915" checksum = "490aa7465ee685b2ced076bb87ef654a47724a7844e2c7d3af4e749ce5b875dd"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1271,9 +1269,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-bedrockruntime" name = "aws-sdk-bedrockruntime"
version = "1.74.0" version = "1.75.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6938541d1948a543bca23303fec4cff9c36bf0e63b8fa3ae1b337bcb9d5b81af" checksum = "2ddf7475b6f50a1a5be8edb1bcdf6e4ae00feed5b890d14a3f1f0e14d76f5a16"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1295,9 +1293,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-kinesis" name = "aws-sdk-kinesis"
version = "1.61.0" version = "1.62.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89f2163d8704e8fdcd51ec6c2e0441c418471e422ee9690451b17a1c46344e1a" checksum = "e31622345afd0c35d33c1cbba73ccf9fb88e09857413d8963dea2c493e00704d"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1317,9 +1315,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-s3" name = "aws-sdk-s3"
version = "1.76.0" version = "1.77.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e83401ad7287ad15244d557e35502c2a94105ca5b41d656c391f1a4fc04ca2" checksum = "34e87342432a3de0e94e82c99a7cbd9042f99de029ae1f4e368160f9e9929264"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1351,9 +1349,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sso" name = "aws-sdk-sso"
version = "1.58.0" version = "1.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16ff718c9ee45cc1ebd4774a0e086bb80a6ab752b4902edf1c9f56b86ee1f770" checksum = "60186fab60b24376d3e33b9ff0a43485f99efd470e3b75a9160c849741d63d56"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1373,9 +1371,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-ssooidc" name = "aws-sdk-ssooidc"
version = "1.59.0" version = "1.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5183e088715cc135d8d396fdd3bc02f018f0da4c511f53cb8d795b6a31c55809" checksum = "7033130ce1ee13e6018905b7b976c915963755aef299c1521897679d6cd4f8ef"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1395,9 +1393,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sts" name = "aws-sdk-sts"
version = "1.59.0" version = "1.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9f944ef032717596639cea4a2118a3a457268ef51bbb5fde9637e54c465da00" checksum = "c5c1cac7677179d622b4448b0d31bcb359185295dc6fca891920cfb17e2b5156"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-runtime", "aws-runtime",
@@ -1458,9 +1456,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-checksums" name = "aws-smithy-checksums"
version = "0.62.0" version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f45a1c384d7a393026bc5f5c177105aa9fa68e4749653b985707ac27d77295" checksum = "db2dc8d842d872529355c72632de49ef8c5a2949a4472f10e802f28cf925770c"
dependencies = [ dependencies = [
"aws-smithy-http", "aws-smithy-http",
"aws-smithy-types", "aws-smithy-types",
@@ -1810,7 +1808,7 @@ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.10.5",
"lazy_static", "lazy_static",
"lazycell", "lazycell",
"log", "log",
@@ -1833,7 +1831,7 @@ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.10.5",
"log", "log",
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
@@ -2078,7 +2076,6 @@ name = "buffer_diff"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clock",
"ctor", "ctor",
"env_logger 0.11.6", "env_logger 0.11.6",
"futures 0.3.31", "futures 0.3.31",
@@ -2404,25 +2401,6 @@ dependencies = [
"cipher", "cipher",
] ]
[[package]]
name = "cbindgen"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
dependencies = [
"clap",
"heck 0.4.1",
"indexmap",
"log",
"proc-macro2",
"quote",
"serde",
"serde_json",
"syn 2.0.90",
"tempfile",
"toml 0.8.20",
]
[[package]] [[package]]
name = "cbindgen" name = "cbindgen"
version = "0.28.0" version = "0.28.0"
@@ -2520,9 +2498,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.39" version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@@ -2530,7 +2508,7 @@ dependencies = [
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen", "wasm-bindgen",
"windows-targets 0.52.6", "windows-link",
] ]
[[package]] [[package]]
@@ -2863,7 +2841,7 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"project", "project",
"prometheus", "prometheus",
"prompt_store", "prompt_library",
"prost 0.9.0", "prost 0.9.0",
"rand 0.8.5", "rand 0.8.5",
"recent_projects", "recent_projects",
@@ -3527,11 +3505,10 @@ dependencies = [
[[package]] [[package]]
name = "crc64fast-nvme" name = "crc64fast-nvme"
version = "1.1.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5e2ee08013e3f228d6d2394116c4549a6df77708442c62d887d83f68ef2ee37" checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3"
dependencies = [ dependencies = [
"cbindgen 0.27.0",
"crc", "crc",
] ]
@@ -4310,12 +4287,6 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "env_home"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.10.2" version = "0.10.2"
@@ -5425,11 +5396,8 @@ dependencies = [
"anyhow", "anyhow",
"buffer_diff", "buffer_diff",
"collections", "collections",
"component",
"ctor",
"db", "db",
"editor", "editor",
"env_logger 0.11.6",
"feature_flags", "feature_flags",
"futures 0.3.31", "futures 0.3.31",
"fuzzy", "fuzzy",
@@ -5437,9 +5405,6 @@ dependencies = [
"gpui", "gpui",
"itertools 0.14.0", "itertools 0.14.0",
"language", "language",
"linkify",
"linkme",
"log",
"menu", "menu",
"multi_buffer", "multi_buffer",
"panel", "panel",
@@ -5451,7 +5416,6 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"settings", "settings",
"smallvec",
"strum", "strum",
"theme", "theme",
"time", "time",
@@ -5591,7 +5555,7 @@ dependencies = [
"bytemuck", "bytemuck",
"calloop", "calloop",
"calloop-wayland-source", "calloop-wayland-source",
"cbindgen 0.28.0", "cbindgen",
"cocoa 0.26.0", "cocoa 0.26.0",
"collections", "collections",
"core-foundation 0.9.4", "core-foundation 0.9.4",
@@ -7264,9 +7228,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.169" version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]] [[package]]
name = "libdbus-sys" name = "libdbus-sys"
@@ -7671,25 +7635,6 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "lua-src"
version = "547.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42"
dependencies = [
"cc",
]
[[package]]
name = "luajit-src"
version = "210.5.12+a4f56a4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671"
dependencies = [
"cc",
"which 7.0.2",
]
[[package]] [[package]]
name = "lyon" name = "lyon"
version = "1.0.1" version = "1.0.1"
@@ -8103,33 +8048,6 @@ dependencies = [
"strum", "strum",
] ]
[[package]]
name = "mlua"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3f763c1041eff92ffb5d7169968a327e1ed2ebfe425dac0ee5a35f29082534b"
dependencies = [
"bstr",
"either",
"mlua-sys",
"num-traits",
"parking_lot",
"rustc-hash 2.1.1",
]
[[package]]
name = "mlua-sys"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1901c1a635a22fe9250ffcc4fcc937c16b47c2e9e71adba8784af8bca1f69594"
dependencies = [
"cc",
"cfg-if",
"lua-src",
"luajit-src",
"pkg-config",
]
[[package]] [[package]]
name = "msvc_spectre_libs" name = "msvc_spectre_libs"
version = "0.1.2" version = "0.1.2"
@@ -9839,6 +9757,15 @@ dependencies = [
"indexmap", "indexmap",
] ]
[[package]]
name = "pgvector"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0e8871b6d7ca78348c6cd29b911b94851f3429f0cd403130ca17f26c1fb91a6"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.2" version = "0.11.2"
@@ -10383,36 +10310,12 @@ dependencies = [
[[package]] [[package]]
name = "prompt_library" name = "prompt_library"
version = "0.1.0" version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"editor",
"gpui",
"language",
"language_model",
"log",
"menu",
"picker",
"prompt_store",
"release_channel",
"rope",
"serde",
"settings",
"theme",
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
name = "prompt_store"
version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assets", "assets",
"chrono", "chrono",
"collections", "collections",
"editor",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"fuzzy", "fuzzy",
@@ -10420,14 +10323,23 @@ dependencies = [
"handlebars 4.5.0", "handlebars 4.5.0",
"heed", "heed",
"language", "language",
"language_model",
"log", "log",
"menu",
"parking_lot", "parking_lot",
"paths", "paths",
"picker",
"release_channel",
"rope", "rope",
"serde", "serde",
"settings",
"text", "text",
"theme",
"ui",
"util", "util",
"uuid", "uuid",
"workspace",
"zed_actions",
] ]
[[package]] [[package]]
@@ -10478,7 +10390,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [ dependencies = [
"bytes 1.10.0", "bytes 1.10.0",
"heck 0.5.0", "heck 0.5.0",
"itertools 0.12.1", "itertools 0.10.5",
"log", "log",
"multimap 0.10.0", "multimap 0.10.0",
"once_cell", "once_cell",
@@ -10511,7 +10423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.12.1", "itertools 0.10.5",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.90",
@@ -11539,9 +11451,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed" name = "rust-embed"
version = "8.5.0" version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f"
dependencies = [ dependencies = [
"rust-embed-impl", "rust-embed-impl",
"rust-embed-utils", "rust-embed-utils",
@@ -11550,9 +11462,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed-impl" name = "rust-embed-impl"
version = "8.5.0" version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -11563,9 +11475,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-embed-utils" name = "rust-embed-utils"
version = "8.5.0" version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a"
dependencies = [ dependencies = [
"globset", "globset",
"sha2", "sha2",
@@ -11840,9 +11752,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.21" version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
dependencies = [ dependencies = [
"dyn-clone", "dyn-clone",
"indexmap", "indexmap",
@@ -11853,9 +11765,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars_derive" name = "schemars_derive"
version = "0.8.21" version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -11881,23 +11793,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
[[package]]
name = "scripting_tool"
version = "0.1.0"
dependencies = [
"anyhow",
"assistant_tool",
"gpui",
"mlua",
"project",
"regex",
"schemars",
"serde",
"serde_json",
"util",
"workspace",
]
[[package]] [[package]]
name = "scrypt" name = "scrypt"
version = "0.11.0" version = "0.11.0"
@@ -11935,17 +11830,18 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm" name = "sea-orm"
version = "1.1.5" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00733e5418e8ae3758cdb988c3654174e716230cc53ee2cb884207cf86a23029" checksum = "13fba7b2c749b2d0a00303d5cb13e6761e39a4172554bdf930852cac4e7aeabd"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
"bigdecimal", "bigdecimal",
"chrono", "chrono",
"futures 0.3.31", "futures-util",
"log", "log",
"ouroboros", "ouroboros",
"pgvector",
"rust_decimal", "rust_decimal",
"sea-orm-macros", "sea-orm-macros",
"sea-query", "sea-query",
@@ -11963,9 +11859,9 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm-macros" name = "sea-orm-macros"
version = "1.1.5" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a98408f82fb4875d41ef469a79944a7da29767c7b3e4028e22188a3dd613b10f" checksum = "2568cff8d35d5150b4276cc0dd766192a587f64b6ece60ae3706e0872c4eb209"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.4.1",
"proc-macro2", "proc-macro2",
@@ -14885,9 +14781,9 @@ dependencies = [
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.13.2" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
dependencies = [ dependencies = [
"getrandom 0.3.1", "getrandom 0.3.1",
"serde", "serde",
@@ -14989,7 +14885,6 @@ dependencies = [
"multi_buffer", "multi_buffer",
"nvim-rs", "nvim-rs",
"parking_lot", "parking_lot",
"project",
"project_panel", "project_panel",
"regex", "regex",
"release_channel", "release_channel",
@@ -15792,18 +15687,6 @@ dependencies = [
"winsafe", "winsafe",
] ]
[[package]]
name = "which"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283"
dependencies = [
"either",
"env_home",
"rustix",
"winsafe",
]
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.5.2" version = "1.5.2"
@@ -16023,6 +15906,12 @@ dependencies = [
"syn 2.0.90", "syn 2.0.90",
] ]
[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
[[package]] [[package]]
name = "windows-registry" name = "windows-registry"
version = "0.2.0" version = "0.2.0"
@@ -16858,7 +16747,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.178.0" version = "0.177.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"anyhow", "anyhow",
@@ -16936,7 +16825,7 @@ dependencies = [
"project", "project",
"project_panel", "project_panel",
"project_symbols", "project_symbols",
"prompt_store", "prompt_library",
"proto", "proto",
"recent_projects", "recent_projects",
"release_channel", "release_channel",
@@ -16944,7 +16833,6 @@ dependencies = [
"repl", "repl",
"reqwest_client", "reqwest_client",
"rope", "rope",
"scripting_tool",
"search", "search",
"serde", "serde",
"serde_json", "serde_json",
@@ -16963,6 +16851,7 @@ dependencies = [
"tasks_ui", "tasks_ui",
"telemetry", "telemetry",
"telemetry_events", "telemetry_events",
"tempfile",
"terminal_view", "terminal_view",
"theme", "theme",
"theme_extension", "theme_extension",
@@ -16979,6 +16868,7 @@ dependencies = [
"vim", "vim",
"vim_mode_setting", "vim_mode_setting",
"welcome", "welcome",
"which 6.0.3",
"windows 0.58.0", "windows 0.58.0",
"winresource", "winresource",
"workspace", "workspace",

View File

@@ -103,7 +103,6 @@ members = [
"crates/project_panel", "crates/project_panel",
"crates/project_symbols", "crates/project_symbols",
"crates/prompt_library", "crates/prompt_library",
"crates/prompt_store",
"crates/proto", "crates/proto",
"crates/recent_projects", "crates/recent_projects",
"crates/refineable", "crates/refineable",
@@ -117,7 +116,6 @@ members = [
"crates/rope", "crates/rope",
"crates/rpc", "crates/rpc",
"crates/schema_generator", "crates/schema_generator",
"crates/scripting_tool",
"crates/search", "crates/search",
"crates/semantic_index", "crates/semantic_index",
"crates/semantic_version", "crates/semantic_version",
@@ -310,7 +308,6 @@ project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" } project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" } project_symbols = { path = "crates/project_symbols" }
prompt_library = { path = "crates/prompt_library" } prompt_library = { path = "crates/prompt_library" }
prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" } proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" } recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" } refineable = { path = "crates/refineable" }
@@ -322,7 +319,6 @@ reqwest_client = { path = "crates/reqwest_client" }
rich_text = { path = "crates/rich_text" } rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" } rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" } rpc = { path = "crates/rpc" }
scripting_tool = { path = "crates/scripting_tool" }
search = { path = "crates/search" } search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" } semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" } semantic_version = { path = "crates/semantic_version" }
@@ -455,7 +451,6 @@ livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "
], default-features = false } ], default-features = false }
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0" markup5ever_rcdom = "0.3.0"
mlua = { version = "0.10", features = ["lua54", "vendored"] }
nanoid = "0.4" nanoid = "0.4"
nbformat = { version = "0.10.0" } nbformat = { version = "0.10.0" }
nix = "0.29" nix = "0.29"
@@ -754,4 +749,4 @@ should_implement_trait = { level = "allow" }
let_underscore_future = "allow" let_underscore_future = "allow"
[workspace.metadata.cargo-machete] [workspace.metadata.cargo-machete]
ignored = ["bindgen", "cbindgen", "prost_build", "serde", "component", "linkme"] ignored = ["bindgen", "cbindgen", "prost_build", "serde"]

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-cloud"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>

Before

Width:  |  Height:  |  Size: 279 B

View File

@@ -1,6 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 3.25C4.02614 3.25 4.25 3.02614 4.25 2.75C4.25 2.47386 4.02614 2.25 3.75 2.25C3.47386 2.25 3.25 2.47386 3.25 2.75C3.25 3.02614 3.47386 3.25 3.75 3.25ZM3.75 4.25C4.57843 4.25 5.25 3.57843 5.25 2.75C5.25 1.92157 4.57843 1.25 3.75 1.25C2.92157 1.25 2.25 1.92157 2.25 2.75C2.25 3.57843 2.92157 4.25 3.75 4.25Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.25 3.25C8.52614 3.25 8.75 3.02614 8.75 2.75C8.75 2.47386 8.52614 2.25 8.25 2.25C7.97386 2.25 7.75 2.47386 7.75 2.75C7.75 3.02614 7.97386 3.25 8.25 3.25ZM8.25 4.25C9.07843 4.25 9.75 3.57843 9.75 2.75C9.75 1.92157 9.07843 1.25 8.25 1.25C7.42157 1.25 6.75 1.92157 6.75 2.75C6.75 3.57843 7.42157 4.25 8.25 4.25Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 9.75C4.02614 9.75 4.25 9.52614 4.25 9.25C4.25 8.97386 4.02614 8.75 3.75 8.75C3.47386 8.75 3.25 8.97386 3.25 9.25C3.25 9.52614 3.47386 9.75 3.75 9.75ZM3.75 10.75C4.57843 10.75 5.25 10.0784 5.25 9.25C5.25 8.42157 4.57843 7.75 3.75 7.75C2.92157 7.75 2.25 8.42157 2.25 9.25C2.25 10.0784 2.92157 10.75 3.75 10.75Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 3.75H4.25V5.59609C4.67823 5.35824 5.24991 5.25 6 5.25H7.25017C7.5262 5.25 7.75 5.02625 7.75 4.75V3.75H8.75V4.75C8.75 5.57832 8.07871 6.25 7.25017 6.25H6C5.14559 6.25 4.77639 6.41132 4.59684 6.56615C4.42571 6.71373 4.33877 6.92604 4.25 7.30651V8.25H3.25V3.75Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,5 +1,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.5"/> <g clip-path="url(#clip0_2131_1193)">
<path d="M7 11H8M8 11H9M8 11V8.1C8 8.04477 7.95523 8 7.9 8H7" stroke="black" stroke-width="1.5" stroke-linecap="round"/> <circle cx="7" cy="7" r="6" stroke="black" stroke-width="1.5"/>
<path d="M8 6.5C8.55228 6.5 9 6.05228 9 5.5C9 4.94772 8.55228 4.5 8 4.5C7.44772 4.5 7 4.94772 7 5.5C7 6.05228 7.44772 6.5 8 6.5Z" fill="black"/> <path d="M6 10H7M8 10H7M7 10V7.1C7 7.04477 6.95523 7 6.9 7H6" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="7" cy="4.5" r="1" fill="black"/>
</g>
<defs>
<clipPath id="clip0_2131_1193">
<rect width="14" height="14" fill="white"/>
</clipPath>
</defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 479 B

View File

@@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.6" d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
<path d="M14 8L10 12M14 12L10 8" stroke="black" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 296 B

View File

@@ -10,8 +10,8 @@
"pagedown": "menu::SelectLast", "pagedown": "menu::SelectLast",
"ctrl-n": "menu::SelectNext", "ctrl-n": "menu::SelectNext",
"tab": "menu::SelectNext", "tab": "menu::SelectNext",
"ctrl-p": "menu::SelectPrevious", "ctrl-p": "menu::SelectPrev",
"shift-tab": "menu::SelectPrevious", "shift-tab": "menu::SelectPrev",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm", "ctrl-enter": "menu::SecondaryConfirm",
"ctrl-escape": "menu::Cancel", "ctrl-escape": "menu::Cancel",
@@ -38,14 +38,14 @@
{ {
"context": "Picker || menu", "context": "Picker || menu",
"bindings": { "bindings": {
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"down": "menu::SelectNext" "down": "menu::SelectNext"
} }
}, },
{ {
"context": "Prompt", "context": "Prompt",
"bindings": { "bindings": {
"left": "menu::SelectPrevious", "left": "menu::SelectPrev",
"right": "menu::SelectNext" "right": "menu::SelectNext"
} }
}, },
@@ -57,7 +57,7 @@
"backspace": "editor::Backspace", "backspace": "editor::Backspace",
"delete": "editor::Delete", "delete": "editor::Delete",
"tab": "editor::Tab", "tab": "editor::Tab",
"shift-tab": "editor::Backtab", "shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine", "ctrl-k": "editor::CutToEndOfLine",
// "ctrl-t": "editor::Transpose", // "ctrl-t": "editor::Transpose",
"ctrl-k ctrl-q": "editor::Rewrap", "ctrl-k ctrl-q": "editor::Rewrap",
@@ -180,7 +180,7 @@
"ctrl-k c": "assistant::CopyCode", "ctrl-k c": "assistant::CopyCode",
"ctrl-shift-e": "project_panel::ToggleFocus", "ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-g": "search::SelectNextMatch", "ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPreviousMatch", "ctrl-shift-g": "search::SelectPrevMatch",
"ctrl-alt-/": "assistant::ToggleModelSelector", "ctrl-alt-/": "assistant::ToggleModelSelector",
"ctrl-k h": "assistant::DeployHistory", "ctrl-k h": "assistant::DeployHistory",
"ctrl-k l": "assistant::DeployPromptLibrary", "ctrl-k l": "assistant::DeployPromptLibrary",
@@ -203,7 +203,7 @@
"escape": "buffer_search::Dismiss", "escape": "buffer_search::Dismiss",
"tab": "buffer_search::FocusEditor", "tab": "buffer_search::FocusEditor",
"enter": "search::SelectNextMatch", "enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPreviousMatch", "shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches", "alt-enter": "search::SelectAllMatches",
"find": "search::FocusSearch", "find": "search::FocusSearch",
"ctrl-f": "search::FocusSearch", "ctrl-f": "search::FocusSearch",
@@ -272,7 +272,7 @@
"alt-8": ["pane::ActivateItem", 7], "alt-8": ["pane::ActivateItem", 7],
"alt-9": ["pane::ActivateItem", 8], "alt-9": ["pane::ActivateItem", 8],
"alt-0": "pane::ActivateLastItem", "alt-0": "pane::ActivateLastItem",
"ctrl-pageup": "pane::ActivatePreviousItem", "ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft", "ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight", "ctrl-shift-pagedown": "pane::SwapItemRight",
@@ -290,8 +290,8 @@
"forward": "pane::GoForward", "forward": "pane::GoForward",
"ctrl-alt-g": "search::SelectNextMatch", "ctrl-alt-g": "search::SelectNextMatch",
"f3": "search::SelectNextMatch", "f3": "search::SelectNextMatch",
"ctrl-alt-shift-g": "search::SelectPreviousMatch", "ctrl-alt-shift-g": "search::SelectPrevMatch",
"shift-f3": "search::SelectPreviousMatch", "shift-f3": "search::SelectPrevMatch",
"shift-find": "project_search::ToggleFocus", "shift-find": "project_search::ToggleFocus",
"ctrl-shift-f": "project_search::ToggleFocus", "ctrl-shift-f": "project_search::ToggleFocus",
"ctrl-alt-shift-h": "search::ToggleReplace", "ctrl-alt-shift-h": "search::ToggleReplace",
@@ -334,7 +334,7 @@
"ctrl-u": "editor::UndoSelection", "ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection", "ctrl-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic", "f8": "editor::GoToDiagnostic",
"shift-f8": "editor::GoToPreviousDiagnostic", "shift-f8": "editor::GoToPrevDiagnostic",
"f2": "editor::Rename", "f2": "editor::Rename",
"f12": "editor::GoToDefinition", "f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit", "alt-f12": "editor::GoToDefinitionSplit",
@@ -373,7 +373,7 @@
"alt-y": "git::StageAndNext", "alt-y": "git::StageAndNext",
"alt-shift-y": "git::UnstageAndNext", "alt-shift-y": "git::UnstageAndNext",
"alt-.": "editor::GoToHunk", "alt-.": "editor::GoToHunk",
"alt-,": "editor::GoToPreviousHunk" "alt-,": "editor::GoToPrevHunk"
} }
}, },
{ {
@@ -536,8 +536,8 @@
{ {
"context": "Editor && (showing_code_actions || showing_completions)", "context": "Editor && (showing_code_actions || showing_completions)",
"bindings": { "bindings": {
"ctrl-p": "editor::ContextMenuPrevious", "ctrl-p": "editor::ContextMenuPrev",
"up": "editor::ContextMenuPrevious", "up": "editor::ContextMenuPrev",
"ctrl-n": "editor::ContextMenuNext", "ctrl-n": "editor::ContextMenuNext",
"down": "editor::ContextMenuNext", "down": "editor::ContextMenuNext",
"pageup": "editor::ContextMenuFirst", "pageup": "editor::ContextMenuFirst",
@@ -565,7 +565,7 @@
"ctrl-alt-enter": "editor::OpenExcerptsSplit", "ctrl-alt-enter": "editor::OpenExcerptsSplit",
"ctrl-shift-e": "pane::RevealInProjectPanel", "ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk", "ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk", "ctrl-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist", "ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints" "ctrl-:": "editor::ToggleInlayHints"
} }
@@ -612,29 +612,12 @@
"ctrl-alt-e": "assistant2::RemoveAllContext" "ctrl-alt-e": "assistant2::RemoveAllContext"
} }
}, },
{
"context": "AssistantPanel2 && prompt_editor",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "assistant2::NewPromptEditor",
"cmd-alt-t": "assistant2::NewThread"
}
},
{ {
"context": "MessageEditor > Editor", "context": "MessageEditor > Editor",
"bindings": { "bindings": {
"enter": "assistant2::Chat" "enter": "assistant2::Chat"
} }
}, },
{
"context": "EditMessageEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel",
"enter": "menu::Confirm",
"alt-enter": "editor::Newline"
}
},
{ {
"context": "ContextStrip", "context": "ContextStrip",
"bindings": { "bindings": {
@@ -679,7 +662,7 @@
"alt-ctrl-r": "outline_panel::RevealInFileManager", "alt-ctrl-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::Open", "space": "outline_panel::Open",
"shift-down": "menu::SelectNext", "shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious", "shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts", "alt-enter": "editor::OpenExcerpts",
"ctrl-alt-enter": "editor::OpenExcerptsSplit" "ctrl-alt-enter": "editor::OpenExcerptsSplit"
} }
@@ -717,7 +700,7 @@
"shift-find": "project_panel::NewSearchInDirectory", "shift-find": "project_panel::NewSearchInDirectory",
"ctrl-shift-f": "project_panel::NewSearchInDirectory", "ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext", "shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious", "shift-up": "menu::SelectPrev",
"escape": "menu::Cancel" "escape": "menu::Cancel"
} }
}, },
@@ -730,7 +713,7 @@
{ {
"context": "GitPanel && ChangesList", "context": "GitPanel && ChangesList",
"bindings": { "bindings": {
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"space": "git::ToggleStaged", "space": "git::ToggleStaged",
@@ -739,7 +722,7 @@
"tab": "git_panel::FocusEditor", "tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor", "shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus", "escape": "git_panel::ToggleFocus",
"ctrl-enter": "git::ShowCommitEditor", "ctrl-enter": "git::Commit",
"alt-enter": "menu::SecondaryConfirm" "alt-enter": "menu::SecondaryConfirm"
} }
}, },
@@ -753,7 +736,7 @@
{ {
"context": "GitDiff > Editor", "context": "GitDiff > Editor",
"bindings": { "bindings": {
"ctrl-enter": "git::ShowCommitEditor" "ctrl-enter": "git::Commit"
} }
}, },
{ {
@@ -766,6 +749,14 @@
"alt-up": "git_panel::FocusChanges" "alt-up": "git_panel::FocusChanges"
} }
}, },
{
"context": "GitCommit > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
"ctrl-enter": "git::Commit"
}
},
{ {
"context": "CollabPanel && not_editing", "context": "CollabPanel && not_editing",
"bindings": { "bindings": {
@@ -788,9 +779,6 @@
{ {
"context": "Picker > Editor", "context": "Picker > Editor",
"bindings": { "bindings": {
"escape": "menu::Cancel",
"up": "menu::SelectPrevious",
"down": "menu::SelectNext",
"tab": "picker::ConfirmCompletion", "tab": "picker::ConfirmCompletion",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }] "alt-enter": ["picker::ConfirmInput", { "secondary": false }]
} }
@@ -804,7 +792,7 @@
{ {
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)", "context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
"bindings": { "bindings": {
"ctrl-shift-p": "file_finder::SelectPrevious", "ctrl-shift-p": "file_finder::SelectPrev",
"ctrl-j": "pane::SplitDown", "ctrl-j": "pane::SplitDown",
"ctrl-k": "pane::SplitUp", "ctrl-k": "pane::SplitUp",
"ctrl-h": "pane::SplitLeft", "ctrl-h": "pane::SplitLeft",
@@ -814,8 +802,8 @@
{ {
"context": "TabSwitcher", "context": "TabSwitcher",
"bindings": { "bindings": {
"ctrl-shift-tab": "menu::SelectPrevious", "ctrl-shift-tab": "menu::SelectPrev",
"ctrl-up": "menu::SelectPrevious", "ctrl-up": "menu::SelectPrev",
"ctrl-down": "menu::SelectNext", "ctrl-down": "menu::SelectNext",
"ctrl-backspace": "tab_switcher::CloseSelectedItem" "ctrl-backspace": "tab_switcher::CloseSelectedItem"
} }

View File

@@ -1,15 +1,4 @@
[ [
// Moved before Standard macOS bindings so that `cmd-w` is not the last binding for
// `workspace::CloseWindow` and displayed/intercepted by macOS
{
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "prompt_library::NewPrompt",
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
"cmd-w": "workspace::CloseWindow"
}
},
// Standard macOS bindings // Standard macOS bindings
{ {
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -25,19 +14,19 @@
"tab": "menu::SelectNext", "tab": "menu::SelectNext",
"ctrl-n": "menu::SelectNext", "ctrl-n": "menu::SelectNext",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"shift-tab": "menu::SelectPrevious", "shift-tab": "menu::SelectPrev",
"ctrl-p": "menu::SelectPrevious", "ctrl-p": "menu::SelectPrev",
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm", "ctrl-enter": "menu::SecondaryConfirm",
"cmd-enter": "menu::SecondaryConfirm", "cmd-enter": "menu::SecondaryConfirm",
"ctrl-escape": "menu::Cancel", "ctrl-escape": "menu::Cancel",
"cmd-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel", "ctrl-c": "menu::Cancel",
"escape": "menu::Cancel", "escape": "menu::Cancel",
"alt-shift-enter": "menu::Restart", "alt-shift-enter": "menu::Restart",
"cmd-shift-w": "workspace::CloseWindow", "cmd-shift-w": "workspace::CloseWindow",
"shift-escape": "workspace::ToggleZoom", "shift-escape": "workspace::ToggleZoom",
"cmd-escape": "menu::Cancel",
"cmd-o": "workspace::Open", "cmd-o": "workspace::Open",
"cmd-=": ["zed::IncreaseBufferFontSize", { "persist": false }], "cmd-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }], "cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
@@ -65,7 +54,7 @@
"ctrl-d": "editor::Delete", "ctrl-d": "editor::Delete",
"delete": "editor::Delete", "delete": "editor::Delete",
"tab": "editor::Tab", "tab": "editor::Tab",
"shift-tab": "editor::Backtab", "shift-tab": "editor::TabPrev",
"ctrl-t": "editor::Transpose", "ctrl-t": "editor::Transpose",
"ctrl-k": "editor::KillRingCut", "ctrl-k": "editor::KillRingCut",
"ctrl-y": "editor::KillRingYank", "ctrl-y": "editor::KillRingYank",
@@ -129,7 +118,6 @@
"cmd-a": "editor::SelectAll", "cmd-a": "editor::SelectAll",
"cmd-l": "editor::SelectLine", "cmd-l": "editor::SelectLine",
"cmd-shift-i": "editor::Format", "cmd-shift-i": "editor::Format",
"alt-shift-o": "editor::OrganizeImports",
"cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
"ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }],
@@ -219,7 +207,7 @@
"cmd-k c": "assistant::CopyCode", "cmd-k c": "assistant::CopyCode",
"cmd-shift-e": "project_panel::ToggleFocus", "cmd-shift-e": "project_panel::ToggleFocus",
"cmd-g": "search::SelectNextMatch", "cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPreviousMatch", "cmd-shift-g": "search::SelectPrevMatch",
"cmd-alt-/": "assistant::ToggleModelSelector", "cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-k h": "assistant::DeployHistory", "cmd-k h": "assistant::DeployHistory",
"cmd-k l": "assistant::DeployPromptLibrary", "cmd-k l": "assistant::DeployPromptLibrary",
@@ -256,14 +244,6 @@
"cmd-alt-e": "assistant2::RemoveAllContext" "cmd-alt-e": "assistant2::RemoveAllContext"
} }
}, },
{
"context": "AssistantPanel2 && prompt_editor",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "assistant2::NewPromptEditor",
"cmd-alt-t": "assistant2::NewThread"
}
},
{ {
"context": "MessageEditor > Editor", "context": "MessageEditor > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -271,15 +251,6 @@
"enter": "assistant2::Chat" "enter": "assistant2::Chat"
} }
}, },
{
"context": "EditMessageEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel",
"enter": "menu::Confirm",
"alt-enter": "editor::Newline"
}
},
{ {
"context": "ContextStrip", "context": "ContextStrip",
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -298,6 +269,15 @@
"backspace": "assistant2::RemoveSelectedThread" "backspace": "assistant2::RemoveSelectedThread"
} }
}, },
{
"context": "PromptLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "prompt_library::NewPrompt",
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
"cmd-w": "workspace::CloseWindow"
}
},
{ {
"context": "BufferSearchBar", "context": "BufferSearchBar",
"use_key_equivalents": true, "use_key_equivalents": true,
@@ -305,7 +285,7 @@
"escape": "buffer_search::Dismiss", "escape": "buffer_search::Dismiss",
"tab": "buffer_search::FocusEditor", "tab": "buffer_search::FocusEditor",
"enter": "search::SelectNextMatch", "enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPreviousMatch", "shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches", "alt-enter": "search::SelectAllMatches",
"cmd-f": "search::FocusSearch", "cmd-f": "search::FocusSearch",
"cmd-alt-f": "search::ToggleReplace", "cmd-alt-f": "search::ToggleReplace",
@@ -372,8 +352,8 @@
"context": "Pane", "context": "Pane",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"alt-cmd-left": "pane::ActivatePreviousItem", "alt-cmd-left": "pane::ActivatePrevItem",
"cmd-{": "pane::ActivatePreviousItem", "cmd-{": "pane::ActivatePrevItem",
"alt-cmd-right": "pane::ActivateNextItem", "alt-cmd-right": "pane::ActivateNextItem",
"cmd-}": "pane::ActivateNextItem", "cmd-}": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft", "ctrl-shift-pageup": "pane::SwapItemLeft",
@@ -387,7 +367,7 @@
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }], "cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
"cmd-f": "project_search::ToggleFocus", "cmd-f": "project_search::ToggleFocus",
"cmd-g": "search::SelectNextMatch", "cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPreviousMatch", "cmd-shift-g": "search::SelectPrevMatch",
"cmd-shift-h": "search::ToggleReplace", "cmd-shift-h": "search::ToggleReplace",
"cmd-alt-l": "search::ToggleSelection", "cmd-alt-l": "search::ToggleSelection",
"alt-enter": "search::SelectAllMatches", "alt-enter": "search::SelectAllMatches",
@@ -427,7 +407,7 @@
"cmd-u": "editor::UndoSelection", "cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection", "cmd-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic", "f8": "editor::GoToDiagnostic",
"shift-f8": "editor::GoToPreviousDiagnostic", "shift-f8": "editor::GoToPrevDiagnostic",
"f2": "editor::Rename", "f2": "editor::Rename",
"f12": "editor::GoToDefinition", "f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit", "alt-f12": "editor::GoToDefinitionSplit",
@@ -633,8 +613,8 @@
"context": "Editor && (showing_code_actions || showing_completions)", "context": "Editor && (showing_code_actions || showing_completions)",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"up": "editor::ContextMenuPrevious", "up": "editor::ContextMenuPrev",
"ctrl-p": "editor::ContextMenuPrevious", "ctrl-p": "editor::ContextMenuPrev",
"down": "editor::ContextMenuNext", "down": "editor::ContextMenuNext",
"ctrl-n": "editor::ContextMenuNext", "ctrl-n": "editor::ContextMenuNext",
"pageup": "editor::ContextMenuFirst", "pageup": "editor::ContextMenuFirst",
@@ -660,7 +640,7 @@
"cmd-alt-enter": "editor::OpenExcerptsSplit", "cmd-alt-enter": "editor::OpenExcerptsSplit",
"cmd-shift-e": "pane::RevealInProjectPanel", "cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk", "cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPreviousHunk", "cmd-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist", "ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints" "ctrl-:": "editor::ToggleInlayHints"
} }
@@ -703,7 +683,7 @@
"alt-cmd-r": "outline_panel::RevealInFileManager", "alt-cmd-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::Open", "space": "outline_panel::Open",
"shift-down": "menu::SelectNext", "shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious", "shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts", "alt-enter": "editor::OpenExcerpts",
"cmd-alt-enter": "editor::OpenExcerptsSplit" "cmd-alt-enter": "editor::OpenExcerptsSplit"
} }
@@ -733,7 +713,7 @@
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }], "cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-shift-f": "project_panel::NewSearchInDirectory", "cmd-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext", "shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious", "shift-up": "menu::SelectPrev",
"escape": "menu::Cancel" "escape": "menu::Cancel"
} }
}, },
@@ -748,7 +728,7 @@
"context": "GitPanel && ChangesList", "context": "GitPanel && ChangesList",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"cmd-up": "menu::SelectFirst", "cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast", "cmd-down": "menu::SelectLast",
@@ -760,14 +740,14 @@
"tab": "git_panel::FocusEditor", "tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor", "shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus", "escape": "git_panel::ToggleFocus",
"cmd-enter": "git::ShowCommitEditor" "cmd-enter": "git::Commit"
} }
}, },
{ {
"context": "GitDiff > Editor", "context": "GitDiff > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-enter": "git::ShowCommitEditor" "cmd-enter": "git::Commit"
} }
}, },
{ {
@@ -815,9 +795,6 @@
"context": "Picker > Editor", "context": "Picker > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"escape": "menu::Cancel",
"up": "menu::SelectPrevious",
"down": "menu::SelectNext",
"tab": "picker::ConfirmCompletion", "tab": "picker::ConfirmCompletion",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }], "alt-enter": ["picker::ConfirmInput", { "secondary": false }],
"cmd-alt-enter": ["picker::ConfirmInput", { "secondary": true }] "cmd-alt-enter": ["picker::ConfirmInput", { "secondary": true }]
@@ -834,7 +811,7 @@
"context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)", "context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-shift-p": "file_finder::SelectPrevious", "cmd-shift-p": "file_finder::SelectPrev",
"cmd-j": "pane::SplitDown", "cmd-j": "pane::SplitDown",
"cmd-k": "pane::SplitUp", "cmd-k": "pane::SplitUp",
"cmd-h": "pane::SplitLeft", "cmd-h": "pane::SplitLeft",
@@ -845,8 +822,8 @@
"context": "TabSwitcher", "context": "TabSwitcher",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"ctrl-shift-tab": "menu::SelectPrevious", "ctrl-shift-tab": "menu::SelectPrev",
"ctrl-up": "menu::SelectPrevious", "ctrl-up": "menu::SelectPrev",
"ctrl-down": "menu::SelectNext", "ctrl-down": "menu::SelectNext",
"ctrl-backspace": "tab_switcher::CloseSelectedItem" "ctrl-backspace": "tab_switcher::CloseSelectedItem"
} }

View File

@@ -39,7 +39,7 @@
"context": "BufferSearchBar", "context": "BufferSearchBar",
"bindings": { "bindings": {
"ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected "ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected
"ctrl-shift-f3": "search::SelectPreviousMatch" // find-and-replace:find-previous-selected "ctrl-shift-f3": "search::SelectPrevMatch" // find-and-replace:find-previous-selected
} }
}, },
{ {

View File

@@ -122,7 +122,7 @@
"context": "BufferSearchBar > Editor", "context": "BufferSearchBar > Editor",
"bindings": { "bindings": {
"ctrl-s": "search::SelectNextMatch", "ctrl-s": "search::SelectNextMatch",
"ctrl-r": "search::SelectPreviousMatch", "ctrl-r": "search::SelectPrevMatch",
"ctrl-g": "buffer_search::Dismiss" "ctrl-g": "buffer_search::Dismiss"
} }
}, },

View File

@@ -2,7 +2,7 @@
{ {
"bindings": { "bindings": {
"ctrl-alt-s": "zed::OpenSettings", "ctrl-alt-s": "zed::OpenSettings",
"ctrl-{": "pane::ActivatePreviousItem", "ctrl-{": "pane::ActivatePrevItem",
"ctrl-}": "pane::ActivateNextItem" "ctrl-}": "pane::ActivateNextItem"
} }
}, },
@@ -41,9 +41,9 @@
"ctrl-shift-b": "editor::GoToTypeDefinition", "ctrl-shift-b": "editor::GoToTypeDefinition",
"ctrl-alt-shift-b": "editor::GoToTypeDefinitionSplit", "ctrl-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"f2": "editor::GoToDiagnostic", "f2": "editor::GoToDiagnostic",
"shift-f2": "editor::GoToPreviousDiagnostic", "shift-f2": "editor::GoToPrevDiagnostic",
"ctrl-alt-shift-down": "editor::GoToHunk", "ctrl-alt-shift-down": "editor::GoToHunk",
"ctrl-alt-shift-up": "editor::GoToPreviousHunk", "ctrl-alt-shift-up": "editor::GoToPrevHunk",
"ctrl-alt-z": "git::Restore", "ctrl-alt-z": "git::Restore",
"ctrl-home": "editor::MoveToBeginning", "ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd", "ctrl-end": "editor::MoveToEnd",

View File

@@ -1,9 +1,9 @@
[ [
{ {
"bindings": { "bindings": {
"ctrl-{": "pane::ActivatePreviousItem", "ctrl-{": "pane::ActivatePrevItem",
"ctrl-}": "pane::ActivateNextItem", "ctrl-}": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePreviousItem", "ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-1": ["workspace::ActivatePane", 0], "ctrl-1": ["workspace::ActivatePane", 0],
"ctrl-2": ["workspace::ActivatePane", 1], "ctrl-2": ["workspace::ActivatePane", 1],
@@ -44,7 +44,7 @@
"shift-f12": "editor::FindAllReferences", "shift-f12": "editor::FindAllReferences",
"ctrl-shift-f12": "editor::FindAllReferences", "ctrl-shift-f12": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk", "ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPreviousHunk", "ctrl-,": "editor::GoToPrevHunk",
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", "ctrl-k ctrl-u": "editor::ConvertToUpperCase",
"ctrl-k ctrl-l": "editor::ConvertToLowerCase", "ctrl-k ctrl-l": "editor::ConvertToLowerCase",
"shift-alt-m": "markdown::OpenPreviewToTheSide", "shift-alt-m": "markdown::OpenPreviewToTheSide",
@@ -62,7 +62,7 @@
"context": "Pane", "context": "Pane",
"bindings": { "bindings": {
"f4": "search::SelectNextMatch", "f4": "search::SelectNextMatch",
"shift-f4": "search::SelectPreviousMatch", "shift-f4": "search::SelectPrevMatch",
"alt-1": ["pane::ActivateItem", 0], "alt-1": ["pane::ActivateItem", 0],
"alt-2": ["pane::ActivateItem", 1], "alt-2": ["pane::ActivateItem", 1],
"alt-3": ["pane::ActivateItem", 2], "alt-3": ["pane::ActivateItem", 2],

View File

@@ -40,7 +40,7 @@
"context": "BufferSearchBar", "context": "BufferSearchBar",
"bindings": { "bindings": {
"cmd-f3": "search::SelectNextMatch", "cmd-f3": "search::SelectNextMatch",
"cmd-shift-f3": "search::SelectPreviousMatch" "cmd-shift-f3": "search::SelectPrevMatch"
} }
}, },
{ {

View File

@@ -122,7 +122,7 @@
"context": "BufferSearchBar > Editor", "context": "BufferSearchBar > Editor",
"bindings": { "bindings": {
"ctrl-s": "search::SelectNextMatch", "ctrl-s": "search::SelectNextMatch",
"ctrl-r": "search::SelectPreviousMatch", "ctrl-r": "search::SelectPrevMatch",
"ctrl-g": "buffer_search::Dismiss" "ctrl-g": "buffer_search::Dismiss"
} }
}, },

View File

@@ -1,7 +1,7 @@
[ [
{ {
"bindings": { "bindings": {
"cmd-{": "pane::ActivatePreviousItem", "cmd-{": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem" "cmd-}": "pane::ActivateNextItem"
} }
}, },
@@ -39,9 +39,9 @@
"cmd-shift-b": "editor::GoToTypeDefinition", "cmd-shift-b": "editor::GoToTypeDefinition",
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit", "cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"f2": "editor::GoToDiagnostic", "f2": "editor::GoToDiagnostic",
"shift-f2": "editor::GoToPreviousDiagnostic", "shift-f2": "editor::GoToPrevDiagnostic",
"ctrl-alt-shift-down": "editor::GoToHunk", "ctrl-alt-shift-down": "editor::GoToHunk",
"ctrl-alt-shift-up": "editor::GoToPreviousHunk", "ctrl-alt-shift-up": "editor::GoToPrevHunk",
"cmd-home": "editor::MoveToBeginning", "cmd-home": "editor::MoveToBeginning",
"cmd-end": "editor::MoveToEnd", "cmd-end": "editor::MoveToEnd",
"cmd-shift-home": "editor::SelectToBeginning", "cmd-shift-home": "editor::SelectToBeginning",
@@ -61,7 +61,7 @@
{ {
"context": "BufferSearchBar > Editor", "context": "BufferSearchBar > Editor",
"bindings": { "bindings": {
"shift-enter": "search::SelectPreviousMatch" "shift-enter": "search::SelectPrevMatch"
} }
}, },
{ {

View File

@@ -1,9 +1,9 @@
[ [
{ {
"bindings": { "bindings": {
"cmd-{": "pane::ActivatePreviousItem", "cmd-{": "pane::ActivatePrevItem",
"cmd-}": "pane::ActivateNextItem", "cmd-}": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePreviousItem", "ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-1": ["workspace::ActivatePane", 0], "ctrl-1": ["workspace::ActivatePane", 0],
"ctrl-2": ["workspace::ActivatePane", 1], "ctrl-2": ["workspace::ActivatePane", 1],
@@ -45,7 +45,7 @@
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit", "ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
"alt-shift-cmd-down": "editor::FindAllReferences", "alt-shift-cmd-down": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk", "ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPreviousHunk", "ctrl-,": "editor::GoToPrevHunk",
"cmd-k cmd-u": "editor::ConvertToUpperCase", "cmd-k cmd-u": "editor::ConvertToUpperCase",
"cmd-k cmd-l": "editor::ConvertToLowerCase", "cmd-k cmd-l": "editor::ConvertToLowerCase",
"cmd-shift-j": "editor::JoinLines", "cmd-shift-j": "editor::JoinLines",
@@ -64,7 +64,7 @@
"context": "Pane", "context": "Pane",
"bindings": { "bindings": {
"f4": "search::SelectNextMatch", "f4": "search::SelectNextMatch",
"shift-f4": "search::SelectPreviousMatch", "shift-f4": "search::SelectPrevMatch",
"cmd-1": ["pane::ActivateItem", 0], "cmd-1": ["pane::ActivateItem", 0],
"cmd-2": ["pane::ActivateItem", 1], "cmd-2": ["pane::ActivateItem", 1],
"cmd-3": ["pane::ActivateItem", 2], "cmd-3": ["pane::ActivateItem", 2],

View File

@@ -47,7 +47,7 @@
"context": "BufferSearchBar", "context": "BufferSearchBar",
"bindings": { "bindings": {
"ctrl-s": "search::SelectNextMatch", "ctrl-s": "search::SelectNextMatch",
"ctrl-shift-s": "search::SelectPreviousMatch" "ctrl-shift-s": "search::SelectPrevMatch"
} }
}, },
{ {

View File

@@ -13,9 +13,9 @@
"tab": "menu::SelectNext", "tab": "menu::SelectNext",
"ctrl-n": "menu::SelectNext", "ctrl-n": "menu::SelectNext",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"shift-tab": "menu::SelectPrevious", "shift-tab": "menu::SelectPrev",
"ctrl-p": "menu::SelectPrevious", "ctrl-p": "menu::SelectPrev",
"up": "menu::SelectPrevious", "up": "menu::SelectPrev",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm", "ctrl-enter": "menu::SecondaryConfirm",
"cmd-enter": "menu::SecondaryConfirm", "cmd-enter": "menu::SecondaryConfirm",

View File

@@ -62,9 +62,9 @@
"g /": "pane::DeploySearch", "g /": "pane::DeploySearch",
"?": ["vim::Search", { "backwards": true }], "?": ["vim::Search", { "backwards": true }],
"*": "vim::MoveToNext", "*": "vim::MoveToNext",
"#": "vim::MoveToPrevious", "#": "vim::MoveToPrev",
"n": "vim::MoveToNextMatch", "n": "vim::MoveToNextMatch",
"shift-n": "vim::MoveToPreviousMatch", "shift-n": "vim::MoveToPrevMatch",
"%": "vim::Matching", "%": "vim::Matching",
"] }": ["vim::UnmatchedForward", { "char": "}" }], "] }": ["vim::UnmatchedForward", { "char": "}" }],
"[ {": ["vim::UnmatchedBackward", { "char": "{" }], "[ {": ["vim::UnmatchedBackward", { "char": "{" }],
@@ -106,7 +106,7 @@
"g g": "vim::StartOfDocument", "g g": "vim::StartOfDocument",
"g h": "editor::Hover", "g h": "editor::Hover",
"g t": "pane::ActivateNextItem", "g t": "pane::ActivateNextItem",
"g shift-t": "pane::ActivatePreviousItem", "g shift-t": "pane::ActivatePrevItem",
"g d": "editor::GoToDefinition", "g d": "editor::GoToDefinition",
"g shift-d": "editor::GoToDeclaration", "g shift-d": "editor::GoToDeclaration",
"g y": "editor::GoToTypeDefinition", "g y": "editor::GoToTypeDefinition",
@@ -126,7 +126,7 @@
"g shift-a": "editor::FindAllReferences", // zed specific "g shift-a": "editor::FindAllReferences", // zed specific
"g space": "editor::OpenExcerpts", // zed specific "g space": "editor::OpenExcerpts", // zed specific
"g *": ["vim::MoveToNext", { "partial_word": true }], "g *": ["vim::MoveToNext", { "partial_word": true }],
"g #": ["vim::MoveToPrevious", { "partial_word": true }], "g #": ["vim::MoveToPrev", { "partial_word": true }],
"g j": ["vim::Down", { "display_lines": true }], "g j": ["vim::Down", { "display_lines": true }],
"g down": ["vim::Down", { "display_lines": true }], "g down": ["vim::Down", { "display_lines": true }],
"g k": ["vim::Up", { "display_lines": true }], "g k": ["vim::Up", { "display_lines": true }],
@@ -138,7 +138,7 @@
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }], "g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
"g v": "vim::RestoreVisualSelection", "g v": "vim::RestoreVisualSelection",
"g ]": "editor::GoToDiagnostic", "g ]": "editor::GoToDiagnostic",
"g [": "editor::GoToPreviousDiagnostic", "g [": "editor::GoToPrevDiagnostic",
"g i": "vim::InsertAtPrevious", "g i": "vim::InsertAtPrevious",
"g ,": "vim::ChangeListNewer", "g ,": "vim::ChangeListNewer",
"g ;": "vim::ChangeListOlder", "g ;": "vim::ChangeListOlder",
@@ -231,15 +231,15 @@
"g w": "vim::PushRewrap", "g w": "vim::PushRewrap",
"g q": "vim::PushRewrap", "g q": "vim::PushRewrap",
"ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePreviousItem", "ctrl-pageup": "pane::ActivatePrevItem",
"insert": "vim::InsertBefore", "insert": "vim::InsertBefore",
// tree-sitter related commands // tree-sitter related commands
"[ x": "vim::SelectLargerSyntaxNode", "[ x": "vim::SelectLargerSyntaxNode",
"] x": "vim::SelectSmallerSyntaxNode", "] x": "vim::SelectSmallerSyntaxNode",
"] d": "editor::GoToDiagnostic", "] d": "editor::GoToDiagnostic",
"[ d": "editor::GoToPreviousDiagnostic", "[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk", "] c": "editor::GoToHunk",
"[ c": "editor::GoToPreviousHunk", "[ c": "editor::GoToPrevHunk",
"g c": "vim::PushToggleComments" "g c": "vim::PushToggleComments"
} }
}, },
@@ -272,7 +272,7 @@
"shift-s": "vim::SubstituteLine", "shift-s": "vim::SubstituteLine",
"~": "vim::ChangeCase", "~": "vim::ChangeCase",
"*": ["vim::MoveToNext", { "partial_word": true }], "*": ["vim::MoveToNext", { "partial_word": true }],
"#": ["vim::MoveToPrevious", { "partial_word": true }], "#": ["vim::MoveToPrev", { "partial_word": true }],
"ctrl-a": "vim::Increment", "ctrl-a": "vim::Increment",
"ctrl-x": "vim::Decrement", "ctrl-x": "vim::Decrement",
"g ctrl-a": ["vim::Increment", { "step": true }], "g ctrl-a": ["vim::Increment", { "step": true }],
@@ -448,10 +448,7 @@
"d": "vim::CurrentLine", "d": "vim::CurrentLine",
"s": "vim::PushDeleteSurrounds", "s": "vim::PushDeleteSurrounds",
"o": "editor::ToggleSelectedDiffHunks", // "d o" "o": "editor::ToggleSelectedDiffHunks", // "d o"
"shift-o": "git::ToggleStaged", "p": "git::Restore" // "d p"
"p": "git::Restore", // "d p"
"u": "git::StageAndNext", // "d u"
"shift-u": "git::UnstageAndNext" // "d shift-u"
} }
}, },
{ {
@@ -623,8 +620,8 @@
"ctrl-w =": "vim::ResetPaneSizes", "ctrl-w =": "vim::ResetPaneSizes",
"ctrl-w g t": "pane::ActivateNextItem", "ctrl-w g t": "pane::ActivateNextItem",
"ctrl-w ctrl-g t": "pane::ActivateNextItem", "ctrl-w ctrl-g t": "pane::ActivateNextItem",
"ctrl-w g shift-t": "pane::ActivatePreviousItem", "ctrl-w g shift-t": "pane::ActivatePrevItem",
"ctrl-w ctrl-g shift-t": "pane::ActivatePreviousItem", "ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem",
"ctrl-w w": "workspace::ActivateNextPane", "ctrl-w w": "workspace::ActivateNextPane",
"ctrl-w ctrl-w": "workspace::ActivateNextPane", "ctrl-w ctrl-w": "workspace::ActivateNextPane",
"ctrl-w p": "workspace::ActivatePreviousPane", "ctrl-w p": "workspace::ActivatePreviousPane",
@@ -667,7 +664,7 @@
"escape": "project_panel::ToggleFocus", "escape": "project_panel::ToggleFocus",
"h": "project_panel::CollapseSelectedEntry", "h": "project_panel::CollapseSelectedEntry",
"j": "menu::SelectNext", "j": "menu::SelectNext",
"k": "menu::SelectPrevious", "k": "menu::SelectPrev",
"l": "project_panel::ExpandSelectedEntry", "l": "project_panel::ExpandSelectedEntry",
"o": "project_panel::OpenPermanent", "o": "project_panel::OpenPermanent",
"shift-d": "project_panel::Delete", "shift-d": "project_panel::Delete",
@@ -693,7 +690,7 @@
"context": "OutlinePanel && not_editing", "context": "OutlinePanel && not_editing",
"bindings": { "bindings": {
"j": "menu::SelectNext", "j": "menu::SelectNext",
"k": "menu::SelectPrevious", "k": "menu::SelectPrev",
"shift-g": "menu::SelectLast", "shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst" "g g": "menu::SelectFirst"
} }
@@ -702,7 +699,7 @@
"context": "GitPanel && ChangesList", "context": "GitPanel && ChangesList",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"k": "menu::SelectPrevious", "k": "menu::SelectPrev",
"j": "menu::SelectNext", "j": "menu::SelectNext",
"g g": "menu::SelectFirst", "g g": "menu::SelectFirst",
"shift-g": "menu::SelectLast", "shift-g": "menu::SelectLast",

View File

@@ -648,19 +648,11 @@
// Show git status colors in the editor tabs. // Show git status colors in the editor tabs.
"git_status": false, "git_status": false,
// Position of the close button on the editor tabs. // Position of the close button on the editor tabs.
// One of: ["right", "left", "hidden"]
"close_position": "right", "close_position": "right",
// Whether to show the file icon for a tab. // Whether to show the file icon for a tab.
"file_icons": false, "file_icons": false,
// Controls the appearance behavior of the tab's close button. // Whether to always show the close button on tabs.
// "always_show_close_button": false,
// 1. Show it just upon hovering the tab. (default)
// "hover"
// 2. Show it persistently.
// "always"
// 3. Never show it, even if hovering it.
// "hidden"
"show_close_button": "hover",
// What to do after closing the current tab. // What to do after closing the current tab.
// //
// 1. Activate the tab that was open previously (default) // 1. Activate the tab that was open previously (default)
@@ -837,15 +829,7 @@
// //
// The minimum column number to show the inline blame information at // The minimum column number to show the inline blame information at
// "min_column": 0 // "min_column": 0
}, }
// How git hunks are displayed visually in the editor.
// This setting can take two values:
//
// 1. Show unstaged hunks with a transparent background (default):
// "hunk_style": "transparent"
// 2. Show unstaged hunks with a pattern background:
// "hunk_style": "pattern"
"hunk_style": "transparent"
}, },
// Configuration for how direnv configuration should be loaded. May take 2 values: // Configuration for how direnv configuration should be loaded. May take 2 values:
// 1. Load direnv configuration using `direnv export json` directly. // 1. Load direnv configuration using `direnv export json` directly.
@@ -859,7 +843,15 @@
// Any addition to this list will be merged with the default list. // Any addition to this list will be merged with the default list.
// Globs are matched relative to the worktree root, // Globs are matched relative to the worktree root,
// except when starting with a slash (/) or equivalent in Windows. // except when starting with a slash (/) or equivalent in Windows.
"disabled_globs": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/.dev.vars", "**/secrets.yml"], "disabled_globs": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/.dev.vars",
"**/secrets.yml"
],
// When to show edit predictions previews in buffer. // When to show edit predictions previews in buffer.
// This setting takes two possible values: // This setting takes two possible values:
// 1. Display predictions inline when there are no language server completions available. // 1. Display predictions inline when there are no language server completions available.

View File

@@ -51,7 +51,6 @@ parking_lot.workspace = true
paths.workspace = true paths.workspace = true
project.workspace = true project.workspace = true
prompt_library.workspace = true prompt_library.workspace = true
prompt_store.workspace = true
proto.workspace = true proto.workspace = true
rope.workspace = true rope.workspace = true
schemars.workspace = true schemars.workspace = true

View File

@@ -19,7 +19,7 @@ use gpui::{actions, App, Global, UpdateGlobal};
use language_model::{ use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
}; };
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use semantic_index::{CloudEmbeddingProvider, SemanticDb}; use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::Deserialize; use serde::Deserialize;
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};

View File

@@ -24,8 +24,7 @@ use language_model::{
AuthenticateError, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID, AuthenticateError, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
}; };
use project::Project; use project::Project;
use prompt_library::{open_prompt_library, PromptLibrary}; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
use prompt_store::PromptBuilder;
use search::{buffer_search::DivRegistrar, BufferSearchBar}; use search::{buffer_search::DivRegistrar, BufferSearchBar};
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use smol::stream::StreamExt; use smol::stream::StreamExt;

View File

@@ -39,7 +39,7 @@ use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction}; use project::{CodeAction, ProjectTransaction};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use rope::Rope; use rope::Rope;
use settings::{update_settings_file, Settings, SettingsStore}; use settings::{update_settings_file, Settings, SettingsStore};
use smol::future::FutureExt; use smol::future::FutureExt;

View File

@@ -20,7 +20,7 @@ use language_model::{
LanguageModelRequestMessage, Role, LanguageModelRequestMessage, Role,
}; };
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector}; use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use std::{ use std::{
cmp, cmp,

View File

@@ -56,7 +56,6 @@ paths.workspace = true
picker.workspace = true picker.workspace = true
project.workspace = true project.workspace = true
prompt_library.workspace = true prompt_library.workspace = true
prompt_store.workspace = true
proto.workspace = true proto.workspace = true
rope.workspace = true rope.workspace = true
serde.workspace = true serde.workspace = true

View File

@@ -2,18 +2,17 @@ use std::sync::Arc;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use collections::HashMap; use collections::HashMap;
use editor::{Editor, MultiBuffer};
use gpui::{ use gpui::{
list, AbsoluteLength, AnyElement, App, DefiniteLength, EdgesRefinement, Empty, Entity, list, AbsoluteLength, AnyElement, App, DefiniteLength, EdgesRefinement, Empty, Entity, Length,
Focusable, Length, ListAlignment, ListOffset, ListState, StyleRefinement, Subscription, ListAlignment, ListOffset, ListState, StyleRefinement, Subscription, TextStyleRefinement,
TextStyleRefinement, UnderlineStyle, WeakEntity, UnderlineStyle, WeakEntity,
}; };
use language::{Buffer, LanguageRegistry}; use language::LanguageRegistry;
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role}; use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
use markdown::{Markdown, MarkdownStyle}; use markdown::{Markdown, MarkdownStyle};
use settings::Settings as _; use settings::Settings as _;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, Disclosure, KeyBinding}; use ui::{prelude::*, Disclosure};
use workspace::Workspace; use workspace::Workspace;
use crate::thread::{MessageId, RequestKind, Thread, ThreadError, ThreadEvent}; use crate::thread::{MessageId, RequestKind, Thread, ThreadError, ThreadEvent};
@@ -30,16 +29,11 @@ pub struct ActiveThread {
messages: Vec<MessageId>, messages: Vec<MessageId>,
list_state: ListState, list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>, rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
editing_message: Option<(MessageId, EditMessageState)>,
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>, expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
last_error: Option<ThreadError>, last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
struct EditMessageState {
editor: Entity<Editor>,
}
impl ActiveThread { impl ActiveThread {
pub fn new( pub fn new(
thread: Entity<Thread>, thread: Entity<Thread>,
@@ -66,12 +60,11 @@ impl ActiveThread {
expanded_tool_uses: HashMap::default(), expanded_tool_uses: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), { list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.entity().downgrade(); let this = cx.entity().downgrade();
move |ix, window: &mut Window, cx: &mut App| { move |ix, _: &mut Window, cx: &mut App| {
this.update(cx, |this, cx| this.render_message(ix, window, cx)) this.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap() .unwrap()
} }
}), }),
editing_message: None,
last_error: None, last_error: None,
_subscriptions: subscriptions, _subscriptions: subscriptions,
}; };
@@ -124,44 +117,6 @@ impl ActiveThread {
self.messages.push(*id); self.messages.push(*id);
self.list_state.splice(old_len..old_len, 1); self.list_state.splice(old_len..old_len, 1);
let markdown = self.render_markdown(text.into(), window, cx);
self.rendered_messages_by_id.insert(*id, markdown);
self.list_state.scroll_to(ListOffset {
item_ix: old_len,
offset_in_item: Pixels(0.0),
});
}
fn edited_message(
&mut self,
id: &MessageId,
text: String,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
return;
};
self.list_state.splice(index..index + 1, 1);
let markdown = self.render_markdown(text.into(), window, cx);
self.rendered_messages_by_id.insert(*id, markdown);
}
fn deleted_message(&mut self, id: &MessageId) {
let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
return;
};
self.messages.remove(index);
self.list_state.splice(index..index + 1, 0);
self.rendered_messages_by_id.remove(id);
}
fn render_markdown(
&self,
text: SharedString,
window: &Window,
cx: &mut Context<Self>,
) -> Entity<Markdown> {
let theme_settings = ThemeSettings::get_global(cx); let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors(); let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx); let ui_font_size = TextSize::Default.rems(cx);
@@ -179,8 +134,6 @@ impl ActiveThread {
base_text_style: text_style, base_text_style: text_style,
syntax: cx.theme().syntax().clone(), syntax: cx.theme().syntax().clone(),
selection_background_color: cx.theme().players().local().selection, selection_background_color: cx.theme().players().local().selection,
code_block_overflow_x_scroll: true,
table_overflow_x_scroll: true,
code_block: StyleRefinement { code_block: StyleRefinement {
margin: EdgesRefinement { margin: EdgesRefinement {
top: Some(Length::Definite(rems(0.).into())), top: Some(Length::Definite(rems(0.).into())),
@@ -227,15 +180,20 @@ impl ActiveThread {
..Default::default() ..Default::default()
}; };
cx.new(|cx| { let markdown = cx.new(|cx| {
Markdown::new( Markdown::new(
text, text.into(),
markdown_style, markdown_style,
Some(self.language_registry.clone()), Some(self.language_registry.clone()),
None, None,
cx, cx,
) )
}) });
self.rendered_messages_by_id.insert(*id, markdown);
self.list_state.scroll_to(ListOffset {
item_ix: old_len,
offset_in_item: Pixels(0.0),
});
} }
fn handle_thread_event( fn handle_thread_event(
@@ -281,35 +239,6 @@ impl ActiveThread {
cx.notify(); cx.notify();
} }
ThreadEvent::MessageEdited(message_id) => {
if let Some(message_text) = self
.thread
.read(cx)
.message(*message_id)
.map(|message| message.text.clone())
{
self.edited_message(message_id, message_text, window, cx);
}
self.thread_store
.update(cx, |thread_store, cx| {
thread_store.save_thread(&self.thread, cx)
})
.detach_and_log_err(cx);
cx.notify();
}
ThreadEvent::MessageDeleted(message_id) => {
self.deleted_message(message_id);
self.thread_store
.update(cx, |thread_store, cx| {
thread_store.save_thread(&self.thread, cx)
})
.detach_and_log_err(cx);
cx.notify();
}
ThreadEvent::UsePendingTools => { ThreadEvent::UsePendingTools => {
let pending_tool_uses = self let pending_tool_uses = self
.thread .thread
@@ -341,15 +270,8 @@ impl ActiveThread {
let model_registry = LanguageModelRegistry::read_global(cx); let model_registry = LanguageModelRegistry::read_global(cx);
if let Some(model) = model_registry.active_model() { if let Some(model) = model_registry.active_model() {
self.thread.update(cx, |thread, cx| { self.thread.update(cx, |thread, cx| {
// Insert a user message to contain the tool results. // Insert an empty user message to contain the tool results.
thread.insert_user_message( thread.insert_user_message("", Vec::new(), cx);
// TODO: Sending up a user message without any content results in the model sending back
// responses that also don't have any content. We currently don't handle this case well,
// so for now we provide some text to keep the model on track.
"Here are the tool results.",
Vec::new(),
cx,
);
thread.send_to_model(model, RequestKind::Chat, true, cx); thread.send_to_model(model, RequestKind::Chat, true, cx);
}); });
} }
@@ -358,101 +280,7 @@ impl ActiveThread {
} }
} }
fn start_editing_message( fn render_message(&self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
&mut self,
message_id: MessageId,
message_text: String,
window: &mut Window,
cx: &mut Context<Self>,
) {
let buffer = cx.new(|cx| {
MultiBuffer::singleton(cx.new(|cx| Buffer::local(message_text.clone(), cx)), cx)
});
let editor = cx.new(|cx| {
let mut editor = Editor::new(
editor::EditorMode::AutoHeight { max_lines: 8 },
buffer,
None,
false,
window,
cx,
);
editor.focus_handle(cx).focus(window);
editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
editor
});
self.editing_message = Some((
message_id,
EditMessageState {
editor: editor.clone(),
},
));
cx.notify();
}
fn cancel_editing_message(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
self.editing_message.take();
cx.notify();
}
fn confirm_editing_message(
&mut self,
_: &menu::Confirm,
_: &mut Window,
cx: &mut Context<Self>,
) {
let Some((message_id, state)) = self.editing_message.take() else {
return;
};
let edited_text = state.editor.read(cx).text(cx);
self.thread.update(cx, |thread, cx| {
thread.edit_message(message_id, Role::User, edited_text, cx);
for message_id in self.messages_after(message_id) {
thread.delete_message(*message_id, cx);
}
});
let provider = LanguageModelRegistry::read_global(cx).active_provider();
if provider
.as_ref()
.map_or(false, |provider| provider.must_accept_terms(cx))
{
cx.notify();
return;
}
let model_registry = LanguageModelRegistry::read_global(cx);
let Some(model) = model_registry.active_model() else {
return;
};
self.thread.update(cx, |thread, cx| {
thread.send_to_model(model, RequestKind::Chat, false, cx)
});
cx.notify();
}
fn last_user_message(&self, cx: &Context<Self>) -> Option<MessageId> {
self.messages
.iter()
.rev()
.find(|message_id| {
self.thread
.read(cx)
.message(**message_id)
.map_or(false, |message| message.role == Role::User)
})
.cloned()
}
fn messages_after(&self, message_id: MessageId) -> &[MessageId] {
self.messages
.iter()
.position(|id| *id == message_id)
.map(|index| &self.messages[index + 1..])
.unwrap_or(&[])
}
fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let message_id = self.messages[ix]; let message_id = self.messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else { let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any(); return Empty.into_any();
@@ -467,32 +295,15 @@ impl ActiveThread {
let colors = cx.theme().colors(); let colors = cx.theme().colors();
// Don't render user messages that are just there for returning tool results. // Don't render user messages that are just there for returning tool results.
if message.role == Role::User && self.thread.read(cx).message_has_tool_results(message_id) { if message.role == Role::User
&& message.text.is_empty()
&& self.thread.read(cx).message_has_tool_results(message_id)
{
return Empty.into_any(); return Empty.into_any();
} }
let allow_editing_message =
message.role == Role::User && self.last_user_message(cx) == Some(message_id);
let edit_message_editor = self
.editing_message
.as_ref()
.filter(|(id, _)| *id == message_id)
.map(|(_, state)| state.editor.clone());
let message_content = v_flex() let message_content = v_flex()
.child( .child(div().p_2p5().text_ui(cx).child(markdown.clone()))
if let Some(edit_message_editor) = edit_message_editor.clone() {
div()
.key_context("EditMessageEditor")
.on_action(cx.listener(Self::cancel_editing_message))
.on_action(cx.listener(Self::confirm_editing_message))
.p_2p5()
.child(edit_message_editor)
} else {
div().p_2p5().text_ui(cx).child(markdown.clone())
},
)
.when_some(context, |parent, context| { .when_some(context, |parent, context| {
if !context.is_empty() { if !context.is_empty() {
parent.child( parent.child(
@@ -541,55 +352,6 @@ impl ActiveThread {
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted), .color(Color::Muted),
), ),
)
.when_some(
edit_message_editor.clone(),
|this, edit_message_editor| {
let focus_handle = edit_message_editor.focus_handle(cx);
this.child(
h_flex()
.gap_1()
.child(
Button::new("cancel-edit-message", "Cancel")
.key_binding(KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)),
)
.child(
Button::new(
"confirm-edit-message",
"Regenerate",
)
.key_binding(KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)),
),
)
},
)
.when(
edit_message_editor.is_none() && allow_editing_message,
|this| {
this.child(Button::new("edit-message", "Edit").on_click(
cx.listener({
let message_text = message.text.clone();
move |this, _, window, cx| {
this.start_editing_message(
message_id,
message_text.clone(),
window,
cx,
);
}
}),
))
},
), ),
) )
.child(message_content), .child(message_content),

View File

@@ -27,7 +27,7 @@ use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt}; use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
use fs::Fs; use fs::Fs;
use gpui::{actions, App}; use gpui::{actions, App};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use settings::Settings as _; use settings::Settings as _;
pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate}; pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate};

View File

@@ -15,14 +15,12 @@ use editor::Editor;
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter, prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
FocusHandle, Focusable, FontWeight, KeyContext, Pixels, Subscription, Task, UpdateGlobal, FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry}; use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
use project::Project; use project::Project;
use prompt_library::{open_prompt_library, PromptLibrary}; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
use prompt_store::PromptBuilder;
use settings::{update_settings_file, Settings}; use settings::{update_settings_file, Settings};
use time::UtcOffset; use time::UtcOffset;
use ui::{prelude::*, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip}; use ui::{prelude::*, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip};
@@ -610,7 +608,7 @@ impl AssistantPanel {
.id("title") .id("title")
.overflow_x_scroll() .overflow_x_scroll()
.px(DynamicSpacing::Base08.rems(cx)) .px(DynamicSpacing::Base08.rems(cx))
.child(Label::new(title).truncate()), .child(Label::new(title).text_ellipsis()),
) )
.child( .child(
h_flex() h_flex()
@@ -994,21 +992,12 @@ impl AssistantPanel {
) )
.into_any() .into_any()
} }
fn key_context(&self) -> KeyContext {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("AssistantPanel2");
if matches!(self.active_view, ActiveView::PromptEditor) {
key_context.add("prompt_editor");
}
key_context
}
} }
impl Render for AssistantPanel { impl Render for AssistantPanel {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex() v_flex()
.key_context(self.key_context()) .key_context("AssistantPanel2")
.justify_between() .justify_between()
.size_full() .size_full()
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))

View File

@@ -14,7 +14,7 @@ use language_model::{
}; };
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use rope::Rope; use rope::Rope;
use smol::future::FutureExt; use smol::future::FutureExt;
use std::{ use std::{

View File

@@ -28,7 +28,7 @@ use language_model::{report_assistant_event, LanguageModelRegistry};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction}; use project::{CodeAction, ProjectTransaction};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView};

View File

@@ -16,7 +16,7 @@ use language_model::{
report_assistant_event, LanguageModelRegistry, LanguageModelRequest, report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role, LanguageModelRequestMessage, Role,
}; };
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use std::sync::Arc; use std::sync::Arc;
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::TerminalView; use terminal_view::TerminalView;

View File

@@ -8,9 +8,8 @@ use futures::StreamExt as _;
use gpui::{App, Context, EventEmitter, SharedString, Task}; use gpui::{App, Context, EventEmitter, SharedString, Task};
use language_model::{ use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest, LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult, LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolUseId,
LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason,
Role, StopReason,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use util::{post_inc, TryFutureExt as _}; use util::{post_inc, TryFutureExt as _};
@@ -89,7 +88,7 @@ impl Thread {
completion_count: 0, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
tools, tools,
tool_use: ToolUseState::new(), tool_use: ToolUseState::default(),
} }
} }
@@ -99,14 +98,7 @@ impl Thread {
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
_cx: &mut Context<Self>, _cx: &mut Context<Self>,
) -> Self { ) -> Self {
let next_message_id = MessageId( let next_message_id = MessageId(saved.messages.len());
saved
.messages
.last()
.map(|message| message.id.0 + 1)
.unwrap_or(0),
);
let tool_use = ToolUseState::from_saved_messages(&saved.messages);
Self { Self {
id, id,
@@ -128,7 +120,7 @@ impl Thread {
completion_count: 0, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
tools, tools,
tool_use, tool_use: ToolUseState::default(),
} }
} }
@@ -197,10 +189,6 @@ impl Thread {
self.tool_use.tool_uses_for_message(id) self.tool_use.tool_uses_for_message(id)
} }
pub fn tool_results_for_message(&self, id: MessageId) -> Vec<&LanguageModelToolResult> {
self.tool_use.tool_results_for_message(id)
}
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool { pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
self.tool_use.message_has_tool_results(message_id) self.tool_use.message_has_tool_results(message_id)
} }
@@ -235,34 +223,6 @@ impl Thread {
id id
} }
pub fn edit_message(
&mut self,
id: MessageId,
new_role: Role,
new_text: String,
cx: &mut Context<Self>,
) -> bool {
let Some(message) = self.messages.iter_mut().find(|message| message.id == id) else {
return false;
};
message.role = new_role;
message.text = new_text;
self.touch_updated_at();
cx.emit(ThreadEvent::MessageEdited(id));
true
}
pub fn delete_message(&mut self, id: MessageId, cx: &mut Context<Self>) -> bool {
let Some(index) = self.messages.iter().position(|message| message.id == id) else {
return false;
};
self.messages.remove(index);
self.context_by_message.remove(&id);
self.touch_updated_at();
cx.emit(ThreadEvent::MessageDeleted(id));
true
}
/// Returns the representation of this [`Thread`] in a textual form. /// Returns the representation of this [`Thread`] in a textual form.
/// ///
/// This is the representation we use when attaching a thread as context to another thread. /// This is the representation we use when attaching a thread as context to another thread.
@@ -601,8 +561,6 @@ pub enum ThreadEvent {
StreamedCompletion, StreamedCompletion,
StreamedAssistantText(MessageId, String), StreamedAssistantText(MessageId, String),
MessageAdded(MessageId), MessageAdded(MessageId),
MessageEdited(MessageId),
MessageDeleted(MessageId),
SummaryChanged, SummaryChanged,
UsePendingTools, UsePendingTools,
ToolFinished { ToolFinished {

View File

@@ -33,9 +33,9 @@ impl ThreadHistory {
} }
} }
pub fn select_previous( pub fn select_prev(
&mut self, &mut self,
_: &menu::SelectPrevious, _: &menu::SelectPrev,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
@@ -166,7 +166,7 @@ impl Render for ThreadHistory {
.overflow_y_scroll() .overflow_y_scroll()
.size_full() .size_full()
.p_1() .p_1()
.on_action(cx.listener(Self::select_previous)) .on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::select_next)) .on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_first)) .on_action(cx.listener(Self::select_first))
.on_action(cx.listener(Self::select_last)) .on_action(cx.listener(Self::select_last))
@@ -260,7 +260,7 @@ impl RenderOnce for PastThread {
.start_slot( .start_slot(
div() div()
.max_w_4_5() .max_w_4_5()
.child(Label::new(summary).size(LabelSize::Small).truncate()), .child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
) )
.end_slot( .end_slot(
h_flex() h_flex()
@@ -356,7 +356,7 @@ impl RenderOnce for PastContext {
.start_slot( .start_slot(
div() div()
.max_w_4_5() .max_w_4_5()
.child(Label::new(summary).size(LabelSize::Small).truncate()), .child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
) )
.end_slot( .end_slot(
h_flex() h_flex()

View File

@@ -12,9 +12,9 @@ use futures::FutureExt as _;
use gpui::{ use gpui::{
prelude::*, App, BackgroundExecutor, Context, Entity, Global, ReadGlobal, SharedString, Task, prelude::*, App, BackgroundExecutor, Context, Entity, Global, ReadGlobal, SharedString, Task,
}; };
use heed::types::{SerdeBincode, SerdeJson}; use heed::types::SerdeBincode;
use heed::Database; use heed::Database;
use language_model::{LanguageModelToolUseId, Role}; use language_model::Role;
use project::Project; use project::Project;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use util::ResultExt as _; use util::ResultExt as _;
@@ -113,24 +113,6 @@ impl ThreadStore {
id: message.id, id: message.id,
role: message.role, role: message.role,
text: message.text.clone(), text: message.text.clone(),
tool_uses: thread
.tool_uses_for_message(message.id)
.into_iter()
.map(|tool_use| SavedToolUse {
id: tool_use.id,
name: tool_use.name,
input: tool_use.input,
})
.collect(),
tool_results: thread
.tool_results_for_message(message.id)
.into_iter()
.map(|tool_result| SavedToolResult {
tool_use_id: tool_result.tool_use_id.clone(),
is_error: tool_result.is_error,
content: tool_result.content.clone(),
})
.collect(),
}) })
.collect(), .collect(),
}; };
@@ -257,29 +239,11 @@ pub struct SavedThread {
pub messages: Vec<SavedMessage>, pub messages: Vec<SavedMessage>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SavedMessage { pub struct SavedMessage {
pub id: MessageId, pub id: MessageId,
pub role: Role, pub role: Role,
pub text: String, pub text: String,
#[serde(default)]
pub tool_uses: Vec<SavedToolUse>,
#[serde(default)]
pub tool_results: Vec<SavedToolResult>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SavedToolUse {
pub id: LanguageModelToolUseId,
pub name: SharedString,
pub input: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SavedToolResult {
pub tool_use_id: LanguageModelToolUseId,
pub is_error: bool,
pub content: Arc<str>,
} }
struct GlobalThreadsDatabase( struct GlobalThreadsDatabase(
@@ -291,7 +255,7 @@ impl Global for GlobalThreadsDatabase {}
pub(crate) struct ThreadsDatabase { pub(crate) struct ThreadsDatabase {
executor: BackgroundExecutor, executor: BackgroundExecutor,
env: heed::Env, env: heed::Env,
threads: Database<SerdeBincode<ThreadId>, SerdeJson<SavedThread>>, threads: Database<SerdeBincode<ThreadId>, SerdeBincode<SavedThread>>,
} }
impl ThreadsDatabase { impl ThreadsDatabase {
@@ -306,7 +270,7 @@ impl ThreadsDatabase {
let database_future = executor let database_future = executor
.spawn({ .spawn({
let executor = executor.clone(); let executor = executor.clone();
let database_path = paths::support_dir().join("threads/threads-db.1.mdb"); let database_path = paths::support_dir().join("threads/threads-db.0.mdb");
async move { ThreadsDatabase::new(database_path, executor) } async move { ThreadsDatabase::new(database_path, executor) }
}) })
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new))) .then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))

View File

@@ -7,11 +7,10 @@ use futures::FutureExt as _;
use gpui::{SharedString, Task}; use gpui::{SharedString, Task};
use language_model::{ use language_model::{
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse, LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent, Role, LanguageModelToolUseId, MessageContent,
}; };
use crate::thread::MessageId; use crate::thread::MessageId;
use crate::thread_store::SavedMessage;
#[derive(Debug)] #[derive(Debug)]
pub struct ToolUse { pub struct ToolUse {
@@ -29,6 +28,7 @@ pub enum ToolUseStatus {
Error(SharedString), Error(SharedString),
} }
#[derive(Default)]
pub struct ToolUseState { pub struct ToolUseState {
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>, tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>, tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>,
@@ -37,65 +37,6 @@ pub struct ToolUseState {
} }
impl ToolUseState { impl ToolUseState {
pub fn new() -> Self {
Self {
tool_uses_by_assistant_message: HashMap::default(),
tool_uses_by_user_message: HashMap::default(),
tool_results: HashMap::default(),
pending_tool_uses_by_id: HashMap::default(),
}
}
pub fn from_saved_messages(messages: &[SavedMessage]) -> Self {
let mut this = Self::new();
for message in messages {
match message.role {
Role::Assistant => {
if !message.tool_uses.is_empty() {
this.tool_uses_by_assistant_message.insert(
message.id,
message
.tool_uses
.iter()
.map(|tool_use| LanguageModelToolUse {
id: tool_use.id.clone(),
name: tool_use.name.clone().into(),
input: tool_use.input.clone(),
})
.collect(),
);
}
}
Role::User => {
if !message.tool_results.is_empty() {
let tool_uses_by_user_message = this
.tool_uses_by_user_message
.entry(message.id)
.or_default();
for tool_result in &message.tool_results {
let tool_use_id = tool_result.tool_use_id.clone();
tool_uses_by_user_message.push(tool_use_id.clone());
this.tool_results.insert(
tool_use_id.clone(),
LanguageModelToolResult {
tool_use_id,
is_error: tool_result.is_error,
content: tool_result.content.clone(),
},
);
}
}
}
Role::System => {}
}
}
this
}
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> { pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.pending_tool_uses_by_id.values().collect() self.pending_tool_uses_by_id.values().collect()
} }
@@ -143,17 +84,6 @@ impl ToolUseState {
tool_uses tool_uses
} }
pub fn tool_results_for_message(&self, message_id: MessageId) -> Vec<&LanguageModelToolResult> {
let empty = Vec::new();
self.tool_uses_by_user_message
.get(&message_id)
.unwrap_or(&empty)
.iter()
.filter_map(|tool_use_id| self.tool_results.get(&tool_use_id))
.collect()
}
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool { pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
self.tool_uses_by_user_message self.tool_uses_by_user_message
.get(&message_id) .get(&message_id)

View File

@@ -37,7 +37,7 @@ parking_lot.workspace = true
paths.workspace = true paths.workspace = true
picker.workspace = true picker.workspace = true
project.workspace = true project.workspace = true
prompt_store.workspace = true prompt_library.workspace = true
regex.workspace = true regex.workspace = true
rope.workspace = true rope.workspace = true
rpc.workspace = true rpc.workspace = true

View File

@@ -27,7 +27,7 @@ use language_model::{
use open_ai::Model as OpenAiModel; use open_ai::Model as OpenAiModel;
use paths::contexts_dir; use paths::contexts_dir;
use project::Project; use project::Project;
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{

View File

@@ -20,7 +20,7 @@ use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Rol
use parking_lot::Mutex; use parking_lot::Mutex;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use project::Project; use project::Project;
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use rand::prelude::*; use rand::prelude::*;
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::SettingsStore;
@@ -671,7 +671,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
#[gpui::test] #[gpui::test]
async fn test_workflow_step_parsing(cx: &mut TestAppContext) { async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx.update(prompt_store::init); cx.update(prompt_library::init);
let mut settings_store = cx.update(SettingsStore::test); let mut settings_store = cx.update(SettingsStore::test);
cx.update(|cx| { cx.update(|cx| {
settings_store settings_store

View File

@@ -16,7 +16,7 @@ use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task,
use language::LanguageRegistry; use language::LanguageRegistry;
use paths::contexts_dir; use paths::contexts_dir;
use project::Project; use project::Project;
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use regex::Regex; use regex::Regex;
use rpc::AnyProtoClient; use rpc::AnyProtoClient;
use std::sync::LazyLock; use std::sync::LazyLock;

View File

@@ -207,31 +207,24 @@ impl PickerDelegate for SlashCommandDelegate {
.child( .child(
h_flex() h_flex()
.gap_1p5() .gap_1p5()
.child( .child(Icon::new(info.icon).size(IconSize::XSmall))
Icon::new(info.icon) .child(div().font_buffer(cx).child({
.size(IconSize::XSmall)
.color(Color::Muted),
)
.child({
let mut label = format!("{}", info.name); let mut label = format!("{}", info.name);
if let Some(args) = info.args.as_ref().filter(|_| selected) if let Some(args) = info.args.as_ref().filter(|_| selected)
{ {
label.push_str(&args); label.push_str(&args);
} }
Label::new(label) Label::new(label).single_line().size(LabelSize::Small)
.single_line() }))
.size(LabelSize::Small)
.buffer_font(cx)
})
.children(info.args.clone().filter(|_| !selected).map( .children(info.args.clone().filter(|_| !selected).map(
|args| { |args| {
div() div()
.font_buffer(cx)
.child( .child(
Label::new(args) Label::new(args)
.single_line() .single_line()
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted) .color(Color::Muted),
.buffer_font(cx),
) )
.visible_on_hover(format!( .visible_on_hover(format!(
"command-entry-label-{ix}" "command-entry-label-{ix}"
@@ -243,7 +236,7 @@ impl PickerDelegate for SlashCommandDelegate {
Label::new(info.description.clone()) Label::new(info.description.clone())
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Muted) .color(Color::Muted)
.truncate(), .text_ellipsis(),
), ),
), ),
), ),
@@ -301,9 +294,10 @@ where
.gap_1p5() .gap_1p5()
.child(Icon::new(IconName::Plus).size(IconSize::XSmall)) .child(Icon::new(IconName::Plus).size(IconSize::XSmall))
.child( .child(
div().font_buffer(cx).child(
Label::new("create-your-command") Label::new("create-your-command")
.size(LabelSize::Small) .size(LabelSize::Small),
.buffer_font(cx), ),
), ),
) )
.child( .child(
@@ -347,7 +341,7 @@ where
.anchor(gpui::Corner::BottomLeft) .anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point { .offset(gpui::Point {
x: px(0.0), x: px(0.0),
y: px(-2.0), y: px(-16.0),
}) })
.when_some(handle, |this, handle| this.with_handle(handle)) .when_some(handle, |this, handle| this.with_handle(handle))
} }

View File

@@ -32,7 +32,7 @@ language.workspace = true
language_model.workspace = true language_model.workspace = true
log.workspace = true log.workspace = true
project.workspace = true project.workspace = true
prompt_store.workspace = true prompt_library.workspace = true
rope.workspace = true rope.workspace = true
schemars.workspace = true schemars.workspace = true
semantic_index.workspace = true semantic_index.workspace = true

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
}; };
use gpui::{Task, WeakEntity}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use prompt_store::PromptStore; use prompt_library::PromptStore;
use std::{ use std::{
fmt::Write, fmt::Write,
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc},

View File

@@ -13,7 +13,7 @@ use feature_flags::FeatureFlag;
use gpui::{App, Task, WeakEntity}; use gpui::{App, Task, WeakEntity};
use language::{Anchor, CodeLabel, LspAdapterDelegate}; use language::{Anchor, CodeLabel, LspAdapterDelegate};
use language_model::{LanguageModelRegistry, LanguageModelTool}; use language_model::{LanguageModelRegistry, LanguageModelTool};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use schemars::JsonSchema; use schemars::JsonSchema;
use semantic_index::SemanticDb; use semantic_index::SemanticDb;
use serde::Deserialize; use serde::Deserialize;

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
}; };
use gpui::{Task, WeakEntity}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use prompt_store::PromptStore; use prompt_library::PromptStore;
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};
use ui::prelude::*; use ui::prelude::*;
use workspace::Workspace; use workspace::Workspace;

View File

@@ -141,16 +141,15 @@ pub fn notify_if_app_was_updated(cx: &mut App) {
cx, cx,
move |cx| { move |cx| {
let workspace_handle = cx.entity().downgrade(); let workspace_handle = cx.entity().downgrade();
cx.new(|cx| { cx.new(|_cx| {
MessageNotification::new( MessageNotification::new(format!("Updated to {app_name} {}", version))
format!("Updated to {app_name} {}", version),
cx,
)
.primary_message("View Release Notes") .primary_message("View Release Notes")
.primary_on_click(move |window, cx| { .primary_on_click(move |window, cx| {
if let Some(workspace) = workspace_handle.upgrade() { if let Some(workspace) = workspace_handle.upgrade() {
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
crate::view_release_notes_locally(workspace, window, cx); crate::view_release_notes_locally(
workspace, window, cx,
);
}) })
} }
cx.emit(DismissEvent); cx.emit(DismissEvent);

View File

@@ -82,7 +82,7 @@ impl Render for Breadcrumbs {
text_style.color = Color::Muted.color(cx); text_style.color = Color::Muted.color(cx);
StyledText::new(segment.text.replace('\n', "")) StyledText::new(segment.text.replace('\n', ""))
.with_default_highlights(&text_style, segment.highlights.unwrap_or_default()) .with_highlights(&text_style, segment.highlights.unwrap_or_default())
.into_any() .into_any()
}); });
let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || { let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {

View File

@@ -16,7 +16,6 @@ test-support = []
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
clock.workspace = true
futures.workspace = true futures.workspace = true
git2.workspace = true git2.workspace = true
gpui.workspace = true gpui.workspace = true

View File

@@ -1,12 +1,11 @@
use futures::channel::oneshot; use futures::{channel::oneshot, future::OptionFuture};
use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task}; use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter};
use language::{Language, LanguageRegistry}; use language::{Language, LanguageRegistry};
use rope::Rope; use rope::Rope;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::mem;
use std::{future::Future, iter, ops::Range, sync::Arc}; use std::{future::Future, iter, ops::Range, sync::Arc};
use sum_tree::{SumTree, TreeMap}; use sum_tree::SumTree;
use text::ToOffset as _; use text::ToOffset as _;
use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point}; use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point};
use util::ResultExt; use util::ResultExt;
@@ -21,14 +20,13 @@ pub struct BufferDiff {
pub struct BufferDiffSnapshot { pub struct BufferDiffSnapshot {
inner: BufferDiffInner, inner: BufferDiffInner,
secondary_diff: Option<Box<BufferDiffSnapshot>>, secondary_diff: Option<Box<BufferDiffSnapshot>>,
pub is_single_insertion: bool,
} }
#[derive(Clone)] #[derive(Clone)]
struct BufferDiffInner { struct BufferDiffInner {
hunks: SumTree<InternalDiffHunk>, hunks: SumTree<InternalDiffHunk>,
pending_hunks: TreeMap<usize, PendingHunk>, base_text: Option<language::BufferSnapshot>,
base_text: language::BufferSnapshot,
base_text_exists: bool,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -49,15 +47,23 @@ pub enum DiffHunkSecondaryStatus {
HasSecondaryHunk, HasSecondaryHunk,
OverlapsWithSecondaryHunk, OverlapsWithSecondaryHunk,
None, None,
SecondaryHunkAdditionPending, }
SecondaryHunkRemovalPending,
impl DiffHunkSecondaryStatus {
pub fn is_secondary(&self) -> bool {
match self {
DiffHunkSecondaryStatus::HasSecondaryHunk => true,
DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk => true,
DiffHunkSecondaryStatus::None => false,
}
}
} }
/// A diff hunk resolved to rows in the buffer. /// A diff hunk resolved to rows in the buffer.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffHunk { pub struct DiffHunk {
/// The buffer range as points. /// The buffer range, expressed in terms of rows.
pub range: Range<Point>, pub row_range: Range<u32>,
/// The range in the buffer to which this hunk corresponds. /// The range in the buffer to which this hunk corresponds.
pub buffer_range: Range<Anchor>, pub buffer_range: Range<Anchor>,
/// The range in the buffer's diff base text to which this hunk corresponds. /// The range in the buffer's diff base text to which this hunk corresponds.
@@ -72,17 +78,6 @@ struct InternalDiffHunk {
diff_base_byte_range: Range<usize>, diff_base_byte_range: Range<usize>,
} }
#[derive(Debug, Clone, PartialEq, Eq)]
struct PendingHunk {
buffer_version: clock::Global,
new_status: DiffHunkSecondaryStatus,
}
#[derive(Debug, Default, Clone)]
pub struct DiffHunkSummary {
buffer_range: Range<Anchor>,
}
impl sum_tree::Item for InternalDiffHunk { impl sum_tree::Item for InternalDiffHunk {
type Summary = DiffHunkSummary; type Summary = DiffHunkSummary;
@@ -93,6 +88,11 @@ impl sum_tree::Item for InternalDiffHunk {
} }
} }
#[derive(Debug, Default, Clone)]
pub struct DiffHunkSummary {
buffer_range: Range<Anchor>,
}
impl sum_tree::Summary for DiffHunkSummary { impl sum_tree::Summary for DiffHunkSummary {
type Context = text::BufferSnapshot; type Context = text::BufferSnapshot;
@@ -159,166 +159,131 @@ impl BufferDiffSnapshot {
self.inner.hunks_intersecting_range_rev(range, buffer) self.inner.hunks_intersecting_range_rev(range, buffer)
} }
pub fn base_text(&self) -> &language::BufferSnapshot { pub fn base_text(&self) -> Option<&language::BufferSnapshot> {
&self.inner.base_text self.inner.base_text.as_ref()
} }
pub fn base_texts_eq(&self, other: &Self) -> bool { pub fn base_texts_eq(&self, other: &Self) -> bool {
if self.inner.base_text_exists != other.inner.base_text_exists { match (other.base_text(), self.base_text()) {
return false; (None, None) => true,
} (None, Some(_)) => false,
let left = &self.inner.base_text; (Some(_), None) => false,
let right = &other.inner.base_text; (Some(old), Some(new)) => {
let (old_id, old_empty) = (left.remote_id(), left.is_empty()); let (old_id, old_empty) = (old.remote_id(), old.is_empty());
let (new_id, new_empty) = (right.remote_id(), right.is_empty()); let (new_id, new_empty) = (new.remote_id(), new.is_empty());
new_id == old_id || (new_empty && old_empty) new_id == old_id || (new_empty && old_empty)
} }
} }
}
impl BufferDiffInner { pub fn new_secondary_text_for_stage_or_unstage(
fn stage_or_unstage_hunks( &self,
&mut self,
unstaged_diff: &Self,
stage: bool, stage: bool,
hunks: &[DiffHunk], hunks: impl Iterator<Item = (Range<Anchor>, Range<usize>)>,
buffer: &text::BufferSnapshot, buffer: &text::BufferSnapshot,
file_exists: bool, cx: &mut App,
) -> (Option<Rope>, Vec<(usize, PendingHunk)>) { ) -> Option<Rope> {
let head_text = self let secondary_diff = self.secondary_diff()?;
.base_text_exists let head_text = self.base_text().map(|text| text.as_rope().clone());
.then(|| self.base_text.as_rope().clone()); let index_text = secondary_diff
let index_text = unstaged_diff .base_text()
.base_text_exists .map(|text| text.as_rope().clone());
.then(|| unstaged_diff.base_text.as_rope().clone());
// If the file doesn't exist in either HEAD or the index, then the
// entire file must be either created or deleted in the index.
let (index_text, head_text) = match (index_text, head_text) { let (index_text, head_text) = match (index_text, head_text) {
(Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text), (Some(index_text), Some(head_text)) => (index_text, head_text),
(_, head_text @ _) => { // file is deleted in both index and head
if stage { (None, None) => return None,
log::debug!("stage all"); // file is deleted in index
return ( (None, Some(head_text)) => {
file_exists.then(|| buffer.as_rope().clone()), return if stage {
vec![( Some(buffer.as_rope().clone())
0,
PendingHunk {
buffer_version: buffer.version().clone(),
new_status: DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,
},
)],
);
} else { } else {
log::debug!("unstage all"); Some(head_text)
return ( }
head_text, }
vec![( // file exists in the index, but is deleted in head
0, (Some(_), None) => {
PendingHunk { return if stage {
buffer_version: buffer.version().clone(), Some(buffer.as_rope().clone())
new_status: DiffHunkSecondaryStatus::SecondaryHunkAdditionPending, } else {
}, None
)],
);
} }
} }
}; };
let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer); let mut secondary_cursor = secondary_diff.inner.hunks.cursor::<DiffHunkSummary>(buffer);
unstaged_hunk_cursor.next(buffer); secondary_cursor.next(buffer);
let mut edits = Vec::new(); let mut edits = Vec::new();
let mut pending_hunks = Vec::new(); let mut prev_secondary_hunk_buffer_offset = 0;
let mut prev_unstaged_hunk_buffer_offset = 0; let mut prev_secondary_hunk_base_text_offset = 0;
let mut prev_unstaged_hunk_base_text_offset = 0; for (buffer_range, diff_base_byte_range) in hunks {
for DiffHunk { let skipped_hunks = secondary_cursor.slice(&buffer_range.start, Bias::Left, buffer);
buffer_range,
diff_base_byte_range,
secondary_status,
..
} in hunks.iter().cloned()
{
if (stage && secondary_status == DiffHunkSecondaryStatus::None)
|| (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
{
continue;
}
let skipped_hunks = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left, buffer);
if let Some(secondary_hunk) = skipped_hunks.last() { if let Some(secondary_hunk) = skipped_hunks.last() {
prev_unstaged_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end; prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end;
prev_unstaged_hunk_buffer_offset = prev_secondary_hunk_buffer_offset =
secondary_hunk.buffer_range.end.to_offset(buffer); secondary_hunk.buffer_range.end.to_offset(buffer);
} }
let mut buffer_offset_range = buffer_range.to_offset(buffer); let mut buffer_offset_range = buffer_range.to_offset(buffer);
let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_offset; let start_overshoot = buffer_offset_range.start - prev_secondary_hunk_buffer_offset;
let mut index_start = prev_unstaged_hunk_base_text_offset + start_overshoot; let mut secondary_base_text_start =
prev_secondary_hunk_base_text_offset + start_overshoot;
while let Some(unstaged_hunk) = unstaged_hunk_cursor.item().filter(|item| { while let Some(secondary_hunk) = secondary_cursor.item().filter(|item| {
item.buffer_range item.buffer_range
.start .start
.cmp(&buffer_range.end, buffer) .cmp(&buffer_range.end, buffer)
.is_le() .is_le()
}) { }) {
let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer); let secondary_hunk_offset_range = secondary_hunk.buffer_range.to_offset(buffer);
prev_unstaged_hunk_base_text_offset = unstaged_hunk.diff_base_byte_range.end; prev_secondary_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end;
prev_unstaged_hunk_buffer_offset = unstaged_hunk_offset_range.end; prev_secondary_hunk_buffer_offset = secondary_hunk_offset_range.end;
index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start); secondary_base_text_start =
secondary_base_text_start.min(secondary_hunk.diff_base_byte_range.start);
buffer_offset_range.start = buffer_offset_range buffer_offset_range.start = buffer_offset_range
.start .start
.min(unstaged_hunk_offset_range.start); .min(secondary_hunk_offset_range.start);
unstaged_hunk_cursor.next(buffer); secondary_cursor.next(buffer);
} }
let end_overshoot = buffer_offset_range let end_overshoot = buffer_offset_range
.end .end
.saturating_sub(prev_unstaged_hunk_buffer_offset); .saturating_sub(prev_secondary_hunk_buffer_offset);
let index_end = prev_unstaged_hunk_base_text_offset + end_overshoot; let secondary_base_text_end = prev_secondary_hunk_base_text_offset + end_overshoot;
let index_range = index_start..index_end; let secondary_base_text_range = secondary_base_text_start..secondary_base_text_end;
buffer_offset_range.end = buffer_offset_range buffer_offset_range.end = buffer_offset_range
.end .end
.max(prev_unstaged_hunk_buffer_offset); .max(prev_secondary_hunk_buffer_offset);
let replacement_text = if stage { let replacement_text = if stage {
log::debug!("stage hunk {:?}", buffer_offset_range); log::debug!("staging");
buffer buffer
.text_for_range(buffer_offset_range) .text_for_range(buffer_offset_range)
.collect::<String>() .collect::<String>()
} else { } else {
log::debug!("unstage hunk {:?}", buffer_offset_range); log::debug!("unstaging");
head_text head_text
.chunks_in_range(diff_base_byte_range.clone()) .chunks_in_range(diff_base_byte_range.clone())
.collect::<String>() .collect::<String>()
}; };
pending_hunks.push(( edits.push((secondary_base_text_range, replacement_text));
diff_base_byte_range.start,
PendingHunk {
buffer_version: buffer.version().clone(),
new_status: if stage {
DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
} else {
DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
},
},
));
edits.push((index_range, replacement_text));
} }
let mut new_index_text = Rope::new(); let buffer = cx.new(|cx| {
let mut index_cursor = index_text.cursor(0); language::Buffer::local_normalized(index_text, text::LineEnding::default(), cx)
for (old_range, replacement_text) in edits { });
new_index_text.append(index_cursor.slice(old_range.start)); let new_text = buffer.update(cx, |buffer, cx| {
index_cursor.seek_forward(old_range.end); buffer.edit(edits, None, cx);
new_index_text.push(&replacement_text); buffer.as_rope().clone()
} });
new_index_text.append(index_cursor.suffix()); Some(new_text)
(Some(new_index_text), pending_hunks)
} }
}
impl BufferDiffInner {
fn hunks_intersecting_range<'a>( fn hunks_intersecting_range<'a>(
&'a self, &'a self,
range: Range<Anchor>, range: Range<Anchor>,
@@ -353,16 +318,12 @@ impl BufferDiffInner {
] ]
}); });
let mut secondary_cursor = None; let mut secondary_cursor = secondary.as_ref().map(|diff| {
let mut pending_hunks = TreeMap::default(); let mut cursor = diff.hunks.cursor::<DiffHunkSummary>(buffer);
if let Some(secondary) = secondary.as_ref() {
let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);
cursor.next(buffer); cursor.next(buffer);
secondary_cursor = Some(cursor); cursor
pending_hunks = secondary.pending_hunks.clone(); });
}
let max_point = buffer.max_point();
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter); let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
iter::from_fn(move || loop { iter::from_fn(move || loop {
let (start_point, (start_anchor, start_base)) = summaries.next()?; let (start_point, (start_anchor, start_base)) = summaries.next()?;
@@ -372,26 +333,14 @@ impl BufferDiffInner {
continue; continue;
} }
if end_point.column > 0 && end_point < max_point { if end_point.column > 0 {
end_point.row += 1; end_point.row += 1;
end_point.column = 0; end_point.column = 0;
end_anchor = buffer.anchor_before(end_point); end_anchor = buffer.anchor_before(end_point);
} }
let mut secondary_status = DiffHunkSecondaryStatus::None; let mut secondary_status = DiffHunkSecondaryStatus::None;
if let Some(secondary_cursor) = secondary_cursor.as_mut() {
let mut has_pending = false;
if let Some(pending_hunk) = pending_hunks.get(&start_base) {
if !buffer.has_edits_since_in_range(
&pending_hunk.buffer_version,
start_anchor..end_anchor,
) {
has_pending = true;
secondary_status = pending_hunk.new_status;
}
}
if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {
if start_anchor if start_anchor
.cmp(&secondary_cursor.start().buffer_range.start, buffer) .cmp(&secondary_cursor.start().buffer_range.start, buffer)
.is_gt() .is_gt()
@@ -405,19 +354,18 @@ impl BufferDiffInner {
secondary_range.end.row += 1; secondary_range.end.row += 1;
secondary_range.end.column = 0; secondary_range.end.column = 0;
} }
if secondary_range.is_empty() && secondary_hunk.diff_base_byte_range.is_empty() if secondary_range == (start_point..end_point) {
{
// ignore
} else if secondary_range == (start_point..end_point) {
secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk; secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
} else if secondary_range.start <= end_point { } else if secondary_range.start <= end_point {
secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk; secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
} }
} }
} else {
log::debug!("no secondary cursor!!");
} }
return Some(DiffHunk { return Some(DiffHunk {
range: start_point..end_point, row_range: start_point.row..end_point.row,
diff_base_byte_range: start_base..end_base, diff_base_byte_range: start_base..end_base,
buffer_range: start_anchor..end_anchor, buffer_range: start_anchor..end_anchor,
secondary_status, secondary_status,
@@ -443,9 +391,14 @@ impl BufferDiffInner {
let hunk = cursor.item()?; let hunk = cursor.item()?;
let range = hunk.buffer_range.to_point(buffer); let range = hunk.buffer_range.to_point(buffer);
let end_row = if range.end.column > 0 {
range.end.row + 1
} else {
range.end.row
};
Some(DiffHunk { Some(DiffHunk {
range, row_range: range.start.row..end_row,
diff_base_byte_range: hunk.diff_base_byte_range.clone(), diff_base_byte_range: hunk.diff_base_byte_range.clone(),
buffer_range: hunk.buffer_range.clone(), buffer_range: hunk.buffer_range.clone(),
// The secondary status is not used by callers of this method. // The secondary status is not used by callers of this method.
@@ -565,14 +518,6 @@ fn compute_hunks(
tree.push(hunk, &buffer); tree.push(hunk, &buffer);
} }
} }
} else {
tree.push(
InternalDiffHunk {
buffer_range: Anchor::MIN..Anchor::MAX,
diff_base_byte_range: 0..0,
},
&buffer,
);
} }
tree tree
@@ -686,71 +631,95 @@ impl BufferDiff {
fn build( fn build(
buffer: text::BufferSnapshot, buffer: text::BufferSnapshot,
base_text: Option<Arc<String>>, diff_base: Option<Arc<String>>,
language: Option<Arc<Language>>, language: Option<Arc<Language>>,
language_registry: Option<Arc<LanguageRegistry>>, language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut App, cx: &mut App,
) -> impl Future<Output = BufferDiffInner> { ) -> impl Future<Output = BufferDiffInner> {
let base_text_pair; let diff_base =
let base_text_exists; diff_base.map(|diff_base| (diff_base.clone(), Rope::from(diff_base.as_str())));
let base_text_snapshot; let base_text_snapshot = diff_base.as_ref().map(|(_, diff_base)| {
if let Some(text) = &base_text { language::Buffer::build_snapshot(
let base_text_rope = Rope::from(text.as_str()); diff_base.clone(),
base_text_pair = Some((text.clone(), base_text_rope.clone()));
let snapshot = language::Buffer::build_snapshot(
base_text_rope,
language.clone(), language.clone(),
language_registry.clone(), language_registry.clone(),
cx, cx,
); )
base_text_snapshot = cx.background_spawn(snapshot); });
base_text_exists = true; let base_text_snapshot = cx.background_spawn(OptionFuture::from(base_text_snapshot));
} else {
base_text_pair = None;
base_text_snapshot = Task::ready(language::Buffer::build_empty_snapshot(cx));
base_text_exists = false;
};
let hunks = cx.background_spawn({ let hunks = cx.background_spawn({
let buffer = buffer.clone(); let buffer = buffer.clone();
async move { compute_hunks(base_text_pair, buffer) } async move { compute_hunks(diff_base, buffer) }
}); });
async move { async move {
let (base_text, hunks) = futures::join!(base_text_snapshot, hunks); let (base_text, hunks) = futures::join!(base_text_snapshot, hunks);
BufferDiffInner { BufferDiffInner { base_text, hunks }
base_text,
hunks,
base_text_exists,
pending_hunks: TreeMap::default(),
}
} }
} }
fn build_with_base_buffer( fn build_with_base_buffer(
buffer: text::BufferSnapshot, buffer: text::BufferSnapshot,
base_text: Option<Arc<String>>, diff_base: Option<Arc<String>>,
base_text_snapshot: language::BufferSnapshot, diff_base_buffer: Option<language::BufferSnapshot>,
cx: &App, cx: &App,
) -> impl Future<Output = BufferDiffInner> { ) -> impl Future<Output = BufferDiffInner> {
let base_text_exists = base_text.is_some(); let diff_base = diff_base.clone().zip(
let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone())); diff_base_buffer
.clone()
.map(|buffer| buffer.as_rope().clone()),
);
cx.background_spawn(async move { cx.background_spawn(async move {
BufferDiffInner { BufferDiffInner {
base_text: base_text_snapshot, hunks: compute_hunks(diff_base, buffer),
hunks: compute_hunks(base_text_pair, buffer), base_text: diff_base_buffer,
pending_hunks: TreeMap::default(),
base_text_exists,
} }
}) })
} }
fn build_empty(buffer: &text::BufferSnapshot, cx: &mut App) -> BufferDiffInner { fn build_empty(buffer: &text::BufferSnapshot) -> BufferDiffInner {
BufferDiffInner { BufferDiffInner {
base_text: language::Buffer::build_empty_snapshot(cx),
hunks: SumTree::new(buffer), hunks: SumTree::new(buffer),
pending_hunks: TreeMap::default(), base_text: None,
base_text_exists: false, }
}
pub fn build_with_single_insertion(
insertion_present_in_secondary_diff: bool,
buffer: language::BufferSnapshot,
cx: &mut App,
) -> BufferDiffSnapshot {
let base_text = language::Buffer::build_empty_snapshot(cx);
let hunks = SumTree::from_item(
InternalDiffHunk {
buffer_range: Anchor::MIN..Anchor::MAX,
diff_base_byte_range: 0..0,
},
&base_text,
);
BufferDiffSnapshot {
inner: BufferDiffInner {
hunks: hunks.clone(),
base_text: Some(base_text.clone()),
},
secondary_diff: Some(Box::new(BufferDiffSnapshot {
inner: BufferDiffInner {
hunks: if insertion_present_in_secondary_diff {
hunks
} else {
SumTree::new(&buffer.text)
},
base_text: Some(if insertion_present_in_secondary_diff {
base_text
} else {
buffer
}),
},
secondary_diff: None,
is_single_insertion: true,
})),
is_single_insertion: true,
} }
} }
@@ -759,38 +728,7 @@ impl BufferDiff {
} }
pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> { pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
self.secondary_diff.clone() Some(self.secondary_diff.as_ref()?.clone())
}
pub fn stage_or_unstage_hunks(
&mut self,
stage: bool,
hunks: &[DiffHunk],
buffer: &text::BufferSnapshot,
file_exists: bool,
cx: &mut Context<Self>,
) -> Option<Rope> {
let (new_index_text, pending_hunks) = self.inner.stage_or_unstage_hunks(
&self.secondary_diff.as_ref()?.read(cx).inner,
stage,
&hunks,
buffer,
file_exists,
);
if let Some(unstaged_diff) = &self.secondary_diff {
unstaged_diff.update(cx, |diff, _| {
for (offset, pending_hunk) in pending_hunks {
diff.inner.pending_hunks.insert(offset, pending_hunk);
}
});
}
if let Some((first, last)) = hunks.first().zip(hunks.last()) {
let changed_range = first.buffer_range.start..last.buffer_range.end;
cx.emit(BufferDiffEvent::DiffChanged {
changed_range: Some(changed_range),
});
}
new_index_text
} }
pub fn range_to_hunk_range( pub fn range_to_hunk_range(
@@ -839,7 +777,7 @@ impl BufferDiff {
Self::build_with_base_buffer( Self::build_with_base_buffer(
buffer.clone(), buffer.clone(),
base_text, base_text,
this.base_text().clone(), this.base_text().cloned(),
cx, cx,
) )
})? })?
@@ -861,33 +799,22 @@ impl BufferDiff {
fn set_state( fn set_state(
&mut self, &mut self,
new_state: BufferDiffInner, inner: BufferDiffInner,
buffer: &text::BufferSnapshot, buffer: &text::BufferSnapshot,
) -> Option<Range<Anchor>> { ) -> Option<Range<Anchor>> {
let (base_text_changed, changed_range) = let changed_range = match (self.inner.base_text.as_ref(), inner.base_text.as_ref()) {
match (self.inner.base_text_exists, new_state.base_text_exists) { (None, None) => None,
(false, false) => (true, None), (Some(old), Some(new)) if old.remote_id() == new.remote_id() => {
(true, true) inner.compare(&self.inner, buffer)
if self.inner.base_text.remote_id() == new_state.base_text.remote_id() =>
{
(false, new_state.compare(&self.inner, buffer))
} }
_ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)), _ => Some(text::Anchor::MIN..text::Anchor::MAX),
}; };
let pending_hunks = mem::take(&mut self.inner.pending_hunks); self.inner = inner;
self.inner = new_state;
if !base_text_changed {
self.inner.pending_hunks = pending_hunks;
}
changed_range changed_range
} }
pub fn base_text(&self) -> &language::BufferSnapshot { pub fn base_text(&self) -> Option<&language::BufferSnapshot> {
&self.inner.base_text self.inner.base_text.as_ref()
}
pub fn base_text_exists(&self) -> bool {
self.inner.base_text_exists
} }
pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot { pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
@@ -897,6 +824,7 @@ impl BufferDiff {
.secondary_diff .secondary_diff
.as_ref() .as_ref()
.map(|diff| Box::new(diff.read(cx).snapshot(cx))), .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
is_single_insertion: false,
} }
} }
@@ -973,16 +901,15 @@ impl BufferDiff {
rx rx
} }
#[cfg(any(test, feature = "test-support"))]
pub fn base_text_string(&self) -> Option<String> { pub fn base_text_string(&self) -> Option<String> {
self.inner self.inner.base_text.as_ref().map(|buffer| buffer.text())
.base_text_exists
.then(|| self.inner.base_text.text())
} }
pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self { pub fn new(buffer: &text::BufferSnapshot) -> Self {
BufferDiff { BufferDiff {
buffer_id: buffer.remote_id(), buffer_id: buffer.remote_id(),
inner: BufferDiff::build_empty(buffer, cx), inner: BufferDiff::build_empty(buffer),
secondary_diff: None, secondary_diff: None,
} }
} }
@@ -1012,10 +939,14 @@ impl BufferDiff {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) { pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
let base_text = self.base_text_string().map(Arc::new); let base_text = self
.inner
.base_text
.as_ref()
.map(|base_text| base_text.text());
let snapshot = BufferDiff::build_with_base_buffer( let snapshot = BufferDiff::build_with_base_buffer(
buffer.clone(), buffer.clone(),
base_text, base_text.clone().map(Arc::new),
self.inner.base_text.clone(), self.inner.base_text.clone(),
cx, cx,
); );
@@ -1026,10 +957,6 @@ impl BufferDiff {
} }
impl DiffHunk { impl DiffHunk {
pub fn is_created_file(&self) -> bool {
self.diff_base_byte_range == (0..0) && self.buffer_range == (Anchor::MIN..Anchor::MAX)
}
pub fn status(&self) -> DiffHunkStatus { pub fn status(&self) -> DiffHunkStatus {
let kind = if self.buffer_range.start == self.buffer_range.end { let kind = if self.buffer_range.start == self.buffer_range.end {
DiffHunkStatusKind::Deleted DiffHunkStatusKind::Deleted
@@ -1046,23 +973,6 @@ impl DiffHunk {
} }
impl DiffHunkStatus { impl DiffHunkStatus {
pub fn has_secondary_hunk(&self) -> bool {
matches!(
self.secondary,
DiffHunkSecondaryStatus::HasSecondaryHunk
| DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
| DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
)
}
pub fn is_pending(&self) -> bool {
matches!(
self.secondary,
DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
| DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
)
}
pub fn is_deleted(&self) -> bool { pub fn is_deleted(&self) -> bool {
self.kind == DiffHunkStatusKind::Deleted self.kind == DiffHunkStatusKind::Deleted
} }
@@ -1096,6 +1006,7 @@ impl DiffHunkStatus {
} }
} }
#[cfg(any(test, feature = "test-support"))]
pub fn deleted_none() -> Self { pub fn deleted_none() -> Self {
Self { Self {
kind: DiffHunkStatusKind::Deleted, kind: DiffHunkStatusKind::Deleted,
@@ -1103,6 +1014,7 @@ impl DiffHunkStatus {
} }
} }
#[cfg(any(test, feature = "test-support"))]
pub fn added_none() -> Self { pub fn added_none() -> Self {
Self { Self {
kind: DiffHunkStatusKind::Added, kind: DiffHunkStatusKind::Added,
@@ -1110,6 +1022,7 @@ impl DiffHunkStatus {
} }
} }
#[cfg(any(test, feature = "test-support"))]
pub fn modified_none() -> Self { pub fn modified_none() -> Self {
Self { Self {
kind: DiffHunkStatusKind::Modified, kind: DiffHunkStatusKind::Modified,
@@ -1132,10 +1045,12 @@ pub fn assert_hunks<Iter>(
let actual_hunks = diff_hunks let actual_hunks = diff_hunks
.map(|hunk| { .map(|hunk| {
( (
hunk.range.clone(), hunk.row_range.clone(),
&diff_base[hunk.diff_base_byte_range.clone()], &diff_base[hunk.diff_base_byte_range.clone()],
buffer buffer
.text_for_range(hunk.range.clone()) .text_for_range(
Point::new(hunk.row_range.start, 0)..Point::new(hunk.row_range.end, 0),
)
.collect::<String>(), .collect::<String>(),
hunk.status(), hunk.status(),
) )
@@ -1144,14 +1059,7 @@ pub fn assert_hunks<Iter>(
let expected_hunks: Vec<_> = expected_hunks let expected_hunks: Vec<_> = expected_hunks
.iter() .iter()
.map(|(r, old_text, new_text, status)| { .map(|(r, s, h, status)| (r.clone(), *s, h.to_string(), *status))
(
Point::new(r.start, 0)..Point::new(r.end, 0),
*old_text,
new_text.to_string(),
*status,
)
})
.collect(); .collect();
assert_eq!(actual_hunks, expected_hunks); assert_eq!(actual_hunks, expected_hunks);
@@ -1212,7 +1120,7 @@ mod tests {
], ],
); );
diff = cx.update(|cx| BufferDiff::build_empty(&buffer, cx)); diff = BufferDiff::build_empty(&buffer);
assert_hunks( assert_hunks(
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None), diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None),
&buffer, &buffer,
@@ -1527,55 +1435,43 @@ mod tests {
for example in table { for example in table {
let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false); let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text); let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let hunk_range = let uncommitted_diff =
buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end); BufferDiff::build_sync(buffer.clone(), example.head_text.clone(), cx);
let unstaged_diff =
BufferDiff::build_sync(buffer.clone(), example.index_text.clone(), cx);
let uncommitted_diff = BufferDiffSnapshot {
inner: uncommitted_diff,
secondary_diff: Some(Box::new(BufferDiffSnapshot {
inner: unstaged_diff,
is_single_insertion: false,
secondary_diff: None,
})),
is_single_insertion: false,
};
let unstaged = BufferDiff::build_sync(buffer.clone(), example.index_text.clone(), cx); let range = buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
let uncommitted = BufferDiff::build_sync(buffer.clone(), example.head_text.clone(), cx);
let unstaged_diff = cx.new(|cx| { let new_index_text = cx
let mut diff = BufferDiff::new(&buffer, cx); .update(|cx| {
diff.set_state(unstaged, &buffer); uncommitted_diff.new_secondary_text_for_stage_or_unstage(
diff true,
}); uncommitted_diff
.hunks_intersecting_range(range, &buffer)
let uncommitted_diff = cx.new(|cx| { .map(|hunk| {
let mut diff = BufferDiff::new(&buffer, cx); (hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone())
diff.set_state(uncommitted, &buffer); }),
diff.set_secondary_diff(unstaged_diff); &buffer,
diff cx,
}); )
})
uncommitted_diff.update(cx, |diff, cx| {
let hunks = diff
.hunks_intersecting_range(hunk_range.clone(), &buffer, &cx)
.collect::<Vec<_>>();
for hunk in &hunks {
assert_ne!(hunk.secondary_status, DiffHunkSecondaryStatus::None)
}
let new_index_text = diff
.stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
.unwrap() .unwrap()
.to_string(); .to_string();
let hunks = diff
.hunks_intersecting_range(hunk_range.clone(), &buffer, &cx)
.collect::<Vec<_>>();
for hunk in &hunks {
assert_eq!(
hunk.secondary_status,
DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
)
}
pretty_assertions::assert_eq!( pretty_assertions::assert_eq!(
new_index_text, new_index_text,
example.final_index_text, example.final_index_text,
"example: {}", "example: {}",
example.name example.name
); );
});
} }
} }
@@ -1609,7 +1505,7 @@ mod tests {
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1); let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
let empty_diff = cx.update(|cx| BufferDiff::build_empty(&buffer, cx)); let empty_diff = BufferDiff::build_empty(&buffer);
let diff_1 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx); let diff_1 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
let range = diff_1.compare(&empty_diff, &buffer).unwrap(); let range = diff_1.compare(&empty_diff, &buffer).unwrap();
assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0)); assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
@@ -1772,7 +1668,7 @@ mod tests {
index_text: &Rope, index_text: &Rope,
head_text: String, head_text: String,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> Entity<BufferDiff> { ) -> BufferDiff {
let inner = BufferDiff::build_sync(working_copy.text.clone(), head_text, cx); let inner = BufferDiff::build_sync(working_copy.text.clone(), head_text, cx);
let secondary = BufferDiff { let secondary = BufferDiff {
buffer_id: working_copy.remote_id(), buffer_id: working_copy.remote_id(),
@@ -1784,11 +1680,11 @@ mod tests {
secondary_diff: None, secondary_diff: None,
}; };
let secondary = cx.new(|_| secondary); let secondary = cx.new(|_| secondary);
cx.new(|_| BufferDiff { BufferDiff {
buffer_id: working_copy.remote_id(), buffer_id: working_copy.remote_id(),
inner, inner,
secondary_diff: Some(secondary), secondary_diff: Some(secondary),
}) }
} }
let operations = std::env::var("OPERATIONS") let operations = std::env::var("OPERATIONS")
@@ -1816,7 +1712,7 @@ mod tests {
}; };
let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx); let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
let mut hunks = diff.update(cx, |diff, cx| { let mut hunks = cx.update(|cx| {
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx) diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
@@ -1827,7 +1723,6 @@ mod tests {
for _ in 0..operations { for _ in 0..operations {
let i = rng.gen_range(0..hunks.len()); let i = rng.gen_range(0..hunks.len());
let hunk = &mut hunks[i]; let hunk = &mut hunks[i];
let hunk_to_change = hunk.clone();
let stage = match hunk.secondary_status { let stage = match hunk.secondary_status {
DiffHunkSecondaryStatus::HasSecondaryHunk => { DiffHunkSecondaryStatus::HasSecondaryHunk => {
hunk.secondary_status = DiffHunkSecondaryStatus::None; hunk.secondary_status = DiffHunkSecondaryStatus::None;
@@ -1840,13 +1735,21 @@ mod tests {
_ => unreachable!(), _ => unreachable!(),
}; };
index_text = diff.update(cx, |diff, cx| { let snapshot = cx.update(|cx| diff.snapshot(cx));
diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx) index_text = cx.update(|cx| {
snapshot
.new_secondary_text_for_stage_or_unstage(
stage,
[(hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone())]
.into_iter(),
&working_copy,
cx,
)
.unwrap() .unwrap()
}); });
diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx); diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
let found_hunks = diff.update(cx, |diff, cx| { let found_hunks = cx.update(|cx| {
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx) diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });

View File

@@ -614,19 +614,12 @@ mod windows {
let path = if let Some(path) = path { let path = if let Some(path) = path {
path.to_path_buf().canonicalize()? path.to_path_buf().canonicalize()?
} else { } else {
let cli = std::env::current_exe()?; std::env::current_exe()?
let dir = cli.parent().context("no parent path for cli")?; .parent()
.context("no parent path for cli")?
// ../Zed.exe is the standard, lib/zed is for MSYS2, ./zed.exe is for the target .parent()
// directory in development builds. .context("no parent path for cli folder")?
let possible_locations = ["../Zed.exe", "../lib/zed/zed-editor.exe", "./zed.exe"]; .join("Zed.exe")
possible_locations
.iter()
.find_map(|p| dir.join(p).canonicalize().ok().filter(|path| path != &cli))
.context(format!(
"could not find any of: {}",
possible_locations.join(", ")
))?
}; };
Ok(App(path)) Ok(App(path))

View File

@@ -111,7 +111,7 @@ node_runtime.workspace = true
notifications = { workspace = true, features = ["test-support"] } notifications = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true pretty_assertions.workspace = true
project = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] }
prompt_store.workspace = true prompt_library.workspace = true
recent_projects = { workspace = true } recent_projects = { workspace = true }
release_channel.workspace = true release_channel.workspace = true
remote = { workspace = true, features = ["test-support"] } remote = { workspace = true, features = ["test-support"] }

View File

@@ -328,7 +328,6 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::PrepareRename>) .add_request_handler(forward_mutating_project_request::<proto::PrepareRename>)
.add_request_handler(forward_mutating_project_request::<proto::PerformRename>) .add_request_handler(forward_mutating_project_request::<proto::PerformRename>)
.add_request_handler(forward_mutating_project_request::<proto::ReloadBuffers>) .add_request_handler(forward_mutating_project_request::<proto::ReloadBuffers>)
.add_request_handler(forward_mutating_project_request::<proto::ApplyCodeActionKind>)
.add_request_handler(forward_mutating_project_request::<proto::FormatBuffers>) .add_request_handler(forward_mutating_project_request::<proto::FormatBuffers>)
.add_request_handler(forward_mutating_project_request::<proto::CreateProjectEntry>) .add_request_handler(forward_mutating_project_request::<proto::CreateProjectEntry>)
.add_request_handler(forward_mutating_project_request::<proto::RenameProjectEntry>) .add_request_handler(forward_mutating_project_request::<proto::RenameProjectEntry>)

View File

@@ -14,7 +14,7 @@ use client::{User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::{FakeFs, Fs as _, RemoveOptions}; use fs::{FakeFs, Fs as _, RemoveOptions};
use futures::{channel::mpsc, StreamExt as _}; use futures::{channel::mpsc, StreamExt as _};
use prompt_store::PromptBuilder; use prompt_library::PromptBuilder;
use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode}; use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
use gpui::{ use gpui::{

View File

@@ -17,7 +17,7 @@ use gpui::{
ListState, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, SharedString, ListState, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, SharedString,
Styled, Subscription, Task, TextStyle, WeakEntity, Window, Styled, Subscription, Task, TextStyle, WeakEntity, Window,
}; };
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrevious}; use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
use project::{Fs, Project}; use project::{Fs, Project};
use rpc::{ use rpc::{
proto::{self, ChannelVisibility, PeerId}, proto::{self, ChannelVisibility, PeerId},
@@ -1430,7 +1430,7 @@ impl CollabPanel {
cx.notify(); cx.notify();
} }
fn select_previous(&mut self, _: &SelectPrevious, _: &mut Window, cx: &mut Context<Self>) { fn select_prev(&mut self, _: &SelectPrev, _: &mut Window, cx: &mut Context<Self>) {
let ix = self.selection.take().unwrap_or(0); let ix = self.selection.take().unwrap_or(0);
if ix > 0 { if ix > 0 {
self.selection = Some(ix - 1); self.selection = Some(ix - 1);
@@ -2878,7 +2878,7 @@ impl Render for CollabPanel {
.key_context("CollabPanel") .key_context("CollabPanel")
.on_action(cx.listener(CollabPanel::cancel)) .on_action(cx.listener(CollabPanel::cancel))
.on_action(cx.listener(CollabPanel::select_next)) .on_action(cx.listener(CollabPanel::select_next))
.on_action(cx.listener(CollabPanel::select_previous)) .on_action(cx.listener(CollabPanel::select_prev))
.on_action(cx.listener(CollabPanel::confirm)) .on_action(cx.listener(CollabPanel::confirm))
.on_action(cx.listener(CollabPanel::insert_space)) .on_action(cx.listener(CollabPanel::insert_space))
.on_action(cx.listener(CollabPanel::remove_selected_channel)) .on_action(cx.listener(CollabPanel::remove_selected_channel))

View File

@@ -22,7 +22,7 @@ use ui::{
h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip,
}; };
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
use workspace::notifications::{Notification as WorkspaceNotification, NotificationId}; use workspace::notifications::NotificationId;
use workspace::{ use workspace::{
dock::{DockPosition, Panel, PanelEvent}, dock::{DockPosition, Panel, PanelEvent},
Workspace, Workspace,
@@ -570,12 +570,11 @@ impl NotificationPanel {
workspace.dismiss_notification(&id, cx); workspace.dismiss_notification(&id, cx);
workspace.show_notification(id, cx, |cx| { workspace.show_notification(id, cx, |cx| {
let workspace = cx.entity().downgrade(); let workspace = cx.entity().downgrade();
cx.new(|cx| NotificationToast { cx.new(|_| NotificationToast {
notification_id, notification_id,
actor, actor,
text, text,
workspace, workspace,
focus_handle: cx.focus_handle(),
}) })
}) })
}) })
@@ -772,17 +771,8 @@ pub struct NotificationToast {
actor: Option<Arc<User>>, actor: Option<Arc<User>>,
text: String, text: String,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
} }
impl Focusable for NotificationToast {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl WorkspaceNotification for NotificationToast {}
impl NotificationToast { impl NotificationToast {
fn focus_notification_panel(&self, window: &mut Window, cx: &mut Context<Self>) { fn focus_notification_panel(&self, window: &mut Window, cx: &mut Context<Self>) {
let workspace = self.workspace.clone(); let workspace = self.workspace.clone();

View File

@@ -18,7 +18,7 @@ pub trait Component {
} }
pub trait ComponentPreview: Component { pub trait ComponentPreview: Component {
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement; fn preview(_window: &mut Window, _cx: &App) -> AnyElement;
} }
#[distributed_slice] #[distributed_slice]
@@ -32,7 +32,7 @@ pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
pub struct ComponentRegistry { pub struct ComponentRegistry {
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>, components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
previews: HashMap<&'static str, fn(&mut Window, &mut App) -> AnyElement>, previews: HashMap<&'static str, fn(&mut Window, &App) -> AnyElement>,
} }
impl ComponentRegistry { impl ComponentRegistry {
@@ -62,10 +62,7 @@ pub fn register_component<T: Component>() {
} }
pub fn register_preview<T: ComponentPreview>() { pub fn register_preview<T: ComponentPreview>() {
let preview_data = ( let preview_data = (T::name(), T::preview as fn(&mut Window, &App) -> AnyElement);
T::name(),
T::preview as fn(&mut Window, &mut App) -> AnyElement,
);
COMPONENT_DATA COMPONENT_DATA
.write() .write()
.previews .previews
@@ -80,7 +77,7 @@ pub struct ComponentMetadata {
name: SharedString, name: SharedString,
scope: Option<SharedString>, scope: Option<SharedString>,
description: Option<SharedString>, description: Option<SharedString>,
preview: Option<fn(&mut Window, &mut App) -> AnyElement>, preview: Option<fn(&mut Window, &App) -> AnyElement>,
} }
impl ComponentMetadata { impl ComponentMetadata {
@@ -96,7 +93,7 @@ impl ComponentMetadata {
self.description.clone() self.description.clone()
} }
pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> AnyElement> { pub fn preview(&self) -> Option<fn(&mut Window, &App) -> AnyElement> {
self.preview self.preview
} }
} }
@@ -238,7 +235,6 @@ pub struct ComponentExampleGroup {
pub title: Option<SharedString>, pub title: Option<SharedString>,
pub examples: Vec<ComponentExample>, pub examples: Vec<ComponentExample>,
pub grow: bool, pub grow: bool,
pub vertical: bool,
} }
impl RenderOnce for ComponentExampleGroup { impl RenderOnce for ComponentExampleGroup {
@@ -274,7 +270,6 @@ impl RenderOnce for ComponentExampleGroup {
.child( .child(
div() div()
.flex() .flex()
.when(self.vertical, |this| this.flex_col())
.items_start() .items_start()
.w_full() .w_full()
.gap_6() .gap_6()
@@ -292,7 +287,6 @@ impl ComponentExampleGroup {
title: None, title: None,
examples, examples,
grow: false, grow: false,
vertical: false,
} }
} }
@@ -302,7 +296,6 @@ impl ComponentExampleGroup {
title: Some(title.into()), title: Some(title.into()),
examples, examples,
grow: false, grow: false,
vertical: false,
} }
} }
@@ -311,12 +304,6 @@ impl ComponentExampleGroup {
self.grow = true; self.grow = true;
self self
} }
/// Lay the group out vertically.
pub fn vertical(mut self) -> Self {
self.vertical = true;
self
}
} }
/// Create a single example /// Create a single example

View File

@@ -93,7 +93,7 @@ impl ComponentPreview {
&self, &self,
ix: usize, ix: usize,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
let component = self.get_component(ix); let component = self.get_component(ix);
@@ -226,7 +226,3 @@ impl Item for ComponentPreview {
f(*event) f(*event)
} }
} }
// TODO: impl serializable item for component preview so it will restore with the workspace
// ref: https://github.com/zed-industries/zed/blob/32201ac70a501e63dfa2ade9c00f85aea2d4dd94/crates/image_viewer/src/image_viewer.rs#L199
// Use `ImageViewer` as a model for how to do it, except it'll be even simpler

View File

@@ -995,7 +995,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
h_flex() h_flex()
.gap_1() .gap_1()
.child( .child(
StyledText::new(message.clone()).with_default_highlights( StyledText::new(message.clone()).with_highlights(
&cx.window.text_style(), &cx.window.text_style(),
code_ranges code_ranges
.iter() .iter()

View File

@@ -35,13 +35,6 @@ pub struct SelectToBeginningOfLine {
pub stop_at_indent: bool, pub stop_at_indent: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DeleteToBeginningOfLine {
#[serde(default)]
pub(super) stop_at_indent: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct MovePageUp { pub struct MovePageUp {
@@ -219,7 +212,6 @@ impl_actions!(
ComposeCompletion, ComposeCompletion,
ConfirmCodeAction, ConfirmCodeAction,
ConfirmCompletion, ConfirmCompletion,
DeleteToBeginningOfLine,
DeleteToNextWordEnd, DeleteToNextWordEnd,
DeleteToPreviousWordStart, DeleteToPreviousWordStart,
ExpandExcerpts, ExpandExcerpts,
@@ -265,7 +257,7 @@ gpui::actions!(
ContextMenuFirst, ContextMenuFirst,
ContextMenuLast, ContextMenuLast,
ContextMenuNext, ContextMenuNext,
ContextMenuPrevious, ContextMenuPrev,
ConvertToKebabCase, ConvertToKebabCase,
ConvertToLowerCamelCase, ConvertToLowerCamelCase,
ConvertToLowerCase, ConvertToLowerCase,
@@ -284,6 +276,7 @@ gpui::actions!(
CutToEndOfLine, CutToEndOfLine,
Delete, Delete,
DeleteLine, DeleteLine,
DeleteToBeginningOfLine,
DeleteToEndOfLine, DeleteToEndOfLine,
DeleteToNextSubwordEnd, DeleteToNextSubwordEnd,
DeleteToPreviousSubwordStart, DeleteToPreviousSubwordStart,
@@ -308,10 +301,10 @@ gpui::actions!(
GoToDefinitionSplit, GoToDefinitionSplit,
GoToDiagnostic, GoToDiagnostic,
GoToHunk, GoToHunk,
GoToPreviousHunk,
GoToImplementation, GoToImplementation,
GoToImplementationSplit, GoToImplementationSplit,
GoToPreviousDiagnostic, GoToPrevDiagnostic,
GoToPrevHunk,
GoToTypeDefinition, GoToTypeDefinition,
GoToTypeDefinitionSplit, GoToTypeDefinitionSplit,
HalfPageDown, HalfPageDown,
@@ -355,7 +348,6 @@ gpui::actions!(
OpenPermalinkToLine, OpenPermalinkToLine,
OpenSelectionsInMultibuffer, OpenSelectionsInMultibuffer,
OpenUrl, OpenUrl,
OrganizeImports,
Outdent, Outdent,
AutoIndent, AutoIndent,
PageDown, PageDown,
@@ -406,7 +398,7 @@ gpui::actions!(
SplitSelectionIntoLines, SplitSelectionIntoLines,
SwitchSourceHeader, SwitchSourceHeader,
Tab, Tab,
Backtab, TabPrev,
ToggleAutoSignatureHelp, ToggleAutoSignatureHelp,
ToggleGitBlame, ToggleGitBlame,
ToggleGitBlameInline, ToggleGitBlameInline,

View File

@@ -514,7 +514,7 @@ impl CompletionsMenu {
); );
let completion_label = StyledText::new(completion.label.text.clone()) let completion_label = StyledText::new(completion.label.text.clone())
.with_default_highlights(&style.text, highlights); .with_highlights(&style.text, highlights);
let documentation_label = if let Some( let documentation_label = if let Some(
CompletionDocumentation::SingleLine(text), CompletionDocumentation::SingleLine(text),
) = documentation ) = documentation

View File

@@ -1124,11 +1124,6 @@ impl DisplaySnapshot {
self.block_snapshot.is_block_line(BlockRow(display_row.0)) self.block_snapshot.is_block_line(BlockRow(display_row.0))
} }
pub fn is_folded_buffer_header(&self, display_row: DisplayRow) -> bool {
self.block_snapshot
.is_folded_buffer_header(BlockRow(display_row.0))
}
pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> { pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
let wrap_row = self let wrap_row = self
.block_snapshot .block_snapshot

View File

@@ -1618,15 +1618,6 @@ impl BlockSnapshot {
cursor.item().map_or(false, |t| t.block.is_some()) cursor.item().map_or(false, |t| t.block.is_some())
} }
pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&row, Bias::Right, &());
let Some(transform) = cursor.item() else {
return false;
};
matches!(transform.block, Some(Block::FoldedBuffer { .. }))
}
pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool { pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
let wrap_point = self let wrap_point = self
.wrap_snapshot .wrap_snapshot

View File

@@ -52,7 +52,7 @@ pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
use aho_corasick::AhoCorasick; use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use blink_manager::BlinkManager; use blink_manager::BlinkManager;
use buffer_diff::DiffHunkStatus; use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus};
use client::{Collaborator, ParticipantIndex}; use client::{Collaborator, ParticipantIndex};
use clock::ReplicaId; use clock::ReplicaId;
use collections::{BTreeMap, HashMap, HashSet, VecDeque}; use collections::{BTreeMap, HashMap, HashSet, VecDeque};
@@ -73,7 +73,7 @@ use futures::{
}; };
use fuzzy::StringMatchCandidate; use fuzzy::StringMatchCandidate;
use ::git::Restore; use ::git::{status::FileStatus, Restore};
use code_context_menus::{ use code_context_menus::{
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu, AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
CompletionsMenu, ContextMenuOrigin, CompletionsMenu, ContextMenuOrigin,
@@ -85,8 +85,8 @@ use gpui::{
ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler,
EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global,
HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
ParentElement, Pixels, Render, SharedString, Size, Stateful, Styled, StyledText, Subscription, ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription, Task,
Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
WeakEntity, WeakFocusHandle, Window, WeakEntity, WeakFocusHandle, Window,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
@@ -120,8 +120,8 @@ use task::{ResolvedTask, TaskTemplate, TaskVariables};
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight}; use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
pub use lsp::CompletionContext; pub use lsp::CompletionContext;
use lsp::{ use lsp::{
CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity, InsertTextFormat,
InsertTextFormat, LanguageServerId, LanguageServerName, LanguageServerId, LanguageServerName,
}; };
use language::BufferSnapshot; use language::BufferSnapshot;
@@ -203,7 +203,6 @@ pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
#[doc(hidden)] #[doc(hidden)]
pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250); pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5); pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1); pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
@@ -2877,7 +2876,7 @@ impl Editor {
} }
if let Some(bracket_pair) = bracket_pair { if let Some(bracket_pair) = bracket_pair {
let snapshot_settings = snapshot.language_settings_at(selection.start, cx); let snapshot_settings = snapshot.settings_at(selection.start, cx);
let autoclose = self.use_autoclose && snapshot_settings.use_autoclose; let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
let auto_surround = let auto_surround =
self.use_auto_surround && snapshot_settings.use_auto_surround; self.use_auto_surround && snapshot_settings.use_auto_surround;
@@ -2942,7 +2941,7 @@ impl Editor {
} }
let always_treat_brackets_as_autoclosed = snapshot let always_treat_brackets_as_autoclosed = snapshot
.language_settings_at(selection.start, cx) .settings_at(selection.start, cx)
.always_treat_brackets_as_autoclosed; .always_treat_brackets_as_autoclosed;
if always_treat_brackets_as_autoclosed if always_treat_brackets_as_autoclosed
&& is_bracket_pair_end && is_bracket_pair_end
@@ -3225,7 +3224,7 @@ impl Editor {
return None; return None;
} }
if !multi_buffer.language_settings(cx).extend_comment_on_newline { if !multi_buffer.settings_at(0, cx).extend_comment_on_newline {
return None; return None;
} }
@@ -3554,7 +3553,7 @@ impl Editor {
} }
let always_treat_brackets_as_autoclosed = buffer let always_treat_brackets_as_autoclosed = buffer
.language_settings_at(selection.start, cx) .settings_at(selection.start, cx)
.always_treat_brackets_as_autoclosed; .always_treat_brackets_as_autoclosed;
if !always_treat_brackets_as_autoclosed { if !always_treat_brackets_as_autoclosed {
@@ -3691,7 +3690,6 @@ impl Editor {
InlayHintRefreshReason::SettingsChange(_) InlayHintRefreshReason::SettingsChange(_)
| InlayHintRefreshReason::Toggle(_) | InlayHintRefreshReason::Toggle(_)
| InlayHintRefreshReason::ExcerptsRemoved(_) | InlayHintRefreshReason::ExcerptsRemoved(_)
| InlayHintRefreshReason::ModifiersChanged(_)
); );
let (invalidate_cache, required_languages) = match reason { let (invalidate_cache, required_languages) = match reason {
InlayHintRefreshReason::ModifiersChanged(enabled) => { InlayHintRefreshReason::ModifiersChanged(enabled) => {
@@ -6294,9 +6292,6 @@ impl Editor {
const BORDER_WIDTH: Pixels = px(1.); const BORDER_WIDTH: Pixels = px(1.);
let keybind = self.render_edit_prediction_accept_keybind(window, cx);
let has_keybind = keybind.is_some();
let mut element = h_flex() let mut element = h_flex()
.items_start() .items_start()
.child( .child(
@@ -6327,19 +6322,7 @@ impl Editor {
.border(BORDER_WIDTH) .border(BORDER_WIDTH)
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.rounded_r_lg() .rounded_r_lg()
.id("edit_prediction_diff_popover_keybind") .children(self.render_edit_prediction_accept_keybind(window, cx)),
.when(!has_keybind, |el| {
let status_colors = cx.theme().status();
el.bg(status_colors.error_background)
.border_color(status_colors.error.opacity(0.6))
.child(Icon::new(IconName::Info).color(Color::Error))
.cursor_default()
.hoverable_tooltip(move |_window, cx| {
cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
})
})
.children(keybind),
) )
.into_any(); .into_any();
@@ -6437,11 +6420,7 @@ impl Editor {
} }
} }
fn render_edit_prediction_accept_keybind( fn render_edit_prediction_accept_keybind(&self, window: &mut Window, cx: &App) -> Option<Div> {
&self,
window: &mut Window,
cx: &App,
) -> Option<AnyElement> {
let accept_binding = self.accept_edit_prediction_keybind(window, cx); let accept_binding = self.accept_edit_prediction_keybind(window, cx);
let accept_keystroke = accept_binding.keystroke()?; let accept_keystroke = accept_binding.keystroke()?;
@@ -6477,7 +6456,6 @@ impl Editor {
.size(Some(IconSize::XSmall.rems().into())), .size(Some(IconSize::XSmall.rems().into())),
) )
}) })
.into_any()
.into() .into()
} }
@@ -6487,14 +6465,10 @@ impl Editor {
icon: Option<IconName>, icon: Option<IconName>,
window: &mut Window, window: &mut Window,
cx: &App, cx: &App,
) -> Option<Stateful<Div>> { ) -> Option<Div> {
let padding_right = if icon.is_some() { px(4.) } else { px(8.) }; let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
let keybind = self.render_edit_prediction_accept_keybind(window, cx);
let has_keybind = keybind.is_some();
let result = h_flex() let result = h_flex()
.id("ep-line-popover")
.py_0p5() .py_0p5()
.pl_1() .pl_1()
.pr(padding_right) .pr(padding_right)
@@ -6504,35 +6478,8 @@ impl Editor {
.bg(Self::edit_prediction_line_popover_bg_color(cx)) .bg(Self::edit_prediction_line_popover_bg_color(cx))
.border_color(Self::edit_prediction_callout_popover_border_color(cx)) .border_color(Self::edit_prediction_callout_popover_border_color(cx))
.shadow_sm() .shadow_sm()
.when(!has_keybind, |el| { .children(self.render_edit_prediction_accept_keybind(window, cx))
let status_colors = cx.theme().status(); .child(Label::new(label).size(LabelSize::Small))
el.bg(status_colors.error_background)
.border_color(status_colors.error.opacity(0.6))
.pl_2()
.child(Icon::new(IconName::ZedPredictError).color(Color::Error))
.cursor_default()
.hoverable_tooltip(move |_window, cx| {
cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
})
})
.children(keybind)
.child(
Label::new(label)
.size(LabelSize::Small)
.when(!has_keybind, |el| {
el.color(cx.theme().status().error.into()).strikethrough()
}),
)
.when(!has_keybind, |el| {
el.child(
h_flex().ml_1().child(
Icon::new(IconName::Info)
.size(IconSize::Small)
.color(cx.theme().status().error.into()),
),
)
})
.when_some(icon, |element, icon| { .when_some(icon, |element, icon| {
element.child( element.child(
div() div()
@@ -6629,9 +6576,6 @@ impl Editor {
.elevation_2(cx) .elevation_2(cx)
.border(BORDER_WIDTH) .border(BORDER_WIDTH)
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.when(accept_keystroke.is_none(), |el| {
el.border_color(cx.theme().status().error)
})
.rounded(RADIUS) .rounded(RADIUS)
.rounded_tl(px(0.)) .rounded_tl(px(0.))
.overflow_hidden() .overflow_hidden()
@@ -6660,37 +6604,16 @@ impl Editor {
el.child( el.child(
Label::new("Hold") Label::new("Hold")
.size(LabelSize::Small) .size(LabelSize::Small)
.when(accept_keystroke.is_none(), |el| {
el.strikethrough()
})
.line_height_style(LineHeightStyle::UiLabel), .line_height_style(LineHeightStyle::UiLabel),
) )
}) })
.id("edit_prediction_cursor_popover_keybind") .child(h_flex().children(ui::render_modifiers(
.when(accept_keystroke.is_none(), |el| { &accept_keystroke?.modifiers,
let status_colors = cx.theme().status();
el.bg(status_colors.error_background)
.border_color(status_colors.error.opacity(0.6))
.child(Icon::new(IconName::Info).color(Color::Error))
.cursor_default()
.hoverable_tooltip(move |_window, cx| {
cx.new(|_| MissingEditPredictionKeybindingTooltip)
.into()
})
})
.when_some(
accept_keystroke.as_ref(),
|el, accept_keystroke| {
el.child(h_flex().children(ui::render_modifiers(
&accept_keystroke.modifiers,
PlatformStyle::platform(), PlatformStyle::platform(),
Some(Color::Default), Some(Color::Default),
Some(IconSize::XSmall.rems().into()), Some(IconSize::XSmall.rems().into()),
false, false,
))) ))),
},
),
) )
.into_any(), .into_any(),
); );
@@ -6856,7 +6779,7 @@ impl Editor {
.first_line_preview(); .first_line_preview();
let styled_text = gpui::StyledText::new(highlighted_edits.text) let styled_text = gpui::StyledText::new(highlighted_edits.text)
.with_default_highlights(&style.text, highlighted_edits.highlights); .with_highlights(&style.text, highlighted_edits.highlights);
let preview = h_flex() let preview = h_flex()
.gap_1() .gap_1()
@@ -7267,7 +7190,7 @@ impl Editor {
}); });
} }
pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) { pub fn tab_prev(&mut self, _: &TabPrev, window: &mut Window, cx: &mut Context<Self>) {
if self.move_to_prev_snippet_tabstop(window, cx) { if self.move_to_prev_snippet_tabstop(window, cx) {
return; return;
} }
@@ -7328,7 +7251,7 @@ impl Editor {
} }
// Otherwise, insert a hard or soft tab. // Otherwise, insert a hard or soft tab.
let settings = buffer.language_settings_at(cursor, cx); let settings = buffer.settings_at(cursor, cx);
let tab_size = if settings.hard_tabs { let tab_size = if settings.hard_tabs {
IndentSize::tab() IndentSize::tab()
} else { } else {
@@ -7392,7 +7315,7 @@ impl Editor {
delta_for_start_row: u32, delta_for_start_row: u32,
cx: &App, cx: &App,
) -> u32 { ) -> u32 {
let settings = buffer.language_settings_at(selection.start, cx); let settings = buffer.settings_at(selection.start, cx);
let tab_size = settings.tab_size.get(); let tab_size = settings.tab_size.get();
let indent_kind = if settings.hard_tabs { let indent_kind = if settings.hard_tabs {
IndentKind::Tab IndentKind::Tab
@@ -7472,7 +7395,7 @@ impl Editor {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
for selection in &selections { for selection in &selections {
let settings = buffer.language_settings_at(selection.start, cx); let settings = buffer.settings_at(selection.start, cx);
let tab_size = settings.tab_size.get(); let tab_size = settings.tab_size.get();
let mut rows = selection.spanned_rows(false, &display_map); let mut rows = selection.spanned_rows(false, &display_map);
@@ -7796,9 +7719,14 @@ impl Editor {
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) { ) {
let mut revert_changes = HashMap::default(); let mut revert_changes = HashMap::default();
let snapshot = self.buffer.read(cx).snapshot(cx);
let Some(project) = &self.project else {
return;
};
let chunk_by = self let chunk_by = self
.snapshot(window, cx) .snapshot(window, cx)
.hunks_for_ranges(ranges) .hunks_for_ranges(ranges.into_iter())
.into_iter() .into_iter()
.chunk_by(|hunk| hunk.buffer_id); .chunk_by(|hunk| hunk.buffer_id);
for (buffer_id, hunks) in &chunk_by { for (buffer_id, hunks) in &chunk_by {
@@ -7806,7 +7734,15 @@ impl Editor {
for hunk in &hunks { for hunk in &hunks {
self.prepare_restore_change(&mut revert_changes, hunk, cx); self.prepare_restore_change(&mut revert_changes, hunk, cx);
} }
self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), window, cx); Self::do_stage_or_unstage(
project,
false,
buffer_id,
hunks.into_iter(),
&snapshot,
window,
cx,
);
} }
drop(chunk_by); drop(chunk_by);
if !revert_changes.is_empty() { if !revert_changes.is_empty() {
@@ -7851,6 +7787,7 @@ impl Editor {
let original_text = diff let original_text = diff
.read(cx) .read(cx)
.base_text() .base_text()
.as_ref()?
.as_rope() .as_rope()
.slice(hunk.diff_base_byte_range.clone()); .slice(hunk.diff_base_byte_range.clone());
let buffer_snapshot = buffer.snapshot(); let buffer_snapshot = buffer.snapshot();
@@ -8484,7 +8421,7 @@ impl Editor {
continue; continue;
} }
let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size; let tab_size = buffer.settings_at(selection.head(), cx).tab_size;
// Since not all lines in the selection may be at the same indent // Since not all lines in the selection may be at the same indent
// level, choose the indent size that is the most common between all // level, choose the indent size that is the most common between all
@@ -8530,7 +8467,7 @@ impl Editor {
inside_comment = true; inside_comment = true;
} }
let language_settings = buffer.language_settings_at(selection.head(), cx); let language_settings = buffer.settings_at(selection.head(), cx);
let allow_rewrap_based_on_language = match language_settings.allow_rewrap { let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
RewrapBehavior::InComments => inside_comment, RewrapBehavior::InComments => inside_comment,
RewrapBehavior::InSelections => !selection.is_empty(), RewrapBehavior::InSelections => !selection.is_empty(),
@@ -8586,7 +8523,7 @@ impl Editor {
}; };
let wrap_column = buffer let wrap_column = buffer
.language_settings_at(Point::new(start_row, 0), cx) .settings_at(Point::new(start_row, 0), cx)
.preferred_line_length as usize; .preferred_line_length as usize;
let wrapped_text = wrap_with_prefix( let wrapped_text = wrap_with_prefix(
line_prefix, line_prefix,
@@ -8772,9 +8709,8 @@ impl Editor {
this.buffer.update(cx, |buffer, cx| { this.buffer.update(cx, |buffer, cx| {
let snapshot = buffer.read(cx); let snapshot = buffer.read(cx);
auto_indent_on_paste = snapshot auto_indent_on_paste =
.language_settings_at(cursor_offset, cx) snapshot.settings_at(cursor_offset, cx).auto_indent_on_paste;
.auto_indent_on_paste;
let mut start_offset = 0; let mut start_offset = 0;
let mut edits = Vec::new(); let mut edits = Vec::new();
@@ -9312,7 +9248,7 @@ impl Editor {
pub fn context_menu_prev( pub fn context_menu_prev(
&mut self, &mut self,
_: &ContextMenuPrevious, _: &ContextMenuPrev,
_window: &mut Window, _window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
@@ -9592,7 +9528,7 @@ impl Editor {
pub fn delete_to_beginning_of_line( pub fn delete_to_beginning_of_line(
&mut self, &mut self,
action: &DeleteToBeginningOfLine, _: &DeleteToBeginningOfLine,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
@@ -9606,7 +9542,7 @@ impl Editor {
this.select_to_beginning_of_line( this.select_to_beginning_of_line(
&SelectToBeginningOfLine { &SelectToBeginningOfLine {
stop_at_soft_wraps: false, stop_at_soft_wraps: false,
stop_at_indent: action.stop_at_indent, stop_at_indent: false,
}, },
window, window,
cx, cx,
@@ -11339,7 +11275,7 @@ impl Editor {
fn go_to_prev_diagnostic( fn go_to_prev_diagnostic(
&mut self, &mut self,
_: &GoToPreviousDiagnostic, _: &GoToPrevDiagnostic,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
@@ -11491,85 +11427,63 @@ impl Editor {
fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) { fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let selection = self.selections.newest::<Point>(cx); let selection = self.selections.newest::<Point>(cx);
self.go_to_hunk_after_or_before_position( self.go_to_hunk_after_position(&snapshot, selection.head(), window, cx);
&snapshot,
selection.head(),
Direction::Next,
window,
cx,
);
} }
fn go_to_hunk_after_or_before_position( fn go_to_hunk_after_position(
&mut self, &mut self,
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
position: Point, position: Point,
direction: Direction,
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) -> Option<MultiBufferDiffHunk> { ) -> Option<MultiBufferDiffHunk> {
let hunk = if direction == Direction::Next { let mut hunk = snapshot
self.hunk_after_position(snapshot, position) .buffer_snapshot
} else { .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
self.hunk_before_position(snapshot, position) .find(|hunk| hunk.row_range.start.0 > position.row);
}; if hunk.is_none() {
hunk = snapshot
.buffer_snapshot
.diff_hunks_in_range(Point::zero()..position)
.find(|hunk| hunk.row_range.end.0 < position.row)
}
if let Some(hunk) = &hunk { if let Some(hunk) = &hunk {
let destination = Point::new(hunk.row_range.start.0, 0); let destination = Point::new(hunk.row_range.start.0, 0);
let autoscroll = Autoscroll::center();
self.unfold_ranges(&[destination..destination], false, false, cx); self.unfold_ranges(&[destination..destination], false, false, cx);
self.change_selections(Some(autoscroll), window, cx, |s| { self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select_ranges([destination..destination]); s.select_ranges(vec![destination..destination]);
}); });
} }
hunk hunk
} }
fn hunk_after_position( fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, window: &mut Window, cx: &mut Context<Self>) {
&mut self,
snapshot: &EditorSnapshot,
position: Point,
) -> Option<MultiBufferDiffHunk> {
snapshot
.buffer_snapshot
.diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
.find(|hunk| hunk.row_range.start.0 > position.row)
.or_else(|| {
snapshot
.buffer_snapshot
.diff_hunks_in_range(Point::zero()..position)
.find(|hunk| hunk.row_range.end.0 < position.row)
})
}
fn go_to_prev_hunk(
&mut self,
_: &GoToPreviousHunk,
window: &mut Window,
cx: &mut Context<Self>,
) {
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let selection = self.selections.newest::<Point>(cx); let selection = self.selections.newest::<Point>(cx);
self.go_to_hunk_after_or_before_position( self.go_to_hunk_before_position(&snapshot, selection.head(), window, cx);
&snapshot,
selection.head(),
Direction::Prev,
window,
cx,
);
} }
fn hunk_before_position( fn go_to_hunk_before_position(
&mut self, &mut self,
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
position: Point, position: Point,
window: &mut Window,
cx: &mut Context<Editor>,
) -> Option<MultiBufferDiffHunk> { ) -> Option<MultiBufferDiffHunk> {
snapshot let mut hunk = snapshot.buffer_snapshot.diff_hunk_before(position);
.buffer_snapshot if hunk.is_none() {
.diff_hunk_before(position) hunk = snapshot.buffer_snapshot.diff_hunk_before(Point::MAX);
.or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX)) }
if let Some(hunk) = &hunk {
let destination = Point::new(hunk.row_range.start.0, 0);
self.unfold_ranges(&[destination..destination], false, false, cx);
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select_ranges(vec![destination..destination]);
});
}
hunk
} }
pub fn go_to_definition( pub fn go_to_definition(
@@ -12580,6 +12494,7 @@ impl Editor {
buffer.push_transaction(&transaction.0, cx); buffer.push_transaction(&transaction.0, cx);
} }
} }
cx.notify(); cx.notify();
}) })
.ok(); .ok();
@@ -12588,60 +12503,6 @@ impl Editor {
}) })
} }
fn organize_imports(
&mut self,
_: &OrganizeImports,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Task<Result<()>>> {
let project = match &self.project {
Some(project) => project.clone(),
None => return None,
};
Some(self.perform_code_action_kind(
project,
CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
window,
cx,
))
}
fn perform_code_action_kind(
&mut self,
project: Entity<Project>,
kind: CodeActionKind,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let buffer = self.buffer.clone();
let buffers = buffer.read(cx).all_buffers();
let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
let apply_action = project.update(cx, |project, cx| {
project.apply_code_action_kind(buffers, kind, true, cx)
});
cx.spawn_in(window, |_, mut cx| async move {
let transaction = futures::select_biased! {
() = timeout => {
log::warn!("timed out waiting for executing code action");
None
}
transaction = apply_action.log_err().fuse() => transaction,
};
buffer
.update(&mut cx, |buffer, cx| {
// check if we need this
if let Some(transaction) = transaction {
if !buffer.is_singleton() {
buffer.push_transaction(&transaction.0, cx);
}
}
cx.notify();
})
.ok();
Ok(())
})
}
fn restart_language_server( fn restart_language_server(
&mut self, &mut self,
_: &RestartLanguageServer, _: &RestartLanguageServer,
@@ -13034,18 +12895,13 @@ impl Editor {
} }
} else { } else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx); let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let buffer_ids: HashSet<_> = self let buffer_ids: HashSet<_> = multi_buffer_snapshot
.selections .ranges_to_buffer_ranges(self.selections.disjoint_anchor_ranges())
.disjoint_anchor_ranges() .map(|(snapshot, _, _)| snapshot.remote_id())
.flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
.collect(); .collect();
let should_unfold = buffer_ids
.iter()
.any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
for buffer_id in buffer_ids { for buffer_id in buffer_ids {
if should_unfold { if self.is_buffer_folded(buffer_id, cx) {
self.unfold_buffer(buffer_id, cx); self.unfold_buffer(buffer_id, cx);
} else { } else {
self.fold_buffer(buffer_id, cx); self.fold_buffer(buffer_id, cx);
@@ -13122,11 +12978,11 @@ impl Editor {
self.fold_creases(to_fold, true, window, cx); self.fold_creases(to_fold, true, window, cx);
} else { } else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx); let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let buffer_ids = self
.selections let buffer_ids: HashSet<_> = multi_buffer_snapshot
.disjoint_anchor_ranges() .ranges_to_buffer_ranges(self.selections.disjoint_anchor_ranges())
.flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range)) .map(|(snapshot, _, _)| snapshot.remote_id())
.collect::<HashSet<_>>(); .collect();
for buffer_id in buffer_ids { for buffer_id in buffer_ids {
self.fold_buffer(buffer_id, cx); self.fold_buffer(buffer_id, cx);
} }
@@ -13299,11 +13155,10 @@ impl Editor {
self.unfold_ranges(&ranges, true, true, cx); self.unfold_ranges(&ranges, true, true, cx);
} else { } else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx); let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let buffer_ids = self let buffer_ids: HashSet<_> = multi_buffer_snapshot
.selections .ranges_to_buffer_ranges(self.selections.disjoint_anchor_ranges())
.disjoint_anchor_ranges() .map(|(snapshot, _, _)| snapshot.remote_id())
.flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range)) .collect();
.collect::<HashSet<_>>();
for buffer_id in buffer_ids { for buffer_id in buffer_ids {
self.unfold_buffer(buffer_id, cx); self.unfold_buffer(buffer_id, cx);
} }
@@ -13615,7 +13470,7 @@ impl Editor {
snapshot: &MultiBufferSnapshot, snapshot: &MultiBufferSnapshot,
) -> bool { ) -> bool {
let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot); let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
hunks.any(|hunk| hunk.status().has_secondary_hunk()) hunks.any(|hunk| hunk.secondary_status != DiffHunkSecondaryStatus::None)
} }
pub fn toggle_staged_selected_diff_hunks( pub fn toggle_staged_selected_diff_hunks(
@@ -13656,11 +13511,15 @@ impl Editor {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let Some(project) = &self.project else {
return;
};
let chunk_by = self let chunk_by = self
.diff_hunks_in_ranges(&ranges, &snapshot) .diff_hunks_in_ranges(&ranges, &snapshot)
.chunk_by(|hunk| hunk.buffer_id); .chunk_by(|hunk| hunk.buffer_id);
for (buffer_id, hunks) in &chunk_by { for (buffer_id, hunks) in &chunk_by {
self.do_stage_or_unstage(stage, buffer_id, hunks, window, cx); Self::do_stage_or_unstage(project, stage, buffer_id, hunks, &snapshot, window, cx);
} }
} }
@@ -13670,47 +13529,80 @@ impl Editor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>(); let mut ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
if ranges.iter().any(|range| range.start != range.end) { if ranges.iter().any(|range| range.start != range.end) {
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx); self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
return; return;
} }
let snapshot = self.snapshot(window, cx); if !self.buffer().read(cx).is_singleton() {
let newest_range = self.selections.newest::<Point>(cx).range(); if let Some((excerpt_id, buffer, range)) = self.active_excerpt(cx) {
if buffer.read(cx).is_empty() {
let run_twice = snapshot let buffer = buffer.read(cx);
.hunks_for_ranges([newest_range]) let Some(file) = buffer.file() else {
.first() return;
.is_some_and(|hunk| { };
let next_line = Point::new(hunk.row_range.end.0 + 1, 0); let project_path = project::ProjectPath {
self.hunk_after_position(&snapshot, next_line) worktree_id: file.worktree_id(cx),
.is_some_and(|other| other.row_range == hunk.row_range) path: file.path().clone(),
}); };
if run_twice {
self.go_to_next_hunk(&GoToHunk, window, cx);
}
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
self.go_to_next_hunk(&GoToHunk, window, cx);
}
fn do_stage_or_unstage(
&self,
stage: bool,
buffer_id: BufferId,
hunks: impl Iterator<Item = MultiBufferDiffHunk>,
window: &mut Window,
cx: &mut App,
) {
let Some(project) = self.project.as_ref() else { let Some(project) = self.project.as_ref() else {
return; return;
}; };
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else { let project = project.read(cx);
let Some(repo) = project.git_store().read(cx).active_repository() else {
return; return;
}; };
let Some(diff) = self.buffer.read(cx).diff_for(buffer_id) else {
repo.update(cx, |repo, cx| {
let Some(repo_path) = repo.project_path_to_repo_path(&project_path) else {
return;
};
let Some(status) = repo.repository_entry.status_for_path(&repo_path) else {
return;
};
if stage && status.status == FileStatus::Untracked {
repo.stage_entries(vec![repo_path], cx)
.detach_and_log_err(cx);
return;
}
})
}
ranges = vec![multi_buffer::Anchor::range_in_buffer(
excerpt_id,
buffer.read(cx).remote_id(),
range,
)];
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
let snapshot = self.buffer().read(cx).snapshot(cx);
let mut point = ranges.last().unwrap().end.to_point(&snapshot);
if point.row < snapshot.max_row().0 {
point.row += 1;
point.column = 0;
point = snapshot.clip_point(point, Bias::Right);
self.change_selections(Some(Autoscroll::top_relative(6)), window, cx, |s| {
s.select_ranges([point..point]);
})
}
return;
}
}
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
self.go_to_next_hunk(&Default::default(), window, cx);
}
fn do_stage_or_unstage(
project: &Entity<Project>,
stage: bool,
buffer_id: BufferId,
hunks: impl Iterator<Item = MultiBufferDiffHunk>,
snapshot: &MultiBufferSnapshot,
window: &mut Window,
cx: &mut App,
) {
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
log::debug!("no buffer for id");
return; return;
}; };
let buffer_snapshot = buffer.read(cx).snapshot(); let buffer_snapshot = buffer.read(cx).snapshot();
@@ -13724,31 +13616,37 @@ impl Editor {
log::debug!("no git repo for buffer id"); log::debug!("no git repo for buffer id");
return; return;
}; };
let Some(diff) = snapshot.diff_for_buffer_id(buffer_id) else {
log::debug!("no diff for buffer id");
return;
};
let new_index_text = diff.update(cx, |diff, cx| { let new_index_text = if !stage && diff.is_single_insertion || stage && !file_exists {
diff.stage_or_unstage_hunks( log::debug!("removing from index");
None
} else {
diff.new_secondary_text_for_stage_or_unstage(
stage, stage,
&hunks hunks.filter_map(|hunk| {
.map(|hunk| buffer_diff::DiffHunk { if stage && hunk.secondary_status == DiffHunkSecondaryStatus::None {
buffer_range: hunk.buffer_range, return None;
diff_base_byte_range: hunk.diff_base_byte_range, } else if !stage
secondary_status: hunk.secondary_status, && hunk.secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk
range: Point::zero()..Point::zero(), // unused {
}) return None;
.collect::<Vec<_>>(), }
Some((hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone()))
}),
&buffer_snapshot, &buffer_snapshot,
file_exists,
cx, cx,
) )
}); };
if file_exists { if file_exists {
let buffer_store = project.read(cx).buffer_store().clone(); let buffer_store = project.read(cx).buffer_store().clone();
buffer_store buffer_store
.update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx)) .update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
let recv = repo let recv = repo
.read(cx) .read(cx)
.set_index_text(&path, new_index_text.map(|rope| rope.to_string())); .set_index_text(&path, new_index_text.map(|rope| rope.to_string()));
@@ -13823,7 +13721,7 @@ impl Editor {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx)); let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx).into_iter());
let mut ranges_by_buffer = HashMap::default(); let mut ranges_by_buffer = HashMap::default();
self.transact(window, cx, |editor, _window, cx| { self.transact(window, cx, |editor, _window, cx| {
for hunk in hunks { for hunk in hunks {
@@ -14007,17 +13905,13 @@ impl Editor {
return wrap_guides; return wrap_guides;
} }
let settings = self.buffer.read(cx).language_settings(cx); let settings = self.buffer.read(cx).settings_at(0, cx);
if settings.show_wrap_guides { if settings.show_wrap_guides {
match self.soft_wrap_mode(cx) { if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
SoftWrap::Column(soft_wrap) => {
wrap_guides.push((soft_wrap as usize, true)); wrap_guides.push((soft_wrap as usize, true));
} } else if let SoftWrap::Bounded(soft_wrap) = self.soft_wrap_mode(cx) {
SoftWrap::Bounded(soft_wrap) => {
wrap_guides.push((soft_wrap as usize, true)); wrap_guides.push((soft_wrap as usize, true));
} }
SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
}
wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false))) wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
} }
@@ -14025,7 +13919,7 @@ impl Editor {
} }
pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap { pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
let settings = self.buffer.read(cx).language_settings(cx); let settings = self.buffer.read(cx).settings_at(0, cx);
let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap); let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
match mode { match mode {
language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => { language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
@@ -14124,7 +14018,7 @@ impl Editor {
let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| { let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
self.buffer self.buffer
.read(cx) .read(cx)
.language_settings(cx) .settings_at(0, cx)
.indent_guides .indent_guides
.enabled .enabled
}); });
@@ -15773,7 +15667,7 @@ impl Editor {
let copilot_enabled_for_language = self let copilot_enabled_for_language = self
.buffer .buffer
.read(cx) .read(cx)
.language_settings(cx) .settings_at(0, cx)
.show_edit_predictions; .show_edit_predictions;
let project = project.read(cx); let project = project.read(cx);
@@ -16045,9 +15939,9 @@ impl Editor {
if let Some(buffer) = multi_buffer.buffer(buffer_id) { if let Some(buffer) = multi_buffer.buffer(buffer_id) {
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
buffer.edit( buffer.edit(
changes changes.into_iter().map(|(range, text)| {
.into_iter() (range, text.to_string().map(Arc::<str>::from))
.map(|(range, text)| (range, text.to_string())), }),
None, None,
cx, cx,
); );
@@ -17154,7 +17048,7 @@ impl EditorSnapshot {
pub fn hunks_for_ranges( pub fn hunks_for_ranges(
&self, &self,
ranges: impl IntoIterator<Item = Range<Point>>, ranges: impl Iterator<Item = Range<Point>>,
) -> Vec<MultiBufferDiffHunk> { ) -> Vec<MultiBufferDiffHunk> {
let mut hunks = Vec::new(); let mut hunks = Vec::new();
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> = let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
@@ -17165,14 +17059,17 @@ impl EditorSnapshot {
for hunk in self.buffer_snapshot.diff_hunks_in_range( for hunk in self.buffer_snapshot.diff_hunks_in_range(
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0), Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
) { ) {
// Include deleted hunks that are adjacent to the query range, because // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
// otherwise they would be missed. // when the caret is just above or just below the deleted hunk.
let mut intersects_range = hunk.row_range.overlaps(&query_rows); let allow_adjacent = hunk.status().is_deleted();
if hunk.status().is_deleted() { let related_to_selection = if allow_adjacent {
intersects_range |= hunk.row_range.start == query_rows.end; hunk.row_range.overlaps(&query_rows)
intersects_range |= hunk.row_range.end == query_rows.start; || hunk.row_range.start == query_rows.end
} || hunk.row_range.end == query_rows.start
if intersects_range { } else {
hunk.row_range.overlaps(&query_rows)
};
if related_to_selection {
if !processed_buffer_rows if !processed_buffer_rows
.entry(hunk.buffer_id) .entry(hunk.buffer_id)
.or_default() .or_default()
@@ -18013,7 +17910,7 @@ pub fn diagnostic_block_renderer(
) )
.child(buttons(&diagnostic)) .child(buttons(&diagnostic))
.child(div().flex().flex_shrink_0().child( .child(div().flex().flex_shrink_0().child(
StyledText::new(text_without_backticks.clone()).with_default_highlights( StyledText::new(text_without_backticks.clone()).with_highlights(
&text_style, &text_style,
code_ranges.iter().map(|range| { code_ranges.iter().map(|range| {
( (
@@ -18332,37 +18229,3 @@ fn all_edits_insertions_or_deletions(
} }
all_insertions || all_deletions all_insertions || all_deletions
} }
struct MissingEditPredictionKeybindingTooltip;
impl Render for MissingEditPredictionKeybindingTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
ui::tooltip_container(window, cx, |container, _, cx| {
container
.flex_shrink_0()
.max_w_80()
.min_h(rems_from_px(124.))
.justify_between()
.child(
v_flex()
.flex_1()
.text_ui_sm(cx)
.child(Label::new("Conflict with Accept Keybinding"))
.child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
)
.child(
h_flex()
.pb_1()
.gap_1()
.items_end()
.w_full()
.child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
}))
.child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
})),
)
})
}
}

View File

@@ -7,7 +7,7 @@ use crate::{
}, },
JoinLines, JoinLines,
}; };
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind}; use buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
@@ -1514,10 +1514,6 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
stop_at_indent: true, stop_at_indent: true,
}; };
let delete_to_beg = DeleteToBeginningOfLine {
stop_at_indent: false,
};
let move_to_end = MoveToEndOfLine { let move_to_end = MoveToEndOfLine {
stop_at_soft_wraps: true, stop_at_soft_wraps: true,
}; };
@@ -1676,7 +1672,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
}); });
_ = editor.update(cx, |editor, window, cx| { _ = editor.update(cx, |editor, window, cx| {
editor.delete_to_beginning_of_line(&delete_to_beg, window, cx); editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
assert_eq!(editor.display_text(cx), "\n"); assert_eq!(editor.display_text(cx), "\n");
assert_eq!( assert_eq!(
editor.selections.display_ranges(cx), editor.selections.display_ranges(cx),
@@ -1782,107 +1778,6 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
}); });
} }
#[gpui::test]
fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let move_to_beg = MoveToBeginningOfLine {
stop_at_soft_wraps: true,
stop_at_indent: true,
};
let select_to_beg = SelectToBeginningOfLine {
stop_at_soft_wraps: true,
stop_at_indent: true,
};
let delete_to_beg = DeleteToBeginningOfLine {
stop_at_indent: true,
};
let move_to_end = MoveToEndOfLine {
stop_at_soft_wraps: false,
};
let editor = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_simple("abc\n def", cx);
build_editor(buffer, window, cx)
});
_ = editor.update(cx, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| {
s.select_display_ranges([
DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
]);
});
// Moving to the beginning of the line should put the first cursor at the beginning of the line,
// and the second cursor at the first non-whitespace character in the line.
editor.move_to_beginning_of_line(&move_to_beg, window, cx);
assert_eq!(
editor.selections.display_ranges(cx),
&[
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
]
);
// Moving to the beginning of the line again should be a no-op for the first cursor,
// and should move the second cursor to the beginning of the line.
editor.move_to_beginning_of_line(&move_to_beg, window, cx);
assert_eq!(
editor.selections.display_ranges(cx),
&[
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
]
);
// Moving to the beginning of the line again should still be a no-op for the first cursor,
// and should move the second cursor back to the first non-whitespace character in the line.
editor.move_to_beginning_of_line(&move_to_beg, window, cx);
assert_eq!(
editor.selections.display_ranges(cx),
&[
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
]
);
// Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
// and to the first non-whitespace character in the line for the second cursor.
editor.move_to_end_of_line(&move_to_end, window, cx);
editor.move_left(&MoveLeft, window, cx);
editor.select_to_beginning_of_line(&select_to_beg, window, cx);
assert_eq!(
editor.selections.display_ranges(cx),
&[
DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
]
);
// Selecting to the beginning of the line again should be a no-op for the first cursor,
// and should select to the beginning of the line for the second cursor.
editor.select_to_beginning_of_line(&select_to_beg, window, cx);
assert_eq!(
editor.selections.display_ranges(cx),
&[
DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
]
);
// Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
// and should delete to the first non-whitespace character in the line for the second cursor.
editor.move_to_end_of_line(&move_to_end, window, cx);
editor.move_left(&MoveLeft, window, cx);
editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
assert_eq!(editor.text(cx), "c\n f");
});
}
#[gpui::test] #[gpui::test]
fn test_prev_next_word_boundary(cx: &mut TestAppContext) { fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
@@ -2400,13 +2295,7 @@ async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
let mut cx = EditorTestContext::new(cx).await; let mut cx = EditorTestContext::new(cx).await;
cx.set_state("one «two threeˇ» four"); cx.set_state("one «two threeˇ» four");
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.delete_to_beginning_of_line( editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
&DeleteToBeginningOfLine {
stop_at_indent: false,
},
window,
cx,
);
assert_eq!(editor.text(cx), " four"); assert_eq!(editor.text(cx), " four");
}); });
} }
@@ -2965,7 +2854,7 @@ async fn test_indent_outdent(cx: &mut TestAppContext) {
four four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
«oneˇ» «twoˇ» «oneˇ» «twoˇ»
three three
@@ -2985,7 +2874,7 @@ async fn test_indent_outdent(cx: &mut TestAppContext) {
ˇ» four ˇ» four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
t«hree t«hree
@@ -3010,7 +2899,7 @@ async fn test_indent_outdent(cx: &mut TestAppContext) {
ˇ three ˇ three
four four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
ˇthree ˇthree
@@ -3044,13 +2933,13 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
three three
four four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
\t«oneˇ» «twoˇ» \t«oneˇ» «twoˇ»
three three
four four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
«oneˇ» «twoˇ» «oneˇ» «twoˇ»
three three
@@ -3075,13 +2964,13 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
\t\tt«hree \t\tt«hree
ˇ»four ˇ»four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
\tt«hree \tt«hree
ˇ»four ˇ»four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
t«hree t«hree
@@ -3094,7 +2983,7 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
ˇthree ˇthree
four four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
ˇthree ˇthree
@@ -3106,7 +2995,7 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
\tˇthree \tˇthree
four four
"}); "});
cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx)); cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
one two one two
ˇthree ˇthree
@@ -3211,7 +3100,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
"}, "},
cx, cx,
); );
editor.backtab(&Backtab, window, cx); editor.tab_prev(&TabPrev, window, cx);
assert_text_with_selections( assert_text_with_selections(
&mut editor, &mut editor,
indoc! {" indoc! {"
@@ -7986,157 +7875,6 @@ async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
); );
} }
#[gpui::test]
async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
))
});
let fs = FakeFs::new(cx.executor());
fs.insert_file(path!("/file.ts"), Default::default()).await;
let project = Project::test(fs, [path!("/").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
..LanguageConfig::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
)));
update_test_language_settings(cx, |settings| {
settings.defaults.prettier = Some(PrettierSettings {
allowed: true,
..PrettierSettings::default()
});
});
let mut fake_servers = language_registry.register_fake_lsp(
"TypeScript",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
..Default::default()
},
..Default::default()
},
);
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(path!("/file.ts"), cx)
})
.await
.unwrap();
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| {
build_editor_with_project(project.clone(), buffer, window, cx)
});
editor.update_in(cx, |editor, window, cx| {
editor.set_text(
"import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
window,
cx,
)
});
cx.executor().start_waiting();
let fake_server = fake_servers.next().await.unwrap();
let format = editor
.update_in(cx, |editor, window, cx| {
editor.perform_code_action_kind(
project.clone(),
CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
window,
cx,
)
})
.unwrap();
fake_server
.handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(path!("/file.ts")).unwrap()
);
Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
lsp::CodeAction {
title: "Organize Imports".to_string(),
kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
edit: Some(lsp::WorkspaceEdit {
changes: Some(
[(
params.text_document.uri.clone(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(1, 0),
lsp::Position::new(2, 0),
),
"".to_string(),
)],
)]
.into_iter()
.collect(),
),
..Default::default()
}),
..Default::default()
},
)]))
})
.next()
.await;
cx.executor().start_waiting();
format.await;
assert_eq!(
editor.update(cx, |editor, cx| editor.text(cx)),
"import { a } from 'module';\n\nconst x = a;\n"
);
editor.update_in(cx, |editor, window, cx| {
editor.set_text(
"import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
window,
cx,
)
});
// Ensure we don't lock if code action hangs.
fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Url::from_file_path(path!("/file.ts")).unwrap()
);
futures::future::pending::<()>().await;
unreachable!()
},
);
let format = editor
.update_in(cx, |editor, window, cx| {
editor.perform_code_action_kind(
project,
CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
window,
cx,
)
})
.unwrap();
cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
cx.executor().start_waiting();
format.await;
assert_eq!(
editor.update(cx, |editor, cx| editor.text(cx)),
"import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
);
}
#[gpui::test] #[gpui::test]
async fn test_concurrent_format_requests(cx: &mut TestAppContext) { async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
init_test(cx, |_| {}); init_test(cx, |_| {});
@@ -11026,7 +10764,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
executor.run_until_parked(); executor.run_until_parked();
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
@@ -11035,7 +10773,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
"}); "});
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
@@ -11044,7 +10782,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
"}); "});
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
@@ -11053,7 +10791,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
"}); "});
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
@@ -11133,7 +10871,7 @@ async fn cycle_through_same_place_diagnostics(
// Fourth diagnostic // Fourth diagnostic
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn func(abc def: i32) -> ˇu32 { fn func(abc def: i32) -> ˇu32 {
@@ -11142,7 +10880,7 @@ async fn cycle_through_same_place_diagnostics(
// Third diagnostic // Third diagnostic
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn func(abc ˇdef: i32) -> u32 { fn func(abc ˇdef: i32) -> u32 {
@@ -11151,7 +10889,7 @@ async fn cycle_through_same_place_diagnostics(
// Second diagnostic, same place // Second diagnostic, same place
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn func(abc ˇdef: i32) -> u32 { fn func(abc ˇdef: i32) -> u32 {
@@ -11160,7 +10898,7 @@ async fn cycle_through_same_place_diagnostics(
// First diagnostic // First diagnostic
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn func(abcˇ def: i32) -> u32 { fn func(abcˇ def: i32) -> u32 {
@@ -11169,7 +10907,7 @@ async fn cycle_through_same_place_diagnostics(
// Wrapped over, fourth diagnostic // Wrapped over, fourth diagnostic
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx); editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
}); });
cx.assert_editor_state(indoc! {" cx.assert_editor_state(indoc! {"
fn func(abc def: i32) -> ˇu32 { fn func(abc def: i32) -> ˇu32 {
@@ -11435,7 +11173,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
//Wrap around the top of the buffer //Wrap around the top of the buffer
for _ in 0..2 { for _ in 0..2 {
editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx); editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
} }
}); });
@@ -11455,7 +11193,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
); );
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx); editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
}); });
cx.assert_editor_state( cx.assert_editor_state(
@@ -11474,7 +11212,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
); );
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx); editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
}); });
cx.assert_editor_state( cx.assert_editor_state(
@@ -11494,7 +11232,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
for _ in 0..2 { for _ in 0..2 {
editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx); editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
} }
}); });
@@ -12229,7 +11967,7 @@ async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
}); });
cx.run_until_parked(); cx.run_until_parked();
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.context_menu_prev(&ContextMenuPrevious, window, cx); editor.context_menu_prev(&ContextMenuPrev, window, cx);
}); });
cx.run_until_parked(); cx.run_until_parked();
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
@@ -12419,7 +12157,7 @@ async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext)
resolved_items.lock().clear(); resolved_items.lock().clear();
cx.update_editor(|editor, window, cx| { cx.update_editor(|editor, window, cx| {
editor.context_menu_prev(&ContextMenuPrevious, window, cx); editor.context_menu_prev(&ContextMenuPrev, window, cx);
}); });
cx.run_until_parked(); cx.run_until_parked();
// Completions that have already been resolved are skipped. // Completions that have already been resolved are skipped.
@@ -12666,7 +12404,7 @@ async fn test_addition_reverts(cx: &mut TestAppContext) {
struct Row9.2; struct Row9.2;
struct Row9.3; struct Row9.3;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added], vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
struct Row1.1; struct Row1.1;
@@ -12704,7 +12442,7 @@ async fn test_addition_reverts(cx: &mut TestAppContext) {
struct Row8; struct Row8;
struct Row9; struct Row9;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added], vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
struct Row2; struct Row2;
@@ -12751,11 +12489,11 @@ async fn test_addition_reverts(cx: &mut TestAppContext) {
«ˇ// something on bottom» «ˇ// something on bottom»
struct Row10;"#}, struct Row10;"#},
vec![ vec![
DiffHunkStatusKind::Added, DiffHunkStatus::added_none(),
DiffHunkStatusKind::Added, DiffHunkStatus::added_none(),
DiffHunkStatusKind::Added, DiffHunkStatus::added_none(),
DiffHunkStatusKind::Added, DiffHunkStatus::added_none(),
DiffHunkStatusKind::Added, DiffHunkStatus::added_none(),
], ],
indoc! {r#"struct Row; indoc! {r#"struct Row;
ˇstruct Row1; ˇstruct Row1;
@@ -12803,7 +12541,10 @@ async fn test_modification_reverts(cx: &mut TestAppContext) {
struct Row99; struct Row99;
struct Row9; struct Row9;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified], vec![
DiffHunkStatus::modified_none(),
DiffHunkStatus::modified_none(),
],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
struct Row33; struct Row33;
@@ -12830,7 +12571,10 @@ async fn test_modification_reverts(cx: &mut TestAppContext) {
struct Row99; struct Row99;
struct Row9; struct Row9;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified], vec![
DiffHunkStatus::modified_none(),
DiffHunkStatus::modified_none(),
],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
struct Row33; struct Row33;
@@ -12859,12 +12603,12 @@ async fn test_modification_reverts(cx: &mut TestAppContext) {
struct Row9; struct Row9;
struct Row1011;ˇ"#}, struct Row1011;ˇ"#},
vec![ vec![
DiffHunkStatusKind::Modified, DiffHunkStatus::modified_none(),
DiffHunkStatusKind::Modified, DiffHunkStatus::modified_none(),
DiffHunkStatusKind::Modified, DiffHunkStatus::modified_none(),
DiffHunkStatusKind::Modified, DiffHunkStatus::modified_none(),
DiffHunkStatusKind::Modified, DiffHunkStatus::modified_none(),
DiffHunkStatusKind::Modified, DiffHunkStatus::modified_none(),
], ],
indoc! {r#"struct Row; indoc! {r#"struct Row;
ˇstruct Row1; ˇstruct Row1;
@@ -12942,7 +12686,10 @@ struct Row10;"#};
ˇ ˇ
struct Row8; struct Row8;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted], vec![
DiffHunkStatus::deleted_none(),
DiffHunkStatus::deleted_none(),
],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row2; struct Row2;
@@ -12965,7 +12712,10 @@ struct Row10;"#};
ˇ» ˇ»
struct Row8; struct Row8;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted], vec![
DiffHunkStatus::deleted_none(),
DiffHunkStatus::deleted_none(),
],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row2; struct Row2;
@@ -12990,7 +12740,10 @@ struct Row10;"#};
struct Row8;ˇ struct Row8;ˇ
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted], vec![
DiffHunkStatus::deleted_none(),
DiffHunkStatus::deleted_none(),
],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
ˇstruct Row2; ˇstruct Row2;
@@ -13015,9 +12768,9 @@ struct Row10;"#};
struct Row8;ˇ» struct Row8;ˇ»
struct Row10;"#}, struct Row10;"#},
vec![ vec![
DiffHunkStatusKind::Deleted, DiffHunkStatus::deleted_none(),
DiffHunkStatusKind::Deleted, DiffHunkStatus::deleted_none(),
DiffHunkStatusKind::Deleted, DiffHunkStatus::deleted_none(),
], ],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
@@ -16934,13 +16687,14 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
#[track_caller] #[track_caller]
fn assert_hunk_revert( fn assert_hunk_revert(
not_reverted_text_with_selections: &str, not_reverted_text_with_selections: &str,
expected_hunk_statuses_before: Vec<DiffHunkStatusKind>, expected_hunk_statuses_before: Vec<DiffHunkStatus>,
expected_reverted_text_with_selections: &str, expected_reverted_text_with_selections: &str,
base_text: &str, base_text: &str,
cx: &mut EditorLspTestContext, cx: &mut EditorLspTestContext,
) { ) {
cx.set_state(not_reverted_text_with_selections); cx.set_state(not_reverted_text_with_selections);
cx.set_head_text(base_text); cx.set_head_text(base_text);
cx.clear_index_text();
cx.executor().run_until_parked(); cx.executor().run_until_parked();
let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| { let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
@@ -16948,7 +16702,7 @@ fn assert_hunk_revert(
let reverted_hunk_statuses = snapshot let reverted_hunk_statuses = snapshot
.buffer_snapshot .buffer_snapshot
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len()) .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
.map(|hunk| hunk.status().kind) .map(|hunk| hunk.status())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
editor.git_restore(&Default::default(), window, cx); editor.git_restore(&Default::default(), window, cx);

View File

@@ -19,30 +19,28 @@ use crate::{
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayDiffHunk, DisplayPoint, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayDiffHunk, DisplayPoint,
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
GoToPreviousHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown,
PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap, PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR, StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
}; };
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind}; use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
use client::ParticipantIndex; use client::ParticipantIndex;
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use file_icons::FileIcons; use file_icons::FileIcons;
use git::{blame::BlameEntry, status::FileStatus, Oid}; use git::{blame::BlameEntry, status::FileStatus, Oid};
use gpui::{ use gpui::{
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, pattern_slash, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
point, px, quad, relative, size, solid_background, svg, transparent_black, Action, AnyElement, relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
App, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase,
Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox,
Focusable as _, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Hsla, InteractiveElement, IntoElement, Keystroke, Length, ModifiersChangedEvent, MouseButton,
Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
SharedString, Size, StatefulInteractiveElement, Style, Styled, Subscription, TextRun, Subscription, TextRun, TextStyleRefinement, Window,
TextStyleRefinement, Window,
}; };
use inline_completion::Direction;
use itertools::Itertools; use itertools::Itertools;
use language::{ use language::{
language_settings::{ language_settings::{
@@ -56,7 +54,7 @@ use multi_buffer::{
Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow, Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow,
RowInfo, RowInfo,
}; };
use project::project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings}; use project::project_settings::{self, GitGutterSetting, ProjectSettings};
use settings::Settings; use settings::Settings;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::{ use std::{
@@ -197,7 +195,7 @@ impl EditorElement {
register_action(editor, window, Editor::backspace); register_action(editor, window, Editor::backspace);
register_action(editor, window, Editor::delete); register_action(editor, window, Editor::delete);
register_action(editor, window, Editor::tab); register_action(editor, window, Editor::tab);
register_action(editor, window, Editor::backtab); register_action(editor, window, Editor::tab_prev);
register_action(editor, window, Editor::indent); register_action(editor, window, Editor::indent);
register_action(editor, window, Editor::outdent); register_action(editor, window, Editor::outdent);
register_action(editor, window, Editor::autoindent); register_action(editor, window, Editor::autoindent);
@@ -431,13 +429,6 @@ impl EditorElement {
cx.propagate(); cx.propagate();
} }
}); });
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.organize_imports(action, window, cx) {
task.detach_and_notify_err(window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, Editor::restart_language_server); register_action(editor, window, Editor::restart_language_server);
register_action(editor, window, Editor::show_character_palette); register_action(editor, window, Editor::show_character_palette);
register_action(editor, window, |editor, action, window, cx| { register_action(editor, window, |editor, action, window, cx| {
@@ -2724,10 +2715,7 @@ impl EditorElement {
.shadow_md() .shadow_md()
.border_1() .border_1()
.map(|div| { .map(|div| {
let border_color = if is_selected let border_color = if is_selected && is_folded {
&& is_folded
&& focus_handle.contains_focused(window, cx)
{
colors.border_focused colors.border_focused
} else { } else {
colors.border colors.border
@@ -4348,9 +4336,7 @@ impl EditorElement {
} }
} }
fn paint_gutter_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { fn paint_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
let is_light = cx.theme().appearance().is_light();
if layout.display_hunks.is_empty() { if layout.display_hunks.is_empty() {
return; return;
} }
@@ -4370,7 +4356,7 @@ impl EditorElement {
hunk_bounds, hunk_bounds,
cx.theme().colors().version_control_modified, cx.theme().colors().version_control_modified,
Corners::all(px(0.)), Corners::all(px(0.)),
DiffHunkStatus::modified_none(), DiffHunkSecondaryStatus::None,
)) ))
} }
DisplayDiffHunk::Unfolded { DisplayDiffHunk::Unfolded {
@@ -4382,19 +4368,19 @@ impl EditorElement {
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().colors().version_control_added, cx.theme().colors().version_control_added,
Corners::all(px(0.)), Corners::all(px(0.)),
*status, status.secondary,
), ),
DiffHunkStatusKind::Modified => ( DiffHunkStatusKind::Modified => (
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().colors().version_control_modified, cx.theme().colors().version_control_modified,
Corners::all(px(0.)), Corners::all(px(0.)),
*status, status.secondary,
), ),
DiffHunkStatusKind::Deleted if !display_row_range.is_empty() => ( DiffHunkStatusKind::Deleted if !display_row_range.is_empty() => (
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().colors().version_control_deleted, cx.theme().colors().version_control_deleted,
Corners::all(px(0.)), Corners::all(px(0.)),
*status, status.secondary,
), ),
DiffHunkStatusKind::Deleted => ( DiffHunkStatusKind::Deleted => (
Bounds::new( Bounds::new(
@@ -4406,31 +4392,23 @@ impl EditorElement {
), ),
cx.theme().colors().version_control_deleted, cx.theme().colors().version_control_deleted,
Corners::all(1. * line_height), Corners::all(1. * line_height),
*status, status.secondary,
), ),
}), }),
}; };
if let Some((hunk_bounds, mut background_color, corner_radii, secondary_status)) = if let Some((hunk_bounds, background_color, corner_radii, secondary_status)) =
hunk_to_paint hunk_to_paint
{ {
if secondary_status.has_secondary_hunk() { let background_color = if secondary_status != DiffHunkSecondaryStatus::None {
background_color = background_color.opacity(0.3)
background_color.opacity(if is_light { 0.2 } else { 0.32 }); } else {
} background_color.opacity(1.0)
};
// Flatten the background color with the editor color to prevent
// elements below transparent hunks from showing through
let flattened_background_color = cx
.theme()
.colors()
.editor_background
.blend(background_color);
window.paint_quad(quad( window.paint_quad(quad(
hunk_bounds, hunk_bounds,
corner_radii, corner_radii,
flattened_background_color, background_color,
Edges::default(), Edges::default(),
transparent_black(), transparent_black(),
)); ));
@@ -4558,7 +4536,7 @@ impl EditorElement {
) )
}); });
if show_git_gutter { if show_git_gutter {
Self::paint_gutter_diff_hunks(layout, window, cx) Self::paint_diff_hunks(layout, window, cx)
} }
let highlight_width = 0.275 * layout.position_map.line_height; let highlight_width = 0.275 * layout.position_map.line_height;
@@ -4696,7 +4674,7 @@ impl EditorElement {
.read(cx) .read(cx)
.buffer .buffer
.read(cx) .read(cx)
.language_settings(cx) .settings_at(0, cx)
.show_whitespaces; .show_whitespaces;
for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() { for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
@@ -5117,15 +5095,9 @@ impl EditorElement {
end_display_row.0 -= 1; end_display_row.0 -= 1;
} }
let color = match &hunk.status().kind { let color = match &hunk.status().kind {
DiffHunkStatusKind::Added => { DiffHunkStatusKind::Added => theme.status().created,
theme.colors().version_control_added DiffHunkStatusKind::Modified => theme.status().modified,
} DiffHunkStatusKind::Deleted => theme.status().deleted,
DiffHunkStatusKind::Modified => {
theme.colors().version_control_modified
}
DiffHunkStatusKind::Deleted => {
theme.colors().version_control_deleted
}
}; };
ColoredRange { ColoredRange {
start: start_display_row, start: start_display_row,
@@ -6721,17 +6693,14 @@ impl Element for EditorElement {
.editor .editor
.update(cx, |editor, cx| editor.highlighted_display_rows(window, cx)); .update(cx, |editor, cx| editor.highlighted_display_rows(window, cx));
let is_light = cx.theme().appearance().is_light();
let use_pattern = ProjectSettings::get_global(cx)
.git
.hunk_style
.map_or(false, |style| matches!(style, GitHunkStyleSetting::Pattern));
for (ix, row_info) in row_infos.iter().enumerate() { for (ix, row_info) in row_infos.iter().enumerate() {
let Some(diff_status) = row_info.diff_status else { let Some(diff_status) = row_info.diff_status else {
continue; continue;
}; };
let staged_opacity = 0.10;
let unstaged_opacity = 0.04;
let background_color = match diff_status.kind { let background_color = match diff_status.kind {
DiffHunkStatusKind::Added => cx.theme().colors().version_control_added, DiffHunkStatusKind::Added => cx.theme().colors().version_control_added,
DiffHunkStatusKind::Deleted => { DiffHunkStatusKind::Deleted => {
@@ -6742,34 +6711,16 @@ impl Element for EditorElement {
continue; continue;
} }
}; };
let background_color =
let unstaged = diff_status.has_secondary_hunk(); if diff_status.secondary == DiffHunkSecondaryStatus::None {
let hunk_opacity = if is_light { 0.16 } else { 0.12 }; background_color.opacity(staged_opacity)
let staged_background =
solid_background(background_color.opacity(hunk_opacity));
let unstaged_background = if use_pattern {
pattern_slash(
background_color.opacity(hunk_opacity),
window.rem_size().0 * 1.125, // ~18 by default
)
} else { } else {
solid_background(background_color.opacity(if is_light { background_color.opacity(unstaged_opacity)
0.08
} else {
0.04
}))
};
let background = if unstaged {
unstaged_background
} else {
staged_background
}; };
highlighted_rows highlighted_rows
.entry(start_row + DisplayRow(ix as u32)) .entry(start_row + DisplayRow(ix as u32))
.or_insert(background); .or_insert(background_color.into());
} }
let highlighted_ranges = self.editor.read(cx).background_highlights_in_range( let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
@@ -8812,37 +8763,9 @@ fn diff_hunk_controls(
.rounded_b_lg() .rounded_b_lg()
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.gap_1() .gap_1()
.child(if status.has_secondary_hunk() { .when(status.secondary == DiffHunkSecondaryStatus::None, |el| {
Button::new(("stage", row as u64), "Stage") el.child(
.alpha(if status.is_pending() { 0.66 } else { 1.0 }) Button::new("unstage", "Unstage")
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Stage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks(
true,
&[hunk_range.start..hunk_range.start],
window,
cx,
);
});
}
})
} else {
Button::new(("unstage", row as u64), "Unstage")
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({ .tooltip({
let focus_handle = editor.focus_handle(cx); let focus_handle = editor.focus_handle(cx);
move |window, cx| { move |window, cx| {
@@ -8867,7 +8790,38 @@ fn diff_hunk_controls(
); );
}); });
} }
}),
)
}) })
.when(status.secondary != DiffHunkSecondaryStatus::None, |el| {
el.child(
Button::new("stage", "Stage")
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Stage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
})
.on_click({
let editor = editor.clone();
move |_event, window, cx| {
editor.update(cx, |editor, cx| {
editor.stage_or_unstage_diff_hunks(
true,
&[hunk_range.start..hunk_range.start],
window,
cx,
);
});
}
}),
)
}) })
.child( .child(
Button::new("discard", "Restore") Button::new("discard", "Restore")
@@ -8921,13 +8875,8 @@ fn diff_hunk_controls(
let snapshot = editor.snapshot(window, cx); let snapshot = editor.snapshot(window, cx);
let position = let position =
hunk_range.end.to_point(&snapshot.buffer_snapshot); hunk_range.end.to_point(&snapshot.buffer_snapshot);
editor.go_to_hunk_after_or_before_position( editor
&snapshot, .go_to_hunk_after_position(&snapshot, position, window, cx);
position,
Direction::Next,
window,
cx,
);
editor.expand_selected_diff_hunks(cx); editor.expand_selected_diff_hunks(cx);
}); });
} }
@@ -8943,7 +8892,7 @@ fn diff_hunk_controls(
move |window, cx| { move |window, cx| {
Tooltip::for_action_in( Tooltip::for_action_in(
"Previous Hunk", "Previous Hunk",
&GoToPreviousHunk, &GoToPrevHunk,
&focus_handle, &focus_handle,
window, window,
cx, cx,
@@ -8957,13 +8906,7 @@ fn diff_hunk_controls(
let snapshot = editor.snapshot(window, cx); let snapshot = editor.snapshot(window, cx);
let point = let point =
hunk_range.start.to_point(&snapshot.buffer_snapshot); hunk_range.start.to_point(&snapshot.buffer_snapshot);
editor.go_to_hunk_after_or_before_position( editor.go_to_hunk_before_position(&snapshot, point, window, cx);
&snapshot,
point,
Direction::Prev,
window,
cx,
);
editor.expand_selected_diff_hunks(cx); editor.expand_selected_diff_hunks(cx);
}); });
} }

View File

@@ -618,12 +618,12 @@ pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
}, },
syntax: cx.theme().syntax().clone(), syntax: cx.theme().syntax().clone(),
selection_background_color: { cx.theme().players().local().selection }, selection_background_color: { cx.theme().players().local().selection },
heading: StyleRefinement::default() heading: StyleRefinement::default()
.font_weight(FontWeight::BOLD) .font_weight(FontWeight::BOLD)
.text_base() .text_base()
.mt(rems(1.)) .mt(rems(1.))
.mb_0(), .mb_0(),
..Default::default()
} }
} }

View File

@@ -581,7 +581,6 @@ impl InlayHintCache {
self.version += 1; self.version += 1;
} }
self.update_tasks.clear(); self.update_tasks.clear();
self.refresh_task = Task::ready(());
self.hints.clear(); self.hints.clear();
} }

View File

@@ -618,8 +618,11 @@ impl Item for Editor {
ItemSettings::get_global(cx) ItemSettings::get_global(cx)
.file_icons .file_icons
.then(|| { .then(|| {
path_for_buffer(&self.buffer, 0, true, cx) self.buffer
.and_then(|path| FileIcons::get_icon(path.as_ref(), cx)) .read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).project_path(cx))
.and_then(|path| FileIcons::get_icon(path.path.as_ref(), cx))
}) })
.flatten() .flatten()
.map(Icon::from_path) .map(Icon::from_path)

View File

@@ -185,7 +185,7 @@ impl ProposedChangesEditor {
} else { } else {
branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx)); branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
new_diffs.push(cx.new(|cx| { new_diffs.push(cx.new(|cx| {
let mut diff = BufferDiff::new(&branch_buffer.read(cx).snapshot(), cx); let mut diff = BufferDiff::new(branch_buffer.read(cx));
let _ = diff.set_base_text( let _ = diff.set_base_text(
location.buffer.clone(), location.buffer.clone(),
branch_buffer.read(cx).text_snapshot(), branch_buffer.read(cx).text_snapshot(),

View File

@@ -310,7 +310,7 @@ impl SignatureHelpPopover {
.child( .child(
div().px_4().pb_1().child( div().px_4().pb_1().child(
StyledText::new(self.label.clone()) StyledText::new(self.label.clone())
.with_default_highlights(&self.style, self.highlights.iter().cloned()), .with_highlights(&self.style, self.highlights.iter().cloned()),
), ),
) )
.into_any_element() .into_any_element()

View File

@@ -89,16 +89,6 @@ impl EditorTestContext {
Path::new("/root") Path::new("/root")
} }
pub async fn for_editor_in(editor: Entity<Editor>, cx: &mut gpui::VisualTestContext) -> Self {
cx.focus(&editor);
Self {
window: cx.windows()[0],
cx: cx.clone(),
editor,
assertion_cx: AssertionContextManager::new(),
}
}
pub async fn for_editor(editor: WindowHandle<Editor>, cx: &mut gpui::TestAppContext) -> Self { pub async fn for_editor(editor: WindowHandle<Editor>, cx: &mut gpui::TestAppContext) -> Self {
let editor_view = editor.root(cx).unwrap(); let editor_view = editor.root(cx).unwrap();
Self { Self {
@@ -391,76 +381,6 @@ impl EditorTestContext {
assert_state_with_diff(&self.editor, &mut self.cx, &expected_diff_text); assert_state_with_diff(&self.editor, &mut self.cx, &expected_diff_text);
} }
#[track_caller]
pub fn assert_excerpts_with_selections(&mut self, marked_text: &str) {
let expected_excerpts = marked_text
.strip_prefix("[EXCERPT]\n")
.unwrap()
.split("[EXCERPT]\n")
.collect::<Vec<_>>();
let (selections, excerpts) = self.update_editor(|editor, _, cx| {
let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
let selections = editor.selections.disjoint_anchors();
let excerpts = multibuffer_snapshot
.excerpts()
.map(|(e_id, snapshot, range)| (e_id, snapshot.clone(), range))
.collect::<Vec<_>>();
(selections, excerpts)
});
assert_eq!(excerpts.len(), expected_excerpts.len());
for (ix, (excerpt_id, snapshot, range)) in excerpts.into_iter().enumerate() {
let is_folded = self
.update_editor(|editor, _, cx| editor.is_buffer_folded(snapshot.remote_id(), cx));
let (expected_text, expected_selections) =
marked_text_ranges(expected_excerpts[ix], true);
if expected_text == "[FOLDED]\n" {
assert!(is_folded, "excerpt {} should be folded", ix);
let is_selected = selections.iter().any(|s| s.head().excerpt_id == excerpt_id);
if expected_selections.len() > 0 {
assert!(
is_selected,
"excerpt {} should be selected. Got {:?}",
ix,
self.editor_state()
);
} else {
assert!(!is_selected, "excerpt {} should not be selected", ix);
}
continue;
}
assert!(!is_folded, "excerpt {} should not be folded", ix);
assert_eq!(
snapshot
.text_for_range(range.context.clone())
.collect::<String>(),
expected_text
);
let selections = selections
.iter()
.filter(|s| s.head().excerpt_id == excerpt_id)
.map(|s| {
let head = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
- text::ToOffset::to_offset(&range.context.start, &snapshot);
let tail = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
- text::ToOffset::to_offset(&range.context.start, &snapshot);
tail..head
})
.collect::<Vec<_>>();
// todo: selections that cross excerpt boundaries..
assert_eq!(
selections, expected_selections,
"excerpt {} has incorrect selections",
ix,
);
}
}
/// Make an assertion about the editor's text and the ranges and directions /// Make an assertion about the editor's text and the ranges and directions
/// of its selections using a string containing embedded range markers. /// of its selections using a string containing embedded range markers.
/// ///
@@ -472,17 +392,6 @@ impl EditorTestContext {
self.assert_selections(expected_selections, marked_text.to_string()) self.assert_selections(expected_selections, marked_text.to_string())
} }
/// Make an assertion about the editor's text and the ranges and directions
/// of its selections using a string containing embedded range markers.
///
/// See the `util::test::marked_text_ranges` function for more information.
#[track_caller]
pub fn assert_display_state(&mut self, marked_text: &str) {
let (expected_text, expected_selections) = marked_text_ranges(marked_text, true);
pretty_assertions::assert_eq!(self.display_text(), expected_text, "unexpected buffer text");
self.assert_selections(expected_selections, marked_text.to_string())
}
pub fn editor_state(&mut self) -> String { pub fn editor_state(&mut self) -> String {
generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true) generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
} }

View File

@@ -275,7 +275,11 @@ async fn run_evaluation(
let db_path = Path::new(EVAL_DB_PATH); let db_path = Path::new(EVAL_DB_PATH);
let api_key = std::env::var("OPENAI_API_KEY").unwrap(); let api_key = std::env::var("OPENAI_API_KEY").unwrap();
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new()); let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
let fs = Arc::new(RealFs::new(git_hosting_provider_registry, None)) as Arc<dyn Fs>; let fs = Arc::new(RealFs::new(
git_hosting_provider_registry,
None,
PathBuf::from("/non/existent/askpass"),
)) as Arc<dyn Fs>;
let clock = Arc::new(RealSystemClock); let clock = Arc::new(RealSystemClock);
let client = cx let client = cx
.update(|cx| { .update(|cx| {

View File

@@ -339,20 +339,6 @@ async fn test_themes(
let theme_path = extension_path.join(relative_theme_path); let theme_path = extension_path.join(relative_theme_path);
let theme_family = theme::read_user_theme(&theme_path, fs.clone()).await?; let theme_family = theme::read_user_theme(&theme_path, fs.clone()).await?;
log::info!("loaded theme family {}", theme_family.name); log::info!("loaded theme family {}", theme_family.name);
for theme in &theme_family.themes {
if theme
.style
.colors
.deprecated_scrollbar_thumb_background
.is_some()
{
bail!(
r#"Theme "{theme_name}" is using a deprecated style property: scrollbar_thumb.background. Use `scrollbar.thumb.background` instead."#,
theme_name = theme.name
)
}
}
} }
Ok(()) Ok(())

View File

@@ -168,14 +168,11 @@ pub(crate) fn suggest(buffer: Entity<Buffer>, window: &mut Window, cx: &mut Cont
); );
workspace.show_notification(notification_id, cx, |cx| { workspace.show_notification(notification_id, cx, |cx| {
cx.new(move |cx| { cx.new(move |_cx| {
MessageNotification::new( MessageNotification::new(format!(
format!(
"Do you want to install the recommended '{}' extension for '{}' files?", "Do you want to install the recommended '{}' extension for '{}' files?",
extension_id, file_name_or_extension extension_id, file_name_or_extension
), ))
cx,
)
.primary_message("Yes, install extension") .primary_message("Yes, install extension")
.primary_icon(IconName::Check) .primary_icon(IconName::Check)
.primary_icon_color(Color::Success) .primary_icon_color(Color::Success)

View File

@@ -522,7 +522,7 @@ impl ExtensionsPage {
extension.authors.join(", ") extension.authors.join(", ")
)) ))
.size(LabelSize::Small) .size(LabelSize::Small)
.truncate(), .text_ellipsis(),
) )
.child(Label::new("<>").size(LabelSize::Small)), .child(Label::new("<>").size(LabelSize::Small)),
) )
@@ -534,7 +534,7 @@ impl ExtensionsPage {
Label::new(description.clone()) Label::new(description.clone())
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Default) .color(Color::Default)
.truncate() .text_ellipsis()
})) }))
.children(repository_url.map(|repository_url| { .children(repository_url.map(|repository_url| {
IconButton::new( IconButton::new(
@@ -665,7 +665,7 @@ impl ExtensionsPage {
extension.manifest.authors.join(", ") extension.manifest.authors.join(", ")
)) ))
.size(LabelSize::Small) .size(LabelSize::Small)
.truncate(), .text_ellipsis(),
) )
.child( .child(
Label::new(format!( Label::new(format!(
@@ -683,7 +683,7 @@ impl ExtensionsPage {
Label::new(description.clone()) Label::new(description.clone())
.size(LabelSize::Small) .size(LabelSize::Small)
.color(Color::Default) .color(Color::Default)
.truncate() .text_ellipsis()
})) }))
.child( .child(
h_flex() h_flex()

View File

@@ -1,7 +1,5 @@
#[cfg(test)] #[cfg(test)]
mod file_finder_tests; mod file_finder_tests;
#[cfg(test)]
mod open_path_prompt_tests;
pub mod file_finder_settings; pub mod file_finder_settings;
mod new_path_prompt; mod new_path_prompt;
@@ -46,7 +44,7 @@ use workspace::{
Workspace, Workspace,
}; };
actions!(file_finder, [SelectPrevious, ToggleMenu]); actions!(file_finder, [SelectPrev, ToggleMenu]);
impl ModalView for FileFinder { impl ModalView for FileFinder {
fn on_before_dismiss( fn on_before_dismiss(
@@ -201,14 +199,9 @@ impl FileFinder {
} }
} }
fn handle_select_prev( fn handle_select_prev(&mut self, _: &SelectPrev, window: &mut Window, cx: &mut Context<Self>) {
&mut self,
_: &SelectPrevious,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.init_modifiers = Some(window.modifiers()); self.init_modifiers = Some(window.modifiers());
window.dispatch_action(Box::new(menu::SelectPrevious), cx); window.dispatch_action(Box::new(menu::SelectPrev), cx);
} }
fn handle_toggle_menu(&mut self, _: &ToggleMenu, window: &mut Window, cx: &mut Context<Self>) { fn handle_toggle_menu(&mut self, _: &ToggleMenu, window: &mut Window, cx: &mut Context<Self>) {

View File

@@ -3,7 +3,7 @@ use std::{assert_eq, future::IntoFuture, path::Path, time::Duration};
use super::*; use super::*;
use editor::Editor; use editor::Editor;
use gpui::{Entity, TestAppContext, VisualTestContext}; use gpui::{Entity, TestAppContext, VisualTestContext};
use menu::{Confirm, SelectNext, SelectPrevious}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{RemoveOptions, FS_WATCH_LATENCY}; use project::{RemoveOptions, FS_WATCH_LATENCY};
use serde_json::json; use serde_json::json;
use util::path; use util::path;
@@ -2059,7 +2059,7 @@ async fn test_switches_between_release_norelease_modes_on_backward_nav(
// Switch to navigating with other shortcuts // Switch to navigating with other shortcuts
// Don't open file on modifiers release // Don't open file on modifiers release
cx.simulate_modifiers_change(Modifiers::control()); cx.simulate_modifiers_change(Modifiers::control());
cx.dispatch_action(menu::SelectPrevious); cx.dispatch_action(menu::SelectPrev);
cx.simulate_modifiers_change(Modifiers::none()); cx.simulate_modifiers_change(Modifiers::none());
picker.update(cx, |finder, _| { picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3); assert_eq!(finder.delegate.matches.len(), 3);
@@ -2071,7 +2071,7 @@ async fn test_switches_between_release_norelease_modes_on_backward_nav(
// Back to navigation with initial shortcut // Back to navigation with initial shortcut
// Open file on modifiers release // Open file on modifiers release
cx.simulate_modifiers_change(Modifiers::secondary_key()); cx.simulate_modifiers_change(Modifiers::secondary_key());
cx.dispatch_action(SelectPrevious); // <-- File Finder's SelectPrevious, not menu's cx.dispatch_action(SelectPrev); // <-- File Finder's SelectPrev, not menu's
cx.simulate_modifiers_change(Modifiers::none()); cx.simulate_modifiers_change(Modifiers::none());
cx.read(|cx| { cx.read(|cx| {
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap(); let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();

View File

@@ -192,7 +192,7 @@ impl Match {
} }
} }
StyledText::new(text).with_default_highlights(&window.text_style().clone(), highlights) StyledText::new(text).with_highlights(&window.text_style().clone(), highlights)
} }
} }

View File

@@ -3,7 +3,7 @@ use fuzzy::StringMatchCandidate;
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::DirectoryLister; use project::DirectoryLister;
use std::{ use std::{
path::{Path, PathBuf, MAIN_SEPARATOR_STR}, path::{Path, PathBuf},
sync::{ sync::{
atomic::{self, AtomicBool}, atomic::{self, AtomicBool},
Arc, Arc,
@@ -38,38 +38,14 @@ impl OpenPathDelegate {
should_dismiss: true, should_dismiss: true,
} }
} }
#[cfg(any(test, feature = "test-support"))]
pub fn collect_match_candidates(&self) -> Vec<String> {
if let Some(state) = self.directory_state.as_ref() {
self.matches
.iter()
.filter_map(|&index| {
state
.match_candidates
.get(index)
.map(|candidate| candidate.path.string.clone())
})
.collect()
} else {
Vec::new()
}
}
} }
#[derive(Debug)]
struct DirectoryState { struct DirectoryState {
path: String, path: String,
match_candidates: Vec<CandidateInfo>, match_candidates: Vec<StringMatchCandidate>,
error: Option<SharedString>, error: Option<SharedString>,
} }
#[derive(Debug, Clone)]
struct CandidateInfo {
path: StringMatchCandidate,
is_dir: bool,
}
impl OpenPathPrompt { impl OpenPathPrompt {
pub(crate) fn register( pub(crate) fn register(
workspace: &mut Workspace, workspace: &mut Workspace,
@@ -117,6 +93,8 @@ impl PickerDelegate for OpenPathDelegate {
cx.notify(); cx.notify();
} }
// todo(windows)
// Is this method woring correctly on Windows? This method uses `/` for path separator.
fn update_matches( fn update_matches(
&mut self, &mut self,
query: String, query: String,
@@ -124,27 +102,14 @@ impl PickerDelegate for OpenPathDelegate {
cx: &mut Context<Picker<Self>>, cx: &mut Context<Picker<Self>>,
) -> gpui::Task<()> { ) -> gpui::Task<()> {
let lister = self.lister.clone(); let lister = self.lister.clone();
let query_path = Path::new(&query); let (mut dir, suffix) = if let Some(index) = query.rfind('/') {
let last_item = query_path (query[..index].to_string(), query[index + 1..].to_string())
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let (mut dir, suffix) = if let Some(dir) = query.strip_suffix(&last_item) {
(dir.to_string(), last_item)
} else { } else {
(query, String::new()) (query, String::new())
}; };
if dir == "" { if dir == "" {
#[cfg(not(target_os = "windows"))]
{
dir = "/".to_string(); dir = "/".to_string();
} }
#[cfg(target_os = "windows")]
{
dir = "C:\\".to_string();
}
}
let query = if self let query = if self
.directory_state .directory_state
@@ -169,16 +134,12 @@ impl PickerDelegate for OpenPathDelegate {
this.update(&mut cx, |this, _| { this.update(&mut cx, |this, _| {
this.delegate.directory_state = Some(match paths { this.delegate.directory_state = Some(match paths {
Ok(mut paths) => { Ok(mut paths) => {
paths.sort_by(|a, b| compare_paths((&a.path, true), (&b.path, true))); paths.sort_by(|a, b| compare_paths((a, true), (b, true)));
let match_candidates = paths let match_candidates = paths
.iter() .iter()
.enumerate() .enumerate()
.map(|(ix, item)| CandidateInfo { .map(|(ix, path)| {
path: StringMatchCandidate::new( StringMatchCandidate::new(ix, &path.to_string_lossy())
ix,
&item.path.to_string_lossy(),
),
is_dir: item.is_dir,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -217,7 +178,7 @@ impl PickerDelegate for OpenPathDelegate {
}; };
if !suffix.starts_with('.') { if !suffix.starts_with('.') {
match_candidates.retain(|m| !m.path.string.starts_with('.')); match_candidates.retain(|m| !m.string.starts_with('.'));
} }
if suffix == "" { if suffix == "" {
@@ -225,7 +186,7 @@ impl PickerDelegate for OpenPathDelegate {
this.delegate.matches.clear(); this.delegate.matches.clear();
this.delegate this.delegate
.matches .matches
.extend(match_candidates.iter().map(|m| m.path.id)); .extend(match_candidates.iter().map(|m| m.id));
cx.notify(); cx.notify();
}) })
@@ -233,9 +194,8 @@ impl PickerDelegate for OpenPathDelegate {
return; return;
} }
let candidates = match_candidates.iter().map(|m| &m.path).collect::<Vec<_>>();
let matches = fuzzy::match_strings( let matches = fuzzy::match_strings(
candidates.as_slice(), match_candidates.as_slice(),
&suffix, &suffix,
false, false,
100, 100,
@@ -257,7 +217,7 @@ impl PickerDelegate for OpenPathDelegate {
this.delegate.directory_state.as_ref().and_then(|d| { this.delegate.directory_state.as_ref().and_then(|d| {
d.match_candidates d.match_candidates
.get(*m) .get(*m)
.map(|c| !c.path.string.starts_with(&suffix)) .map(|c| !c.string.starts_with(&suffix))
}), }),
*m, *m,
) )
@@ -279,16 +239,7 @@ impl PickerDelegate for OpenPathDelegate {
let m = self.matches.get(self.selected_index)?; let m = self.matches.get(self.selected_index)?;
let directory_state = self.directory_state.as_ref()?; let directory_state = self.directory_state.as_ref()?;
let candidate = directory_state.match_candidates.get(*m)?; let candidate = directory_state.match_candidates.get(*m)?;
Some(format!( Some(format!("{}/{}", directory_state.path, candidate.string))
"{}{}{}",
directory_state.path,
candidate.path.string,
if candidate.is_dir {
MAIN_SEPARATOR_STR
} else {
""
}
))
}) })
.unwrap_or(query), .unwrap_or(query),
) )
@@ -309,7 +260,7 @@ impl PickerDelegate for OpenPathDelegate {
.resolve_tilde(&directory_state.path, cx) .resolve_tilde(&directory_state.path, cx)
.as_ref(), .as_ref(),
) )
.join(&candidate.path.string); .join(&candidate.string);
if let Some(tx) = self.tx.take() { if let Some(tx) = self.tx.take() {
tx.send(Some(vec![result])).ok(); tx.send(Some(vec![result])).ok();
} }
@@ -343,7 +294,7 @@ impl PickerDelegate for OpenPathDelegate {
.spacing(ListItemSpacing::Sparse) .spacing(ListItemSpacing::Sparse)
.inset(true) .inset(true)
.toggle_state(selected) .toggle_state(selected)
.child(LabelLike::new().child(candidate.path.string.clone())), .child(LabelLike::new().child(candidate.string.clone())),
) )
} }
@@ -356,6 +307,6 @@ impl PickerDelegate for OpenPathDelegate {
} }
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
Arc::from(format!("[directory{MAIN_SEPARATOR_STR}]filename.ext")) Arc::from("[directory/]filename.ext")
} }
} }

View File

@@ -1,324 +0,0 @@
use std::sync::Arc;
use gpui::{AppContext, Entity, TestAppContext, VisualTestContext};
use picker::{Picker, PickerDelegate};
use project::Project;
use serde_json::json;
use ui::rems;
use util::path;
use workspace::{AppState, Workspace};
use crate::OpenPathDelegate;
#[gpui::test]
async fn test_open_path_prompt(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
path!("/root"),
json!({
"a1": "A1",
"a2": "A2",
"a3": "A3",
"dir1": {},
"dir2": {
"c": "C",
"d1": "D1",
"d2": "D2",
"d3": "D3",
"dir3": {},
"dir4": {}
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (picker, cx) = build_open_path_prompt(project, cx);
let query = path!("/root");
insert_query(query, &picker, cx).await;
assert_eq!(collect_match_candidates(&picker, cx), vec!["root"]);
// If the query ends with a slash, the picker should show the contents of the directory.
let query = path!("/root/");
insert_query(query, &picker, cx).await;
assert_eq!(
collect_match_candidates(&picker, cx),
vec!["a1", "a2", "a3", "dir1", "dir2"]
);
// Show candidates for the query "a".
let query = path!("/root/a");
insert_query(query, &picker, cx).await;
assert_eq!(
collect_match_candidates(&picker, cx),
vec!["a1", "a2", "a3"]
);
// Show candidates for the query "d".
let query = path!("/root/d");
insert_query(query, &picker, cx).await;
assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
let query = path!("/root/dir2");
insert_query(query, &picker, cx).await;
assert_eq!(collect_match_candidates(&picker, cx), vec!["dir2"]);
let query = path!("/root/dir2/");
insert_query(query, &picker, cx).await;
assert_eq!(
collect_match_candidates(&picker, cx),
vec!["c", "d1", "d2", "d3", "dir3", "dir4"]
);
// Show candidates for the query "d".
let query = path!("/root/dir2/d");
insert_query(query, &picker, cx).await;
assert_eq!(
collect_match_candidates(&picker, cx),
vec!["d1", "d2", "d3", "dir3", "dir4"]
);
let query = path!("/root/dir2/di");
insert_query(query, &picker, cx).await;
assert_eq!(collect_match_candidates(&picker, cx), vec!["dir3", "dir4"]);
}
#[gpui::test]
async fn test_open_path_prompt_completion(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
path!("/root"),
json!({
"a": "A",
"dir1": {},
"dir2": {
"c": "C",
"d": "D",
"dir3": {},
"dir4": {}
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (picker, cx) = build_open_path_prompt(project, cx);
// Confirm completion for the query "/root", since it's a directory, it should add a trailing slash.
let query = path!("/root");
insert_query(query, &picker, cx).await;
assert_eq!(confirm_completion(query, 0, &picker, cx), path!("/root/"));
// Confirm completion for the query "/root/", selecting the first candidate "a", since it's a file, it should not add a trailing slash.
let query = path!("/root/");
insert_query(query, &picker, cx).await;
assert_eq!(confirm_completion(query, 0, &picker, cx), path!("/root/a"));
// Confirm completion for the query "/root/", selecting the second candidate "dir1", since it's a directory, it should add a trailing slash.
let query = path!("/root/");
insert_query(query, &picker, cx).await;
assert_eq!(
confirm_completion(query, 1, &picker, cx),
path!("/root/dir1/")
);
let query = path!("/root/a");
insert_query(query, &picker, cx).await;
assert_eq!(confirm_completion(query, 0, &picker, cx), path!("/root/a"));
let query = path!("/root/d");
insert_query(query, &picker, cx).await;
assert_eq!(
confirm_completion(query, 1, &picker, cx),
path!("/root/dir2/")
);
let query = path!("/root/dir2");
insert_query(query, &picker, cx).await;
assert_eq!(
confirm_completion(query, 0, &picker, cx),
path!("/root/dir2/")
);
let query = path!("/root/dir2/");
insert_query(query, &picker, cx).await;
assert_eq!(
confirm_completion(query, 0, &picker, cx),
path!("/root/dir2/c")
);
let query = path!("/root/dir2/");
insert_query(query, &picker, cx).await;
assert_eq!(
confirm_completion(query, 2, &picker, cx),
path!("/root/dir2/dir3/")
);
let query = path!("/root/dir2/d");
insert_query(query, &picker, cx).await;
assert_eq!(
confirm_completion(query, 0, &picker, cx),
path!("/root/dir2/d")
);
let query = path!("/root/dir2/d");
insert_query(query, &picker, cx).await;
assert_eq!(
confirm_completion(query, 1, &picker, cx),
path!("/root/dir2/dir3/")
);
let query = path!("/root/dir2/di");
insert_query(query, &picker, cx).await;
assert_eq!(
confirm_completion(query, 1, &picker, cx),
path!("/root/dir2/dir4/")
);
}
#[gpui::test]
#[cfg(target_os = "windows")]
async fn test_open_path_prompt_on_windows(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
path!("/root"),
json!({
"a": "A",
"dir1": {},
"dir2": {}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
let (picker, cx) = build_open_path_prompt(project, cx);
// Support both forward and backward slashes.
let query = "C:/root/";
insert_query(query, &picker, cx).await;
assert_eq!(
collect_match_candidates(&picker, cx),
vec!["a", "dir1", "dir2"]
);
assert_eq!(confirm_completion(query, 0, &picker, cx), "C:/root/a");
let query = "C:\\root/";
insert_query(query, &picker, cx).await;
assert_eq!(
collect_match_candidates(&picker, cx),
vec!["a", "dir1", "dir2"]
);
assert_eq!(confirm_completion(query, 0, &picker, cx), "C:\\root/a");
let query = "C:\\root\\";
insert_query(query, &picker, cx).await;
assert_eq!(
collect_match_candidates(&picker, cx),
vec!["a", "dir1", "dir2"]
);
assert_eq!(confirm_completion(query, 0, &picker, cx), "C:\\root\\a");
// Confirm completion for the query "C:/root/d", selecting the second candidate "dir2", since it's a directory, it should add a trailing slash.
let query = "C:/root/d";
insert_query(query, &picker, cx).await;
assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
assert_eq!(confirm_completion(query, 1, &picker, cx), "C:/root/dir2\\");
let query = "C:\\root/d";
insert_query(query, &picker, cx).await;
assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
assert_eq!(confirm_completion(query, 0, &picker, cx), "C:\\root/dir1\\");
let query = "C:\\root\\d";
insert_query(query, &picker, cx).await;
assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
assert_eq!(
confirm_completion(query, 0, &picker, cx),
"C:\\root\\dir1\\"
);
}
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
cx.update(|cx| {
let state = AppState::test(cx);
theme::init(theme::LoadThemes::JustBase, cx);
language::init(cx);
super::init(cx);
editor::init(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
state
})
}
fn build_open_path_prompt(
project: Entity<Project>,
cx: &mut TestAppContext,
) -> (Entity<Picker<OpenPathDelegate>>, &mut VisualTestContext) {
let (tx, _) = futures::channel::oneshot::channel();
let lister = project::DirectoryLister::Project(project.clone());
let delegate = OpenPathDelegate::new(tx, lister.clone());
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
(
workspace.update_in(cx, |_, window, cx| {
cx.new(|cx| {
let picker = Picker::uniform_list(delegate, window, cx)
.width(rems(34.))
.modal(false);
let query = lister.default_query(cx);
picker.set_query(query, window, cx);
picker
})
}),
cx,
)
}
async fn insert_query(
query: &str,
picker: &Entity<Picker<OpenPathDelegate>>,
cx: &mut VisualTestContext,
) {
picker
.update_in(cx, |f, window, cx| {
f.delegate.update_matches(query.to_string(), window, cx)
})
.await;
}
fn confirm_completion(
query: &str,
select: usize,
picker: &Entity<Picker<OpenPathDelegate>>,
cx: &mut VisualTestContext,
) -> String {
picker
.update_in(cx, |f, window, cx| {
if f.delegate.selected_index() != select {
f.delegate.set_selected_index(select, window, cx);
}
f.delegate.confirm_completion(query.to_string(), window, cx)
})
.unwrap()
}
fn collect_match_candidates(
picker: &Entity<Picker<OpenPathDelegate>>,
cx: &mut VisualTestContext,
) -> Vec<String> {
picker.update(cx, |f, _| f.delegate.collect_match_candidates())
}

View File

@@ -135,7 +135,6 @@ pub trait Fs: Send + Sync {
Arc<dyn Watcher>, Arc<dyn Watcher>,
); );
fn home_dir(&self) -> Option<PathBuf>;
fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>>; fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>>;
fn is_fake(&self) -> bool; fn is_fake(&self) -> bool;
async fn is_case_sensitive(&self) -> Result<bool>; async fn is_case_sensitive(&self) -> Result<bool>;
@@ -249,6 +248,7 @@ impl From<MTime> for proto::Timestamp {
pub struct RealFs { pub struct RealFs {
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>, git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
git_binary_path: Option<PathBuf>, git_binary_path: Option<PathBuf>,
askpass_path: PathBuf,
} }
pub trait FileHandle: Send + Sync + std::fmt::Debug { pub trait FileHandle: Send + Sync + std::fmt::Debug {
@@ -303,10 +303,12 @@ impl RealFs {
pub fn new( pub fn new(
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>, git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
git_binary_path: Option<PathBuf>, git_binary_path: Option<PathBuf>,
askpass_path: PathBuf,
) -> Self { ) -> Self {
Self { Self {
git_hosting_provider_registry, git_hosting_provider_registry,
git_binary_path, git_binary_path,
askpass_path,
} }
} }
} }
@@ -770,6 +772,7 @@ impl Fs for RealFs {
Some(Arc::new(RealGitRepository::new( Some(Arc::new(RealGitRepository::new(
repo, repo,
self.git_binary_path.clone(), self.git_binary_path.clone(),
self.askpass_path.to_owned(),
self.git_hosting_provider_registry.clone(), self.git_hosting_provider_registry.clone(),
))) )))
} }
@@ -814,10 +817,6 @@ impl Fs for RealFs {
temp_dir.close()?; temp_dir.close()?;
case_sensitive case_sensitive
} }
fn home_dir(&self) -> Option<PathBuf> {
Some(paths::home_dir().clone())
}
} }
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))] #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
@@ -851,7 +850,6 @@ struct FakeFsState {
metadata_call_count: usize, metadata_call_count: usize,
read_dir_call_count: usize, read_dir_call_count: usize,
moves: std::collections::HashMap<u64, PathBuf>, moves: std::collections::HashMap<u64, PathBuf>,
home_dir: Option<PathBuf>,
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@@ -1037,7 +1035,6 @@ impl FakeFs {
read_dir_call_count: 0, read_dir_call_count: 0,
metadata_call_count: 0, metadata_call_count: 0,
moves: Default::default(), moves: Default::default(),
home_dir: None,
}), }),
}); });
@@ -1531,10 +1528,6 @@ impl FakeFs {
fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> { fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
self.executor.simulate_random_delay() self.executor.simulate_random_delay()
} }
pub fn set_home_dir(&self, home_dir: PathBuf) {
self.state.lock().home_dir = Some(home_dir);
}
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@@ -2090,10 +2083,6 @@ impl Fs for FakeFs {
fn as_fake(&self) -> Arc<FakeFs> { fn as_fake(&self) -> Arc<FakeFs> {
self.this.upgrade().unwrap() self.this.upgrade().unwrap()
} }
fn home_dir(&self) -> Option<PathBuf> {
self.state.lock().home_dir.clone()
}
} }
fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> { fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {

View File

@@ -1,5 +1,5 @@
use std::{ use std::{
borrow::{Borrow, Cow}, borrow::Cow,
sync::atomic::{self, AtomicBool}, sync::atomic::{self, AtomicBool},
}; };
@@ -50,24 +50,22 @@ impl<'a> Matcher<'a> {
/// Filter and score fuzzy match candidates. Results are returned unsorted, in the same order as /// Filter and score fuzzy match candidates. Results are returned unsorted, in the same order as
/// the input candidates. /// the input candidates.
pub fn match_candidates<C, R, F, T>( pub fn match_candidates<C: MatchCandidate, R, F>(
&mut self, &mut self,
prefix: &[char], prefix: &[char],
lowercase_prefix: &[char], lowercase_prefix: &[char],
candidates: impl Iterator<Item = T>, candidates: impl Iterator<Item = C>,
results: &mut Vec<R>, results: &mut Vec<R>,
cancel_flag: &AtomicBool, cancel_flag: &AtomicBool,
build_match: F, build_match: F,
) where ) where
C: MatchCandidate,
T: Borrow<C>,
F: Fn(&C, f64, &Vec<usize>) -> R, F: Fn(&C, f64, &Vec<usize>) -> R,
{ {
let mut candidate_chars = Vec::new(); let mut candidate_chars = Vec::new();
let mut lowercase_candidate_chars = Vec::new(); let mut lowercase_candidate_chars = Vec::new();
for candidate in candidates { for candidate in candidates {
if !candidate.borrow().has_chars(self.query_char_bag) { if !candidate.has_chars(self.query_char_bag) {
continue; continue;
} }
@@ -77,7 +75,7 @@ impl<'a> Matcher<'a> {
candidate_chars.clear(); candidate_chars.clear();
lowercase_candidate_chars.clear(); lowercase_candidate_chars.clear();
for c in candidate.borrow().to_string().chars() { for c in candidate.to_string().chars() {
candidate_chars.push(c); candidate_chars.push(c);
lowercase_candidate_chars.append(&mut c.to_lowercase().collect::<Vec<_>>()); lowercase_candidate_chars.append(&mut c.to_lowercase().collect::<Vec<_>>());
} }
@@ -100,11 +98,7 @@ impl<'a> Matcher<'a> {
); );
if score > 0.0 { if score > 0.0 {
results.push(build_match( results.push(build_match(&candidate, score, &self.match_positions));
candidate.borrow(),
score,
&self.match_positions,
));
} }
} }
} }

View File

@@ -4,7 +4,7 @@ use crate::{
}; };
use gpui::BackgroundExecutor; use gpui::BackgroundExecutor;
use std::{ use std::{
borrow::{Borrow, Cow}, borrow::Cow,
cmp::{self, Ordering}, cmp::{self, Ordering},
iter, iter,
ops::Range, ops::Range,
@@ -113,17 +113,14 @@ impl Ord for StringMatch {
} }
} }
pub async fn match_strings<T>( pub async fn match_strings(
candidates: &[T], candidates: &[StringMatchCandidate],
query: &str, query: &str,
smart_case: bool, smart_case: bool,
max_results: usize, max_results: usize,
cancel_flag: &AtomicBool, cancel_flag: &AtomicBool,
executor: BackgroundExecutor, executor: BackgroundExecutor,
) -> Vec<StringMatch> ) -> Vec<StringMatch> {
where
T: Borrow<StringMatchCandidate> + Sync,
{
if candidates.is_empty() || max_results == 0 { if candidates.is_empty() || max_results == 0 {
return Default::default(); return Default::default();
} }
@@ -132,10 +129,10 @@ where
return candidates return candidates
.iter() .iter()
.map(|candidate| StringMatch { .map(|candidate| StringMatch {
candidate_id: candidate.borrow().id, candidate_id: candidate.id,
score: 0., score: 0.,
positions: Default::default(), positions: Default::default(),
string: candidate.borrow().string.clone(), string: candidate.string.clone(),
}) })
.collect(); .collect();
} }
@@ -166,12 +163,10 @@ where
matcher.match_candidates( matcher.match_candidates(
&[], &[],
&[], &[],
candidates[segment_start..segment_end] candidates[segment_start..segment_end].iter(),
.iter()
.map(|c| c.borrow()),
results, results,
cancel_flag, cancel_flag,
|candidate: &&StringMatchCandidate, score, positions| StringMatch { |candidate, score, positions| StringMatch {
candidate_id: candidate.id, candidate_id: candidate.id,
score, score,
positions: positions.clone(), positions: positions.clone(),

View File

@@ -30,11 +30,11 @@ schemars.workspace = true
serde.workspace = true serde.workspace = true
smol.workspace = true smol.workspace = true
sum_tree.workspace = true sum_tree.workspace = true
tempfile.workspace = true
text.workspace = true text.workspace = true
time.workspace = true time.workspace = true
url.workspace = true url.workspace = true
util.workspace = true util.workspace = true
tempfile.workspace = true
[dev-dependencies] [dev-dependencies]
pretty_assertions.workspace = true pretty_assertions.workspace = true

View File

@@ -26,7 +26,6 @@ pub static DOT_GIT: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".git
pub static GITIGNORE: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".gitignore")); pub static GITIGNORE: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".gitignore"));
pub static FSMONITOR_DAEMON: LazyLock<&'static OsStr> = pub static FSMONITOR_DAEMON: LazyLock<&'static OsStr> =
LazyLock::new(|| OsStr::new("fsmonitor--daemon")); LazyLock::new(|| OsStr::new("fsmonitor--daemon"));
pub static LFS_DIR: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("lfs"));
pub static COMMIT_MESSAGE: LazyLock<&'static OsStr> = pub static COMMIT_MESSAGE: LazyLock<&'static OsStr> =
LazyLock::new(|| OsStr::new("COMMIT_EDITMSG")); LazyLock::new(|| OsStr::new("COMMIT_EDITMSG"));
pub static INDEX_LOCK: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("index.lock")); pub static INDEX_LOCK: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("index.lock"));
@@ -57,7 +56,6 @@ actions!(
Pull, Pull,
Fetch, Fetch,
Commit, Commit,
ShowCommitEditor,
] ]
); );
action_with_deprecated_aliases!(git, RestoreFile, ["editor::RevertFile"]); action_with_deprecated_aliases!(git, RestoreFile, ["editor::RevertFile"]);

View File

@@ -10,10 +10,11 @@ use rope::Rope;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::env::temp_dir;
use std::io::Write as _; use std::io::Write as _;
#[cfg(not(windows))] use std::os::unix::fs::PermissionsExt as _;
use std::os::unix::fs::PermissionsExt; use std::os::unix::net::UnixListener;
use std::process::Stdio; use std::process::{Command, Stdio};
use std::sync::LazyLock; use std::sync::LazyLock;
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
@@ -63,12 +64,6 @@ pub enum UpstreamTracking {
Tracked(UpstreamTrackingStatus), Tracked(UpstreamTrackingStatus),
} }
impl From<UpstreamTrackingStatus> for UpstreamTracking {
fn from(status: UpstreamTrackingStatus) -> Self {
UpstreamTracking::Tracked(status)
}
}
impl UpstreamTracking { impl UpstreamTracking {
pub fn is_gone(&self) -> bool { pub fn is_gone(&self) -> bool {
matches!(self, UpstreamTracking::Gone) matches!(self, UpstreamTracking::Gone)
@@ -82,18 +77,6 @@ impl UpstreamTracking {
} }
} }
#[derive(Debug)]
pub struct RemoteCommandOutput {
pub stdout: String,
pub stderr: String,
}
impl RemoteCommandOutput {
pub fn is_empty(&self) -> bool {
self.stdout.is_empty() && self.stderr.is_empty()
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct UpstreamTrackingStatus { pub struct UpstreamTrackingStatus {
pub ahead: u32, pub ahead: u32,
@@ -199,10 +182,10 @@ pub trait GitRepository: Send + Sync {
branch_name: &str, branch_name: &str,
upstream_name: &str, upstream_name: &str,
options: Option<PushOptions>, options: Option<PushOptions>,
) -> Result<RemoteCommandOutput>; ) -> Result<()>;
fn pull(&self, branch_name: &str, upstream_name: &str) -> Result<RemoteCommandOutput>; fn pull(&self, branch_name: &str, upstream_name: &str) -> Result<()>;
fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>>; fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>>;
fn fetch(&self) -> Result<RemoteCommandOutput>; fn fetch(&self) -> Result<()>;
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
@@ -220,6 +203,7 @@ impl std::fmt::Debug for dyn GitRepository {
pub struct RealGitRepository { pub struct RealGitRepository {
pub repository: Mutex<git2::Repository>, pub repository: Mutex<git2::Repository>,
pub git_binary_path: PathBuf, pub git_binary_path: PathBuf,
pub askpass_path: PathBuf,
hosting_provider_registry: Arc<GitHostingProviderRegistry>, hosting_provider_registry: Arc<GitHostingProviderRegistry>,
} }
@@ -227,11 +211,13 @@ impl RealGitRepository {
pub fn new( pub fn new(
repository: git2::Repository, repository: git2::Repository,
git_binary_path: Option<PathBuf>, git_binary_path: Option<PathBuf>,
askpass_path: PathBuf,
hosting_provider_registry: Arc<GitHostingProviderRegistry>, hosting_provider_registry: Arc<GitHostingProviderRegistry>,
) -> Self { ) -> Self {
Self { Self {
repository: Mutex::new(repository), repository: Mutex::new(repository),
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")), git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
askpass_path,
hosting_provider_registry, hosting_provider_registry,
} }
} }
@@ -625,30 +611,22 @@ impl GitRepository for RealGitRepository {
branch_name: &str, branch_name: &str,
remote_name: &str, remote_name: &str,
options: Option<PushOptions>, options: Option<PushOptions>,
) -> Result<RemoteCommandOutput> { ) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
// We do this on every operation to ensure that the askpass script exists and is executable. // We don't use the bundled git, so we can ensure that system
#[cfg(not(windows))] // credential management and transfer mechanisms are respected
let (askpass_script_path, _temp_dir) = setup_askpass()?; let output = new_std_command("git")
.env("GIT_ASKPASS", &self.askpass_path)
let mut command = new_std_command("git");
command
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["push"]) .args(["push", "--quiet"])
.args(options.map(|option| match option { .args(options.map(|option| match option {
PushOptions::SetUpstream => "--set-upstream", PushOptions::SetUpstream => "--set-upstream",
PushOptions::Force => "--force-with-lease", PushOptions::Force => "--force-with-lease",
})) }))
.arg(remote_name) .arg(remote_name)
.arg(format!("{}:{}", branch_name, branch_name)); .arg(format!("{}:{}", branch_name, branch_name))
.output()?;
#[cfg(not(windows))]
{
command.env("GIT_ASKPASS", askpass_script_path);
}
let output = command.output()?;
if !output.status.success() { if !output.status.success() {
return Err(anyhow!( return Err(anyhow!(
@@ -656,33 +634,22 @@ impl GitRepository for RealGitRepository {
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
)); ));
} else { } else {
return Ok(RemoteCommandOutput { Ok(())
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
});
} }
} }
fn pull(&self, branch_name: &str, remote_name: &str) -> Result<RemoteCommandOutput> { fn pull(&self, branch_name: &str, remote_name: &str) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
// We do this on every operation to ensure that the askpass script exists and is executable. // We don't use the bundled git, so we can ensure that system
#[cfg(not(windows))] // credential management and transfer mechanisms are respected
let (askpass_script_path, _temp_dir) = setup_askpass()?; let output = new_std_command("git")
.env("GIT_ASKPASS", &self.askpass_path)
let mut command = new_std_command("git");
command
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["pull"]) .args(["pull"])
.arg(remote_name) .arg(remote_name)
.arg(branch_name); .arg(branch_name)
.output()?;
#[cfg(not(windows))]
{
command.env("GIT_ASKPASS", askpass_script_path);
}
let output = command.output()?;
if !output.status.success() { if !output.status.success() {
return Err(anyhow!( return Err(anyhow!(
@@ -690,31 +657,20 @@ impl GitRepository for RealGitRepository {
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
)); ));
} else { } else {
return Ok(RemoteCommandOutput { return Ok(());
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
});
} }
} }
fn fetch(&self) -> Result<RemoteCommandOutput> { fn fetch(&self) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
// We do this on every operation to ensure that the askpass script exists and is executable. // We don't use the bundled git, so we can ensure that system
#[cfg(not(windows))] // credential management and transfer mechanisms are respected
let (askpass_script_path, _temp_dir) = setup_askpass()?; let output = new_std_command("git")
.env("GIT_ASKPASS", &self.askpass_path)
let mut command = new_std_command("git");
command
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["fetch", "--all"]); .args(["fetch", "--quiet", "--all"])
.output()?;
#[cfg(not(windows))]
{
command.env("GIT_ASKPASS", askpass_script_path);
}
let output = command.output()?;
if !output.status.success() { if !output.status.success() {
return Err(anyhow!( return Err(anyhow!(
@@ -722,10 +678,7 @@ impl GitRepository for RealGitRepository {
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
)); ));
} else { } else {
return Ok(RemoteCommandOutput { return Ok(());
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
});
} }
} }
@@ -772,18 +725,6 @@ impl GitRepository for RealGitRepository {
} }
} }
#[cfg(not(windows))]
fn setup_askpass() -> Result<(PathBuf, tempfile::TempDir), anyhow::Error> {
let temp_dir = tempfile::Builder::new()
.prefix("zed-git-askpass")
.tempdir()?;
let askpass_script = "#!/bin/sh\necho ''";
let askpass_script_path = temp_dir.path().join("git-askpass.sh");
std::fs::write(&askpass_script_path, askpass_script)?;
std::fs::set_permissions(&askpass_script_path, std::fs::Permissions::from_mode(0o755))?;
Ok((askpass_script_path, temp_dir))
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FakeGitRepository { pub struct FakeGitRepository {
state: Arc<Mutex<FakeGitRepositoryState>>, state: Arc<Mutex<FakeGitRepositoryState>>,
@@ -967,20 +908,15 @@ impl GitRepository for FakeGitRepository {
unimplemented!() unimplemented!()
} }
fn push( fn push(&self, _branch: &str, _remote: &str, _options: Option<PushOptions>) -> Result<()> {
&self,
_branch: &str,
_remote: &str,
_options: Option<PushOptions>,
) -> Result<RemoteCommandOutput> {
unimplemented!() unimplemented!()
} }
fn pull(&self, _branch: &str, _remote: &str) -> Result<RemoteCommandOutput> { fn pull(&self, _branch: &str, _remote: &str) -> Result<()> {
unimplemented!() unimplemented!()
} }
fn fetch(&self) -> Result<RemoteCommandOutput> { fn fetch(&self) -> Result<()> {
unimplemented!() unimplemented!()
} }

View File

@@ -20,7 +20,6 @@ test-support = ["multi_buffer/test-support"]
anyhow.workspace = true anyhow.workspace = true
buffer_diff.workspace = true buffer_diff.workspace = true
collections.workspace = true collections.workspace = true
component.workspace = true
db.workspace = true db.workspace = true
editor.workspace = true editor.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
@@ -30,9 +29,6 @@ git.workspace = true
gpui.workspace = true gpui.workspace = true
itertools.workspace = true itertools.workspace = true
language.workspace = true language.workspace = true
linkify.workspace = true
linkme.workspace = true
log.workspace = true
menu.workspace = true menu.workspace = true
multi_buffer.workspace = true multi_buffer.workspace = true
panel.workspace = true panel.workspace = true
@@ -44,7 +40,6 @@ serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings.workspace = true settings.workspace = true
smallvec.workspace = true
strum.workspace = true strum.workspace = true
theme.workspace = true theme.workspace = true
time.workspace = true time.workspace = true
@@ -57,8 +52,6 @@ zed_actions.workspace = true
windows.workspace = true windows.workspace = true
[dev-dependencies] [dev-dependencies]
ctor.workspace = true
env_logger.workspace = true
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }
project = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] }

View File

@@ -2,7 +2,7 @@
use crate::branch_picker::{self, BranchList}; use crate::branch_picker::{self, BranchList};
use crate::git_panel::{commit_message_editor, GitPanel}; use crate::git_panel::{commit_message_editor, GitPanel};
use git::{Commit, ShowCommitEditor}; use git::Commit;
use panel::{panel_button, panel_editor_style, panel_filled_button}; use panel::{panel_button, panel_editor_style, panel_filled_button};
use project::Project; use project::Project;
use ui::{prelude::*, KeybindingHint, PopoverButton, Tooltip, TriggerablePopover}; use ui::{prelude::*, KeybindingHint, PopoverButton, Tooltip, TriggerablePopover};
@@ -110,20 +110,17 @@ struct RestoreDock {
impl CommitModal { impl CommitModal {
pub fn register(workspace: &mut Workspace, _: &mut Window, _cx: &mut Context<Workspace>) { pub fn register(workspace: &mut Workspace, _: &mut Window, _cx: &mut Context<Workspace>) {
workspace.register_action(|workspace, _: &ShowCommitEditor, window, cx| { workspace.register_action(|workspace, _: &Commit, window, cx| {
let Some(git_panel) = workspace.panel::<GitPanel>(cx) else { let Some(git_panel) = workspace.panel::<GitPanel>(cx) else {
return; return;
}; };
let (can_open_commit_editor, conflict) = git_panel.update(cx, |git_panel, cx| { let (can_commit, conflict) = git_panel.update(cx, |git_panel, _cx| {
let can_open_commit_editor = git_panel.can_open_commit_editor(); let can_commit = git_panel.can_commit();
let conflict = git_panel.has_unstaged_conflicts(); let conflict = git_panel.has_unstaged_conflicts();
if can_open_commit_editor { (can_commit, conflict)
git_panel.set_modal_open(true, cx);
}
(can_open_commit_editor, conflict)
}); });
if !can_open_commit_editor { if !can_commit {
let message = if conflict { let message = if conflict {
"There are still conflicts. You must stage these before committing." "There are still conflicts. You must stage these before committing."
} else { } else {
@@ -134,7 +131,6 @@ impl CommitModal {
prompt.await.ok(); prompt.await.ok();
}) })
.detach(); .detach();
return;
} }
let dock = workspace.dock_at_position(git_panel.position(window, cx)); let dock = workspace.dock_at_position(git_panel.position(window, cx));
@@ -163,7 +159,7 @@ impl CommitModal {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let panel = git_panel.read(cx); let panel = git_panel.read(cx);
let suggested_commit_message = panel.suggest_commit_message(); let suggested_message = panel.suggest_commit_message();
let commit_editor = git_panel.update(cx, |git_panel, cx| { let commit_editor = git_panel.update(cx, |git_panel, cx| {
git_panel.set_modal_open(true, cx); git_panel.set_modal_open(true, cx);
@@ -174,11 +170,19 @@ impl CommitModal {
let commit_message = commit_editor.read(cx).text(cx); let commit_message = commit_editor.read(cx).text(cx);
if let Some(suggested_commit_message) = suggested_commit_message { if let Some(suggested_message) = suggested_message {
if commit_message.is_empty() { if commit_message.is_empty() {
commit_editor.update(cx, |editor, cx| { commit_editor.update(cx, |editor, cx| {
editor.set_placeholder_text(suggested_commit_message, cx); editor.set_text(suggested_message, window, cx);
editor.select_all(&Default::default(), window, cx);
}); });
} else {
if commit_message.as_str().trim() == suggested_message.trim() {
commit_editor.update(cx, |editor, cx| {
// select the message to make it easy to delete
editor.select_all(&Default::default(), window, cx);
});
}
} }
} }
@@ -242,7 +246,7 @@ impl CommitModal {
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let git_panel = self.git_panel.clone(); let git_panel = self.git_panel.clone();
let (branch, can_commit, tooltip, commit_label, co_authors) = let (branch, tooltip, commit_label, co_authors) =
self.git_panel.update(cx, |git_panel, cx| { self.git_panel.update(cx, |git_panel, cx| {
let branch = git_panel let branch = git_panel
.active_repository .active_repository
@@ -254,10 +258,18 @@ impl CommitModal {
.map(|b| b.name.clone()) .map(|b| b.name.clone())
}) })
.unwrap_or_else(|| "<no branch>".into()); .unwrap_or_else(|| "<no branch>".into());
let (can_commit, tooltip) = git_panel.configure_commit_button(cx); let tooltip = if git_panel.has_staged_changes() {
let title = git_panel.commit_button_title(); "Commit staged changes"
} else {
"Commit changes to tracked files"
};
let title = if git_panel.has_staged_changes() {
"Commit"
} else {
"Commit All"
};
let co_authors = git_panel.render_co_authors(cx); let co_authors = git_panel.render_co_authors(cx);
(branch, can_commit, tooltip, title, co_authors) (branch, tooltip, title, co_authors)
}); });
let branch_picker_button = panel_button(branch) let branch_picker_button = panel_button(branch)
@@ -292,8 +304,9 @@ impl CommitModal {
None None
}; };
let panel_editor_focus_handle = let (panel_editor_focus_handle, can_commit) = git_panel.update(cx, |git_panel, cx| {
git_panel.update(cx, |git_panel, cx| git_panel.editor_focus_handle(cx)); (git_panel.editor_focus_handle(cx), git_panel.can_commit())
});
let commit_button = panel_filled_button(commit_label) let commit_button = panel_filled_button(commit_label)
.tooltip(move |window, cx| { .tooltip(move |window, cx| {
@@ -337,7 +350,6 @@ impl CommitModal {
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) { fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent); cx.emit(DismissEvent);
} }
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) { fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
self.git_panel self.git_panel
.update(cx, |git_panel, cx| git_panel.commit_changes(window, cx)); .update(cx, |git_panel, cx| git_panel.commit_changes(window, cx));

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,6 @@ use git_panel_settings::GitPanelSettings;
use gpui::App; use gpui::App;
use project_diff::ProjectDiff; use project_diff::ProjectDiff;
use ui::{ActiveTheme, Color, Icon, IconName, IntoElement}; use ui::{ActiveTheme, Color, Icon, IconName, IntoElement};
use workspace::Workspace;
pub mod branch_picker; pub mod branch_picker;
mod commit_modal; mod commit_modal;
@@ -12,7 +11,6 @@ pub mod git_panel;
mod git_panel_settings; mod git_panel_settings;
pub mod picker_prompt; pub mod picker_prompt;
pub mod project_diff; pub mod project_diff;
mod remote_output_toast;
pub mod repository_selector; pub mod repository_selector;
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
@@ -20,34 +18,6 @@ pub fn init(cx: &mut App) {
branch_picker::init(cx); branch_picker::init(cx);
cx.observe_new(ProjectDiff::register).detach(); cx.observe_new(ProjectDiff::register).detach();
commit_modal::init(cx); commit_modal::init(cx);
cx.observe_new(|workspace: &mut Workspace, _, _| {
workspace.register_action(|workspace, fetch: &git::Fetch, window, cx| {
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
return;
};
panel.update(cx, |panel, cx| {
panel.fetch(fetch, window, cx);
});
});
workspace.register_action(|workspace, push: &git::Push, window, cx| {
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
return;
};
panel.update(cx, |panel, cx| {
panel.push(push, window, cx);
});
});
workspace.register_action(|workspace, pull: &git::Pull, window, cx| {
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
return;
};
panel.update(cx, |panel, cx| {
panel.pull(pull, window, cx);
});
});
})
.detach();
} }
// TODO: Add updated status colors to theme // TODO: Add updated status colors to theme

View File

@@ -26,7 +26,7 @@ pub fn prompt(
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> Task<Result<Option<usize>>> { ) -> Task<Result<usize>> {
if options.is_empty() { if options.is_empty() {
return Task::ready(Err(anyhow!("No options"))); return Task::ready(Err(anyhow!("No options")));
} }
@@ -43,10 +43,7 @@ pub fn prompt(
}) })
})?; })?;
match rx.await { rx.await?
Ok(selection) => Some(selection).transpose(),
Err(_) => anyhow::Ok(None), // User cancelled
}
}) })
} }

View File

@@ -1,26 +1,24 @@
use crate::git_panel::{GitPanel, GitPanelAddon, GitStatusEntry}; use std::any::{Any, TypeId};
use ::git::UnstageAndNext;
use anyhow::Result; use anyhow::Result;
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
use collections::HashSet; use collections::HashSet;
use editor::{ use editor::{
actions::{GoToHunk, GoToPreviousHunk}, actions::{GoToHunk, GoToPrevHunk},
scroll::Autoscroll, scroll::Autoscroll,
Editor, EditorEvent, Editor, EditorEvent, ToPoint,
}; };
use feature_flags::FeatureFlagViewExt; use feature_flags::FeatureFlagViewExt;
use futures::StreamExt; use futures::StreamExt;
use git::{ use git::{status::FileStatus, Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll};
status::FileStatus, ShowCommitEditor, StageAll, StageAndNext, ToggleStaged, UnstageAll,
UnstageAndNext,
};
use gpui::{ use gpui::{
actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity, actions, Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity,
EventEmitter, FocusHandle, Focusable, Render, Subscription, Task, WeakEntity, EventEmitter, FocusHandle, Focusable, Render, Subscription, Task, WeakEntity,
}; };
use language::{Anchor, Buffer, Capability, OffsetRangeExt}; use language::{Anchor, Buffer, Capability, OffsetRangeExt, Point};
use multi_buffer::{MultiBuffer, PathKey}; use multi_buffer::{MultiBuffer, PathKey};
use project::{git::GitStore, Project, ProjectPath}; use project::{git::GitStore, Project, ProjectPath};
use std::any::{Any, TypeId};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::{prelude::*, vertical_divider, Tooltip}; use ui::{prelude::*, vertical_divider, Tooltip};
use util::ResultExt as _; use util::ResultExt as _;
@@ -31,6 +29,8 @@ use workspace::{
Workspace, Workspace,
}; };
use crate::git_panel::{GitPanel, GitPanelAddon, GitStatusEntry};
actions!(git, [Diff]); actions!(git, [Diff]);
pub struct ProjectDiff { pub struct ProjectDiff {
@@ -192,19 +192,6 @@ impl ProjectDiff {
self.move_to_path(path_key, window, cx) self.move_to_path(path_key, window, cx)
} }
pub fn active_path(&self, cx: &App) -> Option<ProjectPath> {
let editor = self.editor.read(cx);
let position = editor.selections.newest_anchor().head();
let multi_buffer = editor.buffer().read(cx);
let (_, buffer, _) = multi_buffer.excerpt_containing(position, cx)?;
let file = buffer.read(cx).file()?;
Some(ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path().clone(),
})
}
fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) { fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) {
if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) { if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
@@ -243,28 +230,26 @@ impl ProjectDiff {
let mut has_unstaged_hunks = false; let mut has_unstaged_hunks = false;
for hunk in editor.diff_hunks_in_ranges(&ranges, &snapshot) { for hunk in editor.diff_hunks_in_ranges(&ranges, &snapshot) {
match hunk.secondary_status { match hunk.secondary_status {
DiffHunkSecondaryStatus::HasSecondaryHunk DiffHunkSecondaryStatus::HasSecondaryHunk => {
| DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {
has_unstaged_hunks = true; has_unstaged_hunks = true;
} }
DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk => { DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk => {
has_staged_hunks = true; has_staged_hunks = true;
has_unstaged_hunks = true; has_unstaged_hunks = true;
} }
DiffHunkSecondaryStatus::None DiffHunkSecondaryStatus::None => {
| DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {
has_staged_hunks = true; has_staged_hunks = true;
} }
} }
} }
let mut can_open_commit_editor = false; let mut commit = false;
let mut stage_all = false; let mut stage_all = false;
let mut unstage_all = false; let mut unstage_all = false;
self.workspace self.workspace
.read_with(cx, |workspace, cx| { .read_with(cx, |workspace, cx| {
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) { if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
let git_panel = git_panel.read(cx); let git_panel = git_panel.read(cx);
can_open_commit_editor = git_panel.can_open_commit_editor(); commit = git_panel.can_commit();
stage_all = git_panel.can_stage_all(); stage_all = git_panel.can_stage_all();
unstage_all = git_panel.can_unstage_all(); unstage_all = git_panel.can_unstage_all();
} }
@@ -276,7 +261,7 @@ impl ProjectDiff {
unstage: has_staged_hunks, unstage: has_staged_hunks,
prev_next, prev_next,
selection, selection,
can_open_commit_editor, commit,
stage_all, stage_all,
unstage_all, unstage_all,
}; };
@@ -284,26 +269,41 @@ impl ProjectDiff {
fn handle_editor_event( fn handle_editor_event(
&mut self, &mut self,
_: &Entity<Editor>, editor: &Entity<Editor>,
event: &EditorEvent, event: &EditorEvent,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
match event { match event {
EditorEvent::SelectionsChanged { local: true } => { EditorEvent::ScrollPositionChanged { .. } => editor.update(cx, |editor, cx| {
let Some(project_path) = self.active_path(cx) else { let anchor = editor.scroll_manager.anchor().anchor;
let multibuffer = self.multibuffer.read(cx);
let snapshot = multibuffer.snapshot(cx);
let mut point = anchor.to_point(&snapshot);
point.row = (point.row + 1).min(snapshot.max_row().0);
point.column = 0;
let Some((_, buffer, _)) = self.multibuffer.read(cx).excerpt_containing(point, cx)
else {
return;
};
let Some(project_path) = buffer
.read(cx)
.file()
.map(|file| (file.worktree_id(cx), file.path().clone()))
else {
return; return;
}; };
self.workspace self.workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) { if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
git_panel.update(cx, |git_panel, cx| { git_panel.update(cx, |git_panel, cx| {
git_panel.select_entry_by_path(project_path, window, cx) git_panel.select_entry_by_path(project_path.into(), window, cx)
}) })
} }
}) })
.ok(); .ok();
} }),
_ => {} _ => {}
} }
} }
@@ -378,10 +378,13 @@ impl ProjectDiff {
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
let diff = diff.read(cx); let diff = diff.read(cx);
let diff_hunk_ranges = diff let diff_hunk_ranges = if diff.base_text().is_none() {
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx) vec![Point::zero()..snapshot.max_point()]
} else {
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx)
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot)) .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
.collect::<Vec<_>>(); .collect::<Vec<_>>()
};
let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| { let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| {
let was_empty = multibuffer.is_empty(); let was_empty = multibuffer.is_empty();
@@ -398,7 +401,6 @@ impl ProjectDiff {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
if was_empty { if was_empty {
editor.change_selections(None, window, cx, |selections| { editor.change_selections(None, window, cx, |selections| {
// TODO select the very beginning (possibly inside a deletion)
selections.select_ranges([0..0]) selections.select_ranges([0..0])
}); });
} }
@@ -773,7 +775,7 @@ struct ButtonStates {
selection: bool, selection: bool,
stage_all: bool, stage_all: bool,
unstage_all: bool, unstage_all: bool,
can_open_commit_editor: bool, commit: bool,
} }
impl Render for ProjectDiffToolbar { impl Render for ProjectDiffToolbar {
@@ -812,7 +814,7 @@ impl Render for ProjectDiffToolbar {
el.child( el.child(
Button::new("stage", "Stage") Button::new("stage", "Stage")
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Stage and go to next hunk", "Stage",
&StageAndNext, &StageAndNext,
&focus_handle, &focus_handle,
)) ))
@@ -829,7 +831,7 @@ impl Render for ProjectDiffToolbar {
.child( .child(
Button::new("unstage", "Unstage") Button::new("unstage", "Unstage")
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Unstage and go to next hunk", "Unstage",
&UnstageAndNext, &UnstageAndNext,
&focus_handle, &focus_handle,
)) ))
@@ -853,12 +855,12 @@ impl Render for ProjectDiffToolbar {
.shape(ui::IconButtonShape::Square) .shape(ui::IconButtonShape::Square)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Go to previous hunk", "Go to previous hunk",
&GoToPreviousHunk, &GoToPrevHunk,
&focus_handle, &focus_handle,
)) ))
.disabled(!button_states.prev_next) .disabled(!button_states.prev_next)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&GoToPreviousHunk, window, cx) this.dispatch_action(&GoToPrevHunk, window, cx)
})), })),
) )
.child( .child(
@@ -917,14 +919,14 @@ impl Render for ProjectDiffToolbar {
) )
.child( .child(
Button::new("commit", "Commit") Button::new("commit", "Commit")
.disabled(!button_states.can_open_commit_editor) .disabled(!button_states.commit)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Commit", "Commit",
&ShowCommitEditor, &Commit,
&focus_handle, &focus_handle,
)) ))
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&ShowCommitEditor, window, cx); this.dispatch_action(&Commit, window, cx);
})), })),
), ),
) )
@@ -947,11 +949,6 @@ mod tests {
use super::*; use super::*;
#[ctor::ctor]
fn init_logger() {
env_logger::init();
}
fn init_test(cx: &mut TestAppContext) { fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| { cx.update(|cx| {
let store = SettingsStore::test(cx); let store = SettingsStore::test(cx);
@@ -974,7 +971,7 @@ mod tests {
path!("/project"), path!("/project"),
json!({ json!({
".git": {}, ".git": {},
"foo.txt": "FOO\n", "foo": "FOO\n",
}), }),
) )
.await; .await;
@@ -988,15 +985,11 @@ mod tests {
fs.set_head_for_repo( fs.set_head_for_repo(
path!("/project/.git").as_ref(), path!("/project/.git").as_ref(),
&[("foo.txt".into(), "foo\n".into())], &[("foo".into(), "foo\n".into())],
);
fs.set_index_for_repo(
path!("/project/.git").as_ref(),
&[("foo.txt".into(), "foo\n".into())],
); );
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| { fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
state.statuses = HashMap::from_iter([( state.statuses = HashMap::from_iter([(
"foo.txt".into(), "foo".into(),
TrackedStatus { TrackedStatus {
index_status: StatusCode::Unmodified, index_status: StatusCode::Unmodified,
worktree_status: StatusCode::Modified, worktree_status: StatusCode::Modified,
@@ -1027,7 +1020,7 @@ mod tests {
assert_state_with_diff(&editor, cx, &"ˇ".unindent()); assert_state_with_diff(&editor, cx, &"ˇ".unindent());
let text = String::from_utf8(fs.read_file_sync("/project/foo.txt").unwrap()).unwrap(); let text = String::from_utf8(fs.read_file_sync("/project/foo").unwrap()).unwrap();
assert_eq!(text, "foo\n"); assert_eq!(text, "foo\n");
} }
@@ -1124,107 +1117,4 @@ mod tests {
.unindent(), .unindent(),
); );
} }
#[gpui::test]
async fn test_hunks_after_restore_then_modify(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/project"),
json!({
".git": {},
"foo": "modified\n",
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(path!("/project/foo"), cx)
})
.await
.unwrap();
let buffer_editor = cx.new_window_entity(|window, cx| {
Editor::for_buffer(buffer, Some(project.clone()), window, cx)
});
let diff = cx.new_window_entity(|window, cx| {
ProjectDiff::new(project.clone(), workspace, window, cx)
});
cx.run_until_parked();
fs.set_head_for_repo(
path!("/project/.git").as_ref(),
&[("foo".into(), "original\n".into())],
);
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
state.statuses = HashMap::from_iter([(
"foo".into(),
TrackedStatus {
index_status: StatusCode::Unmodified,
worktree_status: StatusCode::Modified,
}
.into(),
)]);
});
cx.run_until_parked();
let diff_editor = diff.update(cx, |diff, _| diff.editor.clone());
assert_state_with_diff(
&diff_editor,
cx,
&"
- original
+ ˇmodified
"
.unindent(),
);
let prev_buffer_hunks =
cx.update_window_entity(&buffer_editor, |buffer_editor, window, cx| {
let snapshot = buffer_editor.snapshot(window, cx);
let snapshot = &snapshot.buffer_snapshot;
let prev_buffer_hunks = buffer_editor
.diff_hunks_in_ranges(&[editor::Anchor::min()..editor::Anchor::max()], snapshot)
.collect::<Vec<_>>();
buffer_editor.git_restore(&Default::default(), window, cx);
prev_buffer_hunks
});
assert_eq!(prev_buffer_hunks.len(), 1);
cx.run_until_parked();
let new_buffer_hunks =
cx.update_window_entity(&buffer_editor, |buffer_editor, window, cx| {
let snapshot = buffer_editor.snapshot(window, cx);
let snapshot = &snapshot.buffer_snapshot;
let new_buffer_hunks = buffer_editor
.diff_hunks_in_ranges(&[editor::Anchor::min()..editor::Anchor::max()], snapshot)
.collect::<Vec<_>>();
buffer_editor.git_restore(&Default::default(), window, cx);
new_buffer_hunks
});
assert_eq!(new_buffer_hunks.as_slice(), &[]);
cx.update_window_entity(&buffer_editor, |buffer_editor, window, cx| {
buffer_editor.set_text("different\n", window, cx);
buffer_editor.save(false, project.clone(), window, cx)
})
.await
.unwrap();
cx.run_until_parked();
assert_state_with_diff(
&diff_editor,
cx,
&"
- original
+ ˇdifferent
"
.unindent(),
);
}
} }

View File

@@ -1,227 +0,0 @@
use std::{ops::Range, time::Duration};
use git::repository::{Remote, RemoteCommandOutput};
use gpui::{
DismissEvent, EventEmitter, FocusHandle, Focusable, HighlightStyle, InteractiveText,
StyledText, Task, UnderlineStyle, WeakEntity,
};
use itertools::Itertools;
use linkify::{LinkFinder, LinkKind};
use ui::{
div, h_flex, px, v_flex, vh, Clickable, Color, Context, FluentBuilder, Icon, IconButton,
IconName, InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ParentElement,
Render, SharedString, Styled, StyledExt, Window,
};
use workspace::{
notifications::{Notification, NotificationId},
Workspace,
};
pub enum RemoteAction {
Fetch,
Pull,
Push(Remote),
}
struct InfoFromRemote {
name: SharedString,
remote_text: SharedString,
links: Vec<Range<usize>>,
}
pub struct RemoteOutputToast {
_workspace: WeakEntity<Workspace>,
_id: NotificationId,
message: SharedString,
remote_info: Option<InfoFromRemote>,
_dismiss_task: Task<()>,
focus_handle: FocusHandle,
}
impl Focusable for RemoteOutputToast {
fn focus_handle(&self, _cx: &ui::App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Notification for RemoteOutputToast {}
const REMOTE_OUTPUT_TOAST_SECONDS: u64 = 5;
impl RemoteOutputToast {
pub fn new(
action: RemoteAction,
output: RemoteCommandOutput,
id: NotificationId,
workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> Self {
let task = cx.spawn({
let workspace = workspace.clone();
let id = id.clone();
|_, mut cx| async move {
cx.background_executor()
.timer(Duration::from_secs(REMOTE_OUTPUT_TOAST_SECONDS))
.await;
workspace
.update(&mut cx, |workspace, cx| {
workspace.dismiss_notification(&id, cx);
})
.ok();
}
});
let mut message: SharedString;
let remote;
match action {
RemoteAction::Fetch | RemoteAction::Pull => {
if output.is_empty() {
message = "Up to date".into();
} else {
message = output.stderr.into();
}
remote = None;
}
RemoteAction::Push(remote_ref) => {
message = output.stdout.trim().to_string().into();
if message.is_empty() {
message = output.stderr.trim().to_string().into();
if message.is_empty() {
message = "Push Successful".into();
}
remote = None;
} else {
let remote_message = get_remote_lines(&output.stderr);
remote = if remote_message.is_empty() {
None
} else {
let finder = LinkFinder::new();
let links = finder
.links(&remote_message)
.filter(|link| *link.kind() == LinkKind::Url)
.map(|link| link.start()..link.end())
.collect_vec();
Some(InfoFromRemote {
name: remote_ref.name,
remote_text: remote_message.into(),
links,
})
}
}
}
}
Self {
_workspace: workspace,
_id: id,
message,
remote_info: remote,
_dismiss_task: task,
focus_handle: cx.focus_handle(),
}
}
}
impl Render for RemoteOutputToast {
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
div()
.occlude()
.w_full()
.max_h(vh(0.8, window))
.elevation_3(cx)
.child(
v_flex()
.p_3()
.overflow_hidden()
.child(
h_flex()
.justify_between()
.items_start()
.child(
h_flex()
.gap_2()
.child(Icon::new(IconName::GitBranch).color(Color::Default))
.child(Label::new("Git")),
)
.child(h_flex().child(
IconButton::new("close", IconName::Close).on_click(
cx.listener(|_, _, _, cx| cx.emit(gpui::DismissEvent)),
),
)),
)
.child(Label::new(self.message.clone()).size(LabelSize::Default))
.when_some(self.remote_info.as_ref(), |this, remote_info| {
this.child(
div()
.border_1()
.border_color(Color::Muted.color(cx))
.rounded_lg()
.text_sm()
.mt_1()
.p_1()
.child(
h_flex()
.gap_2()
.child(Icon::new(IconName::Cloud).color(Color::Default))
.child(
Label::new(remote_info.name.clone())
.size(LabelSize::Default),
),
)
.map(|div| {
let styled_text =
StyledText::new(remote_info.remote_text.clone())
.with_highlights(remote_info.links.iter().map(
|link| {
(
link.clone(),
HighlightStyle {
underline: Some(UnderlineStyle {
thickness: px(1.0),
..Default::default()
}),
..Default::default()
},
)
},
));
let this = cx.weak_entity();
let text = InteractiveText::new("remote-message", styled_text)
.on_click(
remote_info.links.clone(),
move |ix, _window, cx| {
this.update(cx, |this, cx| {
if let Some(remote_info) = &this.remote_info {
cx.open_url(
&remote_info.remote_text
[remote_info.links[ix].clone()],
)
}
})
.ok();
},
);
div.child(text)
}),
)
}),
)
}
}
impl EventEmitter<DismissEvent> for RemoteOutputToast {}
fn get_remote_lines(output: &str) -> String {
output
.lines()
.filter_map(|line| line.strip_prefix("remote:"))
.map(|line| line.trim())
.filter(|line| !line.is_empty())
.collect::<Vec<_>>()
.join("\n")
}

View File

@@ -47,10 +47,6 @@ impl RepositorySelector {
} }
} }
pub(crate) fn repositories_len(&self, cx: &App) -> usize {
self.picker.read(cx).delegate.repository_entries.len()
}
fn handle_project_git_event( fn handle_project_git_event(
&mut self, &mut self,
git_store: &Entity<GitStore>, git_store: &Entity<GitStore>,

View File

@@ -74,9 +74,6 @@ struct ImageShowcase {
impl Render for ImageShowcase { impl Render for ImageShowcase {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div() div()
.id("main")
.overflow_y_scroll()
.p_5()
.size_full() .size_full()
.flex() .flex()
.flex_col() .flex_col()
@@ -119,21 +116,9 @@ impl Render for ImageShowcase {
div() div()
.flex_col() .flex_col()
.child("Auto Height") .child("Auto Height")
.child(img("https://picsum.photos/800/400").w(px(180.))), .child(img("https://picsum.photos/480/640").w(px(180.))),
), ),
) )
.child(
div()
.flex()
.flex_col()
.justify_center()
.items_center()
.w_full()
.border_1()
.border_color(rgb(0xC0C0C0))
.child("image with max width 100%")
.child(img("https://picsum.photos/800/400").max_w_full()),
)
} }
} }
@@ -179,7 +164,9 @@ fn main() {
cx.new(|_| ImageShowcase { cx.new(|_| ImageShowcase {
// Relative path to your root project path // Relative path to your root project path
local_resource: manifest_dir.join("examples/image/app-icon.png").into(), local_resource: manifest_dir.join("examples/image/app-icon.png").into(),
remote_resource: "https://picsum.photos/800/400".into(),
remote_resource: "https://picsum.photos/512/512".into(),
asset_resource: "image/color.svg".into(), asset_resource: "image/color.svg".into(),
}) })
}) })

View File

@@ -399,8 +399,6 @@ macro_rules! action_with_deprecated_aliases {
/// Registers the action and implements the Action trait for any struct that implements Clone, /// Registers the action and implements the Action trait for any struct that implements Clone,
/// Default, PartialEq, serde_deserialize::Deserialize, and schemars::JsonSchema. /// Default, PartialEq, serde_deserialize::Deserialize, and schemars::JsonSchema.
/// ///
/// Similar to `actions!`, but accepts structs with fields.
///
/// Fields and variants that don't make sense for user configuration should be annotated with /// Fields and variants that don't make sense for user configuration should be annotated with
/// #[serde(skip)]. /// #[serde(skip)].
#[macro_export] #[macro_export]

View File

@@ -670,14 +670,6 @@ pub fn pattern_slash(color: Hsla, thickness: f32) -> Background {
} }
} }
/// Creates a solid background color.
pub fn solid_background(color: impl Into<Hsla>) -> Background {
Background {
solid: color.into(),
..Default::default()
}
}
/// Creates a LinearGradient background color. /// Creates a LinearGradient background color.
/// ///
/// The gradient line's angle of direction. A value of `0.` is equivalent to to top; increasing values rotate clockwise from there. /// The gradient line's angle of direction. A value of `0.` is equivalent to to top; increasing values rotate clockwise from there.

View File

@@ -1509,7 +1509,6 @@ impl Interactivity {
|| self.tracked_focus_handle.is_some() || self.tracked_focus_handle.is_some()
|| self.hover_style.is_some() || self.hover_style.is_some()
|| self.group_hover_style.is_some() || self.group_hover_style.is_some()
|| self.hover_listener.is_some()
|| !self.mouse_up_listeners.is_empty() || !self.mouse_up_listeners.is_empty()
|| !self.mouse_down_listeners.is_empty() || !self.mouse_down_listeners.is_empty()
|| !self.mouse_move_listeners.is_empty() || !self.mouse_move_listeners.is_empty()
@@ -2128,7 +2127,6 @@ impl Interactivity {
if let Some(scroll_offset) = self.scroll_offset.clone() { if let Some(scroll_offset) = self.scroll_offset.clone() {
let overflow = style.overflow; let overflow = style.overflow;
let allow_concurrent_scroll = style.allow_concurrent_scroll; let allow_concurrent_scroll = style.allow_concurrent_scroll;
let restrict_scroll_to_axis = style.restrict_scroll_to_axis;
let line_height = window.line_height(); let line_height = window.line_height();
let hitbox = hitbox.clone(); let hitbox = hitbox.clone();
window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| { window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
@@ -2141,7 +2139,7 @@ impl Interactivity {
if overflow.x == Overflow::Scroll { if overflow.x == Overflow::Scroll {
if !delta.x.is_zero() { if !delta.x.is_zero() {
delta_x = delta.x; delta_x = delta.x;
} else if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll { } else if overflow.y != Overflow::Scroll {
delta_x = delta.y; delta_x = delta.y;
} }
} }
@@ -2149,7 +2147,7 @@ impl Interactivity {
if overflow.y == Overflow::Scroll { if overflow.y == Overflow::Scroll {
if !delta.y.is_zero() { if !delta.y.is_zero() {
delta_y = delta.y; delta_y = delta.y;
} else if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll { } else if overflow.x != Overflow::Scroll {
delta_y = delta.x; delta_y = delta.x;
} }
} }

View File

@@ -301,8 +301,6 @@ impl Element for Img {
} }
let image_size = data.size(frame_index); let image_size = data.size(frame_index);
style.aspect_ratio =
Some(image_size.width.0 as f32 / image_size.height.0 as f32);
if let Length::Auto = style.size.width { if let Length::Auto = style.size.width {
style.size.width = match style.size.height { style.size.width = match style.size.height {

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