Compare commits
26 Commits
v0.202.8
...
v0.177.1-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1516ee3e46 | ||
|
|
53af68aa82 | ||
|
|
e897f191f6 | ||
|
|
aba10b73d2 | ||
|
|
46190bd087 | ||
|
|
0b360febad | ||
|
|
11d75c42f1 | ||
|
|
b3de2bf740 | ||
|
|
b2f174a622 | ||
|
|
a3b7c1d9e3 | ||
|
|
f4b83d1fba | ||
|
|
5de7f1bcd5 | ||
|
|
375885e6ec | ||
|
|
7ab9ec904e | ||
|
|
94425051a1 | ||
|
|
7bd4a85a29 | ||
|
|
7d1b50ea85 | ||
|
|
96ce87d2dd | ||
|
|
1c6bf1f9b1 | ||
|
|
8d9d14c2b9 | ||
|
|
46944b679f | ||
|
|
b1386bff7b | ||
|
|
02204dee06 | ||
|
|
a1e613805a | ||
|
|
5852f2e0a4 | ||
|
|
3130b46515 |
131
Cargo.lock
generated
131
Cargo.lock
generated
@@ -1178,9 +1178,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.5.17"
|
||||
version = "1.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490aa7465ee685b2ced076bb87ef654a47724a7844e2c7d3af4e749ce5b875dd"
|
||||
checksum = "50236e4d60fe8458de90a71c0922c761e41755adf091b1b03de1cef537179915"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1271,9 +1271,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-bedrockruntime"
|
||||
version = "1.75.0"
|
||||
version = "1.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ddf7475b6f50a1a5be8edb1bcdf6e4ae00feed5b890d14a3f1f0e14d76f5a16"
|
||||
checksum = "6938541d1948a543bca23303fec4cff9c36bf0e63b8fa3ae1b337bcb9d5b81af"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1295,9 +1295,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-kinesis"
|
||||
version = "1.62.0"
|
||||
version = "1.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31622345afd0c35d33c1cbba73ccf9fb88e09857413d8963dea2c493e00704d"
|
||||
checksum = "89f2163d8704e8fdcd51ec6c2e0441c418471e422ee9690451b17a1c46344e1a"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1317,9 +1317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.77.0"
|
||||
version = "1.76.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34e87342432a3de0e94e82c99a7cbd9042f99de029ae1f4e368160f9e9929264"
|
||||
checksum = "66e83401ad7287ad15244d557e35502c2a94105ca5b41d656c391f1a4fc04ca2"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1351,9 +1351,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.60.0"
|
||||
version = "1.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60186fab60b24376d3e33b9ff0a43485f99efd470e3b75a9160c849741d63d56"
|
||||
checksum = "16ff718c9ee45cc1ebd4774a0e086bb80a6ab752b4902edf1c9f56b86ee1f770"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1373,9 +1373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.61.0"
|
||||
version = "1.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7033130ce1ee13e6018905b7b976c915963755aef299c1521897679d6cd4f8ef"
|
||||
checksum = "5183e088715cc135d8d396fdd3bc02f018f0da4c511f53cb8d795b6a31c55809"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1395,9 +1395,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.61.0"
|
||||
version = "1.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5c1cac7677179d622b4448b0d31bcb359185295dc6fca891920cfb17e2b5156"
|
||||
checksum = "c9f944ef032717596639cea4a2118a3a457268ef51bbb5fde9637e54c465da00"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1458,9 +1458,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-checksums"
|
||||
version = "0.63.0"
|
||||
version = "0.62.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db2dc8d842d872529355c72632de49ef8c5a2949a4472f10e802f28cf925770c"
|
||||
checksum = "f2f45a1c384d7a393026bc5f5c177105aa9fa68e4749653b985707ac27d77295"
|
||||
dependencies = [
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-types",
|
||||
@@ -1810,7 +1810,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
@@ -1833,7 +1833,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@@ -2404,6 +2404,25 @@ dependencies = [
|
||||
"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]]
|
||||
name = "cbindgen"
|
||||
version = "0.28.0"
|
||||
@@ -2501,9 +2520,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -2511,7 +2530,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3508,10 +3527,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc64fast-nvme"
|
||||
version = "1.2.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3"
|
||||
checksum = "d5e2ee08013e3f228d6d2394116c4549a6df77708442c62d887d83f68ef2ee37"
|
||||
dependencies = [
|
||||
"cbindgen 0.27.0",
|
||||
"crc",
|
||||
]
|
||||
|
||||
@@ -5400,8 +5420,10 @@ dependencies = [
|
||||
"buffer_diff",
|
||||
"collections",
|
||||
"component",
|
||||
"ctor",
|
||||
"db",
|
||||
"editor",
|
||||
"env_logger 0.11.6",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
@@ -5563,7 +5585,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cbindgen",
|
||||
"cbindgen 0.28.0",
|
||||
"cocoa 0.26.0",
|
||||
"collections",
|
||||
"core-foundation 0.9.4",
|
||||
@@ -7236,9 +7258,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.170"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@@ -9765,15 +9787,6 @@ dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pgvector"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0e8871b6d7ca78348c6cd29b911b94851f3429f0cd403130ca17f26c1fb91a6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
@@ -10413,7 +10426,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
|
||||
dependencies = [
|
||||
"bytes 1.10.0",
|
||||
"heck 0.5.0",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"multimap 0.10.0",
|
||||
"once_cell",
|
||||
@@ -10446,7 +10459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
@@ -11474,9 +11487,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.6.0"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f"
|
||||
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
@@ -11485,9 +11498,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.6.0"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae"
|
||||
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11498,9 +11511,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.6.0"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a"
|
||||
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2",
|
||||
@@ -11775,9 +11788,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"indexmap",
|
||||
@@ -11788,9 +11801,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.22"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11853,18 +11866,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm"
|
||||
version = "1.1.6"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13fba7b2c749b2d0a00303d5cb13e6761e39a4172554bdf930852cac4e7aeabd"
|
||||
checksum = "00733e5418e8ae3758cdb988c3654174e716230cc53ee2cb884207cf86a23029"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"futures 0.3.31",
|
||||
"log",
|
||||
"ouroboros",
|
||||
"pgvector",
|
||||
"rust_decimal",
|
||||
"sea-orm-macros",
|
||||
"sea-query",
|
||||
@@ -11882,9 +11894,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm-macros"
|
||||
version = "1.1.6"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2568cff8d35d5150b4276cc0dd766192a587f64b6ece60ae3706e0872c4eb209"
|
||||
checksum = "a98408f82fb4875d41ef469a79944a7da29767c7b3e4028e22188a3dd613b10f"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
@@ -14804,9 +14816,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.15.1"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
|
||||
checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"serde",
|
||||
@@ -14908,6 +14920,7 @@ dependencies = [
|
||||
"multi_buffer",
|
||||
"nvim-rs",
|
||||
"parking_lot",
|
||||
"project",
|
||||
"project_panel",
|
||||
"regex",
|
||||
"release_channel",
|
||||
@@ -15929,12 +15942,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
@@ -16770,7 +16777,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.177.0"
|
||||
version = "0.177.1"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -370,10 +370,10 @@
|
||||
"ctrl-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||
"ctrl-alt-y": "git::ToggleStaged",
|
||||
"alt-y": ["git::StageAndNext", { "whole_excerpt": false }],
|
||||
"alt-shift-y": ["git::UnstageAndNext", { "whole_excerpt": false }],
|
||||
"alt-.": ["editor::GoToHunk", { "center_cursor": true }],
|
||||
"alt-,": ["editor::GoToPreviousHunk", { "center_cursor": true }]
|
||||
"alt-y": "git::StageAndNext",
|
||||
"alt-shift-y": "git::UnstageAndNext",
|
||||
"alt-.": "editor::GoToHunk",
|
||||
"alt-,": "editor::GoToPreviousHunk"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -564,8 +564,8 @@
|
||||
"shift-enter": "editor::ExpandExcerpts",
|
||||
"ctrl-alt-enter": "editor::OpenExcerptsSplit",
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": ["editor::GoToHunk", { "center_cursor": true }],
|
||||
"ctrl-shift-f8": ["editor::GoToPreviousHunk", { "center_cursor": true }],
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
@@ -722,7 +722,7 @@
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"ctrl-enter": "git::ShowCommitEditor",
|
||||
"alt-enter": "menu::SecondaryConfirm"
|
||||
}
|
||||
},
|
||||
@@ -736,7 +736,7 @@
|
||||
{
|
||||
"context": "GitDiff > Editor",
|
||||
"bindings": {
|
||||
"ctrl-enter": "git::Commit"
|
||||
"ctrl-enter": "git::ShowCommitEditor"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -749,14 +749,6 @@
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter": "git::Commit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "CollabPanel && not_editing",
|
||||
"bindings": {
|
||||
|
||||
@@ -142,8 +142,8 @@
|
||||
"cmd-;": "editor::ToggleLineNumbers",
|
||||
"cmd-alt-z": "git::Restore",
|
||||
"cmd-alt-y": "git::ToggleStaged",
|
||||
"cmd-y": ["git::StageAndNext", { "whole_excerpt": false }],
|
||||
"cmd-shift-y": ["git::UnstageAndNext", { "whole_excerpt": false }],
|
||||
"cmd-y": "git::StageAndNext",
|
||||
"cmd-shift-y": "git::UnstageAndNext",
|
||||
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||
@@ -642,8 +642,8 @@
|
||||
"shift-enter": "editor::ExpandExcerpts",
|
||||
"cmd-alt-enter": "editor::OpenExcerptsSplit",
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-f8": ["editor::GoToHunk", { "center_cursor": true }],
|
||||
"cmd-shift-f8": ["editor::GoToPreviousHunk", { "center_cursor": true }],
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
@@ -743,14 +743,14 @@
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus",
|
||||
"cmd-enter": "git::Commit"
|
||||
"cmd-enter": "git::ShowCommitEditor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitDiff > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-enter": "git::Commit"
|
||||
"cmd-enter": "git::ShowCommitEditor"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
"ctrl-alt-shift-b": "editor::GoToTypeDefinitionSplit",
|
||||
"f2": "editor::GoToDiagnostic",
|
||||
"shift-f2": "editor::GoToPreviousDiagnostic",
|
||||
"ctrl-alt-shift-down": ["editor::GoToHunk", { "center_cursor": true }],
|
||||
"ctrl-alt-shift-up": ["editor::GoToPreviousHunk", { "center_cursor": true }],
|
||||
"ctrl-alt-shift-down": "editor::GoToHunk",
|
||||
"ctrl-alt-shift-up": "editor::GoToPreviousHunk",
|
||||
"ctrl-alt-z": "git::Restore",
|
||||
"ctrl-home": "editor::MoveToBeginning",
|
||||
"ctrl-end": "editor::MoveToEnd",
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
"ctrl-f12": "editor::GoToDefinitionSplit",
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-.": ["editor::GoToHunk", { "center_cursor": true }],
|
||||
"ctrl-,": ["editor::GoToPreviousHunk", { "center_cursor": true }],
|
||||
"ctrl-.": "editor::GoToHunk",
|
||||
"ctrl-,": "editor::GoToPreviousHunk",
|
||||
"ctrl-k ctrl-u": "editor::ConvertToUpperCase",
|
||||
"ctrl-k ctrl-l": "editor::ConvertToLowerCase",
|
||||
"shift-alt-m": "markdown::OpenPreviewToTheSide",
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
|
||||
"f2": "editor::GoToDiagnostic",
|
||||
"shift-f2": "editor::GoToPreviousDiagnostic",
|
||||
"ctrl-alt-shift-down": ["editor::GoToHunk", { "center_cursor": true }],
|
||||
"ctrl-alt-shift-up": ["editor::GoToPreviousHunk", { "center_cursor": true }],
|
||||
"ctrl-alt-shift-down": "editor::GoToHunk",
|
||||
"ctrl-alt-shift-up": "editor::GoToPreviousHunk",
|
||||
"cmd-home": "editor::MoveToBeginning",
|
||||
"cmd-end": "editor::MoveToEnd",
|
||||
"cmd-shift-home": "editor::SelectToBeginning",
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
"alt-cmd-down": "editor::GoToDefinition",
|
||||
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
|
||||
"alt-shift-cmd-down": "editor::FindAllReferences",
|
||||
"ctrl-.": ["editor::GoToHunk", { "center_cursor": true }],
|
||||
"ctrl-,": ["editor::GoToPreviousHunk", { "center_cursor": true }],
|
||||
"ctrl-.": "editor::GoToHunk",
|
||||
"ctrl-,": "editor::GoToPreviousHunk",
|
||||
"cmd-k cmd-u": "editor::ConvertToUpperCase",
|
||||
"cmd-k cmd-l": "editor::ConvertToLowerCase",
|
||||
"cmd-shift-j": "editor::JoinLines",
|
||||
|
||||
@@ -238,8 +238,8 @@
|
||||
"] x": "vim::SelectSmallerSyntaxNode",
|
||||
"] d": "editor::GoToDiagnostic",
|
||||
"[ d": "editor::GoToPreviousDiagnostic",
|
||||
"] c": ["editor::GoToHunk", { "center_cursor": true }],
|
||||
"[ c": ["editor::GoToPreviousHunk", { "center_cursor": true }],
|
||||
"] c": "editor::GoToHunk",
|
||||
"[ c": "editor::GoToPreviousHunk",
|
||||
"g c": "vim::PushToggleComments"
|
||||
}
|
||||
},
|
||||
@@ -448,7 +448,10 @@
|
||||
"d": "vim::CurrentLine",
|
||||
"s": "vim::PushDeleteSurrounds",
|
||||
"o": "editor::ToggleSelectedDiffHunks", // "d o"
|
||||
"p": "git::Restore" // "d p"
|
||||
"shift-o": "git::ToggleStaged",
|
||||
"p": "git::Restore", // "d p"
|
||||
"u": "git::StageAndNext", // "d u"
|
||||
"shift-u": "git::UnstageAndNext" // "d shift-u"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -837,7 +837,15 @@
|
||||
//
|
||||
// The minimum column number to show the inline blame information at
|
||||
// "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:
|
||||
// 1. Load direnv configuration using `direnv export json` directly.
|
||||
@@ -851,15 +859,7 @@
|
||||
// Any addition to this list will be merged with the default list.
|
||||
// Globs are matched relative to the worktree root,
|
||||
// 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.
|
||||
// This setting takes two possible values:
|
||||
// 1. Display predictions inline when there are no language server completions available.
|
||||
|
||||
@@ -609,7 +609,7 @@ impl AssistantPanel {
|
||||
.id("title")
|
||||
.overflow_x_scroll()
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.child(Label::new(title).text_ellipsis()),
|
||||
.child(Label::new(title).truncate()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -260,7 +260,7 @@ impl RenderOnce for PastThread {
|
||||
.start_slot(
|
||||
div()
|
||||
.max_w_4_5()
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
|
||||
.child(Label::new(summary).size(LabelSize::Small).truncate()),
|
||||
)
|
||||
.end_slot(
|
||||
h_flex()
|
||||
@@ -356,7 +356,7 @@ impl RenderOnce for PastContext {
|
||||
.start_slot(
|
||||
div()
|
||||
.max_w_4_5()
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
|
||||
.child(Label::new(summary).size(LabelSize::Small).truncate()),
|
||||
)
|
||||
.end_slot(
|
||||
h_flex()
|
||||
|
||||
@@ -243,7 +243,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
Label::new(info.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.text_ellipsis(),
|
||||
.truncate(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -56,8 +56,8 @@ pub enum DiffHunkSecondaryStatus {
|
||||
/// A diff hunk resolved to rows in the buffer.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DiffHunk {
|
||||
/// The buffer range, expressed in terms of rows.
|
||||
pub row_range: Range<u32>,
|
||||
/// The buffer range as points.
|
||||
pub range: Range<Point>,
|
||||
/// The range in the buffer to which this hunk corresponds.
|
||||
pub buffer_range: Range<Anchor>,
|
||||
/// The range in the buffer's diff base text to which this hunk corresponds.
|
||||
@@ -362,6 +362,7 @@ impl BufferDiffInner {
|
||||
pending_hunks = secondary.pending_hunks.clone();
|
||||
}
|
||||
|
||||
let max_point = buffer.max_point();
|
||||
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
|
||||
iter::from_fn(move || loop {
|
||||
let (start_point, (start_anchor, start_base)) = summaries.next()?;
|
||||
@@ -371,7 +372,7 @@ impl BufferDiffInner {
|
||||
continue;
|
||||
}
|
||||
|
||||
if end_point.column > 0 {
|
||||
if end_point.column > 0 && end_point < max_point {
|
||||
end_point.row += 1;
|
||||
end_point.column = 0;
|
||||
end_anchor = buffer.anchor_before(end_point);
|
||||
@@ -416,7 +417,7 @@ impl BufferDiffInner {
|
||||
}
|
||||
|
||||
return Some(DiffHunk {
|
||||
row_range: start_point.row..end_point.row,
|
||||
range: start_point..end_point,
|
||||
diff_base_byte_range: start_base..end_base,
|
||||
buffer_range: start_anchor..end_anchor,
|
||||
secondary_status,
|
||||
@@ -442,14 +443,9 @@ impl BufferDiffInner {
|
||||
|
||||
let hunk = cursor.item()?;
|
||||
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 {
|
||||
row_range: range.start.row..end_row,
|
||||
range,
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
// The secondary status is not used by callers of this method.
|
||||
@@ -1136,12 +1132,10 @@ pub fn assert_hunks<Iter>(
|
||||
let actual_hunks = diff_hunks
|
||||
.map(|hunk| {
|
||||
(
|
||||
hunk.row_range.clone(),
|
||||
hunk.range.clone(),
|
||||
&diff_base[hunk.diff_base_byte_range.clone()],
|
||||
buffer
|
||||
.text_for_range(
|
||||
Point::new(hunk.row_range.start, 0)..Point::new(hunk.row_range.end, 0),
|
||||
)
|
||||
.text_for_range(hunk.range.clone())
|
||||
.collect::<String>(),
|
||||
hunk.status(),
|
||||
)
|
||||
@@ -1150,7 +1144,14 @@ pub fn assert_hunks<Iter>(
|
||||
|
||||
let expected_hunks: Vec<_> = expected_hunks
|
||||
.iter()
|
||||
.map(|(r, s, h, status)| (r.clone(), *s, h.to_string(), *status))
|
||||
.map(|(r, old_text, new_text, status)| {
|
||||
(
|
||||
Point::new(r.start, 0)..Point::new(r.end, 0),
|
||||
*old_text,
|
||||
new_text.to_string(),
|
||||
*status,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert_eq!(actual_hunks, expected_hunks);
|
||||
|
||||
@@ -226,3 +226,7 @@ impl Item for ComponentPreview {
|
||||
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
|
||||
|
||||
@@ -196,20 +196,6 @@ pub struct DeleteToPreviousWordStart {
|
||||
pub ignore_newlines: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GoToHunk {
|
||||
#[serde(default)]
|
||||
pub center_cursor: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GoToPreviousHunk {
|
||||
#[serde(default)]
|
||||
pub center_cursor: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
pub struct FoldAtLevel(pub u32);
|
||||
|
||||
@@ -240,8 +226,6 @@ impl_actions!(
|
||||
ExpandExcerptsDown,
|
||||
ExpandExcerptsUp,
|
||||
FoldAt,
|
||||
GoToHunk,
|
||||
GoToPreviousHunk,
|
||||
HandleInput,
|
||||
MoveDownByLines,
|
||||
MovePageDown,
|
||||
@@ -323,6 +307,8 @@ gpui::actions!(
|
||||
GoToDefinition,
|
||||
GoToDefinitionSplit,
|
||||
GoToDiagnostic,
|
||||
GoToHunk,
|
||||
GoToPreviousHunk,
|
||||
GoToImplementation,
|
||||
GoToImplementationSplit,
|
||||
GoToPreviousDiagnostic,
|
||||
|
||||
@@ -1124,6 +1124,11 @@ impl DisplaySnapshot {
|
||||
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> {
|
||||
let wrap_row = self
|
||||
.block_snapshot
|
||||
|
||||
@@ -1618,6 +1618,15 @@ impl BlockSnapshot {
|
||||
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 {
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
|
||||
@@ -73,7 +73,7 @@ use futures::{
|
||||
};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
|
||||
use ::git::{status::FileStatus, Restore};
|
||||
use ::git::Restore;
|
||||
use code_context_menus::{
|
||||
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
|
||||
CompletionsMenu, ContextMenuOrigin,
|
||||
@@ -11412,14 +11412,13 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn go_to_next_hunk(&mut self, action: &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 selection = self.selections.newest::<Point>(cx);
|
||||
self.go_to_hunk_after_or_before_position(
|
||||
&snapshot,
|
||||
selection.head(),
|
||||
true,
|
||||
action.center_cursor,
|
||||
Direction::Next,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -11429,12 +11428,11 @@ impl Editor {
|
||||
&mut self,
|
||||
snapshot: &EditorSnapshot,
|
||||
position: Point,
|
||||
after: bool,
|
||||
scroll_center: bool,
|
||||
direction: Direction,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<MultiBufferDiffHunk> {
|
||||
let hunk = if after {
|
||||
let hunk = if direction == Direction::Next {
|
||||
self.hunk_after_position(snapshot, position)
|
||||
} else {
|
||||
self.hunk_before_position(snapshot, position)
|
||||
@@ -11442,11 +11440,7 @@ impl Editor {
|
||||
|
||||
if let Some(hunk) = &hunk {
|
||||
let destination = Point::new(hunk.row_range.start.0, 0);
|
||||
let autoscroll = if scroll_center {
|
||||
Autoscroll::center()
|
||||
} else {
|
||||
Autoscroll::fit()
|
||||
};
|
||||
let autoscroll = Autoscroll::center();
|
||||
|
||||
self.unfold_ranges(&[destination..destination], false, false, cx);
|
||||
self.change_selections(Some(autoscroll), window, cx, |s| {
|
||||
@@ -11476,7 +11470,7 @@ impl Editor {
|
||||
|
||||
fn go_to_prev_hunk(
|
||||
&mut self,
|
||||
action: &GoToPreviousHunk,
|
||||
_: &GoToPreviousHunk,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -11485,8 +11479,7 @@ impl Editor {
|
||||
self.go_to_hunk_after_or_before_position(
|
||||
&snapshot,
|
||||
selection.head(),
|
||||
false,
|
||||
action.center_cursor,
|
||||
Direction::Prev,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -12965,13 +12958,18 @@ impl Editor {
|
||||
}
|
||||
} else {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let buffer_ids: HashSet<_> = multi_buffer_snapshot
|
||||
.ranges_to_buffer_ranges(self.selections.disjoint_anchor_ranges())
|
||||
.map(|(snapshot, _, _)| snapshot.remote_id())
|
||||
let buffer_ids: HashSet<_> = self
|
||||
.selections
|
||||
.disjoint_anchor_ranges()
|
||||
.flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
|
||||
.collect();
|
||||
|
||||
let should_unfold = buffer_ids
|
||||
.iter()
|
||||
.any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
|
||||
|
||||
for buffer_id in buffer_ids {
|
||||
if self.is_buffer_folded(buffer_id, cx) {
|
||||
if should_unfold {
|
||||
self.unfold_buffer(buffer_id, cx);
|
||||
} else {
|
||||
self.fold_buffer(buffer_id, cx);
|
||||
@@ -13558,20 +13556,20 @@ impl Editor {
|
||||
|
||||
pub fn stage_and_next(
|
||||
&mut self,
|
||||
action: &::git::StageAndNext,
|
||||
_: &::git::StageAndNext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.do_stage_or_unstage_and_next(true, action.whole_excerpt, window, cx);
|
||||
self.do_stage_or_unstage_and_next(true, window, cx);
|
||||
}
|
||||
|
||||
pub fn unstage_and_next(
|
||||
&mut self,
|
||||
action: &::git::UnstageAndNext,
|
||||
_: &::git::UnstageAndNext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.do_stage_or_unstage_and_next(false, action.whole_excerpt, window, cx);
|
||||
self.do_stage_or_unstage_and_next(false, window, cx);
|
||||
}
|
||||
|
||||
pub fn stage_or_unstage_diff_hunks(
|
||||
@@ -13593,102 +13591,33 @@ impl Editor {
|
||||
fn do_stage_or_unstage_and_next(
|
||||
&mut self,
|
||||
stage: bool,
|
||||
whole_excerpt: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
||||
let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
||||
|
||||
if ranges.iter().any(|range| range.start != range.end) {
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if !whole_excerpt {
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let newest_range = self.selections.newest::<Point>(cx).range();
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let newest_range = self.selections.newest::<Point>(cx).range();
|
||||
|
||||
let run_twice = snapshot
|
||||
.hunks_for_ranges([newest_range])
|
||||
.first()
|
||||
.is_some_and(|hunk| {
|
||||
let next_line = Point::new(hunk.row_range.end.0 + 1, 0);
|
||||
self.hunk_after_position(&snapshot, next_line)
|
||||
.is_some_and(|other| other.row_range == hunk.row_range)
|
||||
});
|
||||
let run_twice = snapshot
|
||||
.hunks_for_ranges([newest_range])
|
||||
.first()
|
||||
.is_some_and(|hunk| {
|
||||
let next_line = Point::new(hunk.row_range.end.0 + 1, 0);
|
||||
self.hunk_after_position(&snapshot, next_line)
|
||||
.is_some_and(|other| other.row_range == hunk.row_range)
|
||||
});
|
||||
|
||||
if run_twice {
|
||||
self.go_to_next_hunk(
|
||||
&GoToHunk {
|
||||
center_cursor: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
} else if !self.buffer().read(cx).is_singleton() {
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
||||
|
||||
if let Some((excerpt_id, buffer, range)) = self.active_excerpt(cx) {
|
||||
if buffer.read(cx).is_empty() {
|
||||
let buffer = buffer.read(cx);
|
||||
let Some(file) = buffer.file() else {
|
||||
return;
|
||||
};
|
||||
let project_path = project::ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path().clone(),
|
||||
};
|
||||
let Some(project) = self.project.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(repo) = project.read(cx).git_store().read(cx).active_repository()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
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 {
|
||||
center_cursor: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.go_to_next_hunk(&GoToHunk, window, cx);
|
||||
}
|
||||
|
||||
fn do_stage_or_unstage(
|
||||
@@ -13728,7 +13657,7 @@ impl Editor {
|
||||
buffer_range: hunk.buffer_range,
|
||||
diff_base_byte_range: hunk.diff_base_byte_range,
|
||||
secondary_status: hunk.secondary_status,
|
||||
row_range: 0..0, // unused
|
||||
range: Point::zero()..Point::zero(), // unused
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
&buffer_snapshot,
|
||||
@@ -16036,9 +15965,9 @@ impl Editor {
|
||||
if let Some(buffer) = multi_buffer.buffer(buffer_id) {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
changes.into_iter().map(|(range, text)| {
|
||||
(range, text.to_string().map(Arc::<str>::from))
|
||||
}),
|
||||
changes
|
||||
.into_iter()
|
||||
.map(|(range, text)| (range, text.to_string())),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
@@ -17156,17 +17085,14 @@ impl EditorSnapshot {
|
||||
for hunk in self.buffer_snapshot.diff_hunks_in_range(
|
||||
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
|
||||
) {
|
||||
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
|
||||
// when the caret is just above or just below the deleted hunk.
|
||||
let allow_adjacent = hunk.status().is_deleted();
|
||||
let related_to_selection = if allow_adjacent {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
|| hunk.row_range.start == query_rows.end
|
||||
|| hunk.row_range.end == query_rows.start
|
||||
} else {
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
};
|
||||
if related_to_selection {
|
||||
// Include deleted hunks that are adjacent to the query range, because
|
||||
// otherwise they would be missed.
|
||||
let mut intersects_range = hunk.row_range.overlaps(&query_rows);
|
||||
if hunk.status().is_deleted() {
|
||||
intersects_range |= hunk.row_range.start == query_rows.end;
|
||||
intersects_range |= hunk.row_range.end == query_rows.start;
|
||||
}
|
||||
if intersects_range {
|
||||
if !processed_buffer_rows
|
||||
.entry(hunk.buffer_id)
|
||||
.or_default()
|
||||
|
||||
@@ -11413,7 +11413,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
//Wrap around the bottom of the buffer
|
||||
for _ in 0..3 {
|
||||
editor.go_to_next_hunk(&GoToHunk::default(), window, cx);
|
||||
editor.go_to_next_hunk(&GoToHunk, window, cx);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11435,7 +11435,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
//Wrap around the top of the buffer
|
||||
for _ in 0..2 {
|
||||
editor.go_to_prev_hunk(&GoToPreviousHunk::default(), window, cx);
|
||||
editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11455,7 +11455,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_prev_hunk(&GoToPreviousHunk::default(), window, cx);
|
||||
editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(
|
||||
@@ -11474,7 +11474,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_prev_hunk(&GoToPreviousHunk::default(), window, cx);
|
||||
editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(
|
||||
@@ -11494,7 +11494,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
for _ in 0..2 {
|
||||
editor.go_to_prev_hunk(&GoToPreviousHunk::default(), window, cx);
|
||||
editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11518,7 +11518,7 @@ async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_next_hunk(&GoToHunk::default(), window, cx);
|
||||
editor.go_to_next_hunk(&GoToHunk, window, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(
|
||||
@@ -13525,7 +13525,7 @@ async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_next_hunk(&GoToHunk::default(), window, cx);
|
||||
editor.go_to_next_hunk(&GoToHunk, window, cx);
|
||||
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
@@ -13547,7 +13547,7 @@ async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
for _ in 0..2 {
|
||||
editor.go_to_next_hunk(&GoToHunk::default(), window, cx);
|
||||
editor.go_to_next_hunk(&GoToHunk, window, cx);
|
||||
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
|
||||
}
|
||||
});
|
||||
@@ -13570,7 +13570,7 @@ async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_next_hunk(&GoToHunk::default(), window, cx);
|
||||
editor.go_to_next_hunk(&GoToHunk, window, cx);
|
||||
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -32,15 +32,17 @@ use collections::{BTreeMap, HashMap, HashSet};
|
||||
use file_icons::FileIcons;
|
||||
use git::{blame::BlameEntry, status::FileStatus, Oid};
|
||||
use gpui::{
|
||||
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
|
||||
relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
|
||||
ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase,
|
||||
Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox,
|
||||
Hsla, InteractiveElement, IntoElement, Keystroke, Length, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
|
||||
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
|
||||
Subscription, TextRun, TextStyleRefinement, Window,
|
||||
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, pattern_slash,
|
||||
point, px, quad, relative, size, solid_background, svg, transparent_black, Action, AnyElement,
|
||||
App, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner,
|
||||
Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
|
||||
Focusable as _, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement,
|
||||
Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
|
||||
SharedString, Size, StatefulInteractiveElement, Style, Styled, Subscription, TextRun,
|
||||
TextStyleRefinement, Window,
|
||||
};
|
||||
use inline_completion::Direction;
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::{
|
||||
@@ -54,7 +56,7 @@ use multi_buffer::{
|
||||
Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow,
|
||||
RowInfo,
|
||||
};
|
||||
use project::project_settings::{self, GitGutterSetting, ProjectSettings};
|
||||
use project::project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings};
|
||||
use settings::Settings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{
|
||||
@@ -2722,7 +2724,10 @@ impl EditorElement {
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
.map(|div| {
|
||||
let border_color = if is_selected && is_folded {
|
||||
let border_color = if is_selected
|
||||
&& is_folded
|
||||
&& focus_handle.contains_focused(window, cx)
|
||||
{
|
||||
colors.border_focused
|
||||
} else {
|
||||
colors.border
|
||||
@@ -4343,7 +4348,7 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
|
||||
fn paint_gutter_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() {
|
||||
@@ -4413,10 +4418,19 @@ impl EditorElement {
|
||||
background_color =
|
||||
background_color.opacity(if is_light { 0.2 } else { 0.32 });
|
||||
}
|
||||
|
||||
// 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(
|
||||
hunk_bounds,
|
||||
corner_radii,
|
||||
background_color,
|
||||
flattened_background_color,
|
||||
Edges::default(),
|
||||
transparent_black(),
|
||||
));
|
||||
@@ -4544,7 +4558,7 @@ impl EditorElement {
|
||||
)
|
||||
});
|
||||
if show_git_gutter {
|
||||
Self::paint_diff_hunks(layout, window, cx)
|
||||
Self::paint_gutter_diff_hunks(layout, window, cx)
|
||||
}
|
||||
|
||||
let highlight_width = 0.275 * layout.position_map.line_height;
|
||||
@@ -6708,15 +6722,16 @@ impl Element for EditorElement {
|
||||
.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() {
|
||||
let Some(diff_status) = row_info.diff_status else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let staged_opacity = if is_light { 0.14 } else { 0.10 };
|
||||
let unstaged_opacity = 0.04;
|
||||
|
||||
let background_color = match diff_status.kind {
|
||||
DiffHunkStatusKind::Added => cx.theme().colors().version_control_added,
|
||||
DiffHunkStatusKind::Deleted => {
|
||||
@@ -6727,15 +6742,34 @@ impl Element for EditorElement {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let background_color = if diff_status.has_secondary_hunk() {
|
||||
background_color.opacity(unstaged_opacity)
|
||||
|
||||
let unstaged = diff_status.has_secondary_hunk();
|
||||
let hunk_opacity = if is_light { 0.16 } else { 0.12 };
|
||||
|
||||
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 {
|
||||
background_color.opacity(staged_opacity)
|
||||
solid_background(background_color.opacity(if is_light {
|
||||
0.08
|
||||
} else {
|
||||
0.04
|
||||
}))
|
||||
};
|
||||
|
||||
let background = if unstaged {
|
||||
unstaged_background
|
||||
} else {
|
||||
staged_background
|
||||
};
|
||||
|
||||
highlighted_rows
|
||||
.entry(start_row + DisplayRow(ix as u32))
|
||||
.or_insert(background_color.into());
|
||||
.or_insert(background);
|
||||
}
|
||||
|
||||
let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
|
||||
@@ -8873,7 +8907,7 @@ fn diff_hunk_controls(
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Next Hunk",
|
||||
&GoToHunk::default(),
|
||||
&GoToHunk,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
@@ -8888,7 +8922,11 @@ fn diff_hunk_controls(
|
||||
let position =
|
||||
hunk_range.end.to_point(&snapshot.buffer_snapshot);
|
||||
editor.go_to_hunk_after_or_before_position(
|
||||
&snapshot, position, true, true, window, cx,
|
||||
&snapshot,
|
||||
position,
|
||||
Direction::Next,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.expand_selected_diff_hunks(cx);
|
||||
});
|
||||
@@ -8905,7 +8943,7 @@ fn diff_hunk_controls(
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Previous Hunk",
|
||||
&GoToPreviousHunk::default(),
|
||||
&GoToPreviousHunk,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
@@ -8920,7 +8958,11 @@ fn diff_hunk_controls(
|
||||
let point =
|
||||
hunk_range.start.to_point(&snapshot.buffer_snapshot);
|
||||
editor.go_to_hunk_after_or_before_position(
|
||||
&snapshot, point, false, true, window, cx,
|
||||
&snapshot,
|
||||
point,
|
||||
Direction::Prev,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.expand_selected_diff_hunks(cx);
|
||||
});
|
||||
|
||||
@@ -89,6 +89,16 @@ impl EditorTestContext {
|
||||
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 {
|
||||
let editor_view = editor.root(cx).unwrap();
|
||||
Self {
|
||||
@@ -381,6 +391,76 @@ impl EditorTestContext {
|
||||
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
|
||||
/// of its selections using a string containing embedded range markers.
|
||||
///
|
||||
@@ -392,6 +472,17 @@ impl EditorTestContext {
|
||||
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 {
|
||||
generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
|
||||
}
|
||||
|
||||
@@ -522,7 +522,7 @@ impl ExtensionsPage {
|
||||
extension.authors.join(", ")
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.text_ellipsis(),
|
||||
.truncate(),
|
||||
)
|
||||
.child(Label::new("<>").size(LabelSize::Small)),
|
||||
)
|
||||
@@ -534,7 +534,7 @@ impl ExtensionsPage {
|
||||
Label::new(description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Default)
|
||||
.text_ellipsis()
|
||||
.truncate()
|
||||
}))
|
||||
.children(repository_url.map(|repository_url| {
|
||||
IconButton::new(
|
||||
@@ -665,7 +665,7 @@ impl ExtensionsPage {
|
||||
extension.manifest.authors.join(", ")
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.text_ellipsis(),
|
||||
.truncate(),
|
||||
)
|
||||
.child(
|
||||
Label::new(format!(
|
||||
@@ -683,7 +683,7 @@ impl ExtensionsPage {
|
||||
Label::new(description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Default)
|
||||
.text_ellipsis()
|
||||
.truncate()
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -135,6 +135,7 @@ pub trait Fs: Send + Sync {
|
||||
Arc<dyn Watcher>,
|
||||
);
|
||||
|
||||
fn home_dir(&self) -> Option<PathBuf>;
|
||||
fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>>;
|
||||
fn is_fake(&self) -> bool;
|
||||
async fn is_case_sensitive(&self) -> Result<bool>;
|
||||
@@ -813,6 +814,10 @@ impl Fs for RealFs {
|
||||
temp_dir.close()?;
|
||||
case_sensitive
|
||||
}
|
||||
|
||||
fn home_dir(&self) -> Option<PathBuf> {
|
||||
Some(paths::home_dir().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||
@@ -846,6 +851,7 @@ struct FakeFsState {
|
||||
metadata_call_count: usize,
|
||||
read_dir_call_count: usize,
|
||||
moves: std::collections::HashMap<u64, PathBuf>,
|
||||
home_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -1031,6 +1037,7 @@ impl FakeFs {
|
||||
read_dir_call_count: 0,
|
||||
metadata_call_count: 0,
|
||||
moves: Default::default(),
|
||||
home_dir: None,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1524,6 +1531,10 @@ impl FakeFs {
|
||||
fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
|
||||
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"))]
|
||||
@@ -2079,6 +2090,10 @@ impl Fs for FakeFs {
|
||||
fn as_fake(&self) -> Arc<FakeFs> {
|
||||
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> {
|
||||
|
||||
@@ -36,23 +36,15 @@ pub struct Push {
|
||||
pub options: Option<PushOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, JsonSchema)]
|
||||
pub struct StageAndNext {
|
||||
pub whole_excerpt: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, JsonSchema)]
|
||||
pub struct UnstageAndNext {
|
||||
pub whole_excerpt: bool,
|
||||
}
|
||||
|
||||
impl_actions!(git, [Push, StageAndNext, UnstageAndNext]);
|
||||
impl_actions!(git, [Push]);
|
||||
|
||||
actions!(
|
||||
git,
|
||||
[
|
||||
// per-hunk
|
||||
ToggleStaged,
|
||||
StageAndNext,
|
||||
UnstageAndNext,
|
||||
// per-file
|
||||
StageFile,
|
||||
UnstageFile,
|
||||
|
||||
@@ -57,6 +57,8 @@ zed_actions.workspace = true
|
||||
windows.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -115,15 +115,15 @@ impl CommitModal {
|
||||
return;
|
||||
};
|
||||
|
||||
let (can_commit, conflict) = git_panel.update(cx, |git_panel, cx| {
|
||||
let can_commit = git_panel.can_commit();
|
||||
let (can_open_commit_editor, conflict) = git_panel.update(cx, |git_panel, cx| {
|
||||
let can_open_commit_editor = git_panel.can_open_commit_editor();
|
||||
let conflict = git_panel.has_unstaged_conflicts();
|
||||
if can_commit {
|
||||
if can_open_commit_editor {
|
||||
git_panel.set_modal_open(true, cx);
|
||||
}
|
||||
(can_commit, conflict)
|
||||
(can_open_commit_editor, conflict)
|
||||
});
|
||||
if !can_commit {
|
||||
if !can_open_commit_editor {
|
||||
let message = if conflict {
|
||||
"There are still conflicts. You must stage these before committing."
|
||||
} else {
|
||||
@@ -163,7 +163,7 @@ impl CommitModal {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let panel = git_panel.read(cx);
|
||||
let suggested_message = panel.suggest_commit_message();
|
||||
let suggested_commit_message = panel.suggest_commit_message();
|
||||
|
||||
let commit_editor = git_panel.update(cx, |git_panel, cx| {
|
||||
git_panel.set_modal_open(true, cx);
|
||||
@@ -174,19 +174,11 @@ impl CommitModal {
|
||||
|
||||
let commit_message = commit_editor.read(cx).text(cx);
|
||||
|
||||
if let Some(suggested_message) = suggested_message {
|
||||
if let Some(suggested_commit_message) = suggested_commit_message {
|
||||
if commit_message.is_empty() {
|
||||
commit_editor.update(cx, |editor, cx| {
|
||||
editor.set_text(suggested_message, window, cx);
|
||||
editor.select_all(&Default::default(), window, cx);
|
||||
editor.set_placeholder_text(suggested_commit_message, 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +242,7 @@ impl CommitModal {
|
||||
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let git_panel = self.git_panel.clone();
|
||||
|
||||
let (branch, tooltip, commit_label, co_authors) =
|
||||
let (branch, can_commit, tooltip, commit_label, co_authors) =
|
||||
self.git_panel.update(cx, |git_panel, cx| {
|
||||
let branch = git_panel
|
||||
.active_repository
|
||||
@@ -262,18 +254,10 @@ impl CommitModal {
|
||||
.map(|b| b.name.clone())
|
||||
})
|
||||
.unwrap_or_else(|| "<no branch>".into());
|
||||
let tooltip = if git_panel.has_staged_changes() {
|
||||
"Commit staged changes"
|
||||
} else {
|
||||
"Commit changes to tracked files"
|
||||
};
|
||||
let title = if git_panel.has_staged_changes() {
|
||||
"Commit"
|
||||
} else {
|
||||
"Commit All"
|
||||
};
|
||||
let (can_commit, tooltip) = git_panel.configure_commit_button(cx);
|
||||
let title = git_panel.commit_button_title();
|
||||
let co_authors = git_panel.render_co_authors(cx);
|
||||
(branch, tooltip, title, co_authors)
|
||||
(branch, can_commit, tooltip, title, co_authors)
|
||||
});
|
||||
|
||||
let branch_picker_button = panel_button(branch)
|
||||
@@ -308,9 +292,8 @@ impl CommitModal {
|
||||
None
|
||||
};
|
||||
|
||||
let (panel_editor_focus_handle, can_commit) = git_panel.update(cx, |git_panel, cx| {
|
||||
(git_panel.editor_focus_handle(cx), git_panel.can_commit())
|
||||
});
|
||||
let panel_editor_focus_handle =
|
||||
git_panel.update(cx, |git_panel, cx| git_panel.editor_focus_handle(cx));
|
||||
|
||||
let commit_button = panel_filled_button(commit_label)
|
||||
.tooltip(move |window, cx| {
|
||||
@@ -354,6 +337,7 @@ impl CommitModal {
|
||||
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.git_panel
|
||||
.update(cx, |git_panel, cx| git_panel.commit_changes(window, cx));
|
||||
|
||||
@@ -191,7 +191,6 @@ pub struct GitPanel {
|
||||
pending_remote_operations: RemoteOperations,
|
||||
pub(crate) active_repository: Option<Entity<Repository>>,
|
||||
commit_editor: Entity<Editor>,
|
||||
pub(crate) suggested_commit_message: Option<String>,
|
||||
conflicted_count: usize,
|
||||
conflicted_staged_count: usize,
|
||||
current_modifiers: Modifiers,
|
||||
@@ -319,7 +318,6 @@ impl GitPanel {
|
||||
remote_operation_id: 0,
|
||||
active_repository,
|
||||
commit_editor,
|
||||
suggested_commit_message: None,
|
||||
conflicted_count: 0,
|
||||
conflicted_staged_count: 0,
|
||||
current_modifiers: window.modifiers(),
|
||||
@@ -696,12 +694,31 @@ impl GitPanel {
|
||||
fn open_diff(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
maybe!({
|
||||
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let git_repo = self.active_repository.as_ref()?;
|
||||
|
||||
if let Some(project_diff) = workspace.read(cx).active_item_as::<ProjectDiff>(cx) {
|
||||
if let Some(project_path) = project_diff.read(cx).active_path(cx) {
|
||||
if Some(&entry.repo_path)
|
||||
== git_repo
|
||||
.read(cx)
|
||||
.project_path_to_repo_path(&project_path)
|
||||
.as_ref()
|
||||
{
|
||||
project_diff.focus_handle(cx).focus(window);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
|
||||
})
|
||||
.ok()
|
||||
.ok();
|
||||
self.focus_handle.focus(window);
|
||||
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1154,6 +1171,17 @@ impl GitPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn custom_or_suggested_commit_message(&self, cx: &mut Context<Self>) -> Option<String> {
|
||||
let message = self.commit_editor.read(cx).text(cx);
|
||||
|
||||
if !message.trim().is_empty() {
|
||||
return Some(message.to_string());
|
||||
}
|
||||
|
||||
self.suggest_commit_message()
|
||||
.filter(|message| !message.trim().is_empty())
|
||||
}
|
||||
|
||||
pub(crate) fn commit_changes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(active_repository) = self.active_repository.clone() else {
|
||||
return;
|
||||
@@ -1175,11 +1203,13 @@ impl GitPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut message = self.commit_editor.read(cx).text(cx);
|
||||
if message.trim().is_empty() {
|
||||
let commit_message = self.custom_or_suggested_commit_message(cx);
|
||||
|
||||
let Some(mut message) = commit_message else {
|
||||
self.commit_editor.read(cx).focus_handle(cx).focus(window);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if self.add_coauthors {
|
||||
self.fill_co_authors(&mut message, cx);
|
||||
}
|
||||
@@ -1310,7 +1340,7 @@ impl GitPanel {
|
||||
Some("Update")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}?;
|
||||
|
||||
let file_name = git_status_entry
|
||||
.repo_path
|
||||
@@ -1318,17 +1348,17 @@ impl GitPanel {
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy();
|
||||
|
||||
Some(format!("{} {}", action_text?, file_name))
|
||||
Some(format!("{} {}", action_text, file_name))
|
||||
}
|
||||
|
||||
fn update_editor_placeholder(&mut self, cx: &mut Context<Self>) {
|
||||
let suggested_commit_message = self.suggest_commit_message();
|
||||
let suggested_commit_message = suggested_commit_message
|
||||
let placeholder_text = suggested_commit_message
|
||||
.as_deref()
|
||||
.unwrap_or("Enter commit message");
|
||||
|
||||
self.commit_editor.update(cx, |editor, cx| {
|
||||
editor.set_placeholder_text(Arc::from(suggested_commit_message), cx)
|
||||
editor.set_placeholder_text(Arc::from(placeholder_text), cx)
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
@@ -1665,7 +1695,7 @@ impl GitPanel {
|
||||
git_panel.commit_editor = cx.new(|cx| {
|
||||
commit_message_editor(
|
||||
buffer,
|
||||
git_panel.suggested_commit_message.as_deref(),
|
||||
git_panel.suggest_commit_message().as_deref(),
|
||||
git_panel.project.clone(),
|
||||
true,
|
||||
window,
|
||||
@@ -1898,7 +1928,7 @@ impl GitPanel {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn can_commit(&self) -> bool {
|
||||
pub fn can_open_commit_editor(&self) -> bool {
|
||||
(self.has_staged_changes() || self.has_tracked_changes()) && !self.has_unstaged_conflicts()
|
||||
}
|
||||
|
||||
@@ -1944,7 +1974,7 @@ impl GitPanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_commit_button(&self, cx: &Context<Self>) -> (bool, &'static str) {
|
||||
pub fn configure_commit_button(&self, cx: &mut Context<Self>) -> (bool, &'static str) {
|
||||
if self.has_unstaged_conflicts() {
|
||||
(false, "You must resolve conflicts before committing")
|
||||
} else if !self.has_staged_changes() && !self.has_tracked_changes() {
|
||||
@@ -1954,19 +1984,20 @@ impl GitPanel {
|
||||
)
|
||||
} else if self.pending_commit.is_some() {
|
||||
(false, "Commit in progress")
|
||||
} else if self.commit_editor.read(cx).is_empty(cx) {
|
||||
} else if self.custom_or_suggested_commit_message(cx).is_none() {
|
||||
(false, "No commit message")
|
||||
} else if !self.has_write_access(cx) {
|
||||
(false, "You do not have write access to this project")
|
||||
} else {
|
||||
(
|
||||
true,
|
||||
if self.has_staged_changes() {
|
||||
"Commit"
|
||||
} else {
|
||||
"Commit Tracked"
|
||||
},
|
||||
)
|
||||
(true, self.commit_button_title())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commit_button_title(&self) -> &'static str {
|
||||
if self.has_staged_changes() {
|
||||
"Commit"
|
||||
} else {
|
||||
"Commit Tracked"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1975,123 +2006,116 @@ impl GitPanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
let active_repository = self.active_repository.clone()?;
|
||||
let can_open_commit_editor = self.can_open_commit_editor();
|
||||
let (can_commit, tooltip) = self.configure_commit_button(cx);
|
||||
let project = self.project.clone().read(cx);
|
||||
let active_repository = self.active_repository.clone();
|
||||
let panel_editor_style = panel_editor_style(true, window, cx);
|
||||
|
||||
if let Some(active_repo) = active_repository {
|
||||
let (can_commit, tooltip) = self.configure_commit_button(cx);
|
||||
let enable_coauthors = self.render_co_authors(cx);
|
||||
|
||||
let enable_coauthors = self.render_co_authors(cx);
|
||||
let title = self.commit_button_title();
|
||||
let editor_focus_handle = self.commit_editor.focus_handle(cx);
|
||||
|
||||
let title = if self.has_staged_changes() {
|
||||
"Commit"
|
||||
} else {
|
||||
"Commit Tracked"
|
||||
};
|
||||
let editor_focus_handle = self.commit_editor.focus_handle(cx);
|
||||
let branch = active_repository.read(cx).current_branch().cloned();
|
||||
|
||||
let branch = active_repo.read(cx).current_branch()?.clone();
|
||||
let footer_size = px(32.);
|
||||
let gap = px(8.0);
|
||||
|
||||
let footer_size = px(32.);
|
||||
let gap = px(8.0);
|
||||
let max_height = window.line_height() * 5. + gap + footer_size;
|
||||
|
||||
let max_height = window.line_height() * 5. + gap + footer_size;
|
||||
let expand_button_size = px(16.);
|
||||
|
||||
let expand_button_size = px(16.);
|
||||
|
||||
let git_panel = cx.entity().clone();
|
||||
let display_name = SharedString::from(Arc::from(
|
||||
active_repo
|
||||
.read(cx)
|
||||
.display_name(project, cx)
|
||||
.trim_end_matches("/"),
|
||||
));
|
||||
let branches = branch_picker::popover(self.project.clone(), window, cx);
|
||||
let footer = v_flex()
|
||||
.child(PanelRepoFooter::new(
|
||||
"footer-button",
|
||||
display_name,
|
||||
Some(branch),
|
||||
Some(git_panel),
|
||||
Some(branches),
|
||||
))
|
||||
.child(
|
||||
panel_editor_container(window, cx)
|
||||
.id("commit-editor-container")
|
||||
.relative()
|
||||
.h(max_height)
|
||||
// .w_full()
|
||||
// .border_t_1()
|
||||
// .border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.cursor_text()
|
||||
.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
window.focus(&this.commit_editor.focus_handle(cx));
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.id("commit-footer")
|
||||
.absolute()
|
||||
.bottom_0()
|
||||
.right_2()
|
||||
.h(footer_size)
|
||||
.flex_none()
|
||||
.children(enable_coauthors)
|
||||
.child(
|
||||
panel_filled_button(title)
|
||||
.tooltip(move |window, cx| {
|
||||
if can_commit {
|
||||
Tooltip::for_action_in(
|
||||
tooltip,
|
||||
&Commit,
|
||||
&editor_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::simple(tooltip, cx)
|
||||
}
|
||||
let git_panel = cx.entity().clone();
|
||||
let display_name = SharedString::from(Arc::from(
|
||||
active_repository
|
||||
.read(cx)
|
||||
.display_name(project, cx)
|
||||
.trim_end_matches("/"),
|
||||
));
|
||||
let branches = branch_picker::popover(self.project.clone(), window, cx);
|
||||
let footer = v_flex()
|
||||
.child(PanelRepoFooter::new(
|
||||
"footer-button",
|
||||
display_name,
|
||||
branch,
|
||||
Some(git_panel),
|
||||
Some(branches),
|
||||
))
|
||||
.child(
|
||||
panel_editor_container(window, cx)
|
||||
.id("commit-editor-container")
|
||||
.relative()
|
||||
.h(max_height)
|
||||
// .w_full()
|
||||
// .border_t_1()
|
||||
// .border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.cursor_text()
|
||||
.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
window.focus(&this.commit_editor.focus_handle(cx));
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.id("commit-footer")
|
||||
.absolute()
|
||||
.bottom_0()
|
||||
.right_2()
|
||||
.h(footer_size)
|
||||
.flex_none()
|
||||
.children(enable_coauthors)
|
||||
.child(
|
||||
panel_filled_button(title)
|
||||
.tooltip(move |window, cx| {
|
||||
if can_commit {
|
||||
Tooltip::for_action_in(
|
||||
tooltip,
|
||||
&Commit,
|
||||
&editor_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::simple(tooltip, cx)
|
||||
}
|
||||
})
|
||||
.disabled(!can_commit || self.modal_open)
|
||||
.on_click({
|
||||
cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
this.commit_changes(window, cx)
|
||||
})
|
||||
.disabled(!can_commit || self.modal_open)
|
||||
.on_click({
|
||||
cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
this.commit_changes(window, cx)
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
// .when(!self.modal_open, |el| {
|
||||
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_1()
|
||||
.right_2()
|
||||
.opacity(0.5)
|
||||
.hover(|this| this.opacity(1.0))
|
||||
.w(expand_button_size)
|
||||
.child(
|
||||
panel_icon_button("expand-commit-editor", IconName::Maximize)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.width(expand_button_size.into())
|
||||
.on_click(cx.listener({
|
||||
move |_, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
git::ShowCommitEditor.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
)
|
||||
// .when(!self.modal_open, |el| {
|
||||
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_1()
|
||||
.right_2()
|
||||
.opacity(0.5)
|
||||
.hover(|this| this.opacity(1.0))
|
||||
.w(expand_button_size)
|
||||
.child(
|
||||
panel_icon_button("expand-commit-editor", IconName::Maximize)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.width(expand_button_size.into())
|
||||
.disabled(!can_open_commit_editor)
|
||||
.on_click(cx.listener({
|
||||
move |_, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
git::ShowCommitEditor.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Some(footer)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(footer)
|
||||
}
|
||||
|
||||
fn render_previous_commit(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
|
||||
@@ -2118,7 +2142,7 @@ impl GitPanel {
|
||||
.child(
|
||||
Label::new(commit.subject.clone())
|
||||
.size(LabelSize::Small)
|
||||
.text_ellipsis(),
|
||||
.truncate(),
|
||||
)
|
||||
.id("commit-msg-hover")
|
||||
.hoverable_tooltip(move |window, cx| {
|
||||
@@ -2274,7 +2298,7 @@ impl GitPanel {
|
||||
) -> impl IntoElement {
|
||||
let entry_count = self.entries.len();
|
||||
|
||||
v_flex()
|
||||
h_flex()
|
||||
.size_full()
|
||||
.flex_grow()
|
||||
.overflow_hidden()
|
||||
@@ -2444,7 +2468,7 @@ impl GitPanel {
|
||||
ix: usize,
|
||||
entry: &GitStatusEntry,
|
||||
has_write_access: bool,
|
||||
_: &Window,
|
||||
window: &Window,
|
||||
cx: &Context<Self>,
|
||||
) -> AnyElement {
|
||||
let display_name = entry
|
||||
@@ -2536,6 +2560,10 @@ impl GitPanel {
|
||||
.h(self.list_item_height())
|
||||
.w_full()
|
||||
.items_center()
|
||||
.border_1()
|
||||
.when(selected && self.focus_handle.is_focused(window), |el| {
|
||||
el.border_color(cx.theme().colors().border_focused)
|
||||
})
|
||||
.px(rems(0.75)) // ~12px
|
||||
.overflow_hidden()
|
||||
.flex_none()
|
||||
@@ -2551,6 +2579,7 @@ impl GitPanel {
|
||||
this.open_file(&Default::default(), window, cx)
|
||||
} else {
|
||||
this.open_diff(&Default::default(), window, cx);
|
||||
this.focus_handle.focus(window);
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -3296,41 +3325,32 @@ impl RenderOnce for PanelRepoFooter {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let active_repo = self.active_repository.clone();
|
||||
let overflow_menu_id: SharedString = format!("overflow-menu-{}", active_repo).into();
|
||||
let repo_selector_trigger = Button::new("repo-selector", active_repo)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.size(ButtonSize::None)
|
||||
.label_size(LabelSize::Small)
|
||||
.color(Color::Muted);
|
||||
|
||||
let repo_selector = if let Some(panel) = self.git_panel.clone() {
|
||||
let repo_selector = panel.read(cx).repository_selector.clone();
|
||||
let repo_count = repo_selector.read(cx).repositories_len(cx);
|
||||
if repo_count > 1 {
|
||||
RepositorySelectorPopoverMenu::new(
|
||||
panel.read(cx).repository_selector.clone(),
|
||||
Button::new("repo-selector", active_repo)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.size(ButtonSize::None)
|
||||
.label_size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
Tooltip::text("Choose a repository"),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
Label::new(active_repo)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.line_height_style(LineHeightStyle::UiLabel)
|
||||
.into_any_element()
|
||||
}
|
||||
let single_repo = repo_count == 1;
|
||||
|
||||
RepositorySelectorPopoverMenu::new(
|
||||
panel.read(cx).repository_selector.clone(),
|
||||
repo_selector_trigger.disabled(single_repo).truncate(true),
|
||||
Tooltip::text("Switch active repository"),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
Button::new("repo-selector", active_repo.clone())
|
||||
.style(ButtonStyle::Transparent)
|
||||
.size(ButtonSize::None)
|
||||
.label_size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element()
|
||||
// for rendering preview, we don't have git_panel there
|
||||
repo_selector_trigger.into_any_element()
|
||||
};
|
||||
|
||||
let branch = self.branch.clone();
|
||||
let branch_name = branch
|
||||
.as_ref()
|
||||
.map_or("<no branch>".into(), |branch| branch.name.clone());
|
||||
.map_or(" (no branch)".into(), |branch| branch.name.clone());
|
||||
|
||||
let branches = self.branches.clone();
|
||||
|
||||
@@ -3338,6 +3358,7 @@ impl RenderOnce for PanelRepoFooter {
|
||||
.style(ButtonStyle::Transparent)
|
||||
.size(ButtonSize::None)
|
||||
.label_size(LabelSize::Small)
|
||||
.truncate(true)
|
||||
.tooltip(Tooltip::for_action_title(
|
||||
"Switch Branch",
|
||||
&zed_actions::git::Branch,
|
||||
@@ -3372,36 +3393,31 @@ impl RenderOnce for PanelRepoFooter {
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.relative()
|
||||
.flex_1()
|
||||
.overflow_hidden()
|
||||
.items_center()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
div()
|
||||
// .when(repo_or_branch_has_uppercase, |this| {
|
||||
// this.relative().pt(px(2.))
|
||||
// })
|
||||
.child(
|
||||
Icon::new(IconName::GitBranchSmall)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
div().child(
|
||||
Icon::new(IconName::GitBranchSmall)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(repo_selector)
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.text_sm()
|
||||
.child("/"),
|
||||
)
|
||||
.child(branch_selector),
|
||||
),
|
||||
.child(repo_selector)
|
||||
.when_some(branch.clone(), |this, _| {
|
||||
this.child(
|
||||
div()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.text_sm()
|
||||
.child("/"),
|
||||
)
|
||||
})
|
||||
.child(branch_selector),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.flex_shrink_0()
|
||||
.children(spinner)
|
||||
.child(self.render_overflow_menu(overflow_menu_id))
|
||||
.when_some(branch, |this, branch| {
|
||||
@@ -3462,94 +3478,220 @@ impl ComponentPreview for PanelRepoFooter {
|
||||
}
|
||||
}
|
||||
|
||||
fn custom(branch_name: &str, upstream: Option<UpstreamTracking>) -> Branch {
|
||||
Branch {
|
||||
is_head: true,
|
||||
name: branch_name.to_string().into(),
|
||||
upstream: upstream.map(|tracking| Upstream {
|
||||
ref_name: format!("zed/{}", branch_name).into(),
|
||||
tracking,
|
||||
}),
|
||||
most_recent_commit: Some(CommitSummary {
|
||||
sha: "abc123".into(),
|
||||
subject: "Modify stuff".into(),
|
||||
commit_timestamp: 1710932954,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn active_repository(id: usize) -> SharedString {
|
||||
format!("repo-{}", id).into()
|
||||
}
|
||||
|
||||
let example_width = px(340.);
|
||||
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.w_full()
|
||||
.flex_none()
|
||||
.children(vec![example_group_with_title(
|
||||
"Action Button States",
|
||||
vec![
|
||||
single_example(
|
||||
"No Branch",
|
||||
div()
|
||||
.w(px(180.))
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"no-branch",
|
||||
active_repository(1).clone(),
|
||||
None,
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Remote status unknown",
|
||||
div()
|
||||
.w(px(180.))
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"unknown-upstream",
|
||||
active_repository(2).clone(),
|
||||
Some(branch(unknown_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"No Remote Upstream",
|
||||
div()
|
||||
.w(px(180.))
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"no-remote-upstream",
|
||||
active_repository(3).clone(),
|
||||
Some(branch(no_remote_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Not Ahead or Behind",
|
||||
div()
|
||||
.w(px(180.))
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"not-ahead-or-behind",
|
||||
active_repository(4).clone(),
|
||||
Some(branch(not_ahead_or_behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Behind remote",
|
||||
div()
|
||||
.w(px(180.))
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"behind-remote",
|
||||
active_repository(5).clone(),
|
||||
Some(branch(behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Ahead of remote",
|
||||
div()
|
||||
.w(px(180.))
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"ahead-of-remote",
|
||||
active_repository(6).clone(),
|
||||
Some(branch(ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Ahead and behind remote",
|
||||
div()
|
||||
.w(px(180.))
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"ahead-and-behind",
|
||||
active_repository(7).clone(),
|
||||
Some(branch(ahead_and_behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
)
|
||||
.grow(),
|
||||
],
|
||||
)
|
||||
.grow()
|
||||
.vertical()])
|
||||
.children(vec![example_group_with_title(
|
||||
"Labels",
|
||||
vec![
|
||||
single_example(
|
||||
"Short Branch & Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"short-branch",
|
||||
SharedString::from("zed"),
|
||||
Some(custom("main", behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Long Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"long-branch",
|
||||
SharedString::from("zed"),
|
||||
Some(custom(
|
||||
"redesign-and-update-git-ui-list-entry-style",
|
||||
behind_upstream,
|
||||
)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Long Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"long-repo",
|
||||
SharedString::from("zed-industries-community-examples"),
|
||||
Some(custom("gpui", ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Long Repo & Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"long-repo-and-branch",
|
||||
SharedString::from("zed-industries-community-examples"),
|
||||
Some(custom(
|
||||
"redesign-and-update-git-ui-list-entry-style",
|
||||
behind_upstream,
|
||||
)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Uppercase Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"uppercase-repo",
|
||||
SharedString::from("LICENSES"),
|
||||
Some(custom("main", ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Uppercase Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
"uppercase-branch",
|
||||
SharedString::from("zed"),
|
||||
Some(custom("update-README", behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
],
|
||||
)
|
||||
.grow()
|
||||
.vertical()])
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use collections::HashSet;
|
||||
use editor::{
|
||||
actions::{GoToHunk, GoToPreviousHunk},
|
||||
scroll::Autoscroll,
|
||||
Editor, EditorEvent, ToPoint,
|
||||
Editor, EditorEvent,
|
||||
};
|
||||
use feature_flags::FeatureFlagViewExt;
|
||||
use futures::StreamExt;
|
||||
@@ -192,6 +192,19 @@ impl ProjectDiff {
|
||||
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>) {
|
||||
if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
@@ -244,14 +257,14 @@ impl ProjectDiff {
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut commit = false;
|
||||
let mut can_open_commit_editor = false;
|
||||
let mut stage_all = false;
|
||||
let mut unstage_all = false;
|
||||
self.workspace
|
||||
.read_with(cx, |workspace, cx| {
|
||||
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
|
||||
let git_panel = git_panel.read(cx);
|
||||
commit = git_panel.can_commit();
|
||||
can_open_commit_editor = git_panel.can_open_commit_editor();
|
||||
stage_all = git_panel.can_stage_all();
|
||||
unstage_all = git_panel.can_unstage_all();
|
||||
}
|
||||
@@ -263,7 +276,7 @@ impl ProjectDiff {
|
||||
unstage: has_staged_hunks,
|
||||
prev_next,
|
||||
selection,
|
||||
commit,
|
||||
can_open_commit_editor,
|
||||
stage_all,
|
||||
unstage_all,
|
||||
};
|
||||
@@ -271,41 +284,26 @@ impl ProjectDiff {
|
||||
|
||||
fn handle_editor_event(
|
||||
&mut self,
|
||||
editor: &Entity<Editor>,
|
||||
_: &Entity<Editor>,
|
||||
event: &EditorEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::ScrollPositionChanged { .. } => editor.update(cx, |editor, cx| {
|
||||
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 {
|
||||
EditorEvent::SelectionsChanged { local: true } => {
|
||||
let Some(project_path) = self.active_path(cx) else {
|
||||
return;
|
||||
};
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
|
||||
git_panel.update(cx, |git_panel, cx| {
|
||||
git_panel.select_entry_by_path(project_path.into(), window, cx)
|
||||
git_panel.select_entry_by_path(project_path, window, cx)
|
||||
})
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -400,6 +398,7 @@ impl ProjectDiff {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if was_empty {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
// TODO select the very beginning (possibly inside a deletion)
|
||||
selections.select_ranges([0..0])
|
||||
});
|
||||
}
|
||||
@@ -774,7 +773,7 @@ struct ButtonStates {
|
||||
selection: bool,
|
||||
stage_all: bool,
|
||||
unstage_all: bool,
|
||||
commit: bool,
|
||||
can_open_commit_editor: bool,
|
||||
}
|
||||
|
||||
impl Render for ProjectDiffToolbar {
|
||||
@@ -813,10 +812,8 @@ impl Render for ProjectDiffToolbar {
|
||||
el.child(
|
||||
Button::new("stage", "Stage")
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Stage",
|
||||
&StageAndNext {
|
||||
whole_excerpt: false,
|
||||
},
|
||||
"Stage and go to next hunk",
|
||||
&StageAndNext,
|
||||
&focus_handle,
|
||||
))
|
||||
// don't actually disable the button so it's mashable
|
||||
@@ -826,22 +823,14 @@ impl Render for ProjectDiffToolbar {
|
||||
Color::Disabled
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(
|
||||
&StageAndNext {
|
||||
whole_excerpt: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
this.dispatch_action(&StageAndNext, window, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("unstage", "Unstage")
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Unstage",
|
||||
&UnstageAndNext {
|
||||
whole_excerpt: false,
|
||||
},
|
||||
"Unstage and go to next hunk",
|
||||
&UnstageAndNext,
|
||||
&focus_handle,
|
||||
))
|
||||
.color(if button_states.unstage {
|
||||
@@ -850,13 +839,7 @@ impl Render for ProjectDiffToolbar {
|
||||
Color::Disabled
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(
|
||||
&UnstageAndNext {
|
||||
whole_excerpt: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
this.dispatch_action(&UnstageAndNext, window, cx)
|
||||
})),
|
||||
)
|
||||
}),
|
||||
@@ -870,20 +853,12 @@ impl Render for ProjectDiffToolbar {
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Go to previous hunk",
|
||||
&GoToPreviousHunk {
|
||||
center_cursor: false,
|
||||
},
|
||||
&GoToPreviousHunk,
|
||||
&focus_handle,
|
||||
))
|
||||
.disabled(!button_states.prev_next)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(
|
||||
&GoToPreviousHunk {
|
||||
center_cursor: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
this.dispatch_action(&GoToPreviousHunk, window, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
@@ -891,20 +866,12 @@ impl Render for ProjectDiffToolbar {
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Go to next hunk",
|
||||
&GoToHunk {
|
||||
center_cursor: false,
|
||||
},
|
||||
&GoToHunk,
|
||||
&focus_handle,
|
||||
))
|
||||
.disabled(!button_states.prev_next)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.dispatch_action(
|
||||
&GoToHunk {
|
||||
center_cursor: true,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
this.dispatch_action(&GoToHunk, window, cx)
|
||||
})),
|
||||
),
|
||||
)
|
||||
@@ -950,7 +917,7 @@ impl Render for ProjectDiffToolbar {
|
||||
)
|
||||
.child(
|
||||
Button::new("commit", "Commit")
|
||||
.disabled(!button_states.commit)
|
||||
.disabled(!button_states.can_open_commit_editor)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Commit",
|
||||
&ShowCommitEditor,
|
||||
@@ -980,6 +947,11 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
@@ -1152,4 +1124,107 @@ mod tests {
|
||||
.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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ impl RemoteOutputToast {
|
||||
}
|
||||
});
|
||||
|
||||
let message;
|
||||
let mut message: SharedString;
|
||||
let remote;
|
||||
|
||||
match action {
|
||||
@@ -86,19 +86,32 @@ impl RemoteOutputToast {
|
||||
|
||||
RemoteAction::Push(remote_ref) => {
|
||||
message = output.stdout.trim().to_string().into();
|
||||
let remote_message = get_remote_lines(&output.stderr);
|
||||
let finder = LinkFinder::new();
|
||||
let links = finder
|
||||
.links(&remote_message)
|
||||
.filter(|link| *link.kind() == LinkKind::Url)
|
||||
.map(|link| link.start()..link.end())
|
||||
.collect_vec();
|
||||
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 = Some(InfoFromRemote {
|
||||
name: remote_ref.name,
|
||||
remote_text: remote_message.into(),
|
||||
links,
|
||||
});
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -670,6 +670,14 @@ 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.
|
||||
///
|
||||
/// The gradient line's angle of direction. A value of `0.` is equivalent to to top; increasing values rotate clockwise from there.
|
||||
|
||||
@@ -401,9 +401,9 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
.pl_0p5()
|
||||
.w(px(240.))
|
||||
.child(
|
||||
div().max_w_40().child(
|
||||
Label::new(model_info.model.name().0.clone()).text_ellipsis(),
|
||||
),
|
||||
div()
|
||||
.max_w_40()
|
||||
.child(Label::new(model_info.model.name().0.clone()).truncate()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -2062,7 +2062,12 @@ impl MultiBuffer {
|
||||
if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) {
|
||||
buffer_state.excerpts.retain(|l| l != &excerpt.locator);
|
||||
if buffer_state.excerpts.is_empty() {
|
||||
log::debug!(
|
||||
"removing buffer and diff for buffer {}",
|
||||
excerpt.buffer_id
|
||||
);
|
||||
buffers.remove(&excerpt.buffer_id);
|
||||
self.diffs.remove(&excerpt.buffer_id);
|
||||
}
|
||||
}
|
||||
cursor.next(&());
|
||||
@@ -2716,6 +2721,11 @@ impl MultiBuffer {
|
||||
snapshot.has_deleted_file = has_deleted_file;
|
||||
snapshot.has_conflict = has_conflict;
|
||||
|
||||
snapshot.diffs.retain(|_, _| false);
|
||||
for (id, diff) in self.diffs.iter() {
|
||||
snapshot.diffs.insert(*id, diff.diff.read(cx).snapshot(cx));
|
||||
}
|
||||
|
||||
excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
@@ -3476,7 +3486,10 @@ impl MultiBufferSnapshot {
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + '_ {
|
||||
let query_range = range.start.to_point(self)..range.end.to_point(self);
|
||||
self.lift_buffer_metadata(query_range.clone(), move |buffer, buffer_range| {
|
||||
let diff = self.diffs.get(&buffer.remote_id())?;
|
||||
let Some(diff) = self.diffs.get(&buffer.remote_id()) else {
|
||||
log::debug!("no diff found for {:?}", buffer.remote_id());
|
||||
return None;
|
||||
};
|
||||
let buffer_start = buffer.anchor_before(buffer_range.start);
|
||||
let buffer_end = buffer.anchor_after(buffer_range.end);
|
||||
Some(
|
||||
@@ -3485,17 +3498,12 @@ impl MultiBufferSnapshot {
|
||||
if hunk.is_created_file() && !self.all_diff_hunks_expanded {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
Point::new(hunk.row_range.start, 0)..Point::new(hunk.row_range.end, 0),
|
||||
hunk,
|
||||
))
|
||||
Some((hunk.range.clone(), hunk))
|
||||
}),
|
||||
)
|
||||
})
|
||||
.filter_map(move |(range, hunk, excerpt)| {
|
||||
if range.start != range.end
|
||||
&& range.end == query_range.start
|
||||
&& !hunk.row_range.is_empty()
|
||||
if range.start != range.end && range.end == query_range.start && !hunk.range.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -3828,12 +3836,12 @@ impl MultiBufferSnapshot {
|
||||
&excerpt.buffer,
|
||||
) {
|
||||
let hunk_range = hunk.buffer_range.to_offset(&excerpt.buffer);
|
||||
if hunk.row_range.end >= buffer_end_row {
|
||||
if hunk.range.end >= Point::new(buffer_end_row, 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let hunk_start = Point::new(hunk.row_range.start, 0);
|
||||
let hunk_end = Point::new(hunk.row_range.end, 0);
|
||||
let hunk_start = hunk.range.start;
|
||||
let hunk_end = hunk.range.end;
|
||||
|
||||
cursor.seek_to_buffer_position_in_current_excerpt(&DimensionPair {
|
||||
key: hunk_range.start,
|
||||
|
||||
@@ -2160,6 +2160,7 @@ impl ReferenceMultibuffer {
|
||||
.unwrap();
|
||||
let excerpt = self.excerpts.remove(ix);
|
||||
let buffer = excerpt.buffer.read(cx);
|
||||
let id = buffer.remote_id();
|
||||
log::info!(
|
||||
"Removing excerpt {}: {:?}",
|
||||
ix,
|
||||
@@ -2167,6 +2168,13 @@ impl ReferenceMultibuffer {
|
||||
.text_for_range(excerpt.range.to_offset(buffer))
|
||||
.collect::<String>(),
|
||||
);
|
||||
if !self
|
||||
.excerpts
|
||||
.iter()
|
||||
.any(|excerpt| excerpt.buffer.read(cx).remote_id() == id)
|
||||
{
|
||||
self.diffs.remove(&id);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_excerpt_after(
|
||||
@@ -2266,7 +2274,7 @@ impl ReferenceMultibuffer {
|
||||
}
|
||||
|
||||
if !hunk.buffer_range.start.is_valid(&buffer) {
|
||||
log::trace!("skipping hunk with deleted start: {:?}", hunk.row_range);
|
||||
log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2415,6 +2423,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
.unwrap_or(10);
|
||||
|
||||
let mut buffers: Vec<Entity<Buffer>> = Vec::new();
|
||||
let mut base_texts: HashMap<BufferId, String> = HashMap::default();
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let mut reference = ReferenceMultibuffer::default();
|
||||
let mut anchors = Vec::new();
|
||||
@@ -2522,9 +2531,10 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap();
|
||||
|
||||
log::info!(
|
||||
"expanding diff hunks in range {:?} (excerpt id {:?}) index {excerpt_ix:?})",
|
||||
"expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})",
|
||||
range.to_offset(&snapshot),
|
||||
excerpt.id
|
||||
excerpt.id,
|
||||
excerpt.buffer.read(cx).remote_id(),
|
||||
);
|
||||
reference.expand_diff_hunks(excerpt.id, start..end, cx);
|
||||
multibuffer.expand_diff_hunks(vec![range], cx);
|
||||
@@ -2534,7 +2544,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
for buffer in multibuffer.all_buffers() {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let _ = multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
|
||||
multibuffer.diff_for(snapshot.remote_id()).unwrap().update(
|
||||
cx,
|
||||
|diff, cx| {
|
||||
log::info!(
|
||||
@@ -2551,17 +2561,16 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
}
|
||||
_ => {
|
||||
let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
|
||||
let base_text = util::RandomCharIter::new(&mut rng)
|
||||
let mut base_text = util::RandomCharIter::new(&mut rng)
|
||||
.take(256)
|
||||
.collect::<String>();
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx));
|
||||
let diff = cx.new(|cx| BufferDiff::new_with_base_text(&base_text, &buffer, cx));
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
reference.add_diff(diff.clone(), cx);
|
||||
multibuffer.add_diff(diff, cx)
|
||||
});
|
||||
text::LineEnding::normalize(&mut base_text);
|
||||
base_texts.insert(
|
||||
buffer.read_with(cx, |buffer, _| buffer.remote_id()),
|
||||
base_text,
|
||||
);
|
||||
buffers.push(buffer);
|
||||
buffers.last().unwrap()
|
||||
} else {
|
||||
@@ -2595,6 +2604,18 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
(start_ix..end_ix, anchor_range)
|
||||
});
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
let id = buffer_handle.read(cx).remote_id();
|
||||
if multibuffer.diff_for(id).is_none() {
|
||||
let base_text = base_texts.get(&id).unwrap();
|
||||
let diff = cx.new(|cx| {
|
||||
BufferDiff::new_with_base_text(base_text, &buffer_handle, cx)
|
||||
});
|
||||
reference.add_diff(diff.clone(), cx);
|
||||
multibuffer.add_diff(diff, cx)
|
||||
}
|
||||
});
|
||||
|
||||
let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer
|
||||
.insert_excerpts_after(
|
||||
|
||||
@@ -136,6 +136,15 @@ impl BufferDiffState {
|
||||
let _ = self.diff_bases_changed(buffer, diff_bases_change, cx);
|
||||
}
|
||||
|
||||
pub fn wait_for_recalculation(&mut self) -> Option<oneshot::Receiver<()>> {
|
||||
if self.diff_updated_futures.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.diff_updated_futures.push(tx);
|
||||
Some(rx)
|
||||
}
|
||||
|
||||
fn diff_bases_changed(
|
||||
&mut self,
|
||||
buffer: text::BufferSnapshot,
|
||||
@@ -1362,8 +1371,23 @@ impl BufferStore {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<BufferDiff>>> {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
if let Some(diff) = self.get_unstaged_diff(buffer_id, cx) {
|
||||
return Task::ready(Ok(diff));
|
||||
if let Some(OpenBuffer::Complete { diff_state, .. }) = self.opened_buffers.get(&buffer_id) {
|
||||
if let Some(unstaged_diff) = diff_state
|
||||
.read(cx)
|
||||
.unstaged_diff
|
||||
.as_ref()
|
||||
.and_then(|weak| weak.upgrade())
|
||||
{
|
||||
if let Some(task) =
|
||||
diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
|
||||
{
|
||||
return cx.background_executor().spawn(async move {
|
||||
task.await?;
|
||||
Ok(unstaged_diff)
|
||||
});
|
||||
}
|
||||
return Task::ready(Ok(unstaged_diff));
|
||||
}
|
||||
}
|
||||
|
||||
let task = match self.loading_diffs.entry((buffer_id, DiffKind::Unstaged)) {
|
||||
@@ -1402,8 +1426,24 @@ impl BufferStore {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<BufferDiff>>> {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
if let Some(diff) = self.get_uncommitted_diff(buffer_id, cx) {
|
||||
return Task::ready(Ok(diff));
|
||||
|
||||
if let Some(OpenBuffer::Complete { diff_state, .. }) = self.opened_buffers.get(&buffer_id) {
|
||||
if let Some(uncommitted_diff) = diff_state
|
||||
.read(cx)
|
||||
.uncommitted_diff
|
||||
.as_ref()
|
||||
.and_then(|weak| weak.upgrade())
|
||||
{
|
||||
if let Some(task) =
|
||||
diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
|
||||
{
|
||||
return cx.background_executor().spawn(async move {
|
||||
task.await?;
|
||||
Ok(uncommitted_diff)
|
||||
});
|
||||
}
|
||||
return Task::ready(Ok(uncommitted_diff));
|
||||
}
|
||||
}
|
||||
|
||||
let task = match self.loading_diffs.entry((buffer_id, DiffKind::Uncommitted)) {
|
||||
|
||||
@@ -6,10 +6,7 @@ use client::ProjectId;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::StreamExt as _;
|
||||
use git::repository::{Branch, CommitDetails, PushOptions, Remote, RemoteCommandOutput, ResetMode};
|
||||
use git::{
|
||||
repository::{GitRepository, RepoPath},
|
||||
status::{GitSummary, TrackedSummary},
|
||||
};
|
||||
use git::repository::{GitRepository, RepoPath};
|
||||
use gpui::{
|
||||
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
|
||||
WeakEntity,
|
||||
@@ -24,7 +21,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use text::BufferId;
|
||||
use util::{maybe, ResultExt};
|
||||
use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
|
||||
use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory};
|
||||
|
||||
pub struct GitStore {
|
||||
buffer_store: Entity<BufferStore>,
|
||||
@@ -691,6 +688,33 @@ impl Repository {
|
||||
self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
|
||||
}
|
||||
|
||||
// note: callers must verify these come from the same worktree
|
||||
pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
|
||||
let other_work_dir = &other.read(cx).repository_entry.work_directory;
|
||||
match (&self.repository_entry.work_directory, other_work_dir) {
|
||||
(WorkDirectory::InProject { .. }, WorkDirectory::AboveProject { .. }) => false,
|
||||
(WorkDirectory::AboveProject { .. }, WorkDirectory::InProject { .. }) => true,
|
||||
(
|
||||
WorkDirectory::InProject {
|
||||
relative_path: this_path,
|
||||
},
|
||||
WorkDirectory::InProject {
|
||||
relative_path: other_path,
|
||||
},
|
||||
) => other_path.starts_with(this_path),
|
||||
(
|
||||
WorkDirectory::AboveProject {
|
||||
absolute_path: this_path,
|
||||
..
|
||||
},
|
||||
WorkDirectory::AboveProject {
|
||||
absolute_path: other_path,
|
||||
..
|
||||
},
|
||||
) => other_path.starts_with(this_path),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn worktree_id_path_to_repo_path(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
@@ -1046,18 +1070,6 @@ impl Repository {
|
||||
self.repository_entry.status_len()
|
||||
}
|
||||
|
||||
fn have_changes(&self) -> bool {
|
||||
self.repository_entry.status_summary() != GitSummary::UNCHANGED
|
||||
}
|
||||
|
||||
fn have_staged_changes(&self) -> bool {
|
||||
self.repository_entry.status_summary().index != TrackedSummary::UNCHANGED
|
||||
}
|
||||
|
||||
pub fn can_commit(&self, commit_all: bool) -> bool {
|
||||
return self.have_changes() && (commit_all || self.have_staged_changes());
|
||||
}
|
||||
|
||||
pub fn commit(
|
||||
&self,
|
||||
message: SharedString,
|
||||
|
||||
@@ -4310,16 +4310,26 @@ impl Project {
|
||||
.buffer_for_id(buffer_id, cx)?
|
||||
.read(cx)
|
||||
.project_path(cx)?;
|
||||
self.git_store
|
||||
.read(cx)
|
||||
.all_repositories()
|
||||
.into_iter()
|
||||
.find_map(|repo| {
|
||||
Some((
|
||||
repo.clone(),
|
||||
repo.read(cx).repository_entry.relativize(&path.path).ok()?,
|
||||
))
|
||||
})
|
||||
|
||||
let mut found: Option<(Entity<Repository>, RepoPath)> = None;
|
||||
for repo_handle in self.git_store.read(cx).all_repositories() {
|
||||
let repo = repo_handle.read(cx);
|
||||
if repo.worktree_id != path.worktree_id {
|
||||
continue;
|
||||
}
|
||||
let Ok(relative_path) = repo.repository_entry.relativize(&path.path) else {
|
||||
continue;
|
||||
};
|
||||
if found
|
||||
.as_ref()
|
||||
.is_some_and(|(found, _)| repo.contains_sub_repo(found, cx))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
found = Some((repo_handle.clone(), relative_path))
|
||||
}
|
||||
|
||||
found
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,10 @@ pub struct GitSettings {
|
||||
///
|
||||
/// Default: on
|
||||
pub inline_blame: Option<InlineBlameSettings>,
|
||||
/// How hunks are displayed visually in the editor.
|
||||
///
|
||||
/// Default: transparent
|
||||
pub hunk_style: Option<GitHunkStyleSetting>,
|
||||
}
|
||||
|
||||
impl GitSettings {
|
||||
@@ -200,6 +204,16 @@ impl GitSettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GitHunkStyleSetting {
|
||||
/// Show unstaged hunks with a transparent background
|
||||
#[default]
|
||||
Transparent,
|
||||
/// Show unstaged hunks with a pattern background
|
||||
Pattern,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GitGutterSetting {
|
||||
|
||||
@@ -401,7 +401,7 @@ impl TitleBar {
|
||||
.child(
|
||||
Label::new(nickname.clone())
|
||||
.size(LabelSize::Small)
|
||||
.text_ellipsis(),
|
||||
.truncate(),
|
||||
),
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
|
||||
@@ -97,6 +97,7 @@ pub struct Button {
|
||||
key_binding: Option<KeyBinding>,
|
||||
key_binding_position: KeybindingPosition,
|
||||
alpha: Option<f32>,
|
||||
truncate: bool,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
@@ -123,6 +124,7 @@ impl Button {
|
||||
key_binding: None,
|
||||
key_binding_position: KeybindingPosition::default(),
|
||||
alpha: None,
|
||||
truncate: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +208,15 @@ impl Button {
|
||||
self.alpha = Some(alpha);
|
||||
self
|
||||
}
|
||||
|
||||
/// Truncates overflowing labels with an ellipsis (`…`) if needed.
|
||||
///
|
||||
/// Buttons with static labels should _never_ be truncated, ensure
|
||||
/// this is only used when the label is dynamic and may overflow.
|
||||
pub fn truncate(mut self, truncate: bool) -> Self {
|
||||
self.truncate = truncate;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Toggleable for Button {
|
||||
@@ -437,7 +448,8 @@ impl RenderOnce for Button {
|
||||
.color(label_color)
|
||||
.size(self.label_size.unwrap_or_default())
|
||||
.when_some(self.alpha, |this, alpha| this.alpha(alpha))
|
||||
.line_height_style(LineHeightStyle::UiLabel),
|
||||
.line_height_style(LineHeightStyle::UiLabel)
|
||||
.when(self.truncate, |this| this.truncate()),
|
||||
)
|
||||
.children(self.key_binding),
|
||||
)
|
||||
|
||||
@@ -64,8 +64,8 @@ impl LabelCommon for HighlightedLabel {
|
||||
self
|
||||
}
|
||||
|
||||
fn text_ellipsis(mut self) -> Self {
|
||||
self.base = self.base.text_ellipsis();
|
||||
fn truncate(mut self) -> Self {
|
||||
self.base = self.base.truncate();
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -171,8 +171,9 @@ impl LabelCommon for Label {
|
||||
self
|
||||
}
|
||||
|
||||
fn text_ellipsis(mut self) -> Self {
|
||||
self.base = self.base.text_ellipsis();
|
||||
/// Truncates overflowing text with an ellipsis (`…`) if needed.
|
||||
fn truncate(mut self) -> Self {
|
||||
self.base = self.base.truncate();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -240,7 +241,7 @@ mod label_preview {
|
||||
"Special Cases",
|
||||
vec![
|
||||
single_example("Single Line", Label::new("Line 1\nLine 2\nLine 3").single_line().into_any_element()),
|
||||
single_example("Text Ellipsis", div().max_w_24().child(Label::new("This is a very long file name that should be truncated: very_long_file_name_with_many_words.rs").text_ellipsis()).into_any_element()),
|
||||
single_example("Text Ellipsis", div().max_w_24().child(Label::new("This is a very long file name that should be truncated: very_long_file_name_with_many_words.rs").truncate()).into_any_element()),
|
||||
],
|
||||
),
|
||||
])
|
||||
|
||||
@@ -57,7 +57,7 @@ pub trait LabelCommon {
|
||||
fn alpha(self, alpha: f32) -> Self;
|
||||
|
||||
/// Truncates overflowing text with an ellipsis (`…`) if needed.
|
||||
fn text_ellipsis(self) -> Self;
|
||||
fn truncate(self) -> Self;
|
||||
|
||||
/// Sets the label to render as a single line.
|
||||
fn single_line(self) -> Self;
|
||||
@@ -84,7 +84,7 @@ pub struct LabelLike {
|
||||
alpha: Option<f32>,
|
||||
underline: bool,
|
||||
single_line: bool,
|
||||
text_ellipsis: bool,
|
||||
truncate: bool,
|
||||
}
|
||||
|
||||
impl Default for LabelLike {
|
||||
@@ -109,7 +109,7 @@ impl LabelLike {
|
||||
alpha: None,
|
||||
underline: false,
|
||||
single_line: false,
|
||||
text_ellipsis: false,
|
||||
truncate: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,8 +166,9 @@ impl LabelCommon for LabelLike {
|
||||
self
|
||||
}
|
||||
|
||||
fn text_ellipsis(mut self) -> Self {
|
||||
self.text_ellipsis = true;
|
||||
/// Truncates overflowing text with an ellipsis (`…`) if needed.
|
||||
fn truncate(mut self) -> Self {
|
||||
self.truncate = true;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -220,7 +221,7 @@ impl RenderOnce for LabelLike {
|
||||
})
|
||||
.when(self.strikethrough, |this| this.line_through())
|
||||
.when(self.single_line, |this| this.whitespace_nowrap())
|
||||
.when(self.text_ellipsis, |this| {
|
||||
.when(self.truncate, |this| {
|
||||
this.overflow_x_hidden().text_ellipsis()
|
||||
})
|
||||
.text_color(color)
|
||||
|
||||
@@ -55,6 +55,7 @@ git_ui.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
lsp = { workspace = true, features = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
project_panel.workspace = true
|
||||
|
||||
@@ -1329,12 +1329,25 @@ pub(crate) fn start_of_relative_buffer_row(
|
||||
|
||||
fn up_down_buffer_rows(
|
||||
map: &DisplaySnapshot,
|
||||
point: DisplayPoint,
|
||||
mut point: DisplayPoint,
|
||||
mut goal: SelectionGoal,
|
||||
times: isize,
|
||||
mut times: isize,
|
||||
text_layout_details: &TextLayoutDetails,
|
||||
) -> (DisplayPoint, SelectionGoal) {
|
||||
let bias = if times < 0 { Bias::Left } else { Bias::Right };
|
||||
|
||||
while map.is_folded_buffer_header(point.row()) {
|
||||
if times < 0 {
|
||||
(point, _) = movement::up(map, point, goal, true, text_layout_details);
|
||||
times += 1;
|
||||
} else if times > 0 {
|
||||
(point, _) = movement::down(map, point, goal, true, text_layout_details);
|
||||
times -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let start = map.display_point_to_fold_point(point, Bias::Left);
|
||||
let begin_folded_line = map.fold_point_to_display_point(
|
||||
map.fold_snapshot
|
||||
|
||||
@@ -6,9 +6,13 @@ use std::time::Duration;
|
||||
|
||||
use collections::HashMap;
|
||||
use command_palette::CommandPalette;
|
||||
use editor::{actions::DeleteLine, display_map::DisplayRow, DisplayPoint};
|
||||
use editor::{
|
||||
actions::DeleteLine, display_map::DisplayRow, test::editor_test_context::EditorTestContext,
|
||||
DisplayPoint, Editor, EditorMode, MultiBuffer,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
|
||||
use language::Point;
|
||||
pub use neovim_backed_test_context::*;
|
||||
use settings::SettingsStore;
|
||||
pub use vim_test_context::*;
|
||||
@@ -1707,3 +1711,202 @@ async fn test_ctrl_o_dot(cx: &mut gpui::TestAppContext) {
|
||||
cx.simulate_shared_keystrokes("l l escape .").await;
|
||||
cx.shared_state().await.assert_eq("hellˇllo world.");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
|
||||
VimTestContext::init(cx);
|
||||
cx.update(|cx| {
|
||||
VimTestContext::init_keybindings(true, cx);
|
||||
});
|
||||
let (editor, cx) = cx.add_window_view(|window, cx| {
|
||||
let multi_buffer = MultiBuffer::build_multi(
|
||||
[
|
||||
("111\n222\n333\n444\n", vec![Point::row_range(0..2)]),
|
||||
("aaa\nbbb\nccc\nddd\n", vec![Point::row_range(0..2)]),
|
||||
("AAA\nBBB\nCCC\nDDD\n", vec![Point::row_range(0..2)]),
|
||||
("one\ntwo\nthr\nfou\n", vec![Point::row_range(0..2)]),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::Full,
|
||||
multi_buffer.clone(),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
|
||||
// fold all but the second buffer, so that we test navigating between two
|
||||
// adjacent folded buffers, as well as folded buffers at the start and
|
||||
// end the multibuffer
|
||||
editor.fold_buffer(buffer_ids[0], cx);
|
||||
editor.fold_buffer(buffer_ids[2], cx);
|
||||
editor.fold_buffer(buffer_ids[3], cx);
|
||||
|
||||
editor
|
||||
});
|
||||
let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
|
||||
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
[EXCERPT]
|
||||
aaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystroke("j");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
ˇaaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystroke("j");
|
||||
cx.simulate_keystroke("j");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
aaa
|
||||
bbb
|
||||
ˇ[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystroke("j");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
aaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystroke("j");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
aaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystroke("k");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
aaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystroke("k");
|
||||
cx.simulate_keystroke("k");
|
||||
cx.simulate_keystroke("k");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
ˇaaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystroke("k");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
[EXCERPT]
|
||||
aaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystroke("shift-g");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
aaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystrokes("g g");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
[EXCERPT]
|
||||
aaa
|
||||
bbb
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
|
||||
editor.fold_buffer(buffer_ids[1], cx);
|
||||
});
|
||||
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
cx.simulate_keystrokes("2 j");
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
[EXCERPT]
|
||||
ˇ[FOLDED]
|
||||
[EXCERPT]
|
||||
[FOLDED]
|
||||
"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ impl VimTestContext {
|
||||
git_ui::init(cx);
|
||||
crate::init(cx);
|
||||
search::init(cx);
|
||||
language::init(cx);
|
||||
editor::init_settings(cx);
|
||||
project::Project::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,22 +60,26 @@ impl VimTestContext {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn init_keybindings(enabled: bool, cx: &mut App) {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
|
||||
});
|
||||
let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
|
||||
"keymaps/default-macos.json",
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
cx.bind_keys(default_key_bindings);
|
||||
if enabled {
|
||||
let vim_key_bindings =
|
||||
settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
|
||||
cx.bind_keys(vim_key_bindings);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_lsp(mut cx: EditorLspTestContext, enabled: bool) -> VimTestContext {
|
||||
cx.update(|_, cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
|
||||
});
|
||||
let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
|
||||
"keymaps/default-macos.json",
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
cx.bind_keys(default_key_bindings);
|
||||
if enabled {
|
||||
let vim_key_bindings =
|
||||
settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
|
||||
cx.bind_keys(vim_key_bindings);
|
||||
}
|
||||
Self::init_keybindings(enabled, cx);
|
||||
});
|
||||
|
||||
// Setup search toolbars and keypress hook
|
||||
|
||||
@@ -4292,7 +4292,11 @@ impl BackgroundScanner {
|
||||
let mut containing_git_repository = None;
|
||||
for (index, ancestor) in root_abs_path.as_path().ancestors().enumerate() {
|
||||
if index != 0 {
|
||||
if let Ok(ignore) =
|
||||
if Some(ancestor) == self.fs.home_dir().as_deref() {
|
||||
// Unless $HOME is itself the worktree root, don't consider it as a
|
||||
// containing git repository---expensive and likely unwanted.
|
||||
break;
|
||||
} else if let Ok(ignore) =
|
||||
build_gitignore(&ancestor.join(*GITIGNORE), self.fs.as_ref()).await
|
||||
{
|
||||
self.state
|
||||
@@ -4304,6 +4308,7 @@ impl BackgroundScanner {
|
||||
}
|
||||
|
||||
let ancestor_dot_git = ancestor.join(*DOT_GIT);
|
||||
log::debug!("considering ancestor: {ancestor_dot_git:?}");
|
||||
// Check whether the directory or file called `.git` exists (in the
|
||||
// case of worktrees it's a file.)
|
||||
if self
|
||||
@@ -4312,21 +4317,26 @@ impl BackgroundScanner {
|
||||
.await
|
||||
.is_ok_and(|metadata| metadata.is_some())
|
||||
{
|
||||
log::debug!(".git path exists");
|
||||
if index != 0 {
|
||||
// We canonicalize, since the FS events use the canonicalized path.
|
||||
if let Some(ancestor_dot_git) =
|
||||
self.fs.canonicalize(&ancestor_dot_git).await.log_err()
|
||||
{
|
||||
let location_in_repo = root_abs_path
|
||||
.as_path()
|
||||
.strip_prefix(ancestor)
|
||||
.unwrap()
|
||||
.into();
|
||||
log::debug!(
|
||||
"inserting parent git repo for this worktree: {location_in_repo:?}"
|
||||
);
|
||||
// We associate the external git repo with our root folder and
|
||||
// also mark where in the git repo the root folder is located.
|
||||
let local_repository = self.state.lock().insert_git_repository_for_path(
|
||||
WorkDirectory::AboveProject {
|
||||
absolute_path: ancestor.into(),
|
||||
location_in_repo: root_abs_path
|
||||
.as_path()
|
||||
.strip_prefix(ancestor)
|
||||
.unwrap()
|
||||
.into(),
|
||||
location_in_repo,
|
||||
},
|
||||
ancestor_dot_git.clone().into(),
|
||||
self.fs.as_ref(),
|
||||
@@ -4341,9 +4351,13 @@ impl BackgroundScanner {
|
||||
|
||||
// Reached root of git repository.
|
||||
break;
|
||||
} else {
|
||||
log::debug!(".git path doesn't exist");
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("containing git repository: {containing_git_repository:?}");
|
||||
|
||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||
{
|
||||
let mut state = self.state.lock();
|
||||
|
||||
@@ -2241,6 +2241,73 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_home_dir_as_git_repository(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"home": {
|
||||
".git": {},
|
||||
"project": {
|
||||
"a.txt": "A"
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
fs.set_home_dir(Path::new(path!("/root/home")).to_owned());
|
||||
|
||||
let tree = Worktree::local(
|
||||
Path::new(path!("/root/home/project")),
|
||||
true,
|
||||
fs.clone(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let tree = tree.as_local().unwrap();
|
||||
|
||||
let repo = tree.repository_for_path(path!("a.txt").as_ref());
|
||||
assert!(repo.is_none());
|
||||
});
|
||||
|
||||
let home_tree = Worktree::local(
|
||||
Path::new(path!("/root/home")),
|
||||
true,
|
||||
fs.clone(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| home_tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
home_tree.flush_fs_events(cx).await;
|
||||
|
||||
home_tree.read_with(cx, |home_tree, _cx| {
|
||||
let home_tree = home_tree.as_local().unwrap();
|
||||
|
||||
let repo = home_tree.repository_for_path(path!("project/a.txt").as_ref());
|
||||
assert_eq!(
|
||||
repo.map(|repo| &repo.work_directory),
|
||||
Some(&WorkDirectory::InProject {
|
||||
relative_path: Path::new("").into()
|
||||
})
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_git_repository_for_path(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition.workspace = true
|
||||
name = "zed"
|
||||
version = "0.177.0"
|
||||
version = "0.177.1"
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
preview
|
||||
@@ -1020,7 +1020,7 @@ fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) {
|
||||
let extension_store = ExtensionStore::global(cx);
|
||||
let theme_registry = ThemeRegistry::global(cx);
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
let appearance = cx.window_appearance().into();
|
||||
let appearance = SystemAppearance::global(cx).0;
|
||||
|
||||
if let Some(theme_selection) = theme_settings.theme_selection.as_ref() {
|
||||
let theme_name = theme_selection.theme(appearance);
|
||||
|
||||
@@ -182,18 +182,8 @@ impl Render for QuickActionBar {
|
||||
.action("Next Problem", Box::new(GoToDiagnostic))
|
||||
.action("Previous Problem", Box::new(GoToPreviousDiagnostic))
|
||||
.separator()
|
||||
.action(
|
||||
"Next Hunk",
|
||||
Box::new(GoToHunk {
|
||||
center_cursor: true,
|
||||
}),
|
||||
)
|
||||
.action(
|
||||
"Previous Hunk",
|
||||
Box::new(GoToPreviousHunk {
|
||||
center_cursor: true,
|
||||
}),
|
||||
)
|
||||
.action("Next Hunk", Box::new(GoToHunk))
|
||||
.action("Previous Hunk", Box::new(GoToPreviousHunk))
|
||||
.separator()
|
||||
.action("Move Line Up", Box::new(MoveLineUp))
|
||||
.action("Move Line Down", Box::new(MoveLineDown))
|
||||
|
||||
@@ -10,6 +10,12 @@ To preview the docs locally you will need to install [mdBook](https://rust-lang.
|
||||
mdbook serve docs
|
||||
```
|
||||
|
||||
Before committing, verify that the docs are formatted in the way prettier expects with:
|
||||
|
||||
```
|
||||
cd docs && pnpm dlx prettier@3.5.0 . --write && cd ..
|
||||
```
|
||||
|
||||
## Preprocessor
|
||||
|
||||
We have a custom mdbook preprocessor for interfacing with our crates (`crates/docs_preprocessor`).
|
||||
|
||||
@@ -72,10 +72,15 @@ The following commands use the language server to help you navigate and refactor
|
||||
|
||||
### Git
|
||||
|
||||
| Command | Default Shortcut |
|
||||
| ------------------------- | ---------------- |
|
||||
| Go to next git change | `] c` |
|
||||
| Go to previous git change | `[ c` |
|
||||
| Command | Default Shortcut |
|
||||
| ------------------------------- | ---------------- |
|
||||
| Go to next git change | `] c` |
|
||||
| Go to previous git change | `[ c` |
|
||||
| Expand diff hunk | `d o` |
|
||||
| Toggle staged | `d O` |
|
||||
| Stage and next (in diff view) | `d u` |
|
||||
| Unstage and next (in diff view) | `d U` |
|
||||
| Restore change | `d p` |
|
||||
|
||||
### Treesitter
|
||||
|
||||
|
||||
Reference in New Issue
Block a user