Compare commits
75 Commits
lua-run-cl
...
a-differen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a90f80725f | ||
|
|
4e6c37d23b | ||
|
|
0cf6259fec | ||
|
|
5cb5e92185 | ||
|
|
da61a28839 | ||
|
|
efdb769f9b | ||
|
|
9cce5a650e | ||
|
|
2021ca5bff | ||
|
|
1771250b04 | ||
|
|
18259c0fd4 | ||
|
|
41ddd1cc97 | ||
|
|
e175878008 | ||
|
|
1cfbfc199c | ||
|
|
f59f2caf7e | ||
|
|
401342c6ec | ||
|
|
0df1e4a489 | ||
|
|
9bd3e156f5 | ||
|
|
42c655751b | ||
|
|
ff1d78df3b | ||
|
|
c2e4fdf63d | ||
|
|
bf11b888c3 | ||
|
|
d562f58e76 | ||
|
|
94e4aa626d | ||
|
|
8ceba89d81 | ||
|
|
c37d6d5fed | ||
|
|
1a3597d726 | ||
|
|
c747cccde3 | ||
|
|
d81e7683ea | ||
|
|
8b29ee6033 | ||
|
|
96a75e08af | ||
|
|
06cbff6714 | ||
|
|
ce05813e7c | ||
|
|
4d1d8d6d78 | ||
|
|
1f8b14f4f1 | ||
|
|
082cc6184c | ||
|
|
6cfc4dc857 | ||
|
|
b9c48685e8 | ||
|
|
570c396e84 | ||
|
|
5fd034e604 | ||
|
|
63dab5f891 | ||
|
|
a2d6df3ed6 | ||
|
|
30e86ac939 | ||
|
|
976fc3ee97 | ||
|
|
63091459d8 | ||
|
|
659fae70f8 | ||
|
|
02e970192f | ||
|
|
5ecc67f2ef | ||
|
|
73dfb10c16 | ||
|
|
e513e81046 | ||
|
|
2fc4dec58f | ||
|
|
3891381d3e | ||
|
|
b91e929086 | ||
|
|
013a646799 | ||
|
|
ed52e759d7 | ||
|
|
6da099a9d7 | ||
|
|
5f159bc95e | ||
|
|
a4462577bf | ||
|
|
c147b58558 | ||
|
|
84fe1bfe9b | ||
|
|
657d7a911d | ||
|
|
ee05cc3ad9 | ||
|
|
5ed144f9d2 | ||
|
|
2a862b3c54 | ||
|
|
4a7c84f490 | ||
|
|
230e2e4107 | ||
|
|
d732b8ba0f | ||
|
|
7c3eecc9c7 | ||
|
|
fff37ab823 | ||
|
|
8a7a78fafb | ||
|
|
6de3ac3e17 | ||
|
|
5aae3bdc69 | ||
|
|
e298301b40 | ||
|
|
ed6bf7f161 | ||
|
|
f14d6670ba | ||
|
|
22d9b5d8ca |
160
Cargo.lock
generated
160
Cargo.lock
generated
@@ -84,7 +84,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "alacritty_terminal"
|
||||
version = "0.25.1-dev"
|
||||
source = "git+https://github.com/zed-industries/alacritty.git?rev=03c2907b44b4189aac5fdeaea331f5aab5c7072e#03c2907b44b4189aac5fdeaea331f5aab5c7072e"
|
||||
source = "git+https://github.com/zed-industries/alacritty.git?branch=add-hush-login-flag#828457c9ff1f7ea0a0469337cc8a37ee3a1b0590"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.8.0",
|
||||
@@ -490,6 +490,7 @@ dependencies = [
|
||||
"proto",
|
||||
"rand 0.8.5",
|
||||
"rope",
|
||||
"scripting_tool",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
@@ -1233,27 +1234,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.12.2"
|
||||
version = "1.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca"
|
||||
checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"paste",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.25.0"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71b2ddd3ada61a305e1d8bb6c005d1eaa7d14d903681edfc400406d523a9b491"
|
||||
checksum = "6bbe221bbf523b625a4dd8585c7f38166e31167ec2ca98051dbcb4c3b6e825d2"
|
||||
dependencies = [
|
||||
"bindgen 0.69.5",
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1823,7 +1822,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
@@ -1846,7 +1845,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@@ -2669,7 +2668,7 @@ dependencies = [
|
||||
"serde",
|
||||
"tempfile",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"windows 0.60.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2711,7 +2710,7 @@ dependencies = [
|
||||
"tokio-socks",
|
||||
"url",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"windows 0.60.0",
|
||||
"worktree",
|
||||
]
|
||||
|
||||
@@ -4457,7 +4456,6 @@ dependencies = [
|
||||
"env_logger 0.11.6",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"git",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
@@ -5053,7 +5051,7 @@ dependencies = [
|
||||
"text",
|
||||
"time",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"windows 0.60.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5450,7 +5448,6 @@ dependencies = [
|
||||
"db",
|
||||
"editor",
|
||||
"env_logger 0.11.6",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"git",
|
||||
@@ -5478,10 +5475,11 @@ dependencies = [
|
||||
"telemetry",
|
||||
"theme",
|
||||
"time",
|
||||
"time_format",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"windows 0.60.0",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
@@ -5681,8 +5679,8 @@ dependencies = [
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-plasma",
|
||||
"windows 0.58.0",
|
||||
"windows-core 0.58.0",
|
||||
"windows 0.60.0",
|
||||
"windows-core 0.60.1",
|
||||
"x11-clipboard",
|
||||
"x11rb",
|
||||
"xim",
|
||||
@@ -7329,7 +7327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10507,7 +10505,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
|
||||
dependencies = [
|
||||
"bytes 1.10.1",
|
||||
"heck 0.5.0",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"multimap 0.10.0",
|
||||
"once_cell",
|
||||
@@ -10540,7 +10538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.10.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
@@ -11915,19 +11913,20 @@ name = "scripting_tool"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assistant_tool",
|
||||
"collections",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"mlua",
|
||||
"parking_lot",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"shlex",
|
||||
"util",
|
||||
]
|
||||
|
||||
@@ -13614,7 +13613,7 @@ dependencies = [
|
||||
"theme",
|
||||
"thiserror 1.0.69",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"windows 0.60.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13991,7 +13990,7 @@ dependencies = [
|
||||
"tree-sitter-md",
|
||||
"ui",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"windows 0.60.0",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
"zeta",
|
||||
@@ -14698,7 +14697,7 @@ dependencies = [
|
||||
"theme",
|
||||
"ui_macros",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"windows 0.60.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15913,7 +15912,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15970,6 +15969,28 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.60.1",
|
||||
"windows-future",
|
||||
"windows-link",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec"
|
||||
dependencies = [
|
||||
"windows-core 0.60.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
@@ -16010,10 +16031,33 @@ dependencies = [
|
||||
"windows-implement 0.58.0",
|
||||
"windows-interface 0.58.0",
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings",
|
||||
"windows-strings 0.1.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.60.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247"
|
||||
dependencies = [
|
||||
"windows-implement 0.59.0",
|
||||
"windows-interface 0.59.0",
|
||||
"windows-link",
|
||||
"windows-result 0.3.1",
|
||||
"windows-strings 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0"
|
||||
dependencies = [
|
||||
"windows-core 0.60.1",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
@@ -16036,6 +16080,17 @@ dependencies = [
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
@@ -16058,12 +16113,33 @@ dependencies = [
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"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-numerics"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed"
|
||||
dependencies = [
|
||||
"windows-core 0.60.1",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
@@ -16071,7 +16147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
|
||||
dependencies = [
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings",
|
||||
"windows-strings 0.1.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
@@ -16093,6 +16169,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
@@ -16103,6 +16188,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
@@ -16985,7 +17079,6 @@ dependencies = [
|
||||
"repl",
|
||||
"reqwest_client",
|
||||
"rope",
|
||||
"scripting_tool",
|
||||
"search",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -17020,7 +17113,7 @@ dependencies = [
|
||||
"vim",
|
||||
"vim_mode_setting",
|
||||
"welcome",
|
||||
"windows 0.58.0",
|
||||
"windows 0.60.0",
|
||||
"winresource",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
@@ -17117,13 +17210,6 @@ dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_terraform"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_test_extension"
|
||||
version = "0.1.0"
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -178,7 +178,6 @@ members = [
|
||||
"extensions/ruff",
|
||||
"extensions/slash-commands-example",
|
||||
"extensions/snippets",
|
||||
"extensions/terraform",
|
||||
"extensions/test-extension",
|
||||
"extensions/toml",
|
||||
"extensions/uiua",
|
||||
@@ -370,7 +369,7 @@ zeta = { path = "crates/zeta" }
|
||||
#
|
||||
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e" }
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
@@ -597,12 +596,12 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
version = "0.58"
|
||||
version = "0.60"
|
||||
features = [
|
||||
"implement",
|
||||
"Foundation_Collections",
|
||||
"Foundation_Numerics",
|
||||
"Storage",
|
||||
"Storage_Search",
|
||||
"Storage_Streams",
|
||||
"System_Threading",
|
||||
"UI_StartScreen",
|
||||
"UI_ViewManagement",
|
||||
@@ -623,9 +622,11 @@ features = [
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Console",
|
||||
"Win32_System_DataExchange",
|
||||
"Win32_System_IO",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Ole",
|
||||
"Win32_System_Pipes",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
@@ -751,5 +752,9 @@ new_ret_no_self = { level = "allow" }
|
||||
should_implement_trait = { level = "allow" }
|
||||
let_underscore_future = "allow"
|
||||
|
||||
# in Rust it can be very tedious to reduce argument count without
|
||||
# running afoul of the borrow checker.
|
||||
too_many_arguments = "allow"
|
||||
|
||||
[workspace.metadata.cargo-machete]
|
||||
ignored = ["bindgen", "cbindgen", "prost_build", "serde", "component", "linkme"]
|
||||
|
||||
7
assets/icons/file_icons/wgsl.svg
Normal file
7
assets/icons/file_icons/wgsl.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 13L1.5 5H11.5L6.5 13Z" fill="black"/>
|
||||
<path d="M14 9H9L11.5 5L14 9Z" fill="black" fill-opacity="0.75"/>
|
||||
<path d="M9 9L14 9L11.5 13L9 9Z" fill="black" fill-opacity="0.65"/>
|
||||
<path d="M14 5L15.25 7L12.75 7L14 5Z" fill="black" fill-opacity="0.5"/>
|
||||
<path d="M14 9L12.75 7H15.25L14 9Z" fill="black" fill-opacity="0.55"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 432 B |
@@ -731,28 +731,48 @@
|
||||
"up": "menu::SelectPrevious",
|
||||
"down": "menu::SelectNext",
|
||||
"enter": "menu::Confirm",
|
||||
"alt-y": "git::StageFile",
|
||||
"alt-shift-y": "git::UnstageFile",
|
||||
"ctrl-alt-y": "git::ToggleStaged",
|
||||
"space": "git::ToggleStaged",
|
||||
"ctrl-space": "git::StageAll",
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"alt-enter": "menu::SecondaryConfirm"
|
||||
"alt-enter": "menu::SecondaryConfirm",
|
||||
"backspace": "git::RestoreFile"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel",
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"alt-l": "git::GenerateCommitMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-g ctrl-g": "git::Fetch",
|
||||
"ctrl-g up": "git::Push",
|
||||
"ctrl-g down": "git::Pull",
|
||||
"ctrl-g shift-up": "git::ForcePush",
|
||||
"ctrl-g d": "git::Diff",
|
||||
"ctrl-g backspace": "git::RestoreTrackedFiles",
|
||||
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
|
||||
"ctrl-space": "git::StageAll",
|
||||
"ctrl-shift-space": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitDiff > Editor",
|
||||
"bindings": {
|
||||
"ctrl-enter": "git::Commit"
|
||||
"ctrl-enter": "git::Commit",
|
||||
"ctrl-space": "git::StageAll",
|
||||
"ctrl-shift-space": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -767,6 +787,7 @@
|
||||
"escape": "git_panel::FocusChanges",
|
||||
"tab": "git_panel::FocusChanges",
|
||||
"shift-tab": "git_panel::FocusChanges",
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"alt-up": "git_panel::FocusChanges",
|
||||
"alt-l": "git::GenerateCommitMessage"
|
||||
@@ -840,21 +861,22 @@
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
// Overrides for conflicting keybindings
|
||||
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"],
|
||||
"ctrl-o": ["terminal::SendKeystroke", "ctrl-o"],
|
||||
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
||||
"ctrl-shift-a": "editor::SelectAll",
|
||||
"find": "buffer_search::Deploy",
|
||||
"ctrl-shift-f": "buffer_search::Deploy",
|
||||
"ctrl-shift-l": "terminal::Clear",
|
||||
"ctrl-shift-w": "pane::CloseActiveItem",
|
||||
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"],
|
||||
"up": ["terminal::SendKeystroke", "up"],
|
||||
"pageup": ["terminal::SendKeystroke", "pageup"],
|
||||
"down": ["terminal::SendKeystroke", "down"],
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
"shift-up": "terminal::ScrollLineUp",
|
||||
|
||||
@@ -31,13 +31,13 @@
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"cmd-enter": "menu::SecondaryConfirm",
|
||||
"cmd-escape": "menu::Cancel",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel",
|
||||
"alt-shift-enter": "menu::Restart",
|
||||
"cmd-shift-w": "workspace::CloseWindow",
|
||||
"shift-escape": "workspace::ToggleZoom",
|
||||
"cmd-escape": "menu::Cancel",
|
||||
"cmd-o": "workspace::Open",
|
||||
"cmd-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||
@@ -763,28 +763,25 @@
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm",
|
||||
"cmd-alt-y": "git::ToggleStaged",
|
||||
"space": "git::ToggleStaged",
|
||||
"cmd-shift-space": "git::StageAll",
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"cmd-y": "git::StageFile",
|
||||
"cmd-shift-y": "git::UnstageFile",
|
||||
"alt-down": "git_panel::FocusEditor",
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus",
|
||||
"cmd-enter": "git::Commit"
|
||||
"cmd-enter": "git::Commit",
|
||||
"backspace": "git::RestoreFile"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitDiff > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-enter": "git::Commit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AskPass > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "menu::Confirm"
|
||||
"cmd-enter": "git::Commit",
|
||||
"cmd-ctrl-y": "git::StageAll",
|
||||
"cmd-ctrl-shift-y": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -800,11 +797,27 @@
|
||||
"alt-tab": "git::GenerateCommitMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-g ctrl-g": "git::Fetch",
|
||||
"ctrl-g up": "git::Push",
|
||||
"ctrl-g down": "git::Pull",
|
||||
"ctrl-g shift-up": "git::ForcePush",
|
||||
"ctrl-g d": "git::Diff",
|
||||
"ctrl-g backspace": "git::RestoreTrackedFiles",
|
||||
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
|
||||
"cmd-ctrl-y": "git::StageAll",
|
||||
"cmd-ctrl-shift-y": "git::UnstageAll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"escape": "menu::Cancel",
|
||||
"cmd-enter": "git::Commit",
|
||||
"alt-tab": "git::GenerateCommitMessage"
|
||||
}
|
||||
|
||||
@@ -1300,8 +1300,7 @@
|
||||
},
|
||||
// Settings for auto-closing of JSX tags.
|
||||
"jsx_tag_auto_close": {
|
||||
// // Whether to auto-close JSX tags.
|
||||
// "enabled": true
|
||||
"enabled": true
|
||||
},
|
||||
// LSP Specific settings.
|
||||
"lsp": {
|
||||
|
||||
@@ -383,6 +383,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
@@ -771,6 +776,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
@@ -1159,6 +1169,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
@@ -1547,6 +1562,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#0b6678ff",
|
||||
"font_style": null,
|
||||
@@ -1935,6 +1955,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#0b6678ff",
|
||||
"font_style": null,
|
||||
@@ -2323,6 +2348,11 @@
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#0b6678ff",
|
||||
"font_style": null,
|
||||
|
||||
@@ -386,7 +386,6 @@ impl InlineAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn suggest_assist(
|
||||
&mut self,
|
||||
editor: &Entity<Editor>,
|
||||
@@ -1674,7 +1673,6 @@ impl Focusable for PromptEditor {
|
||||
impl PromptEditor {
|
||||
const MAX_LINES: u8 = 8;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
id: InlineAssistId,
|
||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||
@@ -2333,7 +2331,6 @@ struct InlineAssist {
|
||||
}
|
||||
|
||||
impl InlineAssist {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
assist_id: InlineAssistId,
|
||||
group_id: InlineAssistGroupId,
|
||||
|
||||
@@ -702,7 +702,6 @@ impl Focusable for PromptEditor {
|
||||
impl PromptEditor {
|
||||
const MAX_LINES: u8 = 8;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
id: TerminalInlineAssistId,
|
||||
prompt_history: VecDeque<String>,
|
||||
|
||||
@@ -59,6 +59,7 @@ prompt_library.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
rope.workspace = true
|
||||
scripting_tool.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
@@ -81,8 +82,8 @@ zed_actions.workspace = true
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, "features" = ["test-support"] }
|
||||
language_model = { workspace = true, "features" = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
indoc.workspace = true
|
||||
|
||||
@@ -10,6 +10,7 @@ use gpui::{
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use scripting_tool::{ScriptingTool, ScriptingToolInput};
|
||||
use settings::Settings as _;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, Disclosure, KeyBinding};
|
||||
@@ -28,6 +29,7 @@ pub struct ActiveThread {
|
||||
messages: Vec<MessageId>,
|
||||
list_state: ListState,
|
||||
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
|
||||
rendered_scripting_tool_uses: HashMap<LanguageModelToolUseId, Entity<Markdown>>,
|
||||
editing_message: Option<(MessageId, EditMessageState)>,
|
||||
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
|
||||
last_error: Option<ThreadError>,
|
||||
@@ -58,6 +60,7 @@ impl ActiveThread {
|
||||
save_thread_task: None,
|
||||
messages: Vec::new(),
|
||||
rendered_messages_by_id: HashMap::default(),
|
||||
rendered_scripting_tool_uses: HashMap::default(),
|
||||
expanded_tool_uses: HashMap::default(),
|
||||
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
|
||||
let this = cx.entity().downgrade();
|
||||
@@ -73,6 +76,16 @@ impl ActiveThread {
|
||||
|
||||
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
|
||||
this.push_message(&message.id, message.text.clone(), window, cx);
|
||||
|
||||
for tool_use in thread.read(cx).scripting_tool_uses_for_message(message.id) {
|
||||
this.render_scripting_tool_use_markdown(
|
||||
tool_use.id.clone(),
|
||||
tool_use.name.as_ref(),
|
||||
tool_use.input.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
@@ -239,9 +252,35 @@ impl ActiveThread {
|
||||
})
|
||||
}
|
||||
|
||||
/// Renders the input of a scripting tool use to Markdown.
|
||||
///
|
||||
/// Does nothing if the tool use does not correspond to the scripting tool.
|
||||
fn render_scripting_tool_use_markdown(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: &str,
|
||||
tool_input: serde_json::Value,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if tool_name != ScriptingTool::NAME {
|
||||
return;
|
||||
}
|
||||
|
||||
let lua_script = serde_json::from_value::<ScriptingToolInput>(tool_input)
|
||||
.map(|input| input.lua_script)
|
||||
.unwrap_or_default();
|
||||
|
||||
let lua_script =
|
||||
self.render_markdown(format!("```lua\n{lua_script}\n```").into(), window, cx);
|
||||
|
||||
self.rendered_scripting_tool_uses
|
||||
.insert(tool_use_id, lua_script);
|
||||
}
|
||||
|
||||
fn handle_thread_event(
|
||||
&mut self,
|
||||
_: &Entity<Thread>,
|
||||
_thread: &Entity<Thread>,
|
||||
event: &ThreadEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -296,7 +335,19 @@ impl ActiveThread {
|
||||
thread.use_pending_tools(cx);
|
||||
});
|
||||
}
|
||||
ThreadEvent::ToolFinished { .. } => {
|
||||
ThreadEvent::ToolFinished {
|
||||
pending_tool_use, ..
|
||||
} => {
|
||||
if let Some(tool_use) = pending_tool_use {
|
||||
self.render_scripting_tool_use_markdown(
|
||||
tool_use.id.clone(),
|
||||
tool_use.name.as_ref(),
|
||||
tool_use.input.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
if self.thread.read(cx).all_tools_finished() {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
if let Some(model) = model_registry.active_model() {
|
||||
@@ -445,12 +496,17 @@ impl ActiveThread {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
let context = self.thread.read(cx).context_for_message(message_id);
|
||||
let tool_uses = self.thread.read(cx).tool_uses_for_message(message_id);
|
||||
let colors = cx.theme().colors();
|
||||
let thread = self.thread.read(cx);
|
||||
|
||||
let context = thread.context_for_message(message_id);
|
||||
let tool_uses = thread.tool_uses_for_message(message_id);
|
||||
let scripting_tool_uses = thread.scripting_tool_uses_for_message(message_id);
|
||||
|
||||
// Don't render user messages that are just there for returning tool results.
|
||||
if message.role == Role::User && self.thread.read(cx).message_has_tool_results(message_id) {
|
||||
if message.role == Role::User
|
||||
&& (thread.message_has_tool_results(message_id)
|
||||
|| thread.message_has_scripting_tool_results(message_id))
|
||||
{
|
||||
return Empty.into_any();
|
||||
}
|
||||
|
||||
@@ -463,6 +519,8 @@ impl ActiveThread {
|
||||
.filter(|(id, _)| *id == message_id)
|
||||
.map(|(_, state)| state.editor.clone());
|
||||
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
let message_content = v_flex()
|
||||
.child(
|
||||
if let Some(edit_message_editor) = edit_message_editor.clone() {
|
||||
@@ -598,16 +656,22 @@ impl ActiveThread {
|
||||
.id(("message-container", ix))
|
||||
.child(message_content)
|
||||
.map(|parent| {
|
||||
if tool_uses.is_empty() {
|
||||
if tool_uses.is_empty() && scripting_tool_uses.is_empty() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent.child(
|
||||
v_flex().children(
|
||||
tool_uses
|
||||
.into_iter()
|
||||
.map(|tool_use| self.render_tool_use(tool_use, cx)),
|
||||
),
|
||||
v_flex()
|
||||
.children(
|
||||
tool_uses
|
||||
.into_iter()
|
||||
.map(|tool_use| self.render_tool_use(tool_use, cx)),
|
||||
)
|
||||
.children(
|
||||
scripting_tool_uses
|
||||
.into_iter()
|
||||
.map(|tool_use| self.render_scripting_tool_use(tool_use, cx)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
|
||||
@@ -641,8 +705,13 @@ impl ActiveThread {
|
||||
.pl_1()
|
||||
.pr_2()
|
||||
.bg(cx.theme().colors().editor_foreground.opacity(0.02))
|
||||
.when(is_open, |element| element.border_b_1().rounded_t(px(6.)))
|
||||
.when(!is_open, |element| element.rounded_md())
|
||||
.map(|element| {
|
||||
if is_open {
|
||||
element.border_b_1().rounded_t(px(6.))
|
||||
} else {
|
||||
element.rounded_md()
|
||||
}
|
||||
})
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -716,6 +785,119 @@ impl ActiveThread {
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_scripting_tool_use(
|
||||
&self,
|
||||
tool_use: ToolUse,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let is_open = self
|
||||
.expanded_tool_uses
|
||||
.get(&tool_use.id)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
|
||||
div().px_2p5().child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.py_0p5()
|
||||
.pl_1()
|
||||
.pr_2()
|
||||
.bg(cx.theme().colors().editor_foreground.opacity(0.02))
|
||||
.map(|element| {
|
||||
if is_open {
|
||||
element.border_b_1().rounded_t(px(6.))
|
||||
} else {
|
||||
element.rounded_md()
|
||||
}
|
||||
})
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Disclosure::new("tool-use-disclosure", is_open).on_click(
|
||||
cx.listener({
|
||||
let tool_use_id = tool_use.id.clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
let is_open = this
|
||||
.expanded_tool_uses
|
||||
.entry(tool_use_id.clone())
|
||||
.or_insert(false);
|
||||
|
||||
*is_open = !*is_open;
|
||||
}
|
||||
}),
|
||||
))
|
||||
.child(Label::new(tool_use.name)),
|
||||
)
|
||||
.child(
|
||||
Label::new(match tool_use.status {
|
||||
ToolUseStatus::Pending => "Pending",
|
||||
ToolUseStatus::Running => "Running",
|
||||
ToolUseStatus::Finished(_) => "Finished",
|
||||
ToolUseStatus::Error(_) => "Error",
|
||||
})
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx),
|
||||
),
|
||||
)
|
||||
.map(|parent| {
|
||||
if !is_open {
|
||||
return parent;
|
||||
}
|
||||
|
||||
let lua_script_markdown =
|
||||
self.rendered_scripting_tool_uses.get(&tool_use.id).cloned();
|
||||
|
||||
parent.child(
|
||||
v_flex()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Label::new("Input:"))
|
||||
.map(|parent| {
|
||||
if let Some(markdown) = lua_script_markdown {
|
||||
parent.child(markdown)
|
||||
} else {
|
||||
parent.child(Label::new(
|
||||
"Failed to render script input to Markdown",
|
||||
))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.map(|parent| match tool_use.status {
|
||||
ToolUseStatus::Finished(output) => parent.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(Label::new("Result:"))
|
||||
.child(Label::new(output)),
|
||||
),
|
||||
ToolUseStatus::Error(err) => parent.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(Label::new("Error:"))
|
||||
.child(Label::new(err)),
|
||||
),
|
||||
ToolUseStatus::Pending | ToolUseStatus::Running => parent,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ActiveThread {
|
||||
|
||||
@@ -16,6 +16,7 @@ mod terminal_inline_assistant;
|
||||
mod thread;
|
||||
mod thread_history;
|
||||
mod thread_store;
|
||||
mod tool_selector;
|
||||
mod tool_use;
|
||||
mod ui;
|
||||
|
||||
|
||||
@@ -166,22 +166,24 @@ impl AssistantPanel {
|
||||
let history_store =
|
||||
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
|
||||
|
||||
let thread = cx.new(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
thread_store.clone(),
|
||||
language_registry.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
Self {
|
||||
active_view: ActiveView::Thread,
|
||||
workspace,
|
||||
project: project.clone(),
|
||||
fs: fs.clone(),
|
||||
language_registry: language_registry.clone(),
|
||||
language_registry,
|
||||
thread_store: thread_store.clone(),
|
||||
thread: cx.new(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
thread_store.clone(),
|
||||
language_registry,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
thread,
|
||||
message_editor,
|
||||
context_store,
|
||||
context_editor: None,
|
||||
|
||||
@@ -167,8 +167,8 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
|
||||
"Enter the URL that you would like to fetch".into()
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
|
||||
Some("Enter the URL that you would like to fetch".into())
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
|
||||
pub struct ContextStrip {
|
||||
context_store: Entity<ContextStore>,
|
||||
pub context_picker: Entity<ContextPicker>,
|
||||
context_picker: Entity<ContextPicker>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
focus_handle: FocusHandle,
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
@@ -36,7 +36,6 @@ pub struct ContextStrip {
|
||||
}
|
||||
|
||||
impl ContextStrip {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
context_store: Entity<ContextStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
|
||||
@@ -480,7 +480,6 @@ impl InlineAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn suggest_assist(
|
||||
&mut self,
|
||||
editor: &Entity<Editor>,
|
||||
@@ -1451,7 +1450,6 @@ struct InlineAssistScrollLock {
|
||||
}
|
||||
|
||||
impl EditorInlineAssists {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) -> Self {
|
||||
let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
|
||||
Self {
|
||||
@@ -1563,7 +1561,6 @@ pub struct InlineAssist {
|
||||
}
|
||||
|
||||
impl InlineAssist {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
assist_id: InlineAssistId,
|
||||
group_id: InlineAssistGroupId,
|
||||
|
||||
@@ -816,7 +816,6 @@ impl InlineAssistId {
|
||||
}
|
||||
|
||||
impl PromptEditor<BufferCodegen> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_buffer(
|
||||
id: InlineAssistId,
|
||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||
@@ -976,7 +975,6 @@ impl TerminalInlineAssistId {
|
||||
}
|
||||
|
||||
impl PromptEditor<TerminalCodegen> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_terminal(
|
||||
id: TerminalInlineAssistId,
|
||||
prompt_history: VecDeque<String>,
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use editor::actions::MoveUp;
|
||||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
|
||||
use file_icons::FileIcons;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription, TextStyle,
|
||||
@@ -15,8 +16,8 @@ use std::time::Duration;
|
||||
use text::Bias;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Switch,
|
||||
Tooltip,
|
||||
prelude::*, ButtonLike, Disclosure, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle,
|
||||
Switch, Tooltip,
|
||||
};
|
||||
use vim_mode_setting::VimModeSetting;
|
||||
use workspace::Workspace;
|
||||
@@ -27,6 +28,7 @@ use crate::context_store::{refresh_context_store_text, ContextStore};
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::tool_selector::ToolSelector;
|
||||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker};
|
||||
|
||||
pub struct MessageEditor {
|
||||
@@ -38,7 +40,9 @@ pub struct MessageEditor {
|
||||
inline_context_picker: Entity<ContextPicker>,
|
||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: Entity<AssistantModelSelector>,
|
||||
tool_selector: Entity<ToolSelector>,
|
||||
use_tools: bool,
|
||||
edits_expanded: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -51,6 +55,7 @@ impl MessageEditor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let tools = thread.read(cx).tools().clone();
|
||||
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
@@ -116,7 +121,9 @@ impl MessageEditor {
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
tool_selector: cx.new(|cx| ToolSelector::new(tools, cx)),
|
||||
use_tools: false,
|
||||
edits_expanded: false,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
@@ -303,6 +310,9 @@ impl Render for MessageEditor {
|
||||
px(64.)
|
||||
};
|
||||
|
||||
let changed_buffers = self.thread.read(cx).scripting_changed_buffers(cx);
|
||||
let changed_buffers_count = changed_buffers.len();
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.when(is_streaming_completion, |parent| {
|
||||
@@ -363,6 +373,109 @@ impl Render for MessageEditor {
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(changed_buffers_count > 0, |parent| {
|
||||
parent.child(
|
||||
v_flex()
|
||||
.mx_2()
|
||||
.bg(cx.theme().colors().element_background)
|
||||
.border_1()
|
||||
.border_b_0()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_t_md()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.p_2()
|
||||
.child(
|
||||
Disclosure::new("edits-disclosure", self.edits_expanded)
|
||||
.on_click(cx.listener(|this, _ev, _window, cx| {
|
||||
this.edits_expanded = !this.edits_expanded;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Label::new("Edits")
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Label::new("•").size(LabelSize::XSmall).color(Color::Muted))
|
||||
.child(
|
||||
Label::new(format!(
|
||||
"{} {}",
|
||||
changed_buffers_count,
|
||||
if changed_buffers_count == 1 {
|
||||
"file"
|
||||
} else {
|
||||
"files"
|
||||
}
|
||||
))
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.when(self.edits_expanded, |parent| {
|
||||
parent.child(
|
||||
v_flex().bg(cx.theme().colors().editor_background).children(
|
||||
changed_buffers.enumerate().flat_map(|(index, buffer)| {
|
||||
let file = buffer.read(cx).file()?;
|
||||
let path = file.path();
|
||||
|
||||
let parent_label = path.parent().and_then(|parent| {
|
||||
let parent_str = parent.to_string_lossy();
|
||||
|
||||
if parent_str.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Label::new(format!(
|
||||
"{}{}",
|
||||
parent_str,
|
||||
std::path::MAIN_SEPARATOR_STR
|
||||
))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let name_label = path.file_name().map(|name| {
|
||||
Label::new(name.to_string_lossy().to_string())
|
||||
.size(LabelSize::Small)
|
||||
});
|
||||
|
||||
let file_icon = FileIcons::get_icon(&path, cx)
|
||||
.map(Icon::from_path)
|
||||
.unwrap_or_else(|| Icon::new(IconName::File));
|
||||
|
||||
let element = div()
|
||||
.p_2()
|
||||
.when(index + 1 < changed_buffers_count, |parent| {
|
||||
parent
|
||||
.border_color(cx.theme().colors().border)
|
||||
.border_b_1()
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(file_icon)
|
||||
.child(
|
||||
// TODO: handle overflow
|
||||
h_flex()
|
||||
.children(parent_label)
|
||||
.children(name_label),
|
||||
)
|
||||
// TODO: show lines changed
|
||||
.child(Label::new("+").color(Color::Created))
|
||||
.child(Label::new("-").color(Color::Deleted)),
|
||||
);
|
||||
|
||||
Some(element)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.key_context("MessageEditor")
|
||||
@@ -429,23 +542,25 @@ impl Render for MessageEditor {
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(
|
||||
Switch::new("use-tools", self.use_tools.into())
|
||||
.label("Tools")
|
||||
.on_click(cx.listener(
|
||||
|this, selection, _window, _cx| {
|
||||
this.use_tools = match selection {
|
||||
ToggleState::Selected => true,
|
||||
ToggleState::Unselected
|
||||
| ToggleState::Indeterminate => false,
|
||||
};
|
||||
},
|
||||
))
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&ChatMode,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
h_flex().gap_2().child(self.tool_selector.clone()).child(
|
||||
Switch::new("use-tools", self.use_tools.into())
|
||||
.label("Tools")
|
||||
.on_click(cx.listener(
|
||||
|this, selection, _window, _cx| {
|
||||
this.use_tools = match selection {
|
||||
ToggleState::Selected => true,
|
||||
ToggleState::Unselected
|
||||
| ToggleState::Indeterminate => false,
|
||||
};
|
||||
},
|
||||
))
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&ChatMode,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||
|
||||
@@ -5,7 +5,7 @@ use assistant_tool::ToolWorkingSet;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{App, Context, Entity, EventEmitter, SharedString, Task};
|
||||
use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
|
||||
@@ -13,6 +13,7 @@ use language_model::{
|
||||
Role, StopReason,
|
||||
};
|
||||
use project::Project;
|
||||
use scripting_tool::{ScriptingSession, ScriptingTool};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
use uuid::Uuid;
|
||||
@@ -75,14 +76,18 @@ pub struct Thread {
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
tool_use: ToolUseState,
|
||||
scripting_session: Entity<ScriptingSession>,
|
||||
scripting_tool_use: ToolUseState,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
pub fn new(
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let scripting_session = cx.new(|cx| ScriptingSession::new(project.clone(), cx));
|
||||
|
||||
Self {
|
||||
id: ThreadId::new(),
|
||||
updated_at: Utc::now(),
|
||||
@@ -97,6 +102,8 @@ impl Thread {
|
||||
project,
|
||||
tools,
|
||||
tool_use: ToolUseState::new(),
|
||||
scripting_session,
|
||||
scripting_tool_use: ToolUseState::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +112,7 @@ impl Thread {
|
||||
saved: SavedThread,
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let next_message_id = MessageId(
|
||||
saved
|
||||
@@ -114,7 +121,11 @@ impl Thread {
|
||||
.map(|message| message.id.0 + 1)
|
||||
.unwrap_or(0),
|
||||
);
|
||||
let tool_use = ToolUseState::from_saved_messages(&saved.messages);
|
||||
let tool_use =
|
||||
ToolUseState::from_saved_messages(&saved.messages, |name| name != ScriptingTool::NAME);
|
||||
let scripting_tool_use =
|
||||
ToolUseState::from_saved_messages(&saved.messages, |name| name == ScriptingTool::NAME);
|
||||
let scripting_session = cx.new(|cx| ScriptingSession::new(project.clone(), cx));
|
||||
|
||||
Self {
|
||||
id,
|
||||
@@ -138,6 +149,8 @@ impl Thread {
|
||||
project,
|
||||
tools,
|
||||
tool_use,
|
||||
scripting_session,
|
||||
scripting_tool_use,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,42 +211,65 @@ impl Thread {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||
self.tool_use.pending_tool_uses()
|
||||
}
|
||||
|
||||
/// Returns whether all of the tool uses have finished running.
|
||||
pub fn all_tools_finished(&self) -> bool {
|
||||
let mut all_pending_tool_uses = self
|
||||
.tool_use
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.chain(self.scripting_tool_use.pending_tool_uses());
|
||||
|
||||
// If the only pending tool uses left are the ones with errors, then that means that we've finished running all
|
||||
// of the pending tools.
|
||||
self.pending_tool_uses()
|
||||
.into_iter()
|
||||
.all(|tool_use| tool_use.status.is_error())
|
||||
all_pending_tool_uses.all(|tool_use| tool_use.status.is_error())
|
||||
}
|
||||
|
||||
pub fn tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
|
||||
self.tool_use.tool_uses_for_message(id)
|
||||
}
|
||||
|
||||
pub fn scripting_tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
|
||||
self.scripting_tool_use.tool_uses_for_message(id)
|
||||
}
|
||||
|
||||
pub fn tool_results_for_message(&self, id: MessageId) -> Vec<&LanguageModelToolResult> {
|
||||
self.tool_use.tool_results_for_message(id)
|
||||
}
|
||||
|
||||
pub fn scripting_tool_results_for_message(
|
||||
&self,
|
||||
id: MessageId,
|
||||
) -> Vec<&LanguageModelToolResult> {
|
||||
self.scripting_tool_use.tool_results_for_message(id)
|
||||
}
|
||||
|
||||
pub fn scripting_changed_buffers<'a>(
|
||||
&self,
|
||||
cx: &'a App,
|
||||
) -> impl ExactSizeIterator<Item = &'a Entity<language::Buffer>> {
|
||||
self.scripting_session.read(cx).changed_buffers()
|
||||
}
|
||||
|
||||
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
|
||||
self.tool_use.message_has_tool_results(message_id)
|
||||
}
|
||||
|
||||
pub fn message_has_scripting_tool_results(&self, message_id: MessageId) -> bool {
|
||||
self.scripting_tool_use.message_has_tool_results(message_id)
|
||||
}
|
||||
|
||||
pub fn insert_user_message(
|
||||
&mut self,
|
||||
text: impl Into<String>,
|
||||
context: Vec<ContextSnapshot>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
) -> MessageId {
|
||||
let message_id = self.insert_message(Role::User, text, cx);
|
||||
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
|
||||
self.context
|
||||
.extend(context.into_iter().map(|context| (context.id, context)));
|
||||
self.context_by_message.insert(message_id, context_ids);
|
||||
message_id
|
||||
}
|
||||
|
||||
pub fn insert_message(
|
||||
@@ -312,16 +348,22 @@ impl Thread {
|
||||
let mut request = self.to_completion_request(request_kind, cx);
|
||||
|
||||
if use_tools {
|
||||
request.tools = self
|
||||
.tools()
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.map(|tool| LanguageModelRequestTool {
|
||||
let mut tools = Vec::new();
|
||||
tools.push(LanguageModelRequestTool {
|
||||
name: ScriptingTool::NAME.into(),
|
||||
description: ScriptingTool::DESCRIPTION.into(),
|
||||
input_schema: ScriptingTool::input_schema(),
|
||||
});
|
||||
|
||||
tools.extend(self.tools().enabled_tools(cx).into_iter().map(|tool| {
|
||||
LanguageModelRequestTool {
|
||||
name: tool.name(),
|
||||
description: tool.description(),
|
||||
input_schema: tool.input_schema(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}));
|
||||
|
||||
request.tools = tools;
|
||||
}
|
||||
|
||||
self.stream_completion(request, model, cx);
|
||||
@@ -351,10 +393,13 @@ impl Thread {
|
||||
content: Vec::new(),
|
||||
cache: false,
|
||||
};
|
||||
|
||||
match request_kind {
|
||||
RequestKind::Chat => {
|
||||
self.tool_use
|
||||
.attach_tool_results(message.id, &mut request_message);
|
||||
self.scripting_tool_use
|
||||
.attach_tool_results(message.id, &mut request_message);
|
||||
}
|
||||
RequestKind::Summarize => {
|
||||
// We don't care about tool use during summarization.
|
||||
@@ -371,11 +416,13 @@ impl Thread {
|
||||
RequestKind::Chat => {
|
||||
self.tool_use
|
||||
.attach_tool_uses(message.id, &mut request_message);
|
||||
self.scripting_tool_use
|
||||
.attach_tool_uses(message.id, &mut request_message);
|
||||
}
|
||||
RequestKind::Summarize => {
|
||||
// We don't care about tool use during summarization.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
request.messages.push(request_message);
|
||||
}
|
||||
@@ -439,7 +486,7 @@ impl Thread {
|
||||
// Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
|
||||
// will result in duplicating the text of the chunk in the rendered Markdown.
|
||||
thread.insert_message(Role::Assistant, chunk, cx);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
LanguageModelCompletionEvent::ToolUse(tool_use) => {
|
||||
@@ -448,9 +495,15 @@ impl Thread {
|
||||
.iter()
|
||||
.rfind(|message| message.role == Role::Assistant)
|
||||
{
|
||||
thread
|
||||
.tool_use
|
||||
.request_tool_use(last_assistant_message.id, tool_use);
|
||||
if tool_use.name.as_ref() == ScriptingTool::NAME {
|
||||
thread
|
||||
.scripting_tool_use
|
||||
.request_tool_use(last_assistant_message.id, tool_use);
|
||||
} else {
|
||||
thread
|
||||
.tool_use
|
||||
.request_tool_use(last_assistant_message.id, tool_use);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -570,6 +623,7 @@ impl Thread {
|
||||
|
||||
pub fn use_pending_tools(&mut self, cx: &mut Context<Self>) {
|
||||
let pending_tool_uses = self
|
||||
.tool_use
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.filter(|tool_use| tool_use.status.is_idle())
|
||||
@@ -583,6 +637,45 @@ impl Thread {
|
||||
self.insert_tool_output(tool_use.id.clone(), task, cx);
|
||||
}
|
||||
}
|
||||
|
||||
let pending_scripting_tool_uses = self
|
||||
.scripting_tool_use
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.filter(|tool_use| tool_use.status.is_idle())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for scripting_tool_use in pending_scripting_tool_uses {
|
||||
let task = match ScriptingTool::deserialize_input(scripting_tool_use.input) {
|
||||
Err(err) => Task::ready(Err(err.into())),
|
||||
Ok(input) => {
|
||||
let (script_id, script_task) =
|
||||
self.scripting_session.update(cx, move |session, cx| {
|
||||
session.run_script(input.lua_script, cx)
|
||||
});
|
||||
|
||||
let session = self.scripting_session.clone();
|
||||
cx.spawn(|_, cx| async move {
|
||||
script_task.await;
|
||||
|
||||
let message = session.read_with(&cx, |session, _cx| {
|
||||
// Using a id to get the script output seems impractical.
|
||||
// Why not just include it in the Task result?
|
||||
// This is because we'll later report the script state as it runs,
|
||||
session
|
||||
.get(script_id)
|
||||
.output_message_for_llm()
|
||||
.expect("Script shouldn't still be running")
|
||||
})?;
|
||||
|
||||
Ok(message)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
self.insert_scripting_tool_output(scripting_tool_use.id.clone(), task, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_tool_output(
|
||||
@@ -597,11 +690,14 @@ impl Thread {
|
||||
let output = output.await;
|
||||
thread
|
||||
.update(&mut cx, |thread, cx| {
|
||||
thread
|
||||
let pending_tool_use = thread
|
||||
.tool_use
|
||||
.insert_tool_output(tool_use_id.clone(), output);
|
||||
|
||||
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
|
||||
cx.emit(ThreadEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
pending_tool_use,
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -611,6 +707,35 @@ impl Thread {
|
||||
.run_pending_tool(tool_use_id, insert_output_task);
|
||||
}
|
||||
|
||||
pub fn insert_scripting_tool_output(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let insert_output_task = cx.spawn(|thread, mut cx| {
|
||||
let tool_use_id = tool_use_id.clone();
|
||||
async move {
|
||||
let output = output.await;
|
||||
thread
|
||||
.update(&mut cx, |thread, cx| {
|
||||
let pending_tool_use = thread
|
||||
.scripting_tool_use
|
||||
.insert_tool_output(tool_use_id.clone(), output);
|
||||
|
||||
cx.emit(ThreadEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
pending_tool_use,
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
self.scripting_tool_use
|
||||
.run_pending_tool(tool_use_id, insert_output_task);
|
||||
}
|
||||
|
||||
pub fn send_tool_results_to_model(
|
||||
&mut self,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
@@ -660,6 +785,8 @@ pub enum ThreadEvent {
|
||||
ToolFinished {
|
||||
#[allow(unused)]
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
/// The pending tool use that corresponds to this tool.
|
||||
pending_tool_use: Option<PendingToolUse>,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -116,28 +116,35 @@ impl ThreadStore {
|
||||
updated_at: thread.updated_at(),
|
||||
messages: thread
|
||||
.messages()
|
||||
.map(|message| SavedMessage {
|
||||
id: message.id,
|
||||
role: message.role,
|
||||
text: message.text.clone(),
|
||||
tool_uses: thread
|
||||
.map(|message| {
|
||||
let all_tool_uses = thread
|
||||
.tool_uses_for_message(message.id)
|
||||
.into_iter()
|
||||
.chain(thread.scripting_tool_uses_for_message(message.id))
|
||||
.map(|tool_use| SavedToolUse {
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
})
|
||||
.collect(),
|
||||
tool_results: thread
|
||||
.collect();
|
||||
let all_tool_results = thread
|
||||
.tool_results_for_message(message.id)
|
||||
.into_iter()
|
||||
.chain(thread.scripting_tool_results_for_message(message.id))
|
||||
.map(|tool_result| SavedToolResult {
|
||||
tool_use_id: tool_result.tool_use_id.clone(),
|
||||
is_error: tool_result.is_error,
|
||||
content: tool_result.content.clone(),
|
||||
})
|
||||
.collect(),
|
||||
.collect();
|
||||
|
||||
SavedMessage {
|
||||
id: message.id,
|
||||
role: message.role,
|
||||
text: message.text.clone(),
|
||||
tool_uses: all_tool_uses,
|
||||
tool_results: all_tool_results,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
70
crates/assistant2/src/tool_selector.rs
Normal file
70
crates/assistant2/src/tool_selector.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
use gpui::Entity;
|
||||
use ui::{prelude::*, ContextMenu, IconButtonShape, PopoverMenu, Tooltip};
|
||||
|
||||
pub struct ToolSelector {
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
}
|
||||
|
||||
impl ToolSelector {
|
||||
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut Context<Self>) -> Self {
|
||||
Self { tools }
|
||||
}
|
||||
|
||||
fn build_context_menu(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<ContextMenu> {
|
||||
ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
||||
let tools_by_source = self.tools.tools_by_source(cx);
|
||||
|
||||
for (source, tools) in tools_by_source {
|
||||
menu = match source {
|
||||
ToolSource::Native => menu.header("Zed"),
|
||||
ToolSource::ContextServer { id } => menu.separator().header(id),
|
||||
};
|
||||
|
||||
for tool in tools {
|
||||
let source = tool.source();
|
||||
let name = tool.name().into();
|
||||
let is_enabled = self.tools.is_enabled(&source, &name);
|
||||
|
||||
menu =
|
||||
menu.toggleable_entry(tool.name(), is_enabled, IconPosition::End, None, {
|
||||
let tools = self.tools.clone();
|
||||
move |_window, _cx| {
|
||||
if is_enabled {
|
||||
tools.disable(source.clone(), &[name.clone()]);
|
||||
} else {
|
||||
tools.enable(source.clone(), &[name.clone()]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
menu
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ToolSelector {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
let this = cx.entity().clone();
|
||||
PopoverMenu::new("tool-selector")
|
||||
.menu(move |window, cx| {
|
||||
Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
|
||||
})
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("tool-selector-button", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
Tooltip::text("Customize Tools"),
|
||||
)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
}
|
||||
}
|
||||
@@ -46,25 +46,39 @@ impl ToolUseState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_saved_messages(messages: &[SavedMessage]) -> Self {
|
||||
/// Constructs a [`ToolUseState`] from the given list of [`SavedMessage`]s.
|
||||
///
|
||||
/// Accepts a function to filter the tools that should be used to populate the state.
|
||||
pub fn from_saved_messages(
|
||||
messages: &[SavedMessage],
|
||||
mut filter_by_tool_name: impl FnMut(&str) -> bool,
|
||||
) -> Self {
|
||||
let mut this = Self::new();
|
||||
let mut tool_names_by_id = HashMap::default();
|
||||
|
||||
for message in messages {
|
||||
match message.role {
|
||||
Role::Assistant => {
|
||||
if !message.tool_uses.is_empty() {
|
||||
this.tool_uses_by_assistant_message.insert(
|
||||
message.id,
|
||||
message
|
||||
.tool_uses
|
||||
let tool_uses = message
|
||||
.tool_uses
|
||||
.iter()
|
||||
.filter(|tool_use| (filter_by_tool_name)(tool_use.name.as_ref()))
|
||||
.map(|tool_use| LanguageModelToolUse {
|
||||
id: tool_use.id.clone(),
|
||||
name: tool_use.name.clone().into(),
|
||||
input: tool_use.input.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tool_names_by_id.extend(
|
||||
tool_uses
|
||||
.iter()
|
||||
.map(|tool_use| LanguageModelToolUse {
|
||||
id: tool_use.id.clone(),
|
||||
name: tool_use.name.clone().into(),
|
||||
input: tool_use.input.clone(),
|
||||
})
|
||||
.collect(),
|
||||
.map(|tool_use| (tool_use.id.clone(), tool_use.name.clone())),
|
||||
);
|
||||
|
||||
this.tool_uses_by_assistant_message
|
||||
.insert(message.id, tool_uses);
|
||||
}
|
||||
}
|
||||
Role::User => {
|
||||
@@ -76,6 +90,14 @@ impl ToolUseState {
|
||||
|
||||
for tool_result in &message.tool_results {
|
||||
let tool_use_id = tool_result.tool_use_id.clone();
|
||||
let Some(tool_use) = tool_names_by_id.get(&tool_use_id) else {
|
||||
log::warn!("no tool name found for tool use: {tool_use_id:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
if !(filter_by_tool_name)(tool_use.as_ref()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tool_uses_by_user_message.push(tool_use_id.clone());
|
||||
this.tool_results.insert(
|
||||
@@ -202,7 +224,7 @@ impl ToolUseState {
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Result<String>,
|
||||
) {
|
||||
) -> Option<PendingToolUse> {
|
||||
match output {
|
||||
Ok(output) => {
|
||||
self.tool_results.insert(
|
||||
@@ -213,7 +235,7 @@ impl ToolUseState {
|
||||
is_error: false,
|
||||
},
|
||||
);
|
||||
self.pending_tool_uses_by_id.remove(&tool_use_id);
|
||||
self.pending_tool_uses_by_id.remove(&tool_use_id)
|
||||
}
|
||||
Err(err) => {
|
||||
self.tool_results.insert(
|
||||
@@ -228,6 +250,8 @@ impl ToolUseState {
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Error(err.to_string().into());
|
||||
}
|
||||
|
||||
self.pending_tool_uses_by_id.get(&tool_use_id).cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,6 +291,7 @@ impl ToolUseState {
|
||||
pub struct PendingToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
/// The ID of the Assistant message in which the tool use was requested.
|
||||
#[allow(unused)]
|
||||
pub assistant_message_id: MessageId,
|
||||
pub name: Arc<str>,
|
||||
pub input: serde_json::Value,
|
||||
|
||||
@@ -647,7 +647,6 @@ impl AssistantContext {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
id: ContextId,
|
||||
replica_id: ReplicaId,
|
||||
@@ -768,7 +767,6 @@ impl AssistantContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn deserialize(
|
||||
saved_context: SavedContext,
|
||||
path: PathBuf,
|
||||
|
||||
@@ -535,7 +535,6 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_command(
|
||||
&mut self,
|
||||
command_range: Range<language::Anchor>,
|
||||
@@ -2057,7 +2056,6 @@ impl ContextEditor {
|
||||
.unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_patch_block(
|
||||
&mut self,
|
||||
range: Range<text::Anchor>,
|
||||
|
||||
@@ -134,7 +134,6 @@ impl SlashCommandCompletionProvider {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn complete_command_argument(
|
||||
&self,
|
||||
command_name: &str,
|
||||
|
||||
@@ -88,7 +88,6 @@ pub trait SlashCommand: 'static + Send + Sync {
|
||||
fn accepts_arguments(&self) -> bool {
|
||||
self.requires_argument()
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
|
||||
@@ -4,7 +4,7 @@ mod tool_working_set;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use project::Project;
|
||||
|
||||
pub use crate::tool_registry::*;
|
||||
@@ -14,6 +14,14 @@ pub fn init(cx: &mut App) {
|
||||
ToolRegistry::default_global(cx);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
||||
pub enum ToolSource {
|
||||
/// A native tool built-in to Zed.
|
||||
Native,
|
||||
/// A tool provided by a context server.
|
||||
ContextServer { id: SharedString },
|
||||
}
|
||||
|
||||
/// A tool that can be used by a language model.
|
||||
pub trait Tool: 'static + Send + Sync {
|
||||
/// Returns the name of the tool.
|
||||
@@ -22,6 +30,11 @@ pub trait Tool: 'static + Send + Sync {
|
||||
/// Returns the description of the tool.
|
||||
fn description(&self) -> String;
|
||||
|
||||
/// Returns the source of the tool.
|
||||
fn source(&self) -> ToolSource {
|
||||
ToolSource::Native
|
||||
}
|
||||
|
||||
/// Returns the JSON schema that describes the tool's input.
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
serde_json::Value::Object(serde_json::Map::default())
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collections::HashMap;
|
||||
use collections::{HashMap, HashSet, IndexMap};
|
||||
use gpui::App;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{Tool, ToolRegistry};
|
||||
use crate::{Tool, ToolRegistry, ToolSource};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct ToolId(usize);
|
||||
@@ -19,6 +19,7 @@ pub struct ToolWorkingSet {
|
||||
struct WorkingSetState {
|
||||
context_server_tools_by_id: HashMap<ToolId, Arc<dyn Tool>>,
|
||||
context_server_tools_by_name: HashMap<String, Arc<dyn Tool>>,
|
||||
disabled_tools_by_source: HashMap<ToolSource, HashSet<Arc<str>>>,
|
||||
next_tool_id: ToolId,
|
||||
}
|
||||
|
||||
@@ -45,22 +46,80 @@ impl ToolWorkingSet {
|
||||
tools
|
||||
}
|
||||
|
||||
pub fn insert(&self, command: Arc<dyn Tool>) -> ToolId {
|
||||
pub fn enabled_tools(&self, cx: &App) -> Vec<Arc<dyn Tool>> {
|
||||
let all_tools = self.tools(cx);
|
||||
|
||||
all_tools
|
||||
.into_iter()
|
||||
.filter(|tool| self.is_enabled(&tool.source(), &tool.name().into()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn tools_by_source(&self, cx: &App) -> IndexMap<ToolSource, Vec<Arc<dyn Tool>>> {
|
||||
let mut tools_by_source = IndexMap::default();
|
||||
|
||||
for tool in self.tools(cx) {
|
||||
tools_by_source
|
||||
.entry(tool.source())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(tool);
|
||||
}
|
||||
|
||||
for tools in tools_by_source.values_mut() {
|
||||
tools.sort_by_key(|tool| tool.name());
|
||||
}
|
||||
|
||||
tools_by_source.sort_unstable_keys();
|
||||
|
||||
tools_by_source
|
||||
}
|
||||
|
||||
pub fn insert(&self, tool: Arc<dyn Tool>) -> ToolId {
|
||||
let mut state = self.state.lock();
|
||||
let command_id = state.next_tool_id;
|
||||
let tool_id = state.next_tool_id;
|
||||
state.next_tool_id.0 += 1;
|
||||
state
|
||||
.context_server_tools_by_id
|
||||
.insert(command_id, command.clone());
|
||||
.insert(tool_id, tool.clone());
|
||||
state.tools_changed();
|
||||
command_id
|
||||
tool_id
|
||||
}
|
||||
|
||||
pub fn remove(&self, command_ids_to_remove: &[ToolId]) {
|
||||
pub fn is_enabled(&self, source: &ToolSource, name: &Arc<str>) -> bool {
|
||||
!self.is_disabled(source, name)
|
||||
}
|
||||
|
||||
pub fn is_disabled(&self, source: &ToolSource, name: &Arc<str>) -> bool {
|
||||
let state = self.state.lock();
|
||||
state
|
||||
.disabled_tools_by_source
|
||||
.get(source)
|
||||
.map_or(false, |disabled_tools| disabled_tools.contains(name))
|
||||
}
|
||||
|
||||
pub fn enable(&self, source: ToolSource, tools_to_enable: &[Arc<str>]) {
|
||||
let mut state = self.state.lock();
|
||||
state
|
||||
.disabled_tools_by_source
|
||||
.entry(source)
|
||||
.or_default()
|
||||
.retain(|name| !tools_to_enable.contains(name));
|
||||
}
|
||||
|
||||
pub fn disable(&self, source: ToolSource, tools_to_disable: &[Arc<str>]) {
|
||||
let mut state = self.state.lock();
|
||||
state
|
||||
.disabled_tools_by_source
|
||||
.entry(source)
|
||||
.or_default()
|
||||
.extend(tools_to_disable.into_iter().cloned());
|
||||
}
|
||||
|
||||
pub fn remove(&self, tool_ids_to_remove: &[ToolId]) {
|
||||
let mut state = self.state.lock();
|
||||
state
|
||||
.context_server_tools_by_id
|
||||
.retain(|id, _| !command_ids_to_remove.contains(id));
|
||||
.retain(|id, _| !tool_ids_to_remove.contains(id));
|
||||
state.tools_changed();
|
||||
}
|
||||
}
|
||||
@@ -71,7 +130,7 @@ impl WorkingSetState {
|
||||
self.context_server_tools_by_name.extend(
|
||||
self.context_server_tools_by_id
|
||||
.values()
|
||||
.map(|command| (command.name(), command.clone())),
|
||||
.map(|tool| (tool.name(), tool.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,7 +828,6 @@ impl BufferDiff {
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn update_diff(
|
||||
this: Entity<BufferDiff>,
|
||||
buffer: text::BufferSnapshot,
|
||||
@@ -838,8 +837,8 @@ impl BufferDiff {
|
||||
language: Option<Arc<Language>>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> anyhow::Result<Option<Range<Anchor>>> {
|
||||
let snapshot = if base_text_changed || language_changed {
|
||||
) -> anyhow::Result<BufferDiffSnapshot> {
|
||||
let inner = if base_text_changed || language_changed {
|
||||
cx.update(|cx| {
|
||||
Self::build(
|
||||
buffer.clone(),
|
||||
@@ -861,18 +860,45 @@ impl BufferDiff {
|
||||
})?
|
||||
.await
|
||||
};
|
||||
|
||||
this.update(cx, |this, _| this.set_state(snapshot, &buffer))
|
||||
Ok(BufferDiffSnapshot {
|
||||
inner,
|
||||
secondary_diff: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_diff_from(
|
||||
pub fn set_snapshot(
|
||||
&mut self,
|
||||
buffer: &text::BufferSnapshot,
|
||||
other: &Entity<Self>,
|
||||
new_snapshot: BufferDiffSnapshot,
|
||||
language_changed: bool,
|
||||
secondary_changed_range: Option<Range<Anchor>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Range<Anchor>> {
|
||||
let other = other.read(cx).inner.clone();
|
||||
self.set_state(other, buffer)
|
||||
let changed_range = self.set_state(new_snapshot.inner, buffer);
|
||||
if language_changed {
|
||||
cx.emit(BufferDiffEvent::LanguageChanged);
|
||||
}
|
||||
|
||||
let changed_range = match (secondary_changed_range, changed_range) {
|
||||
(None, None) => None,
|
||||
(Some(unstaged_range), None) => self.range_to_hunk_range(unstaged_range, &buffer, cx),
|
||||
(None, Some(uncommitted_range)) => Some(uncommitted_range),
|
||||
(Some(unstaged_range), Some(uncommitted_range)) => {
|
||||
let mut start = uncommitted_range.start;
|
||||
let mut end = uncommitted_range.end;
|
||||
if let Some(unstaged_range) = self.range_to_hunk_range(unstaged_range, &buffer, cx)
|
||||
{
|
||||
start = unstaged_range.start.min(&uncommitted_range.start, &buffer);
|
||||
end = unstaged_range.end.max(&uncommitted_range.end, &buffer);
|
||||
}
|
||||
Some(start..end)
|
||||
}
|
||||
};
|
||||
|
||||
cx.emit(BufferDiffEvent::DiffChanged {
|
||||
changed_range: changed_range.clone(),
|
||||
});
|
||||
changed_range
|
||||
}
|
||||
|
||||
fn set_state(
|
||||
|
||||
@@ -229,7 +229,6 @@ impl Database {
|
||||
}
|
||||
|
||||
/// Creates a new channel message.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn create_channel_message(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
|
||||
@@ -122,7 +122,6 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn get_or_create_user_by_github_account_tx(
|
||||
&self,
|
||||
github_login: &str,
|
||||
|
||||
@@ -289,7 +289,6 @@ impl LlmDatabase {
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn record_usage(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
@@ -554,7 +553,6 @@ impl LlmDatabase {
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn update_usage_for_measure(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
|
||||
@@ -33,7 +33,6 @@ pub struct LlmTokenClaims {
|
||||
const LLM_TOKEN_LIFETIME: Duration = Duration::from_secs(60 * 60);
|
||||
|
||||
impl LlmTokenClaims {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create(
|
||||
user: &user::Model,
|
||||
is_staff: bool,
|
||||
|
||||
@@ -697,7 +697,6 @@ impl Server {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn handle_connection(
|
||||
self: &Arc<Self>,
|
||||
connection: Connection,
|
||||
@@ -1081,7 +1080,6 @@ pub fn routes(server: Arc<Server>) -> Router<(), Body> {
|
||||
.layer(Extension(server))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn handle_websocket_request(
|
||||
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
|
||||
app_version_header: Option<TypedHeader<AppVersionHeader>>,
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::{
|
||||
tests::{rust_lang, TestServer},
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use collections::HashMap;
|
||||
use editor::{
|
||||
actions::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename,
|
||||
@@ -1983,7 +1982,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
blame_entry("3a3a3a", 2..3),
|
||||
blame_entry("4c4c4c", 3..4),
|
||||
],
|
||||
permalinks: HashMap::default(), // This field is deprecrated
|
||||
messages: [
|
||||
("1b1b1b", "message for idx-0"),
|
||||
("0d0d0d", "message for idx-1"),
|
||||
|
||||
@@ -6770,7 +6770,7 @@ async fn test_remote_git_branches(
|
||||
|
||||
assert_eq!(branches_b, branches_set);
|
||||
|
||||
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch.to_string()))
|
||||
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
@@ -6790,23 +6790,15 @@ async fn test_remote_git_branches(
|
||||
assert_eq!(host_branch.name, branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
repo_b
|
||||
.read(cx)
|
||||
.create_branch("totally-new-branch".to_string())
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.update(|cx| repo_b.read(cx).create_branch("totally-new-branch"))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
cx_b.update(|cx| {
|
||||
repo_b
|
||||
.read(cx)
|
||||
.change_branch("totally-new-branch".to_string())
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.update(|cx| repo_b.read(cx).change_branch("totally-new-branch"))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
|
||||
@@ -463,7 +463,6 @@ impl<T: RandomizedTest> TestPlan<T> {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn apply_server_operation(
|
||||
plan: Arc<Mutex<Self>>,
|
||||
deterministic: BackgroundExecutor,
|
||||
|
||||
@@ -294,7 +294,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
|
||||
assert_eq!(&branches_b, &branches_set);
|
||||
|
||||
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch.to_string()))
|
||||
cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
@@ -316,23 +316,15 @@ async fn test_ssh_collaboration_git_branches(
|
||||
assert_eq!(server_branch.name, branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
repo_b
|
||||
.read(cx)
|
||||
.create_branch("totally-new-branch".to_string())
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.update(|cx| repo_b.read(cx).create_branch("totally-new-branch"))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
cx_b.update(|cx| {
|
||||
repo_b
|
||||
.read(cx)
|
||||
.change_branch("totally-new-branch".to_string())
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.update(|cx| repo_b.read(cx).change_branch("totally-new-branch"))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
executor.run_until_parked();
|
||||
|
||||
|
||||
@@ -869,7 +869,6 @@ impl CollabPanel {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_participant_project(
|
||||
&self,
|
||||
project_id: u64,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use assistant_tool::Tool;
|
||||
use assistant_tool::{Tool, ToolSource};
|
||||
use gpui::{App, Entity, Task};
|
||||
use project::Project;
|
||||
|
||||
@@ -37,6 +37,12 @@ impl Tool for ContextServerTool {
|
||||
self.tool.description.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn source(&self) -> ToolSource {
|
||||
ToolSource::ContextServer {
|
||||
id: self.server_id.clone().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
match &self.tool.input_schema {
|
||||
serde_json::Value::Null => {
|
||||
|
||||
@@ -623,16 +623,21 @@ impl Copilot {
|
||||
|
||||
pub fn sign_out(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
|
||||
if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
|
||||
let server = server.clone();
|
||||
cx.background_spawn(async move {
|
||||
server
|
||||
.request::<request::SignOut>(request::SignOutParams {})
|
||||
.await?;
|
||||
match &self.server {
|
||||
CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) => {
|
||||
let server = server.clone();
|
||||
cx.background_spawn(async move {
|
||||
server
|
||||
.request::<request::SignOut>(request::SignOutParams {})
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
}
|
||||
CopilotServer::Disabled => cx.background_spawn(async move {
|
||||
clear_copilot_config_dir().await;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("copilot hasn't started yet")))
|
||||
}),
|
||||
_ => Task::ready(Err(anyhow!("copilot hasn't started yet"))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1016,6 +1021,10 @@ async fn clear_copilot_dir() {
|
||||
remove_matching(paths::copilot_dir(), |_| true).await
|
||||
}
|
||||
|
||||
async fn clear_copilot_config_dir() {
|
||||
remove_matching(copilot_chat::copilot_chat_config_dir(), |_| true).await
|
||||
}
|
||||
|
||||
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
const SERVER_PATH: &str = "dist/language-server.js";
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@ use std::sync::OnceLock;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::DateTime;
|
||||
use collections::HashSet;
|
||||
use fs::Fs;
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
|
||||
use gpui::{prelude::*, App, AsyncApp, Global};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use paths::home_dir;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::watch_config_file;
|
||||
use settings::watch_config_dir;
|
||||
use strum::EnumIter;
|
||||
|
||||
pub const COPILOT_CHAT_COMPLETION_URL: &str = "https://api.githubcopilot.com/chat/completions";
|
||||
@@ -212,7 +213,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut App) {
|
||||
cx.set_global(GlobalCopilotChat(copilot_chat));
|
||||
}
|
||||
|
||||
fn copilot_chat_config_dir() -> &'static PathBuf {
|
||||
pub fn copilot_chat_config_dir() -> &'static PathBuf {
|
||||
static COPILOT_CHAT_CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
COPILOT_CHAT_CONFIG_DIR.get_or_init(|| {
|
||||
@@ -237,27 +238,18 @@ impl CopilotChat {
|
||||
}
|
||||
|
||||
pub fn new(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &App) -> Self {
|
||||
let config_paths = copilot_chat_config_paths();
|
||||
|
||||
let resolve_config_path = {
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
for config_path in config_paths.iter() {
|
||||
if fs.metadata(config_path).await.is_ok_and(|v| v.is_some()) {
|
||||
return config_path.clone();
|
||||
}
|
||||
}
|
||||
config_paths[0].clone()
|
||||
}
|
||||
};
|
||||
let config_paths: HashSet<PathBuf> = copilot_chat_config_paths().into_iter().collect();
|
||||
let dir_path = copilot_chat_config_dir();
|
||||
|
||||
cx.spawn(|cx| async move {
|
||||
let config_file = resolve_config_path.await;
|
||||
let mut config_file_rx = watch_config_file(cx.background_executor(), fs, config_file);
|
||||
|
||||
while let Some(contents) = config_file_rx.next().await {
|
||||
let mut parent_watch_rx = watch_config_dir(
|
||||
cx.background_executor(),
|
||||
fs.clone(),
|
||||
dir_path.clone(),
|
||||
config_paths,
|
||||
);
|
||||
while let Some(contents) = parent_watch_rx.next().await {
|
||||
let oauth_token = extract_oauth_token(contents);
|
||||
|
||||
cx.update(|cx| {
|
||||
if let Some(this) = Self::global(cx).as_ref() {
|
||||
this.update(cx, |this, cx| {
|
||||
|
||||
@@ -311,7 +311,10 @@ impl ProjectDiagnosticsEditor {
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||
workspace.activate_item(&existing, true, true, window, cx);
|
||||
let is_active = workspace
|
||||
.active_item(cx)
|
||||
.is_some_and(|item| item.item_id() == existing.item_id());
|
||||
workspace.activate_item(&existing, true, !is_active, window, cx);
|
||||
} else {
|
||||
let workspace_handle = cx.entity().downgrade();
|
||||
|
||||
|
||||
@@ -500,7 +500,7 @@ impl CompletionsMenu {
|
||||
highlight.font_weight = None;
|
||||
if completion
|
||||
.source
|
||||
.lsp_completion()
|
||||
.lsp_completion(false)
|
||||
.and_then(|lsp_completion| lsp_completion.deprecated)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@@ -711,10 +711,12 @@ impl CompletionsMenu {
|
||||
|
||||
let completion = &completions[mat.candidate_id];
|
||||
let sort_key = completion.sort_key();
|
||||
let sort_text = completion
|
||||
.source
|
||||
.lsp_completion()
|
||||
.and_then(|lsp_completion| lsp_completion.sort_text.as_deref());
|
||||
let sort_text =
|
||||
if let CompletionSource::Lsp { lsp_completion, .. } = &completion.source {
|
||||
lsp_completion.sort_text.as_deref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let score = Reverse(OrderedFloat(mat.score));
|
||||
|
||||
if mat.score >= 0.2 {
|
||||
|
||||
@@ -113,7 +113,6 @@ pub struct DisplayMap {
|
||||
}
|
||||
|
||||
impl DisplayMap {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
buffer: Entity<MultiBuffer>,
|
||||
font: Font,
|
||||
|
||||
@@ -726,7 +726,6 @@ impl BlockMap {
|
||||
self.show_excerpt_controls
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn header_and_footer_blocks<'a, R, T>(
|
||||
show_excerpt_controls: bool,
|
||||
excerpt_footer_height: u32,
|
||||
|
||||
@@ -5931,7 +5931,6 @@ impl Editor {
|
||||
const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
|
||||
const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_popover(
|
||||
&mut self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
@@ -6043,7 +6042,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_modifier_jump_popover(
|
||||
&mut self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
@@ -6139,7 +6137,6 @@ impl Editor {
|
||||
Some((element, origin))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_scroll_popover(
|
||||
&mut self,
|
||||
to_y: impl Fn(Size<Pixels>) -> Pixels,
|
||||
@@ -6170,7 +6167,6 @@ impl Editor {
|
||||
Some((element, origin))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_eager_jump_popover(
|
||||
&mut self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
@@ -6240,7 +6236,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_end_of_line_popover(
|
||||
self: &mut Editor,
|
||||
label: &'static str,
|
||||
@@ -6299,7 +6294,6 @@ impl Editor {
|
||||
Some((element, origin))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_diff_popover(
|
||||
self: &Editor,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
@@ -6607,7 +6601,6 @@ impl Editor {
|
||||
editor_bg_color.blend(accent_color.opacity(0.6))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_cursor_popover(
|
||||
&self,
|
||||
min_width: Pixels,
|
||||
@@ -11639,7 +11632,7 @@ impl Editor {
|
||||
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(
|
||||
self.go_to_hunk_before_or_after_position(
|
||||
&snapshot,
|
||||
selection.head(),
|
||||
Direction::Next,
|
||||
@@ -11648,7 +11641,7 @@ impl Editor {
|
||||
);
|
||||
}
|
||||
|
||||
fn go_to_hunk_after_or_before_position(
|
||||
fn go_to_hunk_before_or_after_position(
|
||||
&mut self,
|
||||
snapshot: &EditorSnapshot,
|
||||
position: Point,
|
||||
@@ -11699,7 +11692,7 @@ impl Editor {
|
||||
) {
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
self.go_to_hunk_after_or_before_position(
|
||||
self.go_to_hunk_before_or_after_position(
|
||||
&snapshot,
|
||||
selection.head(),
|
||||
Direction::Prev,
|
||||
@@ -13861,21 +13854,6 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
if run_twice {
|
||||
self.go_to_next_hunk(&GoToHunk, window, cx);
|
||||
}
|
||||
self.stage_or_unstage_diff_hunks(stage, ranges, cx);
|
||||
self.go_to_next_hunk(&GoToHunk, window, cx);
|
||||
}
|
||||
@@ -14303,6 +14281,13 @@ impl Editor {
|
||||
EditorSettings::override_global(editor_settings, cx);
|
||||
}
|
||||
|
||||
pub fn line_numbers_enabled(&self, cx: &App) -> bool {
|
||||
if let Some(show_line_numbers) = self.show_line_numbers {
|
||||
return show_line_numbers;
|
||||
}
|
||||
EditorSettings::get_global(cx).gutter.line_numbers
|
||||
}
|
||||
|
||||
pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
|
||||
self.use_relative_line_numbers
|
||||
.unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
|
||||
@@ -17017,6 +17002,7 @@ fn snippet_completions(
|
||||
sort_text: Some(char::MAX.to_string()),
|
||||
..lsp::CompletionItem::default()
|
||||
}),
|
||||
lsp_defaults: None,
|
||||
},
|
||||
label: CodeLabel {
|
||||
text: matching_prefix.clone(),
|
||||
|
||||
@@ -12334,24 +12334,6 @@ async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext)
|
||||
},
|
||||
};
|
||||
|
||||
let item_0_out = lsp::CompletionItem {
|
||||
commit_characters: Some(default_commit_characters.clone()),
|
||||
insert_text_format: Some(default_insert_text_format),
|
||||
..item_0
|
||||
};
|
||||
let items_out = iter::once(item_0_out)
|
||||
.chain(items[1..].iter().map(|item| lsp::CompletionItem {
|
||||
commit_characters: Some(default_commit_characters.clone()),
|
||||
data: Some(default_data.clone()),
|
||||
insert_text_mode: Some(default_insert_text_mode),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
range: default_edit_range,
|
||||
new_text: item.label.clone(),
|
||||
})),
|
||||
..item.clone()
|
||||
}))
|
||||
.collect::<Vec<lsp::CompletionItem>>();
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
@@ -12370,10 +12352,11 @@ async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext)
|
||||
|
||||
let completion_data = default_data.clone();
|
||||
let completion_characters = default_commit_characters.clone();
|
||||
let completion_items = items.clone();
|
||||
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
|
||||
let default_data = completion_data.clone();
|
||||
let default_commit_characters = completion_characters.clone();
|
||||
let items = items.clone();
|
||||
let items = completion_items.clone();
|
||||
async move {
|
||||
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
||||
items,
|
||||
@@ -12422,7 +12405,7 @@ async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext)
|
||||
.iter()
|
||||
.map(|mat| mat.string.clone())
|
||||
.collect::<Vec<String>>(),
|
||||
items_out
|
||||
items
|
||||
.iter()
|
||||
.map(|completion| completion.label.clone())
|
||||
.collect::<Vec<String>>()
|
||||
@@ -12435,14 +12418,18 @@ async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext)
|
||||
// with 4 from the end.
|
||||
assert_eq!(
|
||||
*resolved_items.lock(),
|
||||
[
|
||||
&items_out[0..16],
|
||||
&items_out[items_out.len() - 4..items_out.len()]
|
||||
]
|
||||
.concat()
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<lsp::CompletionItem>>()
|
||||
[&items[0..16], &items[items.len() - 4..items.len()]]
|
||||
.concat()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut item| {
|
||||
if item.data.is_none() {
|
||||
item.data = Some(default_data.clone());
|
||||
}
|
||||
item
|
||||
})
|
||||
.collect::<Vec<lsp::CompletionItem>>(),
|
||||
"Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
|
||||
);
|
||||
resolved_items.lock().clear();
|
||||
|
||||
@@ -12453,9 +12440,15 @@ async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext)
|
||||
// Completions that have already been resolved are skipped.
|
||||
assert_eq!(
|
||||
*resolved_items.lock(),
|
||||
items_out[items_out.len() - 16..items_out.len() - 4]
|
||||
items[items.len() - 16..items.len() - 4]
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut item| {
|
||||
if item.data.is_none() {
|
||||
item.data = Some(default_data.clone());
|
||||
}
|
||||
item
|
||||
})
|
||||
.collect::<Vec<lsp::CompletionItem>>()
|
||||
);
|
||||
resolved_items.lock().clear();
|
||||
|
||||
@@ -958,7 +958,6 @@ impl EditorElement {
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_selections(
|
||||
&self,
|
||||
start_anchor: Anchor,
|
||||
@@ -1130,7 +1129,6 @@ impl EditorElement {
|
||||
cursors
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_visible_cursors(
|
||||
&self,
|
||||
snapshot: &EditorSnapshot,
|
||||
@@ -1484,7 +1482,6 @@ impl EditorElement {
|
||||
axis_pair(horizontal_scrollbar, vertical_scrollbar)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn prepaint_crease_toggles(
|
||||
&self,
|
||||
crease_toggles: &mut [Option<AnyElement>],
|
||||
@@ -1519,7 +1516,6 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn prepaint_crease_trailers(
|
||||
&self,
|
||||
trailers: Vec<Option<AnyElement>>,
|
||||
@@ -1596,7 +1592,6 @@ impl EditorElement {
|
||||
display_hunks
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_inline_diagnostics(
|
||||
&self,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
@@ -1747,7 +1742,6 @@ impl EditorElement {
|
||||
elements
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_inline_blame(
|
||||
&self,
|
||||
display_row: DisplayRow,
|
||||
@@ -1827,7 +1821,6 @@ impl EditorElement {
|
||||
Some(element)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_blame_entries(
|
||||
&self,
|
||||
buffer_rows: &[RowInfo],
|
||||
@@ -1896,7 +1889,6 @@ impl EditorElement {
|
||||
Some(shaped_lines)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_indent_guides(
|
||||
&self,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
@@ -2014,7 +2006,6 @@ impl EditorElement {
|
||||
(offset_y, length)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_run_indicators(
|
||||
&self,
|
||||
line_height: Pixels,
|
||||
@@ -2108,7 +2099,6 @@ impl EditorElement {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_code_actions_indicator(
|
||||
&self,
|
||||
line_height: Pixels,
|
||||
@@ -2207,7 +2197,6 @@ impl EditorElement {
|
||||
relative_rows
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_line_numbers(
|
||||
&self,
|
||||
gutter_hitbox: Option<&Hitbox>,
|
||||
@@ -2423,7 +2412,6 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn prepaint_lines(
|
||||
&self,
|
||||
start_row: DisplayRow,
|
||||
@@ -2450,7 +2438,6 @@ impl EditorElement {
|
||||
line_elements
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_block(
|
||||
&self,
|
||||
block: &Block,
|
||||
@@ -2950,7 +2937,6 @@ impl EditorElement {
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_blocks(
|
||||
&self,
|
||||
rows: Range<DisplayRow>,
|
||||
@@ -3135,7 +3121,6 @@ impl EditorElement {
|
||||
|
||||
/// Returns true if any of the blocks changed size since the previous frame. This will trigger
|
||||
/// a restart of rendering for the editor based on the new sizes.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_blocks(
|
||||
&self,
|
||||
blocks: &mut Vec<BlockLayout>,
|
||||
@@ -3179,7 +3164,6 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_sticky_buffer_header(
|
||||
&self,
|
||||
StickyHeaderExcerpt {
|
||||
@@ -3254,7 +3238,6 @@ impl EditorElement {
|
||||
header
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_cursor_popovers(
|
||||
&self,
|
||||
line_height: Pixels,
|
||||
@@ -3443,7 +3426,6 @@ impl EditorElement {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_gutter_menu(
|
||||
&self,
|
||||
line_height: Pixels,
|
||||
@@ -3496,7 +3478,6 @@ impl EditorElement {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_popovers_above_or_below_line(
|
||||
&self,
|
||||
target_position: gpui::Point<Pixels>,
|
||||
@@ -3610,7 +3591,6 @@ impl EditorElement {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_context_menu_aside(
|
||||
&self,
|
||||
y_flipped: bool,
|
||||
@@ -3806,7 +3786,6 @@ impl EditorElement {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_hover_popovers(
|
||||
&self,
|
||||
snapshot: &EditorSnapshot,
|
||||
@@ -3923,7 +3902,6 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_diff_hunk_controls(
|
||||
&self,
|
||||
row_range: Range<DisplayRow>,
|
||||
@@ -4008,7 +3986,6 @@ impl EditorElement {
|
||||
controls
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_signature_help(
|
||||
&self,
|
||||
hitbox: &Hitbox,
|
||||
@@ -4676,6 +4653,7 @@ impl EditorElement {
|
||||
};
|
||||
window.set_cursor_style(cursor_style, &layout.position_map.text_hitbox);
|
||||
|
||||
self.paint_lines_background(layout, window, cx);
|
||||
let invisible_display_ranges = self.paint_highlights(layout, window);
|
||||
self.paint_lines(&invisible_display_ranges, layout, window, cx);
|
||||
self.paint_redactions(layout, window);
|
||||
@@ -4766,6 +4744,18 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_lines_background(
|
||||
&mut self,
|
||||
layout: &mut EditorLayout,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
|
||||
let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
|
||||
line_with_invisibles.draw_background(layout, row, layout.content_origin, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_redactions(&mut self, layout: &EditorLayout, window: &mut Window) {
|
||||
if layout.redacted_ranges.is_empty() {
|
||||
return;
|
||||
@@ -5304,7 +5294,6 @@ impl EditorElement {
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn paint_highlighted_range(
|
||||
&self,
|
||||
range: Range<DisplayPoint>,
|
||||
@@ -5730,7 +5719,6 @@ impl AcceptEditPredictionBinding {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn prepaint_gutter_button(
|
||||
button: IconButton,
|
||||
row: DisplayRow,
|
||||
@@ -5981,7 +5969,6 @@ impl fmt::Debug for LineFragment {
|
||||
}
|
||||
|
||||
impl LineWithInvisibles {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn from_chunks<'a>(
|
||||
chunks: impl Iterator<Item = HighlightedChunk<'a>>,
|
||||
editor_style: &EditorStyle,
|
||||
@@ -6186,7 +6173,6 @@ impl LineWithInvisibles {
|
||||
layouts
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
line_height: Pixels,
|
||||
@@ -6221,7 +6207,6 @@ impl LineWithInvisibles {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw(
|
||||
&self,
|
||||
layout: &EditorLayout,
|
||||
@@ -6265,7 +6250,35 @@ impl LineWithInvisibles {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw_background(
|
||||
&self,
|
||||
layout: &EditorLayout,
|
||||
row: DisplayRow,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
let line_y = line_height
|
||||
* (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
|
||||
|
||||
let mut fragment_origin =
|
||||
content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
|
||||
|
||||
for fragment in &self.fragments {
|
||||
match fragment {
|
||||
LineFragment::Text(line) => {
|
||||
line.paint_background(fragment_origin, line_height, window, cx)
|
||||
.log_err();
|
||||
fragment_origin.x += line.width;
|
||||
}
|
||||
LineFragment::Element { size, .. } => {
|
||||
fragment_origin.x += size.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_invisibles(
|
||||
&self,
|
||||
selection_ranges: &[Range<DisplayPoint>],
|
||||
@@ -7659,7 +7672,6 @@ struct ScrollbarRangeData {
|
||||
}
|
||||
|
||||
impl ScrollbarRangeData {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
scrollbar_bounds: Bounds<Pixels>,
|
||||
letter_size: Size<Pixels>,
|
||||
@@ -9014,7 +9026,7 @@ fn diff_hunk_controls(
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let position =
|
||||
hunk_range.end.to_point(&snapshot.buffer_snapshot);
|
||||
editor.go_to_hunk_after_or_before_position(
|
||||
editor.go_to_hunk_before_or_after_position(
|
||||
&snapshot,
|
||||
position,
|
||||
Direction::Next,
|
||||
@@ -9050,7 +9062,7 @@ fn diff_hunk_controls(
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let point =
|
||||
hunk_range.start.to_point(&snapshot.buffer_snapshot);
|
||||
editor.go_to_hunk_after_or_before_position(
|
||||
editor.go_to_hunk_before_or_after_position(
|
||||
&snapshot,
|
||||
point,
|
||||
Direction::Prev,
|
||||
|
||||
@@ -370,7 +370,6 @@ impl GitBlame {
|
||||
async move {
|
||||
let Some(Blame {
|
||||
entries,
|
||||
permalinks,
|
||||
messages,
|
||||
remote_url,
|
||||
}) = blame.await?
|
||||
@@ -379,13 +378,8 @@ impl GitBlame {
|
||||
};
|
||||
|
||||
let entries = build_blame_entry_sum_tree(entries, snapshot.max_point().row);
|
||||
let commit_details = parse_commit_messages(
|
||||
messages,
|
||||
remote_url,
|
||||
&permalinks,
|
||||
provider_registry,
|
||||
)
|
||||
.await;
|
||||
let commit_details =
|
||||
parse_commit_messages(messages, remote_url, provider_registry).await;
|
||||
|
||||
anyhow::Ok(Some((entries, commit_details)))
|
||||
}
|
||||
@@ -477,7 +471,6 @@ fn build_blame_entry_sum_tree(entries: Vec<BlameEntry>, max_row: u32) -> SumTree
|
||||
async fn parse_commit_messages(
|
||||
messages: impl IntoIterator<Item = (Oid, String)>,
|
||||
remote_url: Option<String>,
|
||||
deprecated_permalinks: &HashMap<Oid, Url>,
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
) -> HashMap<Oid, ParsedCommitMessage> {
|
||||
let mut commit_details = HashMap::default();
|
||||
@@ -495,11 +488,7 @@ async fn parse_commit_messages(
|
||||
},
|
||||
))
|
||||
} else {
|
||||
// DEPRECATED (18 Apr 24): Sending permalinks over the wire is deprecated. Clients
|
||||
// now do the parsing. This is here for backwards compatibility, so that
|
||||
// when an old peer sends a client no `parsed_remote_url` but `deprecated_permalinks`,
|
||||
// we fall back to that.
|
||||
deprecated_permalinks.get(&oid).cloned()
|
||||
continue;
|
||||
};
|
||||
|
||||
let remote = parsed_remote_url
|
||||
|
||||
@@ -223,7 +223,6 @@ impl ScrollManager {
|
||||
self.anchor.scroll_position(snapshot)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn set_scroll_position(
|
||||
&mut self,
|
||||
scroll_position: gpui::Point<f32>,
|
||||
@@ -298,7 +297,6 @@ impl ScrollManager {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn set_anchor(
|
||||
&mut self,
|
||||
anchor: ScrollAnchor,
|
||||
|
||||
@@ -22,7 +22,6 @@ collections.workspace = true
|
||||
env_logger.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
language.workspace = true
|
||||
|
||||
@@ -5,7 +5,6 @@ use client::{Client, UserStore};
|
||||
use clock::RealSystemClock;
|
||||
use collections::BTreeMap;
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use git::GitHostingProviderRegistry;
|
||||
use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, Entity};
|
||||
use http_client::{HttpClient, Method};
|
||||
use language::LanguageRegistry;
|
||||
@@ -274,8 +273,7 @@ async fn run_evaluation(
|
||||
let repos_dir = Path::new(EVAL_REPOS_DIR);
|
||||
let db_path = Path::new(EVAL_DB_PATH);
|
||||
let api_key = std::env::var("OPENAI_API_KEY").unwrap();
|
||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
let fs = Arc::new(RealFs::new(git_hosting_provider_registry, None)) as Arc<dyn Fs>;
|
||||
let fs = Arc::new(RealFs::new(None)) as Arc<dyn Fs>;
|
||||
let clock = Arc::new(RealSystemClock);
|
||||
let client = cx
|
||||
.update(|cx| {
|
||||
@@ -399,7 +397,6 @@ async fn run_evaluation(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn run_eval_project(
|
||||
evaluation_project: EvaluationProject,
|
||||
user_store: &Entity<UserStore>,
|
||||
|
||||
@@ -195,7 +195,6 @@ static mut EXTENSION: Option<Box<dyn Extension>> = None;
|
||||
pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
|
||||
|
||||
mod wit {
|
||||
#![allow(clippy::too_many_arguments, clippy::missing_safety_doc)]
|
||||
|
||||
wit_bindgen::generate!({
|
||||
skip: ["init-extension"],
|
||||
|
||||
@@ -218,7 +218,6 @@ impl ExtensionStore {
|
||||
cx.global::<GlobalExtensionStore>().0.clone()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
extensions_dir: PathBuf,
|
||||
build_dir: Option<PathBuf>,
|
||||
|
||||
@@ -80,11 +80,6 @@ impl FeatureFlag for PredictEditsNonEagerModeFeatureFlag {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GitUiFeatureFlag;
|
||||
impl FeatureFlag for GitUiFeatureFlag {
|
||||
const NAME: &'static str = "git-ui";
|
||||
}
|
||||
|
||||
pub struct Remoting {}
|
||||
impl FeatureFlag for Remoting {
|
||||
const NAME: &'static str = "remoting";
|
||||
|
||||
@@ -653,7 +653,6 @@ impl FileSearchQuery {
|
||||
}
|
||||
|
||||
impl FileFinderDelegate {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
file_finder: WeakEntity<FileFinder>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
|
||||
@@ -436,8 +436,8 @@ impl PickerDelegate for NewPathDelegate {
|
||||
)
|
||||
}
|
||||
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
|
||||
"Type a path...".into()
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
|
||||
Some("Type a path...".into())
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
|
||||
@@ -347,12 +347,14 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
)
|
||||
}
|
||||
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
|
||||
if let Some(error) = self.directory_state.as_ref().and_then(|s| s.error.clone()) {
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
|
||||
let text = if let Some(error) = self.directory_state.as_ref().and_then(|s| s.error.clone())
|
||||
{
|
||||
error
|
||||
} else {
|
||||
"No such file or directory".into()
|
||||
}
|
||||
};
|
||||
Some(text)
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
|
||||
@@ -11,7 +11,6 @@ use collections::HashMap;
|
||||
use git::status::StatusCode;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use git::status::TrackedStatus;
|
||||
use git::GitHostingProviderRegistry;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use git::{repository::RepoPath, status::FileStatus};
|
||||
|
||||
@@ -247,7 +246,6 @@ impl From<MTime> for proto::Timestamp {
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RealFs {
|
||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -300,14 +298,8 @@ impl FileHandle for std::fs::File {
|
||||
pub struct RealWatcher {}
|
||||
|
||||
impl RealFs {
|
||||
pub fn new(
|
||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
git_hosting_provider_registry,
|
||||
git_binary_path,
|
||||
}
|
||||
pub fn new(git_binary_path: Option<PathBuf>) -> Self {
|
||||
Self { git_binary_path }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,7 +762,6 @@ impl Fs for RealFs {
|
||||
Some(Arc::new(RealGitRepository::new(
|
||||
repo,
|
||||
self.git_binary_path.clone(),
|
||||
self.git_hosting_provider_registry.clone(),
|
||||
)))
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,6 @@ impl<'a> Matcher<'a> {
|
||||
score
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn recursive_score_match(
|
||||
&mut self,
|
||||
path: &[char],
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use crate::commit::get_messages;
|
||||
use crate::{parse_git_remote_url, BuildCommitPermalinkParams, GitHostingProviderRegistry, Oid};
|
||||
use crate::Oid;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::{HashMap, HashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
use std::{ops::Range, path::Path};
|
||||
use text::Rope;
|
||||
use time::macros::format_description;
|
||||
use time::OffsetDateTime;
|
||||
use time::UtcOffset;
|
||||
use url::Url;
|
||||
|
||||
pub use git2 as libgit;
|
||||
|
||||
@@ -19,7 +17,6 @@ pub use git2 as libgit;
|
||||
pub struct Blame {
|
||||
pub entries: Vec<BlameEntry>,
|
||||
pub messages: HashMap<Oid, String>,
|
||||
pub permalinks: HashMap<Oid, Url>,
|
||||
pub remote_url: Option<String>,
|
||||
}
|
||||
|
||||
@@ -30,32 +27,15 @@ impl Blame {
|
||||
path: &Path,
|
||||
content: &Rope,
|
||||
remote_url: Option<String>,
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
) -> Result<Self> {
|
||||
let output = run_git_blame(git_binary, working_directory, path, content)?;
|
||||
let mut entries = parse_git_blame(&output)?;
|
||||
entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start));
|
||||
|
||||
let mut permalinks = HashMap::default();
|
||||
let mut unique_shas = HashSet::default();
|
||||
let parsed_remote_url = remote_url
|
||||
.as_deref()
|
||||
.and_then(|remote_url| parse_git_remote_url(provider_registry, remote_url));
|
||||
|
||||
for entry in entries.iter_mut() {
|
||||
unique_shas.insert(entry.sha);
|
||||
// DEPRECATED (18 Apr 24): Sending permalinks over the wire is deprecated. Clients
|
||||
// now do the parsing.
|
||||
if let Some((provider, remote)) = parsed_remote_url.as_ref() {
|
||||
permalinks.entry(entry.sha).or_insert_with(|| {
|
||||
provider.build_commit_permalink(
|
||||
remote,
|
||||
BuildCommitPermalinkParams {
|
||||
sha: entry.sha.to_string().as_str(),
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let shas = unique_shas.into_iter().collect::<Vec<_>>();
|
||||
@@ -64,7 +44,6 @@ impl Blame {
|
||||
|
||||
Ok(Self {
|
||||
entries,
|
||||
permalinks,
|
||||
messages,
|
||||
remote_url,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::status::FileStatus;
|
||||
use crate::GitHostingProviderRegistry;
|
||||
use crate::SHORT_SHA_LENGTH;
|
||||
use crate::{blame::Blame, status::GitStatus};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use askpass::{AskPassResult, AskPassSession};
|
||||
@@ -57,6 +57,14 @@ pub struct Upstream {
|
||||
pub tracking: UpstreamTracking,
|
||||
}
|
||||
|
||||
impl Upstream {
|
||||
pub fn remote_name(&self) -> Option<&str> {
|
||||
self.ref_name
|
||||
.strip_prefix("refs/remotes/")
|
||||
.and_then(|stripped| stripped.split("/").next())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum UpstreamTracking {
|
||||
/// Remote ref not present in local repository.
|
||||
@@ -120,6 +128,12 @@ pub struct CommitDetails {
|
||||
pub committer_name: SharedString,
|
||||
}
|
||||
|
||||
impl CommitDetails {
|
||||
pub fn short_sha(&self) -> SharedString {
|
||||
self.sha[..SHORT_SHA_LENGTH].to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Remote {
|
||||
pub name: SharedString,
|
||||
@@ -149,7 +163,12 @@ pub trait GitRepository: Send + Sync {
|
||||
/// Also returns `None` for symlinks.
|
||||
fn load_committed_text(&self, path: &RepoPath) -> Option<String>;
|
||||
|
||||
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()>;
|
||||
fn set_index_text(
|
||||
&self,
|
||||
path: &RepoPath,
|
||||
content: Option<String>,
|
||||
env: &HashMap<String, String>,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
/// Returns the URL of the remote with the given name.
|
||||
fn remote_url(&self, name: &str) -> Option<String>;
|
||||
@@ -167,8 +186,13 @@ pub trait GitRepository: Send + Sync {
|
||||
fn create_branch(&self, _: &str) -> Result<()>;
|
||||
fn branch_exits(&self, _: &str) -> Result<bool>;
|
||||
|
||||
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()>;
|
||||
fn checkout_files(&self, commit: &str, paths: &[RepoPath]) -> Result<()>;
|
||||
fn reset(&self, commit: &str, mode: ResetMode, env: &HashMap<String, String>) -> Result<()>;
|
||||
fn checkout_files(
|
||||
&self,
|
||||
commit: &str,
|
||||
paths: &[RepoPath],
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<()>;
|
||||
|
||||
fn show(&self, commit: &str) -> Result<CommitDetails>;
|
||||
|
||||
@@ -189,13 +213,18 @@ pub trait GitRepository: Send + Sync {
|
||||
/// Updates the index to match the worktree at the given paths.
|
||||
///
|
||||
/// If any of the paths have been deleted from the worktree, they will be removed from the index if found there.
|
||||
fn stage_paths(&self, paths: &[RepoPath]) -> Result<()>;
|
||||
fn stage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()>;
|
||||
/// Updates the index to match HEAD at the given paths.
|
||||
///
|
||||
/// If any of the paths were previously staged but do not exist in HEAD, they will be removed from the index.
|
||||
fn unstage_paths(&self, paths: &[RepoPath]) -> Result<()>;
|
||||
fn unstage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()>;
|
||||
|
||||
fn commit(&self, message: &str, name_and_email: Option<(&str, &str)>) -> Result<()>;
|
||||
fn commit(
|
||||
&self,
|
||||
message: &str,
|
||||
name_and_email: Option<(&str, &str)>,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<()>;
|
||||
|
||||
fn push(
|
||||
&self,
|
||||
@@ -203,6 +232,7 @@ pub trait GitRepository: Send + Sync {
|
||||
upstream_name: &str,
|
||||
options: Option<PushOptions>,
|
||||
askpass: AskPassSession,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput>;
|
||||
|
||||
fn pull(
|
||||
@@ -210,8 +240,13 @@ pub trait GitRepository: Send + Sync {
|
||||
branch_name: &str,
|
||||
upstream_name: &str,
|
||||
askpass: AskPassSession,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput>;
|
||||
fn fetch(
|
||||
&self,
|
||||
askpass: AskPassSession,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput>;
|
||||
fn fetch(&self, askpass: AskPassSession) -> Result<RemoteCommandOutput>;
|
||||
|
||||
fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>>;
|
||||
|
||||
@@ -242,19 +277,13 @@ impl std::fmt::Debug for dyn GitRepository {
|
||||
pub struct RealGitRepository {
|
||||
pub repository: Mutex<git2::Repository>,
|
||||
pub git_binary_path: PathBuf,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
}
|
||||
|
||||
impl RealGitRepository {
|
||||
pub fn new(
|
||||
repository: git2::Repository,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
) -> Self {
|
||||
pub fn new(repository: git2::Repository, git_binary_path: Option<PathBuf>) -> Self {
|
||||
Self {
|
||||
repository: Mutex::new(repository),
|
||||
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
||||
hosting_provider_registry,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +337,7 @@ impl GitRepository for RealGitRepository {
|
||||
Ok(details)
|
||||
}
|
||||
|
||||
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()> {
|
||||
fn reset(&self, commit: &str, mode: ResetMode, env: &HashMap<String, String>) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
let mode_flag = match mode {
|
||||
@@ -317,6 +346,7 @@ impl GitRepository for RealGitRepository {
|
||||
};
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.envs(env)
|
||||
.current_dir(&working_directory)
|
||||
.args(["reset", mode_flag, commit])
|
||||
.output()?;
|
||||
@@ -329,7 +359,12 @@ impl GitRepository for RealGitRepository {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn checkout_files(&self, commit: &str, paths: &[RepoPath]) -> Result<()> {
|
||||
fn checkout_files(
|
||||
&self,
|
||||
commit: &str,
|
||||
paths: &[RepoPath],
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<()> {
|
||||
if paths.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -337,6 +372,7 @@ impl GitRepository for RealGitRepository {
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.envs(env)
|
||||
.args(["checkout", commit, "--"])
|
||||
.args(paths.iter().map(|path| path.as_ref()))
|
||||
.output()?;
|
||||
@@ -385,11 +421,17 @@ impl GitRepository for RealGitRepository {
|
||||
Some(content)
|
||||
}
|
||||
|
||||
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()> {
|
||||
fn set_index_text(
|
||||
&self,
|
||||
path: &RepoPath,
|
||||
content: Option<String>,
|
||||
env: &HashMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
if let Some(content) = content {
|
||||
let mut child = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.envs(env)
|
||||
.args(["hash-object", "-w", "--stdin"])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
@@ -402,6 +444,7 @@ impl GitRepository for RealGitRepository {
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.envs(env)
|
||||
.args(["update-index", "--add", "--cacheinfo", "100644", &sha])
|
||||
.arg(path.as_ref())
|
||||
.output()?;
|
||||
@@ -415,6 +458,7 @@ impl GitRepository for RealGitRepository {
|
||||
} else {
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.envs(env)
|
||||
.args(["update-index", "--force-remove"])
|
||||
.arg(path.as_ref())
|
||||
.output()?;
|
||||
@@ -581,7 +625,6 @@ impl GitRepository for RealGitRepository {
|
||||
path,
|
||||
&content,
|
||||
remote_url,
|
||||
self.hosting_provider_registry.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -607,12 +650,13 @@ impl GitRepository for RealGitRepository {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
fn stage_paths(&self, paths: &[RepoPath]) -> Result<()> {
|
||||
fn stage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
if !paths.is_empty() {
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.envs(env)
|
||||
.args(["update-index", "--add", "--remove", "--"])
|
||||
.args(paths.iter().map(|p| p.as_ref()))
|
||||
.output()?;
|
||||
@@ -627,12 +671,13 @@ impl GitRepository for RealGitRepository {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unstage_paths(&self, paths: &[RepoPath]) -> Result<()> {
|
||||
fn unstage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
if !paths.is_empty() {
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.envs(env)
|
||||
.args(["reset", "--quiet", "--"])
|
||||
.args(paths.iter().map(|p| p.as_ref()))
|
||||
.output()?;
|
||||
@@ -647,11 +692,17 @@ impl GitRepository for RealGitRepository {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn commit(&self, message: &str, name_and_email: Option<(&str, &str)>) -> Result<()> {
|
||||
fn commit(
|
||||
&self,
|
||||
message: &str,
|
||||
name_and_email: Option<(&str, &str)>,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
let mut cmd = new_std_command(&self.git_binary_path);
|
||||
cmd.current_dir(&working_directory)
|
||||
.envs(env)
|
||||
.args(["commit", "--quiet", "-m"])
|
||||
.arg(message)
|
||||
.arg("--cleanup=strip");
|
||||
@@ -677,11 +728,13 @@ impl GitRepository for RealGitRepository {
|
||||
remote_name: &str,
|
||||
options: Option<PushOptions>,
|
||||
ask_pass: AskPassSession,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
let mut command = new_smol_command("git");
|
||||
command
|
||||
.envs(env)
|
||||
.env("GIT_ASKPASS", ask_pass.script_path())
|
||||
.env("SSH_ASKPASS", ask_pass.script_path())
|
||||
.env("SSH_ASKPASS_REQUIRE", "force")
|
||||
@@ -705,11 +758,13 @@ impl GitRepository for RealGitRepository {
|
||||
branch_name: &str,
|
||||
remote_name: &str,
|
||||
ask_pass: AskPassSession,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
let mut command = new_smol_command("git");
|
||||
command
|
||||
.envs(env)
|
||||
.env("GIT_ASKPASS", ask_pass.script_path())
|
||||
.env("SSH_ASKPASS", ask_pass.script_path())
|
||||
.env("SSH_ASKPASS_REQUIRE", "force")
|
||||
@@ -724,11 +779,16 @@ impl GitRepository for RealGitRepository {
|
||||
run_remote_command(ask_pass, git_process)
|
||||
}
|
||||
|
||||
fn fetch(&self, ask_pass: AskPassSession) -> Result<RemoteCommandOutput> {
|
||||
fn fetch(
|
||||
&self,
|
||||
ask_pass: AskPassSession,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
let mut command = new_smol_command("git");
|
||||
command
|
||||
.envs(env)
|
||||
.env("GIT_ASKPASS", ask_pass.script_path())
|
||||
.env("SSH_ASKPASS", ask_pass.script_path())
|
||||
.env("SSH_ASKPASS_REQUIRE", "force")
|
||||
@@ -919,7 +979,12 @@ impl GitRepository for FakeGitRepository {
|
||||
state.head_contents.get(path.as_ref()).cloned()
|
||||
}
|
||||
|
||||
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()> {
|
||||
fn set_index_text(
|
||||
&self,
|
||||
path: &RepoPath,
|
||||
content: Option<String>,
|
||||
_env: &HashMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut state = self.state.lock();
|
||||
if let Some(message) = state.simulated_index_write_error_message.clone() {
|
||||
return Err(anyhow::anyhow!(message));
|
||||
@@ -952,11 +1017,11 @@ impl GitRepository for FakeGitRepository {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reset(&self, _: &str, _: ResetMode) -> Result<()> {
|
||||
fn reset(&self, _: &str, _: ResetMode, _: &HashMap<String, String>) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn checkout_files(&self, _: &str, _: &[RepoPath]) -> Result<()> {
|
||||
fn checkout_files(&self, _: &str, _: &[RepoPath], _: &HashMap<String, String>) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -1042,15 +1107,20 @@ impl GitRepository for FakeGitRepository {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn stage_paths(&self, _paths: &[RepoPath]) -> Result<()> {
|
||||
fn stage_paths(&self, _paths: &[RepoPath], _env: &HashMap<String, String>) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn unstage_paths(&self, _paths: &[RepoPath]) -> Result<()> {
|
||||
fn unstage_paths(&self, _paths: &[RepoPath], _env: &HashMap<String, String>) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn commit(&self, _message: &str, _name_and_email: Option<(&str, &str)>) -> Result<()> {
|
||||
fn commit(
|
||||
&self,
|
||||
_message: &str,
|
||||
_name_and_email: Option<(&str, &str)>,
|
||||
_env: &HashMap<String, String>,
|
||||
) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -1060,6 +1130,7 @@ impl GitRepository for FakeGitRepository {
|
||||
_remote: &str,
|
||||
_options: Option<PushOptions>,
|
||||
_ask_pass: AskPassSession,
|
||||
_env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput> {
|
||||
unimplemented!()
|
||||
}
|
||||
@@ -1069,11 +1140,16 @@ impl GitRepository for FakeGitRepository {
|
||||
_branch: &str,
|
||||
_remote: &str,
|
||||
_ask_pass: AskPassSession,
|
||||
_env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn fetch(&self, _ask_pass: AskPassSession) -> Result<RemoteCommandOutput> {
|
||||
fn fetch(
|
||||
&self,
|
||||
_ask_pass: AskPassSession,
|
||||
_env: &HashMap<String, String>,
|
||||
) -> Result<RemoteCommandOutput> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,12 @@ test-support = ["multi_buffer/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
askpass.workspace= true
|
||||
askpass.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
collections.workspace = true
|
||||
component.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
git.workspace = true
|
||||
@@ -51,6 +50,7 @@ strum.workspace = true
|
||||
telemetry.workspace = true
|
||||
theme.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
|
||||
use git::repository::Branch;
|
||||
use gpui::{
|
||||
rems, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
|
||||
Task, Window,
|
||||
InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render,
|
||||
SharedString, Styled, Subscription, Task, Window,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::git::Repository;
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenuHandle};
|
||||
use time::OffsetDateTime;
|
||||
use time_format::format_local_timestamp;
|
||||
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::notifications::DetachAndPromptErr;
|
||||
use workspace::{ModalView, Workspace};
|
||||
@@ -51,7 +53,7 @@ pub fn open(
|
||||
let repository = workspace.project().read(cx).active_repository(cx).clone();
|
||||
let style = BranchListStyle::Modal;
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
BranchList::new(repository, style, 34., window, cx)
|
||||
BranchList::new(repository, style, rems(34.), window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,7 +63,7 @@ pub fn popover(
|
||||
cx: &mut App,
|
||||
) -> Entity<BranchList> {
|
||||
cx.new(|cx| {
|
||||
let list = BranchList::new(repository, BranchListStyle::Popover, 15., window, cx);
|
||||
let list = BranchList::new(repository, BranchListStyle::Popover, rems(20.), window, cx);
|
||||
list.focus_handle(cx).focus(window);
|
||||
list
|
||||
})
|
||||
@@ -74,8 +76,7 @@ enum BranchListStyle {
|
||||
}
|
||||
|
||||
pub struct BranchList {
|
||||
rem_width: f32,
|
||||
pub popover_handle: PopoverMenuHandle<Self>,
|
||||
width: Rems,
|
||||
pub picker: Entity<Picker<BranchListDelegate>>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
@@ -84,20 +85,26 @@ impl BranchList {
|
||||
fn new(
|
||||
repository: Option<Entity<Repository>>,
|
||||
style: BranchListStyle,
|
||||
rem_width: f32,
|
||||
width: Rems,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let popover_handle = PopoverMenuHandle::default();
|
||||
let all_branches_request = repository
|
||||
.clone()
|
||||
.map(|repository| repository.read(cx).branches());
|
||||
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let all_branches = all_branches_request
|
||||
let mut all_branches = all_branches_request
|
||||
.context("No active repository")?
|
||||
.await??;
|
||||
|
||||
all_branches.sort_by_key(|branch| {
|
||||
branch
|
||||
.most_recent_commit
|
||||
.as_ref()
|
||||
.map(|commit| 0 - commit.commit_timestamp)
|
||||
});
|
||||
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
this.picker.update(cx, |picker, cx| {
|
||||
picker.delegate.all_branches = Some(all_branches);
|
||||
@@ -109,7 +116,7 @@ impl BranchList {
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
let delegate = BranchListDelegate::new(repository.clone(), style, 20);
|
||||
let delegate = BranchListDelegate::new(repository.clone(), style);
|
||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||
|
||||
let _subscription = cx.subscribe(&picker, |_, _, _, cx| {
|
||||
@@ -118,11 +125,20 @@ impl BranchList {
|
||||
|
||||
Self {
|
||||
picker,
|
||||
rem_width,
|
||||
popover_handle,
|
||||
width,
|
||||
_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_modifiers_changed(
|
||||
&mut self,
|
||||
ev: &ModifiersChangedEvent,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.picker
|
||||
.update(cx, |picker, _| picker.delegate.modifiers = ev.modifiers)
|
||||
}
|
||||
}
|
||||
impl ModalView for BranchList {}
|
||||
impl EventEmitter<DismissEvent> for BranchList {}
|
||||
@@ -136,7 +152,8 @@ impl Focusable for BranchList {
|
||||
impl Render for BranchList {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.w(rems(self.rem_width))
|
||||
.w(self.width)
|
||||
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
|
||||
.child(self.picker.clone())
|
||||
.on_mouse_down_out({
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
@@ -149,20 +166,10 @@ impl Render for BranchList {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum BranchEntry {
|
||||
Branch(StringMatch),
|
||||
History(String),
|
||||
NewBranch { name: String },
|
||||
}
|
||||
|
||||
impl BranchEntry {
|
||||
fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::Branch(branch) => &branch.string,
|
||||
Self::History(branch) => &branch,
|
||||
Self::NewBranch { name } => &name,
|
||||
}
|
||||
}
|
||||
struct BranchEntry {
|
||||
branch: Branch,
|
||||
positions: Vec<usize>,
|
||||
is_new: bool,
|
||||
}
|
||||
|
||||
pub struct BranchListDelegate {
|
||||
@@ -172,16 +179,11 @@ pub struct BranchListDelegate {
|
||||
style: BranchListStyle,
|
||||
selected_index: usize,
|
||||
last_query: String,
|
||||
/// Max length of branch name before we truncate it and add a trailing `...`.
|
||||
branch_name_trailoff_after: usize,
|
||||
modifiers: Modifiers,
|
||||
}
|
||||
|
||||
impl BranchListDelegate {
|
||||
fn new(
|
||||
repo: Option<Entity<Repository>>,
|
||||
style: BranchListStyle,
|
||||
branch_name_trailoff_after: usize,
|
||||
) -> Self {
|
||||
fn new(repo: Option<Entity<Repository>>, style: BranchListStyle) -> Self {
|
||||
Self {
|
||||
matches: vec![],
|
||||
repo,
|
||||
@@ -189,15 +191,30 @@ impl BranchListDelegate {
|
||||
all_branches: None,
|
||||
selected_index: 0,
|
||||
last_query: Default::default(),
|
||||
branch_name_trailoff_after,
|
||||
modifiers: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn branch_count(&self) -> usize {
|
||||
self.matches
|
||||
.iter()
|
||||
.filter(|item| matches!(item, BranchEntry::Branch(_)))
|
||||
.count()
|
||||
fn create_branch(
|
||||
&self,
|
||||
new_branch_name: SharedString,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
let Some(repo) = self.repo.clone() else {
|
||||
return;
|
||||
};
|
||||
cx.spawn(|_, cx| async move {
|
||||
cx.update(|cx| repo.read(cx).create_branch(&new_branch_name))?
|
||||
.await??;
|
||||
cx.update(|cx| repo.read(cx).change_branch(&new_branch_name))?
|
||||
.await??;
|
||||
Ok(())
|
||||
})
|
||||
.detach_and_prompt_err("Failed to create branch", window, cx, |e, _, _| {
|
||||
Some(e.to_string())
|
||||
});
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,37 +248,28 @@ impl PickerDelegate for BranchListDelegate {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let Some(mut all_branches) = self.all_branches.clone() else {
|
||||
let Some(all_branches) = self.all_branches.clone() else {
|
||||
return Task::ready(());
|
||||
};
|
||||
|
||||
const RECENT_BRANCHES_COUNT: usize = 10;
|
||||
cx.spawn_in(window, move |picker, mut cx| async move {
|
||||
const RECENT_BRANCHES_COUNT: usize = 10;
|
||||
if query.is_empty() {
|
||||
if all_branches.len() > RECENT_BRANCHES_COUNT {
|
||||
// Truncate list of recent branches
|
||||
// Do a partial sort to show recent-ish branches first.
|
||||
all_branches.select_nth_unstable_by(RECENT_BRANCHES_COUNT - 1, |lhs, rhs| {
|
||||
rhs.priority_key().cmp(&lhs.priority_key())
|
||||
});
|
||||
all_branches.truncate(RECENT_BRANCHES_COUNT);
|
||||
}
|
||||
all_branches.sort_unstable_by(|lhs, rhs| {
|
||||
rhs.is_head.cmp(&lhs.is_head).then(lhs.name.cmp(&rhs.name))
|
||||
});
|
||||
}
|
||||
|
||||
let candidates = all_branches
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, command)| StringMatchCandidate::new(ix, &command.name))
|
||||
.collect::<Vec<StringMatchCandidate>>();
|
||||
let matches: Vec<BranchEntry> = if query.is_empty() {
|
||||
candidates
|
||||
let mut matches: Vec<BranchEntry> = if query.is_empty() {
|
||||
all_branches
|
||||
.into_iter()
|
||||
.map(|candidate| BranchEntry::History(candidate.string))
|
||||
.take(RECENT_BRANCHES_COUNT)
|
||||
.map(|branch| BranchEntry {
|
||||
branch,
|
||||
positions: Vec::new(),
|
||||
is_new: false,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
let candidates = all_branches
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(ix, command)| StringMatchCandidate::new(ix, &command.name.clone()))
|
||||
.collect::<Vec<StringMatchCandidate>>();
|
||||
fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
@@ -273,20 +281,35 @@ impl PickerDelegate for BranchListDelegate {
|
||||
.await
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(BranchEntry::Branch)
|
||||
.map(|candidate| BranchEntry {
|
||||
branch: all_branches[candidate.candidate_id].clone(),
|
||||
positions: candidate.positions,
|
||||
is_new: false,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
picker
|
||||
.update(&mut cx, |picker, _| {
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
if !query.is_empty()
|
||||
&& !matches
|
||||
.first()
|
||||
.is_some_and(|entry| entry.branch.name == query)
|
||||
{
|
||||
matches.push(BranchEntry {
|
||||
branch: Branch {
|
||||
name: query.clone().into(),
|
||||
is_head: false,
|
||||
upstream: None,
|
||||
most_recent_commit: None,
|
||||
},
|
||||
positions: Vec::new(),
|
||||
is_new: true,
|
||||
})
|
||||
}
|
||||
let delegate = &mut picker.delegate;
|
||||
delegate.matches = matches;
|
||||
if delegate.matches.is_empty() {
|
||||
if !query.is_empty() {
|
||||
delegate.matches.push(BranchEntry::NewBranch {
|
||||
name: query.trim().replace(' ', "-"),
|
||||
});
|
||||
}
|
||||
|
||||
delegate.selected_index = 0;
|
||||
} else {
|
||||
delegate.selected_index =
|
||||
@@ -298,10 +321,14 @@ impl PickerDelegate for BranchListDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let Some(branch) = self.matches.get(self.selected_index()) else {
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let Some(entry) = self.matches.get(self.selected_index()) else {
|
||||
return;
|
||||
};
|
||||
if entry.is_new {
|
||||
self.create_branch(entry.branch.name.clone(), window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let current_branch = self.repo.as_ref().map(|repo| {
|
||||
repo.update(cx, |repo, _| {
|
||||
@@ -311,14 +338,14 @@ impl PickerDelegate for BranchListDelegate {
|
||||
|
||||
if current_branch
|
||||
.flatten()
|
||||
.is_some_and(|current_branch| current_branch == branch.name())
|
||||
.is_some_and(|current_branch| current_branch == entry.branch.name)
|
||||
{
|
||||
cx.emit(DismissEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
cx.spawn_in(window, {
|
||||
let branch = branch.clone();
|
||||
let branch = entry.branch.clone();
|
||||
|picker, mut cx| async move {
|
||||
let branch_change_task = picker.update(&mut cx, |this, cx| {
|
||||
let repo = this
|
||||
@@ -331,22 +358,8 @@ impl PickerDelegate for BranchListDelegate {
|
||||
let cx = cx.to_async();
|
||||
|
||||
anyhow::Ok(async move {
|
||||
match branch {
|
||||
BranchEntry::Branch(StringMatch {
|
||||
string: branch_name,
|
||||
..
|
||||
})
|
||||
| BranchEntry::History(branch_name) => {
|
||||
cx.update(|cx| repo.read(cx).change_branch(branch_name))?
|
||||
.await?
|
||||
}
|
||||
BranchEntry::NewBranch { name: branch_name } => {
|
||||
cx.update(|cx| repo.read(cx).create_branch(branch_name.clone()))?
|
||||
.await??;
|
||||
cx.update(|cx| repo.read(cx).change_branch(branch_name))?
|
||||
.await?
|
||||
}
|
||||
}
|
||||
cx.update(|cx| repo.read(cx).change_branch(&branch.name))?
|
||||
.await?
|
||||
})
|
||||
})??;
|
||||
|
||||
@@ -366,16 +379,35 @@ impl PickerDelegate for BranchListDelegate {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn render_header(&self, _: &mut Window, _cx: &mut Context<Picker<Self>>) -> Option<AnyElement> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let hit = &self.matches[ix];
|
||||
let shortened_branch_name =
|
||||
util::truncate_and_trailoff(&hit.name(), self.branch_name_trailoff_after);
|
||||
let entry = &self.matches[ix];
|
||||
|
||||
let (commit_time, subject) = entry
|
||||
.branch
|
||||
.most_recent_commit
|
||||
.as_ref()
|
||||
.map(|commit| {
|
||||
let subject = commit.subject.clone();
|
||||
let commit_time = OffsetDateTime::from_unix_timestamp(commit.commit_timestamp)
|
||||
.unwrap_or_else(|_| OffsetDateTime::now_utc());
|
||||
let formatted_time = format_local_timestamp(
|
||||
commit_time,
|
||||
OffsetDateTime::now_utc(),
|
||||
time_format::TimestampFormat::Relative,
|
||||
);
|
||||
(Some(formatted_time), Some(subject))
|
||||
})
|
||||
.unwrap_or_else(|| (None, None));
|
||||
|
||||
Some(
|
||||
ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
|
||||
@@ -386,29 +418,67 @@ impl PickerDelegate for BranchListDelegate {
|
||||
})
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.when(matches!(hit, BranchEntry::History(_)), |el| {
|
||||
el.end_slot(
|
||||
Icon::new(IconName::HistoryRerun)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small),
|
||||
)
|
||||
})
|
||||
.map(|el| match hit {
|
||||
BranchEntry::Branch(branch) => {
|
||||
let highlights: Vec<_> = branch
|
||||
.positions
|
||||
.iter()
|
||||
.filter(|index| index < &&self.branch_name_trailoff_after)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
el.child(HighlightedLabel::new(shortened_branch_name, highlights))
|
||||
}
|
||||
BranchEntry::History(_) => el.child(Label::new(shortened_branch_name)),
|
||||
BranchEntry::NewBranch { name } => {
|
||||
el.child(Label::new(format!("Create branch '{name}'")))
|
||||
}
|
||||
}),
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.flex_shrink()
|
||||
.overflow_x_hidden()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(div().flex_shrink().overflow_x_hidden().child(
|
||||
if entry.is_new {
|
||||
Label::new(format!(
|
||||
"Create branch \"{}\"…",
|
||||
entry.branch.name
|
||||
))
|
||||
.into_any_element()
|
||||
} else {
|
||||
HighlightedLabel::new(
|
||||
entry.branch.name.clone(),
|
||||
entry.positions.clone(),
|
||||
)
|
||||
.truncate()
|
||||
.into_any_element()
|
||||
},
|
||||
))
|
||||
.when_some(commit_time, |el, commit_time| {
|
||||
el.child(
|
||||
Label::new(commit_time)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_element(),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when(self.style == BranchListStyle::Modal, |el| {
|
||||
el.child(div().max_w_96().child({
|
||||
let message = if entry.is_new {
|
||||
if let Some(current_branch) =
|
||||
self.repo.as_ref().and_then(|repo| {
|
||||
repo.read(cx).current_branch().map(|b| b.name.clone())
|
||||
})
|
||||
{
|
||||
format!("based off {}", current_branch)
|
||||
} else {
|
||||
"based off the current branch".to_string()
|
||||
}
|
||||
} else {
|
||||
subject.unwrap_or("no commits found".into()).to_string()
|
||||
};
|
||||
Label::new(message)
|
||||
.size(LabelSize::Small)
|
||||
.truncate()
|
||||
.color(Color::Muted)
|
||||
}))
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::branch_picker::{self, BranchList};
|
||||
use crate::git_panel::{commit_message_editor, GitPanel};
|
||||
use git::{Commit, GenerateCommitMessage};
|
||||
use panel::{panel_button, panel_editor_style, panel_filled_button};
|
||||
use ui::{prelude::*, KeybindingHint, PopoverMenu, Tooltip};
|
||||
use ui::{prelude::*, KeybindingHint, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||
|
||||
use editor::{Editor, EditorElement};
|
||||
use gpui::*;
|
||||
@@ -65,11 +65,11 @@ pub fn init(cx: &mut App) {
|
||||
}
|
||||
|
||||
pub struct CommitModal {
|
||||
branch_list: Entity<BranchList>,
|
||||
git_panel: Entity<GitPanel>,
|
||||
commit_editor: Entity<Editor>,
|
||||
restore_dock: RestoreDock,
|
||||
properties: ModalContainerProperties,
|
||||
branch_list_handle: PopoverMenuHandle<BranchList>,
|
||||
}
|
||||
|
||||
impl Focusable for CommitModal {
|
||||
@@ -146,7 +146,6 @@ impl CommitModal {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let panel = git_panel.read(cx);
|
||||
let active_repository = panel.active_repository.clone();
|
||||
let suggested_commit_message = panel.suggest_commit_message();
|
||||
|
||||
let commit_editor = git_panel.update(cx, |git_panel, cx| {
|
||||
@@ -177,11 +176,7 @@ impl CommitModal {
|
||||
let focus_handle = commit_editor.focus_handle(cx);
|
||||
|
||||
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
|
||||
if !this
|
||||
.branch_list
|
||||
.focus_handle(cx)
|
||||
.contains_focused(window, cx)
|
||||
{
|
||||
if !this.branch_list_handle.is_focused(window, cx) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
})
|
||||
@@ -190,11 +185,11 @@ impl CommitModal {
|
||||
let properties = ModalContainerProperties::new(window, 50);
|
||||
|
||||
Self {
|
||||
branch_list: branch_picker::popover(active_repository.clone(), window, cx),
|
||||
git_panel,
|
||||
commit_editor,
|
||||
restore_dock,
|
||||
properties,
|
||||
branch_list_handle: PopoverMenuHandle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,34 +227,29 @@ 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, can_commit, tooltip, commit_label, co_authors, generate_commit_message) =
|
||||
let (can_commit, tooltip, commit_label, co_authors, generate_commit_message, active_repo) =
|
||||
self.git_panel.update(cx, |git_panel, cx| {
|
||||
let branch = git_panel
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.and_then(|repo| {
|
||||
repo.read(cx)
|
||||
.repository_entry
|
||||
.branch()
|
||||
.map(|b| b.name.clone())
|
||||
})
|
||||
.unwrap_or_else(|| "<no branch>".into());
|
||||
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);
|
||||
let generate_commit_message = git_panel.render_generate_commit_message_button(cx);
|
||||
let active_repo = git_panel.active_repository.clone();
|
||||
(
|
||||
branch,
|
||||
can_commit,
|
||||
tooltip,
|
||||
title,
|
||||
co_authors,
|
||||
generate_commit_message,
|
||||
active_repo,
|
||||
)
|
||||
});
|
||||
|
||||
let branch = active_repo
|
||||
.as_ref()
|
||||
.and_then(|repo| repo.read(cx).repository_entry.branch())
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_else(|| "<no branch>".into());
|
||||
|
||||
let branch_picker_button = panel_button(branch)
|
||||
.icon(IconName::GitBranch)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -276,10 +266,8 @@ impl CommitModal {
|
||||
.style(ButtonStyle::Transparent);
|
||||
|
||||
let branch_picker = PopoverMenu::new("popover-button")
|
||||
.menu({
|
||||
let branch_list = self.branch_list.clone();
|
||||
move |_window, _cx| Some(branch_list.clone())
|
||||
})
|
||||
.menu(move |window, cx| Some(branch_picker::popover(active_repo.clone(), window, cx)))
|
||||
.with_handle(self.branch_list_handle.clone())
|
||||
.trigger_with_tooltip(
|
||||
branch_picker_button,
|
||||
Tooltip::for_action_title("Switch Branch", &zed_actions::git::Branch),
|
||||
@@ -289,6 +277,7 @@ impl CommitModal {
|
||||
x: px(0.0),
|
||||
y: px(-2.0),
|
||||
});
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
let close_kb_hint =
|
||||
if let Some(close_kb) = ui::KeyBinding::for_action(&menu::Cancel, window, cx) {
|
||||
@@ -300,12 +289,9 @@ impl CommitModal {
|
||||
None
|
||||
};
|
||||
|
||||
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({
|
||||
let panel_editor_focus_handle = panel_editor_focus_handle.clone();
|
||||
let panel_editor_focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(tooltip, &Commit, &panel_editor_focus_handle, window, cx)
|
||||
}
|
||||
@@ -330,7 +316,14 @@ impl CommitModal {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(branch_picker)
|
||||
.flex_shrink()
|
||||
.overflow_x_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_shrink()
|
||||
.overflow_x_hidden()
|
||||
.child(branch_picker),
|
||||
)
|
||||
.children(generate_commit_message)
|
||||
.children(co_authors),
|
||||
)
|
||||
@@ -357,6 +350,14 @@ impl CommitModal {
|
||||
.update(cx, |git_panel, cx| git_panel.commit_changes(window, cx));
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn toggle_branch_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.branch_list_handle.is_focused(window, cx) {
|
||||
self.focus_handle(cx).focus(window)
|
||||
} else {
|
||||
self.branch_list_handle.toggle(window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for CommitModal {
|
||||
@@ -379,17 +380,17 @@ impl Render for CommitModal {
|
||||
}))
|
||||
.on_action(
|
||||
cx.listener(|this, _: &zed_actions::git::Branch, window, cx| {
|
||||
toggle_branch_picker(this, window, cx);
|
||||
this.toggle_branch_selector(window, cx);
|
||||
}),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|this, _: &zed_actions::git::CheckoutBranch, window, cx| {
|
||||
toggle_branch_picker(this, window, cx);
|
||||
this.toggle_branch_selector(window, cx);
|
||||
}),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|this, _: &zed_actions::git::Switch, window, cx| {
|
||||
toggle_branch_picker(this, window, cx);
|
||||
this.toggle_branch_selector(window, cx);
|
||||
}),
|
||||
)
|
||||
.elevation_3(cx)
|
||||
@@ -428,13 +429,3 @@ impl Render for CommitModal {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_branch_picker(
|
||||
this: &mut CommitModal,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, CommitModal>,
|
||||
) {
|
||||
this.branch_list.update(cx, |branch_list, cx| {
|
||||
branch_list.popover_handle.toggle(window, cx);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::askpass_modal::AskPassModal;
|
||||
use crate::commit_modal::CommitModal;
|
||||
use crate::git_panel_settings::StatusStyle;
|
||||
use crate::project_diff::Diff;
|
||||
use crate::remote_output_toast::{RemoteAction, RemoteOutputToast};
|
||||
use crate::repository_selector::filtered_repository_entries;
|
||||
use crate::{branch_picker, render_remote_button};
|
||||
@@ -40,7 +41,8 @@ use language_model::{
|
||||
use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
|
||||
use multi_buffer::ExcerptInfo;
|
||||
use panel::{
|
||||
panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button, PanelHeader,
|
||||
panel_button, panel_editor_container, panel_editor_style, panel_filled_button,
|
||||
panel_icon_button, PanelHeader,
|
||||
};
|
||||
use project::{
|
||||
git::{GitEvent, Repository},
|
||||
@@ -101,9 +103,14 @@ enum TrashCancel {
|
||||
Cancel,
|
||||
}
|
||||
|
||||
fn git_panel_context_menu(window: &mut Window, cx: &mut App) -> Entity<ContextMenu> {
|
||||
fn git_panel_context_menu(
|
||||
focus_handle: FocusHandle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<ContextMenu> {
|
||||
ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||
context_menu
|
||||
.context(focus_handle)
|
||||
.action("Stage All", StageAll.boxed_clone())
|
||||
.action("Unstage All", UnstageAll.boxed_clone())
|
||||
.separator()
|
||||
@@ -231,6 +238,7 @@ pub struct GitPanel {
|
||||
fs: Arc<dyn Fs>,
|
||||
hide_scrollbar_task: Option<Task<()>>,
|
||||
new_count: usize,
|
||||
entry_count: usize,
|
||||
new_staged_count: usize,
|
||||
pending: Vec<PendingOperation>,
|
||||
pending_commit: Option<Task<()>>,
|
||||
@@ -381,6 +389,7 @@ impl GitPanel {
|
||||
context_menu: None,
|
||||
workspace,
|
||||
modal_open: false,
|
||||
entry_count: 0,
|
||||
};
|
||||
git_panel.schedule_update(false, window, cx);
|
||||
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
||||
@@ -685,10 +694,6 @@ impl GitPanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn editor_focus_handle(&self, cx: &mut Context<Self>) -> FocusHandle {
|
||||
self.commit_editor.focus_handle(cx).clone()
|
||||
}
|
||||
|
||||
fn focus_editor(&mut self, _: &FocusEditor, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.commit_editor.update(cx, |editor, cx| {
|
||||
window.focus(&editor.focus_handle(cx));
|
||||
@@ -741,6 +746,7 @@ impl GitPanel {
|
||||
.as_ref()
|
||||
{
|
||||
project_diff.focus_handle(cx).focus(window);
|
||||
project_diff.update(cx, |project_diff, cx| project_diff.autoscroll(cx));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -898,13 +904,14 @@ impl GitPanel {
|
||||
let buffers = futures::future::join_all(tasks).await;
|
||||
|
||||
active_repository
|
||||
.update(&mut cx, |repo, _| {
|
||||
.update(&mut cx, |repo, cx| {
|
||||
repo.checkout_files(
|
||||
"HEAD",
|
||||
entries
|
||||
.iter()
|
||||
.map(|entries| entries.repo_path.clone())
|
||||
.collect(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await??;
|
||||
@@ -1078,7 +1085,7 @@ impl GitPanel {
|
||||
});
|
||||
}
|
||||
|
||||
fn stage_all(&mut self, _: &StageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub fn stage_all(&mut self, _: &StageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let entries = self
|
||||
.entries
|
||||
.iter()
|
||||
@@ -1089,7 +1096,7 @@ impl GitPanel {
|
||||
self.change_file_stage(true, entries, cx);
|
||||
}
|
||||
|
||||
fn unstage_all(&mut self, _: &UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub fn unstage_all(&mut self, _: &UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let entries = self
|
||||
.entries
|
||||
.iter()
|
||||
@@ -1228,6 +1235,35 @@ impl GitPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn stage_selected(&mut self, _: &git::StageFile, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(selected_entry) = self.get_selected_entry() else {
|
||||
return;
|
||||
};
|
||||
let Some(status_entry) = selected_entry.status_entry() else {
|
||||
return;
|
||||
};
|
||||
if status_entry.staging != StageStatus::Staged {
|
||||
self.change_file_stage(true, vec![status_entry.clone()], cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn unstage_selected(
|
||||
&mut self,
|
||||
_: &git::UnstageFile,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(selected_entry) = self.get_selected_entry() else {
|
||||
return;
|
||||
};
|
||||
let Some(status_entry) = selected_entry.status_entry() else {
|
||||
return;
|
||||
};
|
||||
if status_entry.staging != StageStatus::Unstaged {
|
||||
self.change_file_stage(false, vec![status_entry.clone()], cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self
|
||||
.commit_editor
|
||||
@@ -1286,7 +1322,8 @@ impl GitPanel {
|
||||
|
||||
let task = if self.has_staged_changes() {
|
||||
// Repository serializes all git operations, so we can just send a commit immediately
|
||||
let commit_task = active_repository.read(cx).commit(message.into(), None);
|
||||
let commit_task =
|
||||
active_repository.update(cx, |repo, cx| repo.commit(message.into(), None, cx));
|
||||
cx.background_spawn(async move { commit_task.await? })
|
||||
} else {
|
||||
let changed_files = self
|
||||
@@ -1307,7 +1344,7 @@ impl GitPanel {
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
stage_task.await?;
|
||||
let commit_task = active_repository
|
||||
.update(&mut cx, |repo, _| repo.commit(message.into(), None))?;
|
||||
.update(&mut cx, |repo, cx| repo.commit(message.into(), None, cx))?;
|
||||
commit_task.await?
|
||||
})
|
||||
};
|
||||
@@ -1343,7 +1380,7 @@ impl GitPanel {
|
||||
if let Ok(true) = confirmation.await {
|
||||
let prior_head = prior_head.await?;
|
||||
|
||||
repo.update(&mut cx, |repo, _| repo.reset("HEAD^", ResetMode::Soft))?
|
||||
repo.update(&mut cx, |repo, cx| repo.reset("HEAD^", ResetMode::Soft, cx))?
|
||||
.await??;
|
||||
|
||||
Ok(Some(prior_head))
|
||||
@@ -1453,6 +1490,10 @@ impl GitPanel {
|
||||
|
||||
/// Generates a commit message using an LLM.
|
||||
pub fn generate_commit_message(&mut self, cx: &mut Context<Self>) {
|
||||
if !self.can_commit() {
|
||||
return;
|
||||
}
|
||||
|
||||
let model = match current_language_model(cx) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
@@ -2133,10 +2174,12 @@ impl GitPanel {
|
||||
self.tracked_count = 0;
|
||||
self.new_staged_count = 0;
|
||||
self.tracked_staged_count = 0;
|
||||
self.entry_count = 0;
|
||||
for entry in &self.entries {
|
||||
let Some(status_entry) = entry.status_entry() else {
|
||||
continue;
|
||||
};
|
||||
self.entry_count += 1;
|
||||
if repo.has_conflict(&status_entry.repo_path) {
|
||||
self.conflicted_count += 1;
|
||||
if self.entry_staging(status_entry).has_staged() {
|
||||
@@ -2263,6 +2306,18 @@ impl GitPanel {
|
||||
self.has_staged_changes()
|
||||
}
|
||||
|
||||
fn render_overflow_menu(&self, id: impl Into<ElementId>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
PopoverMenu::new(id.into())
|
||||
.trigger(
|
||||
IconButton::new("overflow-menu-trigger", IconName::EllipsisVertical)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
)
|
||||
.menu(move |window, cx| Some(git_panel_context_menu(focus_handle.clone(), window, cx)))
|
||||
.anchor(Corner::TopRight)
|
||||
}
|
||||
|
||||
pub(crate) fn render_generate_commit_message_button(
|
||||
&self,
|
||||
cx: &Context<Self>,
|
||||
@@ -2291,14 +2346,25 @@ impl GitPanel {
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
let can_commit = self.can_commit();
|
||||
let editor_focus_handle = self.commit_editor.focus_handle(cx);
|
||||
IconButton::new("generate-commit-message", IconName::AiEdit)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Generate Commit Message",
|
||||
&git::GenerateCommitMessage,
|
||||
&self.commit_editor.focus_handle(cx),
|
||||
))
|
||||
.tooltip(move |window, cx| {
|
||||
if can_commit {
|
||||
Tooltip::for_action_in(
|
||||
"Generate Commit Message",
|
||||
&git::GenerateCommitMessage,
|
||||
&editor_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::simple("No changes to commit", cx)
|
||||
}
|
||||
})
|
||||
.disabled(!can_commit)
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
this.generate_commit_message(cx);
|
||||
}))
|
||||
@@ -2345,10 +2411,7 @@ impl GitPanel {
|
||||
if self.has_unstaged_conflicts() {
|
||||
(false, "You must resolve conflicts before committing")
|
||||
} else if !self.has_staged_changes() && !self.has_tracked_changes() {
|
||||
(
|
||||
false,
|
||||
"You must have either staged changes or tracked files to commit",
|
||||
)
|
||||
(false, "No changes to commit")
|
||||
} else if self.pending_commit.is_some() {
|
||||
(false, "Commit in progress")
|
||||
} else if self.custom_or_suggested_commit_message(cx).is_none() {
|
||||
@@ -2384,6 +2447,61 @@ impl GitPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_panel_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let text;
|
||||
let action;
|
||||
let tooltip;
|
||||
if self.total_staged_count() == self.entry_count && self.entry_count > 0 {
|
||||
text = "Unstage All";
|
||||
action = git::UnstageAll.boxed_clone();
|
||||
tooltip = "git reset";
|
||||
} else {
|
||||
text = "Stage All";
|
||||
action = git::StageAll.boxed_clone();
|
||||
tooltip = "git add --all ."
|
||||
}
|
||||
|
||||
let change_string = match self.entry_count {
|
||||
0 => "No Changes".to_string(),
|
||||
1 => "1 Change".to_string(),
|
||||
_ => format!("{} Changes", self.entry_count),
|
||||
};
|
||||
|
||||
self.panel_header_container(window, cx)
|
||||
.px_2()
|
||||
.child(
|
||||
panel_button(change_string)
|
||||
.color(Color::Muted)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Open diff",
|
||||
&Diff,
|
||||
&self.focus_handle,
|
||||
))
|
||||
.on_click(|_, _, cx| {
|
||||
cx.defer(|cx| {
|
||||
cx.dispatch_action(&Diff);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(div().flex_grow()) // spacer
|
||||
.child(self.render_overflow_menu("overflow_menu"))
|
||||
.child(
|
||||
panel_filled_button(text)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
tooltip,
|
||||
action.as_ref(),
|
||||
&self.focus_handle,
|
||||
))
|
||||
.disabled(self.entry_count == 0)
|
||||
.on_click(move |_, _, cx| {
|
||||
let action = action.boxed_clone();
|
||||
cx.defer(move |cx| {
|
||||
cx.dispatch_action(action.as_ref());
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_footer(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
@@ -2526,15 +2644,14 @@ impl GitPanel {
|
||||
.items_center()
|
||||
.py_2()
|
||||
.px(px(8.))
|
||||
// .bg(cx.theme().colors().background)
|
||||
// .border_t_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap_1p5()
|
||||
.child(
|
||||
div()
|
||||
.flex_grow()
|
||||
.overflow_hidden()
|
||||
.max_w(relative(0.6))
|
||||
.items_center()
|
||||
.max_w(relative(0.85))
|
||||
.h_full()
|
||||
.child(
|
||||
Label::new(commit.subject.clone())
|
||||
@@ -2824,6 +2941,7 @@ impl GitPanel {
|
||||
};
|
||||
let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||
context_menu
|
||||
.context(self.focus_handle.clone())
|
||||
.action(stage_title, ToggleStaged.boxed_clone())
|
||||
.action(restore_title, git::RestoreFile.boxed_clone())
|
||||
.separator()
|
||||
@@ -2840,7 +2958,7 @@ impl GitPanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let context_menu = git_panel_context_menu(window, cx);
|
||||
let context_menu = git_panel_context_menu(self.focus_handle.clone(), window, cx);
|
||||
self.set_context_menu(context_menu, position, window, cx);
|
||||
}
|
||||
|
||||
@@ -2887,6 +3005,9 @@ impl GitPanel {
|
||||
let marked = self.marked_entries.contains(&ix);
|
||||
let status_style = GitPanelSettings::get_global(cx).status_style;
|
||||
let status = entry.status;
|
||||
let modifiers = self.current_modifiers;
|
||||
let shift_held = modifiers.shift;
|
||||
|
||||
let has_conflict = status.is_conflicted();
|
||||
let is_modified = status.is_modified();
|
||||
let is_deleted = status.is_deleted();
|
||||
@@ -2972,7 +3093,7 @@ impl GitPanel {
|
||||
.px(rems(0.75)) // ~12px
|
||||
.overflow_hidden()
|
||||
.flex_none()
|
||||
.gap(DynamicSpacing::Base04.rems(cx))
|
||||
.gap_1p5()
|
||||
.bg(base_bg)
|
||||
.hover(|this| this.bg(hover_bg))
|
||||
.active(|this| this.bg(active_bg))
|
||||
@@ -3017,6 +3138,7 @@ impl GitPanel {
|
||||
.flex_none()
|
||||
.occlude()
|
||||
.cursor_pointer()
|
||||
.ml_neg_0p5()
|
||||
.child(
|
||||
Checkbox::new(checkbox_id, is_staged)
|
||||
.disabled(!has_write_access)
|
||||
@@ -3038,17 +3160,35 @@ impl GitPanel {
|
||||
})
|
||||
})
|
||||
.tooltip(move |window, cx| {
|
||||
let tooltip_name = if entry_staging.is_fully_staged() {
|
||||
"Unstage"
|
||||
let is_staged = entry_staging.is_fully_staged();
|
||||
|
||||
let action = if is_staged { "Unstage" } else { "Stage" };
|
||||
let tooltip_name = if shift_held {
|
||||
format!("{} section", action)
|
||||
} else {
|
||||
"Stage"
|
||||
action.to_string()
|
||||
};
|
||||
|
||||
Tooltip::for_action(tooltip_name, &ToggleStaged, window, cx)
|
||||
let meta = if shift_held {
|
||||
format!(
|
||||
"Release shift to {} single entry",
|
||||
action.to_lowercase()
|
||||
)
|
||||
} else {
|
||||
format!("Shift click to {} section", action.to_lowercase())
|
||||
};
|
||||
|
||||
Tooltip::with_meta(
|
||||
tooltip_name,
|
||||
Some(&ToggleStaged),
|
||||
meta,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(git_status_icon(status, cx))
|
||||
.child(git_status_icon(status))
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
@@ -3107,10 +3247,16 @@ impl Render for GitPanel {
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
|
||||
.when(has_write_access && !project.is_read_only(cx), |this| {
|
||||
this.on_action(cx.listener(|this, &ToggleStaged, window, cx| {
|
||||
this.toggle_staged_for_selected(&ToggleStaged, window, cx)
|
||||
}))
|
||||
.on_action(cx.listener(GitPanel::commit))
|
||||
this.on_action(cx.listener(Self::toggle_staged_for_selected))
|
||||
.on_action(cx.listener(GitPanel::commit))
|
||||
.on_action(cx.listener(Self::stage_all))
|
||||
.on_action(cx.listener(Self::unstage_all))
|
||||
.on_action(cx.listener(Self::stage_selected))
|
||||
.on_action(cx.listener(Self::unstage_selected))
|
||||
.on_action(cx.listener(Self::restore_tracked_files))
|
||||
.on_action(cx.listener(Self::revert_selected))
|
||||
.on_action(cx.listener(Self::clean_all))
|
||||
.on_action(cx.listener(Self::generate_commit_message_action))
|
||||
})
|
||||
.on_action(cx.listener(Self::select_first))
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
@@ -3119,16 +3265,9 @@ impl Render for GitPanel {
|
||||
.on_action(cx.listener(Self::close_panel))
|
||||
.on_action(cx.listener(Self::open_diff))
|
||||
.on_action(cx.listener(Self::open_file))
|
||||
.on_action(cx.listener(Self::revert_selected))
|
||||
.on_action(cx.listener(Self::focus_changes_list))
|
||||
.on_action(cx.listener(Self::focus_editor))
|
||||
.on_action(cx.listener(Self::toggle_staged_for_selected))
|
||||
.on_action(cx.listener(Self::stage_all))
|
||||
.on_action(cx.listener(Self::unstage_all))
|
||||
.on_action(cx.listener(Self::restore_tracked_files))
|
||||
.on_action(cx.listener(Self::clean_all))
|
||||
.on_action(cx.listener(Self::expand_commit_editor))
|
||||
.on_action(cx.listener(Self::generate_commit_message_action))
|
||||
.when(has_write_access && has_co_authors, |git_panel| {
|
||||
git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
|
||||
})
|
||||
@@ -3148,6 +3287,7 @@ impl Render for GitPanel {
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.child(self.render_panel_header(window, cx))
|
||||
.map(|this| {
|
||||
if has_entries {
|
||||
this.child(self.render_entries(has_write_access, window, cx))
|
||||
@@ -3350,23 +3490,11 @@ impl PanelRepoFooter {
|
||||
git_panel: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_overflow_menu(&self, id: impl Into<ElementId>) -> impl IntoElement {
|
||||
PopoverMenu::new(id.into())
|
||||
.trigger(
|
||||
IconButton::new("overflow-menu-trigger", IconName::EllipsisVertical)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
)
|
||||
.menu(move |window, cx| Some(git_panel_context_menu(window, cx)))
|
||||
.anchor(Corner::TopRight)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -3446,6 +3574,7 @@ impl RenderOnce for PanelRepoFooter {
|
||||
.h(px(36.))
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
@@ -3455,7 +3584,11 @@ impl RenderOnce for PanelRepoFooter {
|
||||
div().child(
|
||||
Icon::new(IconName::GitBranchSmall)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
.color(if single_repo {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Muted
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(repo_selector)
|
||||
@@ -3474,7 +3607,6 @@ impl RenderOnce for PanelRepoFooter {
|
||||
.gap_1()
|
||||
.flex_shrink_0()
|
||||
.children(spinner)
|
||||
.child(self.render_overflow_menu(overflow_menu_id))
|
||||
.when_some(branch, |this, branch| {
|
||||
let mut focus_handle = None;
|
||||
if let Some(git_panel) = self.git_panel.as_ref() {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use ::settings::Settings;
|
||||
use git::{
|
||||
repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
|
||||
status::FileStatus,
|
||||
status::{FileStatus, StatusCode, UnmergedStatus, UnmergedStatusCode},
|
||||
};
|
||||
use git_panel_settings::GitPanelSettings;
|
||||
use gpui::{App, Entity, FocusHandle};
|
||||
use project::Project;
|
||||
use project_diff::ProjectDiff;
|
||||
use ui::{ActiveTheme, Color, Icon, IconName, IntoElement, SharedString};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
mod askpass_modal;
|
||||
@@ -29,69 +29,65 @@ pub fn init(cx: &mut App) {
|
||||
|
||||
cx.observe_new(|workspace: &mut Workspace, _, cx| {
|
||||
let project = workspace.project().read(cx);
|
||||
if project.is_via_collab() {
|
||||
if project.is_read_only(cx) {
|
||||
return;
|
||||
}
|
||||
workspace.register_action(|workspace, _: &git::Fetch, window, cx| {
|
||||
if !project.is_via_collab() {
|
||||
workspace.register_action(|workspace, _: &git::Fetch, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.fetch(window, cx);
|
||||
});
|
||||
});
|
||||
workspace.register_action(|workspace, _: &git::Push, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.push(false, window, cx);
|
||||
});
|
||||
});
|
||||
workspace.register_action(|workspace, _: &git::ForcePush, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.push(true, window, cx);
|
||||
});
|
||||
});
|
||||
workspace.register_action(|workspace, _: &git::Pull, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.pull(window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
workspace.register_action(|workspace, action: &git::StageAll, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.fetch(window, cx);
|
||||
panel.stage_all(action, window, cx);
|
||||
});
|
||||
});
|
||||
workspace.register_action(|workspace, _: &git::Push, window, cx| {
|
||||
workspace.register_action(|workspace, action: &git::UnstageAll, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.push(false, window, cx);
|
||||
});
|
||||
});
|
||||
workspace.register_action(|workspace, _: &git::ForcePush, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.push(true, window, cx);
|
||||
});
|
||||
});
|
||||
workspace.register_action(|workspace, _: &git::Pull, window, cx| {
|
||||
let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.pull(window, cx);
|
||||
panel.unstage_all(action, window, cx);
|
||||
});
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
// TODO: Add updated status colors to theme
|
||||
pub fn git_status_icon(status: FileStatus, cx: &App) -> impl IntoElement {
|
||||
let (icon_name, color) = if status.is_conflicted() {
|
||||
(
|
||||
IconName::Warning,
|
||||
cx.theme().colors().version_control_conflict,
|
||||
)
|
||||
} else if status.is_deleted() {
|
||||
(
|
||||
IconName::SquareMinus,
|
||||
cx.theme().colors().version_control_deleted,
|
||||
)
|
||||
} else if status.is_modified() {
|
||||
(
|
||||
IconName::SquareDot,
|
||||
cx.theme().colors().version_control_modified,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
IconName::SquarePlus,
|
||||
cx.theme().colors().version_control_added,
|
||||
)
|
||||
};
|
||||
Icon::new(icon_name).color(Color::Custom(color))
|
||||
pub fn git_status_icon(status: FileStatus) -> impl IntoElement {
|
||||
GitStatusIcon::new(status)
|
||||
}
|
||||
|
||||
fn can_push_and_pull(project: &Entity<Project>, cx: &App) -> bool {
|
||||
@@ -157,6 +153,7 @@ mod remote_button {
|
||||
0,
|
||||
0,
|
||||
Some(IconName::ArrowCircle),
|
||||
keybinding_target.clone(),
|
||||
move |_, window, cx| {
|
||||
window.dispatch_action(Box::new(git::Fetch), cx);
|
||||
},
|
||||
@@ -184,6 +181,7 @@ mod remote_button {
|
||||
ahead as usize,
|
||||
0,
|
||||
None,
|
||||
keybinding_target.clone(),
|
||||
move |_, window, cx| {
|
||||
window.dispatch_action(Box::new(git::Push), cx);
|
||||
},
|
||||
@@ -212,6 +210,7 @@ mod remote_button {
|
||||
ahead as usize,
|
||||
behind as usize,
|
||||
None,
|
||||
keybinding_target.clone(),
|
||||
move |_, window, cx| {
|
||||
window.dispatch_action(Box::new(git::Pull), cx);
|
||||
},
|
||||
@@ -238,6 +237,7 @@ mod remote_button {
|
||||
0,
|
||||
0,
|
||||
Some(IconName::ArrowUpFromLine),
|
||||
keybinding_target.clone(),
|
||||
move |_, window, cx| {
|
||||
window.dispatch_action(Box::new(git::Push), cx);
|
||||
},
|
||||
@@ -264,6 +264,7 @@ mod remote_button {
|
||||
0,
|
||||
0,
|
||||
Some(IconName::ArrowUpFromLine),
|
||||
keybinding_target.clone(),
|
||||
move |_, window, cx| {
|
||||
window.dispatch_action(Box::new(git::Push), cx);
|
||||
},
|
||||
@@ -305,7 +306,10 @@ mod remote_button {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_git_action_menu(id: impl Into<ElementId>) -> impl IntoElement {
|
||||
fn render_git_action_menu(
|
||||
id: impl Into<ElementId>,
|
||||
keybinding_target: Option<FocusHandle>,
|
||||
) -> impl IntoElement {
|
||||
PopoverMenu::new(id.into())
|
||||
.trigger(
|
||||
ui::ButtonLike::new_rounded_right("split-button-right")
|
||||
@@ -320,6 +324,9 @@ mod remote_button {
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||
context_menu
|
||||
.when_some(keybinding_target.clone(), |el, keybinding_target| {
|
||||
el.context(keybinding_target.clone())
|
||||
})
|
||||
.action("Fetch", git::Fetch.boxed_clone())
|
||||
.action("Pull", git::Pull.boxed_clone())
|
||||
.separator()
|
||||
@@ -337,12 +344,14 @@ mod remote_button {
|
||||
}
|
||||
|
||||
impl SplitButton {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
id: impl Into<SharedString>,
|
||||
left_label: impl Into<SharedString>,
|
||||
ahead_count: usize,
|
||||
behind_count: usize,
|
||||
left_icon: Option<IconName>,
|
||||
keybinding_target: Option<FocusHandle>,
|
||||
left_on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
) -> Self {
|
||||
@@ -400,9 +409,10 @@ mod remote_button {
|
||||
.on_click(left_on_click)
|
||||
.tooltip(tooltip);
|
||||
|
||||
let right = render_git_action_menu(ElementId::Name(
|
||||
format!("split-button-right-{}", id).into(),
|
||||
))
|
||||
let right = render_git_action_menu(
|
||||
ElementId::Name(format!("split-button-right-{}", id).into()),
|
||||
keybinding_target,
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
Self { left, right }
|
||||
@@ -433,3 +443,79 @@ mod remote_button {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Version Control")]
|
||||
pub struct GitStatusIcon {
|
||||
status: FileStatus,
|
||||
}
|
||||
|
||||
impl GitStatusIcon {
|
||||
pub fn new(status: FileStatus) -> Self {
|
||||
Self { status }
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for GitStatusIcon {
|
||||
fn render(self, _window: &mut ui::Window, cx: &mut App) -> impl IntoElement {
|
||||
let status = self.status;
|
||||
|
||||
let (icon_name, color) = if status.is_conflicted() {
|
||||
(
|
||||
IconName::Warning,
|
||||
cx.theme().colors().version_control_conflict,
|
||||
)
|
||||
} else if status.is_deleted() {
|
||||
(
|
||||
IconName::SquareMinus,
|
||||
cx.theme().colors().version_control_deleted,
|
||||
)
|
||||
} else if status.is_modified() {
|
||||
(
|
||||
IconName::SquareDot,
|
||||
cx.theme().colors().version_control_modified,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
IconName::SquarePlus,
|
||||
cx.theme().colors().version_control_added,
|
||||
)
|
||||
};
|
||||
|
||||
Icon::new(icon_name).color(Color::Custom(color))
|
||||
}
|
||||
}
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl ComponentPreview for GitStatusIcon {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
fn tracked_file_status(code: StatusCode) -> FileStatus {
|
||||
FileStatus::Tracked(git::status::TrackedStatus {
|
||||
index_status: code,
|
||||
worktree_status: code,
|
||||
})
|
||||
}
|
||||
|
||||
let modified = tracked_file_status(StatusCode::Modified);
|
||||
let added = tracked_file_status(StatusCode::Added);
|
||||
let deleted = tracked_file_status(StatusCode::Deleted);
|
||||
let conflict = UnmergedStatus {
|
||||
first_head: UnmergedStatusCode::Updated,
|
||||
second_head: UnmergedStatusCode::Updated,
|
||||
}
|
||||
.into();
|
||||
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group(vec![
|
||||
single_example("Modified", GitStatusIcon::new(modified).into_any_element()),
|
||||
single_example("Added", GitStatusIcon::new(added).into_any_element()),
|
||||
single_example("Deleted", GitStatusIcon::new(deleted).into_any_element()),
|
||||
single_example(
|
||||
"Conflicted",
|
||||
GitStatusIcon::new(conflict).into_any_element(),
|
||||
),
|
||||
])])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use editor::{
|
||||
scroll::Autoscroll,
|
||||
Editor, EditorEvent,
|
||||
};
|
||||
use feature_flags::FeatureFlagViewExt;
|
||||
use futures::StreamExt;
|
||||
use git::{
|
||||
repository::Branch, status::FileStatus, Commit, StageAll, StageAndNext, ToggleStaged,
|
||||
@@ -64,16 +63,13 @@ const NEW_NAMESPACE: &'static str = "2";
|
||||
|
||||
impl ProjectDiff {
|
||||
pub(crate) fn register(
|
||||
_: &mut Workspace,
|
||||
window: Option<&mut Window>,
|
||||
workspace: &mut Workspace,
|
||||
_window: Option<&mut Window>,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let Some(window) = window else { return };
|
||||
cx.when_flag_enabled::<feature_flags::GitUiFeatureFlag>(window, |workspace, _, _cx| {
|
||||
workspace.register_action(Self::deploy);
|
||||
workspace.register_action(|workspace, _: &Add, window, cx| {
|
||||
Self::deploy(workspace, &Diff, window, cx);
|
||||
});
|
||||
workspace.register_action(Self::deploy);
|
||||
workspace.register_action(|workspace, _: &Add, window, cx| {
|
||||
Self::deploy(workspace, &Diff, window, cx);
|
||||
});
|
||||
|
||||
workspace::register_serializable_item::<ProjectDiff>(cx);
|
||||
@@ -125,6 +121,12 @@ impl ProjectDiff {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn autoscroll(&self, cx: &mut Context<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.request_autoscroll(Autoscroll::fit(), cx);
|
||||
})
|
||||
}
|
||||
|
||||
fn new(
|
||||
project: Entity<Project>,
|
||||
workspace: Entity<Workspace>,
|
||||
@@ -472,7 +474,10 @@ impl ProjectDiff {
|
||||
})?;
|
||||
}
|
||||
}
|
||||
this.update(&mut cx, |this, _| this.pending_scroll.take())?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_scroll.take();
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -152,7 +152,10 @@ impl GoToLine {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
editor::EditorEvent::Blurred => cx.emit(DismissEvent),
|
||||
editor::EditorEvent::Blurred => {
|
||||
self.prev_scroll_position.take();
|
||||
cx.emit(DismissEvent)
|
||||
}
|
||||
editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ blade-macros.workspace = true
|
||||
flume = "0.11"
|
||||
rand.workspace = true
|
||||
windows.workspace = true
|
||||
windows-core = "0.58"
|
||||
windows-core = "0.60"
|
||||
|
||||
[dev-dependencies]
|
||||
backtrace = "0.3"
|
||||
|
||||
@@ -178,6 +178,7 @@ impl EntityMap {
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn double_lease_panic<T>(operation: &str) -> ! {
|
||||
panic!(
|
||||
"cannot {operation} {} while it is already being updated",
|
||||
|
||||
@@ -683,11 +683,11 @@ impl Default for Background {
|
||||
}
|
||||
|
||||
/// Creates a hash pattern background
|
||||
pub fn pattern_slash(color: Hsla, thickness: f32) -> Background {
|
||||
pub fn pattern_slash(color: Hsla, height: f32) -> Background {
|
||||
Background {
|
||||
tag: BackgroundTag::PatternSlash,
|
||||
solid: color,
|
||||
gradient_angle_or_pattern_height: thickness,
|
||||
gradient_angle_or_pattern_height: height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,9 @@ impl Keystroke {
|
||||
}
|
||||
|
||||
/// key syntax is:
|
||||
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
|
||||
/// [secondary-][ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
|
||||
/// key_char syntax is only used for generating test events,
|
||||
/// secondary means "cmd" on macOS and "ctrl" on other platforms
|
||||
/// when matching a key with an key_char set will be matched without it.
|
||||
pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
|
||||
let mut control = false;
|
||||
@@ -95,6 +96,13 @@ impl Keystroke {
|
||||
"alt" => alt = true,
|
||||
"shift" => shift = true,
|
||||
"fn" => function = true,
|
||||
"secondary" => {
|
||||
if cfg!(target_os = "macos") {
|
||||
platform = true
|
||||
} else {
|
||||
control = true
|
||||
};
|
||||
}
|
||||
"cmd" | "super" | "win" => platform = true,
|
||||
_ => {
|
||||
if let Some(next) = components.peek() {
|
||||
|
||||
@@ -115,7 +115,6 @@ pub struct WaylandWindowStatePtr {
|
||||
}
|
||||
|
||||
impl WaylandWindowState {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
handle: AnyWindowHandle,
|
||||
surface: wl_surface::WlSurface,
|
||||
|
||||
@@ -353,7 +353,6 @@ where
|
||||
}
|
||||
|
||||
impl X11WindowState {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
handle: AnyWindowHandle,
|
||||
client: X11ClientStatePtr,
|
||||
@@ -712,7 +711,6 @@ enum WmHintPropertyState {
|
||||
}
|
||||
|
||||
impl X11Window {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
handle: AnyWindowHandle,
|
||||
client: X11ClientStatePtr,
|
||||
|
||||
@@ -347,20 +347,7 @@ impl MacPlatform {
|
||||
msg_send![item, setAllowsAutomaticKeyEquivalentLocalization: NO];
|
||||
}
|
||||
item.setKeyEquivalentModifierMask_(mask);
|
||||
}
|
||||
// For multi-keystroke bindings, render the keystroke as part of the title.
|
||||
else {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut name = format!("{name} [");
|
||||
for (i, keystroke) in keystrokes.iter().enumerate() {
|
||||
if i > 0 {
|
||||
name.push(' ');
|
||||
}
|
||||
write!(&mut name, "{}", keystroke).unwrap();
|
||||
}
|
||||
name.push(']');
|
||||
|
||||
} else {
|
||||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(&name),
|
||||
|
||||
@@ -268,6 +268,10 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C
|
||||
sel!(windowWillEnterFullScreen:),
|
||||
window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(windowWillExitFullScreen:),
|
||||
window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(windowDidMove:),
|
||||
window_did_move as extern "C" fn(&Object, Sel, id),
|
||||
@@ -334,6 +338,7 @@ struct MacWindowState {
|
||||
last_key_equivalent: Option<KeyDownEvent>,
|
||||
synthetic_drag_counter: usize,
|
||||
traffic_light_position: Option<Point<Pixels>>,
|
||||
transparent_titlebar: bool,
|
||||
previous_modifiers_changed_event: Option<PlatformInput>,
|
||||
keystroke_for_do_command: Option<Keystroke>,
|
||||
do_command_handled: Option<bool>,
|
||||
@@ -613,6 +618,9 @@ impl MacWindow {
|
||||
traffic_light_position: titlebar
|
||||
.as_ref()
|
||||
.and_then(|titlebar| titlebar.traffic_light_position),
|
||||
transparent_titlebar: titlebar
|
||||
.as_ref()
|
||||
.map_or(true, |titlebar| titlebar.appears_transparent),
|
||||
previous_modifiers_changed_event: None,
|
||||
keystroke_for_do_command: None,
|
||||
do_command_handled: None,
|
||||
@@ -1490,6 +1498,19 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
lock.fullscreen_restore_bounds = lock.bounds();
|
||||
unsafe {
|
||||
lock.native_window.setTitlebarAppearsTransparent_(NO);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
if lock.transparent_titlebar {
|
||||
unsafe {
|
||||
lock.native_window.setTitlebarAppearsTransparent_(YES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
|
||||
|
||||
@@ -170,7 +170,7 @@ fn set_data_to_clipboard<T>(data: &[T], format: u32) -> Result<()> {
|
||||
let handle = GlobalLock(global);
|
||||
std::ptr::copy_nonoverlapping(data.as_ptr(), handle as _, data.len());
|
||||
let _ = GlobalUnlock(global);
|
||||
SetClipboardData(format, HANDLE(global.0))?;
|
||||
SetClipboardData(format, Some(HANDLE(global.0)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1049,7 +1049,7 @@ impl IDWriteTextRenderer_Impl for TextRenderer_Impl {
|
||||
_measuringmode: DWRITE_MEASURING_MODE,
|
||||
glyphrun: *const DWRITE_GLYPH_RUN,
|
||||
glyphrundescription: *const DWRITE_GLYPH_RUN_DESCRIPTION,
|
||||
_clientdrawingeffect: Option<&windows::core::IUnknown>,
|
||||
_clientdrawingeffect: windows::core::Ref<windows::core::IUnknown>,
|
||||
) -> windows::core::Result<()> {
|
||||
unsafe {
|
||||
let glyphrun = &*glyphrun;
|
||||
@@ -1113,7 +1113,7 @@ impl IDWriteTextRenderer_Impl for TextRenderer_Impl {
|
||||
_baselineoriginx: f32,
|
||||
_baselineoriginy: f32,
|
||||
_underline: *const DWRITE_UNDERLINE,
|
||||
_clientdrawingeffect: Option<&windows::core::IUnknown>,
|
||||
_clientdrawingeffect: windows::core::Ref<windows::core::IUnknown>,
|
||||
) -> windows::core::Result<()> {
|
||||
Err(windows::core::Error::new(
|
||||
E_NOTIMPL,
|
||||
@@ -1127,7 +1127,7 @@ impl IDWriteTextRenderer_Impl for TextRenderer_Impl {
|
||||
_baselineoriginx: f32,
|
||||
_baselineoriginy: f32,
|
||||
_strikethrough: *const DWRITE_STRIKETHROUGH,
|
||||
_clientdrawingeffect: Option<&windows::core::IUnknown>,
|
||||
_clientdrawingeffect: windows::core::Ref<windows::core::IUnknown>,
|
||||
) -> windows::core::Result<()> {
|
||||
Err(windows::core::Error::new(
|
||||
E_NOTIMPL,
|
||||
@@ -1140,10 +1140,10 @@ impl IDWriteTextRenderer_Impl for TextRenderer_Impl {
|
||||
_clientdrawingcontext: *const ::core::ffi::c_void,
|
||||
_originx: f32,
|
||||
_originy: f32,
|
||||
_inlineobject: Option<&IDWriteInlineObject>,
|
||||
_inlineobject: windows::core::Ref<IDWriteInlineObject>,
|
||||
_issideways: BOOL,
|
||||
_isrighttoleft: BOOL,
|
||||
_clientdrawingeffect: Option<&windows::core::IUnknown>,
|
||||
_clientdrawingeffect: windows::core::Ref<windows::core::IUnknown>,
|
||||
) -> windows::core::Result<()> {
|
||||
Err(windows::core::Error::new(
|
||||
E_NOTIMPL,
|
||||
|
||||
@@ -215,7 +215,7 @@ fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
|
||||
let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
|
||||
unsafe {
|
||||
EnumDisplayMonitors(
|
||||
HDC::default(),
|
||||
None,
|
||||
None,
|
||||
Some(monitor_enum_proc),
|
||||
LPARAM(&mut monitors as *mut _ as _),
|
||||
|
||||
@@ -177,7 +177,12 @@ fn handle_size_msg(
|
||||
|
||||
fn handle_size_move_loop(handle: HWND) -> Option<isize> {
|
||||
unsafe {
|
||||
let ret = SetTimer(handle, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
|
||||
let ret = SetTimer(
|
||||
Some(handle),
|
||||
SIZE_MOVE_LOOP_TIMER_ID,
|
||||
USER_TIMER_MINIMUM,
|
||||
None,
|
||||
);
|
||||
if ret == 0 {
|
||||
log::error!(
|
||||
"unable to create timer: {}",
|
||||
@@ -190,7 +195,7 @@ fn handle_size_move_loop(handle: HWND) -> Option<isize> {
|
||||
|
||||
fn handle_size_move_loop_exit(handle: HWND) -> Option<isize> {
|
||||
unsafe {
|
||||
KillTimer(handle, SIZE_MOVE_LOOP_TIMER_ID).log_err();
|
||||
KillTimer(Some(handle), SIZE_MOVE_LOOP_TIMER_ID).log_err();
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -217,7 +222,7 @@ fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Optio
|
||||
request_frame(Default::default());
|
||||
state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
|
||||
}
|
||||
unsafe { ValidateRect(handle, None).ok().log_err() };
|
||||
unsafe { ValidateRect(Some(handle), None).ok().log_err() };
|
||||
Some(0)
|
||||
}
|
||||
|
||||
@@ -776,7 +781,7 @@ fn handle_activate_msg(
|
||||
if state_ptr.hide_title_bar {
|
||||
if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
|
||||
unsafe {
|
||||
InvalidateRect(handle, Some(&titlebar_rect), FALSE)
|
||||
InvalidateRect(Some(handle), Some(&titlebar_rect), false)
|
||||
.ok()
|
||||
.log_err()
|
||||
};
|
||||
@@ -1105,7 +1110,7 @@ fn handle_nc_mouse_up_msg(
|
||||
HTCLOSE => {
|
||||
if last_button == HTCLOSE {
|
||||
unsafe {
|
||||
PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default())
|
||||
PostMessageW(Some(handle), WM_CLOSE, WPARAM::default(), LPARAM::default())
|
||||
.log_err()
|
||||
};
|
||||
handled = true;
|
||||
@@ -1133,7 +1138,7 @@ fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Op
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
|
||||
unsafe { SetCursor(Some(state_ptr.state.borrow().current_cursor)) };
|
||||
Some(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -130,14 +130,9 @@ impl WindowsPlatform {
|
||||
fn redraw_all(&self) {
|
||||
for handle in self.raw_window_handles.read().iter() {
|
||||
unsafe {
|
||||
RedrawWindow(
|
||||
*handle,
|
||||
None,
|
||||
HRGN::default(),
|
||||
RDW_INVALIDATE | RDW_UPDATENOW,
|
||||
)
|
||||
.ok()
|
||||
.log_err();
|
||||
RedrawWindow(Some(*handle), None, None, RDW_INVALIDATE | RDW_UPDATENOW)
|
||||
.ok()
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +151,7 @@ impl WindowsPlatform {
|
||||
.read()
|
||||
.iter()
|
||||
.for_each(|handle| unsafe {
|
||||
PostMessageW(*handle, message, wparam, lparam).log_err();
|
||||
PostMessageW(Some(*handle), message, wparam, lparam).log_err();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -620,7 +615,7 @@ impl Platform for WindowsPlatform {
|
||||
CredReadW(
|
||||
PCWSTR::from_raw(target_name.as_ptr()),
|
||||
CRED_TYPE_GENERIC,
|
||||
0,
|
||||
None,
|
||||
&mut credentials,
|
||||
)?
|
||||
};
|
||||
@@ -648,7 +643,13 @@ impl Platform for WindowsPlatform {
|
||||
.chain(Some(0))
|
||||
.collect_vec();
|
||||
self.foreground_executor().spawn(async move {
|
||||
unsafe { CredDeleteW(PCWSTR::from_raw(target_name.as_ptr()), CRED_TYPE_GENERIC, 0)? };
|
||||
unsafe {
|
||||
CredDeleteW(
|
||||
PCWSTR::from_raw(target_name.as_ptr()),
|
||||
CRED_TYPE_GENERIC,
|
||||
None,
|
||||
)?
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -805,7 +806,7 @@ fn load_icon() -> Result<HICON> {
|
||||
let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
|
||||
let handle = unsafe {
|
||||
LoadImageW(
|
||||
module,
|
||||
Some(module.into()),
|
||||
windows::core::PCWSTR(1 as _),
|
||||
IMAGE_ICON,
|
||||
0,
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::sync::OnceLock;
|
||||
|
||||
use ::util::ResultExt;
|
||||
use windows::{
|
||||
core::BOOL,
|
||||
Wdk::System::SystemServices::RtlGetVersion,
|
||||
Win32::{Foundation::*, Graphics::Dwm::*, UI::WindowsAndMessaging::*},
|
||||
UI::{
|
||||
|
||||
@@ -296,7 +296,7 @@ impl WindowsWindowStatePtr {
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
state_ptr.hwnd,
|
||||
HWND::default(),
|
||||
None,
|
||||
x,
|
||||
y,
|
||||
cx,
|
||||
@@ -433,7 +433,7 @@ impl WindowsWindow {
|
||||
CW_USEDEFAULT,
|
||||
None,
|
||||
None,
|
||||
hinstance,
|
||||
Some(hinstance.into()),
|
||||
lpparam,
|
||||
)
|
||||
};
|
||||
@@ -650,7 +650,7 @@ impl PlatformWindow for WindowsWindow {
|
||||
.spawn(async move {
|
||||
this.set_window_placement().log_err();
|
||||
unsafe { SetActiveWindow(hwnd).log_err() };
|
||||
unsafe { SetFocus(hwnd).log_err() };
|
||||
unsafe { SetFocus(Some(hwnd)).log_err() };
|
||||
// todo(windows)
|
||||
// crate `windows 0.56` reports true as Err
|
||||
unsafe { SetForegroundWindow(hwnd).as_bool() };
|
||||
@@ -817,16 +817,13 @@ impl WindowsDragDropHandler {
|
||||
impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
|
||||
fn DragEnter(
|
||||
&self,
|
||||
pdataobj: Option<&IDataObject>,
|
||||
pdataobj: windows::core::Ref<IDataObject>,
|
||||
_grfkeystate: MODIFIERKEYS_FLAGS,
|
||||
pt: &POINTL,
|
||||
pdweffect: *mut DROPEFFECT,
|
||||
) -> windows::core::Result<()> {
|
||||
unsafe {
|
||||
let Some(idata_obj) = pdataobj else {
|
||||
log::info!("no dragging file or directory detected");
|
||||
return Ok(());
|
||||
};
|
||||
let idata_obj = pdataobj.ok()?;
|
||||
let config = FORMATETC {
|
||||
cfFormat: CF_HDROP.0,
|
||||
ptd: std::ptr::null_mut() as _,
|
||||
@@ -905,7 +902,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
|
||||
|
||||
fn Drop(
|
||||
&self,
|
||||
_pdataobj: Option<&IDataObject>,
|
||||
_pdataobj: windows::core::Ref<IDataObject>,
|
||||
_grfkeystate: MODIFIERKEYS_FLAGS,
|
||||
pt: &POINTL,
|
||||
_pdweffect: *mut DROPEFFECT,
|
||||
|
||||
@@ -81,6 +81,29 @@ impl ShapedLine {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Paint the background of the line to the window.
|
||||
pub fn paint_background(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
paint_line_background(
|
||||
origin,
|
||||
&self.layout,
|
||||
line_height,
|
||||
TextAlign::default(),
|
||||
None,
|
||||
&self.decoration_runs,
|
||||
&[],
|
||||
window,
|
||||
cx,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A line of text that has been shaped, decorated, and wrapped by the text layout system.
|
||||
@@ -132,7 +155,6 @@ impl WrappedLine {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn paint_line(
|
||||
origin: Point<Pixels>,
|
||||
layout: &LineLayout,
|
||||
@@ -160,7 +182,6 @@ fn paint_line(
|
||||
let mut color = black();
|
||||
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
|
||||
let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
|
||||
let text_system = cx.text_system().clone();
|
||||
let mut glyph_origin = point(
|
||||
aligned_origin_x(
|
||||
@@ -183,21 +204,6 @@ fn paint_line(
|
||||
|
||||
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
|
||||
wraps.next();
|
||||
if let Some((background_origin, background_color)) = current_background.as_mut()
|
||||
{
|
||||
if glyph_origin.x == background_origin.x {
|
||||
background_origin.x -= max_glyph_size.width.half()
|
||||
}
|
||||
window.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: *background_origin,
|
||||
size: size(glyph_origin.x - background_origin.x, line_height),
|
||||
},
|
||||
*background_color,
|
||||
));
|
||||
background_origin.x = origin.x;
|
||||
background_origin.y += line_height;
|
||||
}
|
||||
if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
|
||||
if glyph_origin.x == underline_origin.x {
|
||||
underline_origin.x -= max_glyph_size.width.half();
|
||||
@@ -237,7 +243,6 @@ fn paint_line(
|
||||
}
|
||||
prev_glyph_position = glyph.position;
|
||||
|
||||
let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
|
||||
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
|
||||
if glyph.index >= run_end {
|
||||
@@ -253,18 +258,6 @@ fn paint_line(
|
||||
}
|
||||
|
||||
if let Some(style_run) = style_run {
|
||||
if let Some((_, background_color)) = &mut current_background {
|
||||
if style_run.background_color.as_ref() != Some(background_color) {
|
||||
finished_background = current_background.take();
|
||||
}
|
||||
}
|
||||
if let Some(run_background) = style_run.background_color {
|
||||
current_background.get_or_insert((
|
||||
point(glyph_origin.x, glyph_origin.y),
|
||||
run_background,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some((_, underline_style)) = &mut current_underline {
|
||||
if style_run.underline.as_ref() != Some(underline_style) {
|
||||
finished_underline = current_underline.take();
|
||||
@@ -306,26 +299,11 @@ fn paint_line(
|
||||
color = style_run.color;
|
||||
} else {
|
||||
run_end = layout.len;
|
||||
finished_background = current_background.take();
|
||||
finished_underline = current_underline.take();
|
||||
finished_strikethrough = current_strikethrough.take();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((mut background_origin, background_color)) = finished_background {
|
||||
let mut width = glyph_origin.x - background_origin.x;
|
||||
if background_origin.x == glyph_origin.x {
|
||||
background_origin.x -= max_glyph_size.width.half();
|
||||
};
|
||||
window.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: background_origin,
|
||||
size: size(width, line_height),
|
||||
},
|
||||
background_color,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some((mut underline_origin, underline_style)) = finished_underline {
|
||||
if underline_origin.x == glyph_origin.x {
|
||||
underline_origin.x -= max_glyph_size.width.half();
|
||||
@@ -384,19 +362,6 @@ fn paint_line(
|
||||
last_line_end_x -= glyph.position.x;
|
||||
}
|
||||
|
||||
if let Some((mut background_origin, background_color)) = current_background.take() {
|
||||
if last_line_end_x == background_origin.x {
|
||||
background_origin.x -= max_glyph_size.width.half()
|
||||
};
|
||||
window.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: background_origin,
|
||||
size: size(last_line_end_x - background_origin.x, line_height),
|
||||
},
|
||||
background_color,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some((mut underline_start, underline_style)) = current_underline.take() {
|
||||
if last_line_end_x == underline_start.x {
|
||||
underline_start.x -= max_glyph_size.width.half()
|
||||
@@ -423,6 +388,141 @@ fn paint_line(
|
||||
})
|
||||
}
|
||||
|
||||
fn paint_line_background(
|
||||
origin: Point<Pixels>,
|
||||
layout: &LineLayout,
|
||||
line_height: Pixels,
|
||||
align: TextAlign,
|
||||
align_width: Option<Pixels>,
|
||||
decoration_runs: &[DecorationRun],
|
||||
wrap_boundaries: &[WrapBoundary],
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
let line_bounds = Bounds::new(
|
||||
origin,
|
||||
size(
|
||||
layout.width,
|
||||
line_height * (wrap_boundaries.len() as f32 + 1.),
|
||||
),
|
||||
);
|
||||
window.paint_layer(line_bounds, |window| {
|
||||
let mut decoration_runs = decoration_runs.iter();
|
||||
let mut wraps = wrap_boundaries.iter().peekable();
|
||||
let mut run_end = 0;
|
||||
let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
|
||||
let text_system = cx.text_system().clone();
|
||||
let mut glyph_origin = point(
|
||||
aligned_origin_x(
|
||||
origin,
|
||||
align_width.unwrap_or(layout.width),
|
||||
px(0.0),
|
||||
&align,
|
||||
layout,
|
||||
wraps.peek(),
|
||||
),
|
||||
origin.y,
|
||||
);
|
||||
let mut prev_glyph_position = Point::default();
|
||||
let mut max_glyph_size = size(px(0.), px(0.));
|
||||
for (run_ix, run) in layout.runs.iter().enumerate() {
|
||||
max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
|
||||
|
||||
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
||||
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
|
||||
|
||||
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
|
||||
wraps.next();
|
||||
if let Some((background_origin, background_color)) = current_background.as_mut()
|
||||
{
|
||||
if glyph_origin.x == background_origin.x {
|
||||
background_origin.x -= max_glyph_size.width.half()
|
||||
}
|
||||
window.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: *background_origin,
|
||||
size: size(glyph_origin.x - background_origin.x, line_height),
|
||||
},
|
||||
*background_color,
|
||||
));
|
||||
background_origin.x = origin.x;
|
||||
background_origin.y += line_height;
|
||||
}
|
||||
}
|
||||
prev_glyph_position = glyph.position;
|
||||
|
||||
let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
|
||||
if glyph.index >= run_end {
|
||||
let mut style_run = decoration_runs.next();
|
||||
|
||||
// ignore style runs that apply to a partial glyph
|
||||
while let Some(run) = style_run {
|
||||
if glyph.index < run_end + (run.len as usize) {
|
||||
break;
|
||||
}
|
||||
run_end += run.len as usize;
|
||||
style_run = decoration_runs.next();
|
||||
}
|
||||
|
||||
if let Some(style_run) = style_run {
|
||||
if let Some((_, background_color)) = &mut current_background {
|
||||
if style_run.background_color.as_ref() != Some(background_color) {
|
||||
finished_background = current_background.take();
|
||||
}
|
||||
}
|
||||
if let Some(run_background) = style_run.background_color {
|
||||
current_background.get_or_insert((
|
||||
point(glyph_origin.x, glyph_origin.y),
|
||||
run_background,
|
||||
));
|
||||
}
|
||||
run_end += style_run.len as usize;
|
||||
} else {
|
||||
run_end = layout.len;
|
||||
finished_background = current_background.take();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((mut background_origin, background_color)) = finished_background {
|
||||
let mut width = glyph_origin.x - background_origin.x;
|
||||
if background_origin.x == glyph_origin.x {
|
||||
background_origin.x -= max_glyph_size.width.half();
|
||||
};
|
||||
window.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: background_origin,
|
||||
size: size(width, line_height),
|
||||
},
|
||||
background_color,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_line_end_x = origin.x + layout.width;
|
||||
if let Some(boundary) = wrap_boundaries.last() {
|
||||
let run = &layout.runs[boundary.run_ix];
|
||||
let glyph = &run.glyphs[boundary.glyph_ix];
|
||||
last_line_end_x -= glyph.position.x;
|
||||
}
|
||||
|
||||
if let Some((mut background_origin, background_color)) = current_background.take() {
|
||||
if last_line_end_x == background_origin.x {
|
||||
background_origin.x -= max_glyph_size.width.half()
|
||||
};
|
||||
window.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: background_origin,
|
||||
size: size(last_line_end_x - background_origin.x, line_height),
|
||||
},
|
||||
background_color,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn aligned_origin_x(
|
||||
origin: Point<Pixels>,
|
||||
align_width: Pixels,
|
||||
|
||||
@@ -1527,6 +1527,7 @@ impl Buffer {
|
||||
}
|
||||
|
||||
fn did_finish_parsing(&mut self, syntax_snapshot: SyntaxSnapshot, cx: &mut Context<Self>) {
|
||||
self.was_changed();
|
||||
self.non_text_state_update_count += 1;
|
||||
self.syntax_map.lock().did_parse(syntax_snapshot);
|
||||
self.request_autoindent(cx);
|
||||
@@ -1968,7 +1969,12 @@ impl Buffer {
|
||||
/// This allows downstream code to check if the buffer's text has changed without
|
||||
/// waiting for an effect cycle, which would be required if using eents.
|
||||
pub fn record_changes(&mut self, bit: rc::Weak<Cell<bool>>) {
|
||||
self.change_bits.push(bit);
|
||||
if let Err(ix) = self
|
||||
.change_bits
|
||||
.binary_search_by_key(&rc::Weak::as_ptr(&bit), rc::Weak::as_ptr)
|
||||
{
|
||||
self.change_bits.insert(ix, bit);
|
||||
}
|
||||
}
|
||||
|
||||
fn was_changed(&mut self) {
|
||||
@@ -2273,12 +2279,13 @@ impl Buffer {
|
||||
}
|
||||
|
||||
fn did_edit(&mut self, old_version: &clock::Global, was_dirty: bool, cx: &mut Context<Self>) {
|
||||
self.was_changed();
|
||||
|
||||
if self.edits_since::<usize>(old_version).next().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.reparse(cx);
|
||||
|
||||
cx.emit(BufferEvent::Edited);
|
||||
if was_dirty != self.is_dirty() {
|
||||
cx.emit(BufferEvent::DirtyChanged);
|
||||
@@ -2390,7 +2397,6 @@ impl Buffer {
|
||||
}
|
||||
self.text.apply_ops(buffer_ops);
|
||||
self.deferred_ops.insert(deferred_ops);
|
||||
self.was_changed();
|
||||
self.flush_deferred_ops(cx);
|
||||
self.did_edit(&old_version, was_dirty, cx);
|
||||
// Notify independently of whether the buffer was edited as the operations could include a
|
||||
|
||||
@@ -1250,7 +1250,6 @@ fn parse_text(
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn get_injections(
|
||||
config: &InjectionConfig,
|
||||
text: &BufferSnapshot,
|
||||
|
||||
@@ -11,8 +11,8 @@ use futures::future::BoxFuture;
|
||||
use futures::stream::BoxStream;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
percentage, svg, Animation, AnimationExt, AnyView, App, AsyncApp, Entity, Render, Subscription,
|
||||
Task, Transformation,
|
||||
percentage, svg, Action, Animation, AnimationExt, AnyView, App, AsyncApp, Entity, Render,
|
||||
Subscription, Task, Transformation,
|
||||
};
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
|
||||
@@ -337,9 +337,20 @@ impl Render for ConfigurationView {
|
||||
if self.state.read(cx).is_authenticated(cx) {
|
||||
const LABEL: &str = "Authorized.";
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::Check).color(Color::Success))
|
||||
.child(Label::new(LABEL))
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::Check).color(Color::Success))
|
||||
.child(Label::new(LABEL)),
|
||||
)
|
||||
.child(
|
||||
Button::new("sign_out", "Sign Out")
|
||||
.style(ui::ButtonStyle::Filled)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(copilot::SignOut.boxed_clone(), cx);
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
let loading_icon = svg()
|
||||
.size_8()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name = "C++"
|
||||
grammar = "cpp"
|
||||
path_suffixes = ["cc", "hh", "cpp", "h", "hpp", "cxx", "hxx", "c++", "ipp", "inl", "cu", "cuh", "C", "H"]
|
||||
path_suffixes = ["cc", "hh", "cpp", "h", "hpp", "cxx", "hxx", "c++", "ipp", "inl", "ixx", "cu", "cuh", "C", "H"]
|
||||
line_comments = ["// ", "/// ", "//! "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
|
||||
@@ -62,8 +62,8 @@
|
||||
|
||||
; Literals
|
||||
|
||||
(this) @keyword
|
||||
(super) @keyword
|
||||
(this) @variable.special
|
||||
(super) @variable.special
|
||||
|
||||
[
|
||||
(null)
|
||||
|
||||
@@ -62,8 +62,8 @@
|
||||
|
||||
; Literals
|
||||
|
||||
(this) @keyword
|
||||
(super) @keyword
|
||||
(this) @variable.special
|
||||
(super) @variable.special
|
||||
|
||||
[
|
||||
(null)
|
||||
|
||||
@@ -80,8 +80,8 @@
|
||||
|
||||
; Literals
|
||||
|
||||
(this) @keyword
|
||||
(super) @keyword
|
||||
(this) @variable.special
|
||||
(super) @variable.special
|
||||
|
||||
[
|
||||
(null)
|
||||
|
||||
@@ -301,7 +301,6 @@ pub struct AdapterServerCapabilities {
|
||||
|
||||
impl LanguageServer {
|
||||
/// Starts a language server process.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||
server_id: LanguageServerId,
|
||||
@@ -372,7 +371,6 @@ impl LanguageServer {
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_internal<Stdin, Stdout, Stderr, F>(
|
||||
server_id: LanguageServerId,
|
||||
server_name: LanguageServerName,
|
||||
|
||||
@@ -2982,7 +2982,6 @@ impl MultiBuffer {
|
||||
snapshot.check_invariants();
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn recompute_diff_transforms_for_edit(
|
||||
&self,
|
||||
edit: &Edit<TypedOffset<Excerpt>>,
|
||||
@@ -4176,6 +4175,9 @@ impl MultiBufferSnapshot {
|
||||
let region = cursor.region()?;
|
||||
let overshoot = offset - region.range.start;
|
||||
let buffer_offset = region.buffer_range.start + overshoot;
|
||||
if buffer_offset > region.buffer.len() {
|
||||
return None;
|
||||
}
|
||||
Some((region.buffer, buffer_offset))
|
||||
}
|
||||
|
||||
@@ -4184,8 +4186,11 @@ impl MultiBufferSnapshot {
|
||||
cursor.seek(&point);
|
||||
let region = cursor.region()?;
|
||||
let overshoot = point - region.range.start;
|
||||
let buffer_offset = region.buffer_range.start + overshoot;
|
||||
Some((region.buffer, buffer_offset, region.is_main_buffer))
|
||||
let buffer_point = region.buffer_range.start + overshoot;
|
||||
if buffer_point > region.buffer.max_point() {
|
||||
return None;
|
||||
}
|
||||
Some((region.buffer, buffer_point, region.is_main_buffer))
|
||||
}
|
||||
|
||||
pub fn suggested_indents(
|
||||
|
||||
@@ -3378,6 +3378,17 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let point = snapshot.max_point();
|
||||
let Some((buffer, offset)) = snapshot.point_to_buffer_offset(point) else {
|
||||
return;
|
||||
};
|
||||
assert!(offset <= buffer.len(),);
|
||||
|
||||
let Some((buffer, point, _)) = snapshot.point_to_buffer_point(point) else {
|
||||
return;
|
||||
};
|
||||
assert!(point <= buffer.max_point(),);
|
||||
}
|
||||
|
||||
fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
|
||||
|
||||
@@ -2360,7 +2360,6 @@ impl OutlinePanel {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_search_match(
|
||||
&mut self,
|
||||
multi_buffer_snapshot: Option<&MultiBufferSnapshot>,
|
||||
@@ -2452,7 +2451,6 @@ impl OutlinePanel {
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn entry_element(
|
||||
&self,
|
||||
rendered_entry: PanelEntry,
|
||||
@@ -3836,7 +3834,6 @@ impl OutlinePanel {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn push_entry(
|
||||
&self,
|
||||
state: &mut GenerationState,
|
||||
@@ -4054,7 +4051,6 @@ impl OutlinePanel {
|
||||
update_cached_entries
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn add_excerpt_entries(
|
||||
&self,
|
||||
state: &mut GenerationState,
|
||||
@@ -4113,7 +4109,6 @@ impl OutlinePanel {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn add_search_entries(
|
||||
&mut self,
|
||||
state: &mut GenerationState,
|
||||
|
||||
@@ -18,8 +18,6 @@ pub trait PanelHeader: workspace::Panel {
|
||||
.w_full()
|
||||
.px_1()
|
||||
.flex_none()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,8 +96,8 @@ pub trait PickerDelegate: Sized + 'static {
|
||||
None
|
||||
}
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str>;
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
|
||||
"No matches".into()
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
|
||||
Some("No matches".into())
|
||||
}
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
@@ -844,18 +844,17 @@ impl<D: PickerDelegate> Render for Picker<D> {
|
||||
)
|
||||
})
|
||||
.when(self.delegate.match_count() == 0, |el| {
|
||||
el.child(
|
||||
v_flex().flex_grow().py_2().child(
|
||||
ListItem::new("empty_state")
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.disabled(true)
|
||||
.child(
|
||||
Label::new(self.delegate.no_matches_text(window, cx))
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
)
|
||||
el.when_some(self.delegate.no_matches_text(window, cx), |el, text| {
|
||||
el.child(
|
||||
v_flex().flex_grow().py_2().child(
|
||||
ListItem::new("empty_state")
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.disabled(true)
|
||||
.child(Label::new(text).color(Color::Muted)),
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
.children(self.delegate.render_footer(window, cx))
|
||||
.children(match &self.head {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
};
|
||||
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use buffer_diff::{BufferDiff, BufferDiffEvent};
|
||||
use buffer_diff::BufferDiff;
|
||||
use client::Client;
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
@@ -15,7 +15,6 @@ use git::{blame::Blame, repository::RepoPath};
|
||||
use gpui::{
|
||||
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
|
||||
};
|
||||
use http_client::Url;
|
||||
use language::{
|
||||
proto::{
|
||||
deserialize_line_ending, deserialize_version, serialize_line_ending, serialize_version,
|
||||
@@ -34,7 +33,6 @@ use std::{
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
pin::pin,
|
||||
str::FromStr as _,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
@@ -217,39 +215,29 @@ impl BufferDiffState {
|
||||
_ => false,
|
||||
};
|
||||
self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let mut unstaged_changed_range = None;
|
||||
let mut new_unstaged_diff = None;
|
||||
if let Some(unstaged_diff) = &unstaged_diff {
|
||||
unstaged_changed_range = BufferDiff::update_diff(
|
||||
unstaged_diff.clone(),
|
||||
buffer.clone(),
|
||||
index,
|
||||
index_changed,
|
||||
language_changed,
|
||||
language.clone(),
|
||||
language_registry.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
unstaged_diff.update(&mut cx, |_, cx| {
|
||||
if language_changed {
|
||||
cx.emit(BufferDiffEvent::LanguageChanged);
|
||||
}
|
||||
if let Some(changed_range) = unstaged_changed_range.clone() {
|
||||
cx.emit(BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(changed_range),
|
||||
})
|
||||
}
|
||||
})?;
|
||||
new_unstaged_diff = Some(
|
||||
BufferDiff::update_diff(
|
||||
unstaged_diff.clone(),
|
||||
buffer.clone(),
|
||||
index,
|
||||
index_changed,
|
||||
language_changed,
|
||||
language.clone(),
|
||||
language_registry.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
let mut new_uncommitted_diff = None;
|
||||
if let Some(uncommitted_diff) = &uncommitted_diff {
|
||||
let uncommitted_changed_range =
|
||||
if let (Some(unstaged_diff), true) = (&unstaged_diff, index_matches_head) {
|
||||
uncommitted_diff.update(&mut cx, |uncommitted_diff, cx| {
|
||||
uncommitted_diff.update_diff_from(&buffer, unstaged_diff, cx)
|
||||
})?
|
||||
} else {
|
||||
new_uncommitted_diff = if index_matches_head {
|
||||
new_unstaged_diff.clone()
|
||||
} else {
|
||||
Some(
|
||||
BufferDiff::update_diff(
|
||||
uncommitted_diff.clone(),
|
||||
buffer.clone(),
|
||||
@@ -260,32 +248,32 @@ impl BufferDiffState {
|
||||
language_registry.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
|
||||
unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
|
||||
{
|
||||
unstaged_diff.update(&mut cx, |diff, cx| {
|
||||
diff.set_snapshot(&buffer, new_unstaged_diff, language_changed, None, cx)
|
||||
})?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((uncommitted_diff, new_uncommitted_diff)) =
|
||||
uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
|
||||
{
|
||||
uncommitted_diff.update(&mut cx, |uncommitted_diff, cx| {
|
||||
if language_changed {
|
||||
cx.emit(BufferDiffEvent::LanguageChanged);
|
||||
}
|
||||
let changed_range = match (unstaged_changed_range, uncommitted_changed_range) {
|
||||
(None, None) => None,
|
||||
(Some(unstaged_range), None) => {
|
||||
uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx)
|
||||
}
|
||||
(None, Some(uncommitted_range)) => Some(uncommitted_range),
|
||||
(Some(unstaged_range), Some(uncommitted_range)) => {
|
||||
let mut start = uncommitted_range.start;
|
||||
let mut end = uncommitted_range.end;
|
||||
if let Some(unstaged_range) =
|
||||
uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx)
|
||||
{
|
||||
start = unstaged_range.start.min(&uncommitted_range.start, &buffer);
|
||||
end = unstaged_range.end.max(&uncommitted_range.end, &buffer);
|
||||
}
|
||||
Some(start..end)
|
||||
}
|
||||
};
|
||||
cx.emit(BufferDiffEvent::DiffChanged { changed_range });
|
||||
uncommitted_diff.set_snapshot(
|
||||
&buffer,
|
||||
new_uncommitted_diff,
|
||||
language_changed,
|
||||
unstaged_changed_range,
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -813,8 +801,7 @@ impl LocalBufferStore {
|
||||
let Some(buffer) = buffer.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
let buffer = buffer.read(cx);
|
||||
let Some(file) = File::from_dyn(buffer.file()) else {
|
||||
let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
|
||||
continue;
|
||||
};
|
||||
if file.worktree != worktree_handle {
|
||||
@@ -825,7 +812,6 @@ impl LocalBufferStore {
|
||||
.iter()
|
||||
.any(|(work_dir, _)| file.path.starts_with(work_dir))
|
||||
{
|
||||
let snapshot = buffer.text_snapshot();
|
||||
let has_unstaged_diff = diff_state
|
||||
.unstaged_diff
|
||||
.as_ref()
|
||||
@@ -835,7 +821,7 @@ impl LocalBufferStore {
|
||||
.as_ref()
|
||||
.is_some_and(|set| set.is_upgradable());
|
||||
diff_state_updates.push((
|
||||
snapshot.clone(),
|
||||
buffer,
|
||||
file.path.clone(),
|
||||
has_unstaged_diff.then(|| diff_state.index_text.clone()),
|
||||
has_uncommitted_diff.then(|| diff_state.head_text.clone()),
|
||||
@@ -854,36 +840,33 @@ impl LocalBufferStore {
|
||||
.background_spawn(async move {
|
||||
diff_state_updates
|
||||
.into_iter()
|
||||
.filter_map(
|
||||
|(buffer_snapshot, path, current_index_text, current_head_text)| {
|
||||
let local_repo = snapshot.local_repo_for_path(&path)?;
|
||||
let relative_path = local_repo.relativize(&path).ok()?;
|
||||
let index_text = if current_index_text.is_some() {
|
||||
local_repo.repo().load_index_text(&relative_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let head_text = if current_head_text.is_some() {
|
||||
local_repo.repo().load_committed_text(&relative_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
.filter_map(|(buffer, path, current_index_text, current_head_text)| {
|
||||
let local_repo = snapshot.local_repo_for_path(&path)?;
|
||||
let relative_path = local_repo.relativize(&path).ok()?;
|
||||
let index_text = if current_index_text.is_some() {
|
||||
local_repo.repo().load_index_text(&relative_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let head_text = if current_head_text.is_some() {
|
||||
local_repo.repo().load_committed_text(&relative_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Avoid triggering a diff update if the base text has not changed.
|
||||
if let Some((current_index, current_head)) =
|
||||
current_index_text.as_ref().zip(current_head_text.as_ref())
|
||||
// Avoid triggering a diff update if the base text has not changed.
|
||||
if let Some((current_index, current_head)) =
|
||||
current_index_text.as_ref().zip(current_head_text.as_ref())
|
||||
{
|
||||
if current_index.as_deref() == index_text.as_ref()
|
||||
&& current_head.as_deref() == head_text.as_ref()
|
||||
{
|
||||
if current_index.as_deref() == index_text.as_ref()
|
||||
&& current_head.as_deref() == head_text.as_ref()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let diff_bases_change = match (
|
||||
current_index_text.is_some(),
|
||||
current_head_text.is_some(),
|
||||
) {
|
||||
let diff_bases_change =
|
||||
match (current_index_text.is_some(), current_head_text.is_some()) {
|
||||
(true, true) => Some(if index_text == head_text {
|
||||
DiffBasesChange::SetBoth(head_text)
|
||||
} else {
|
||||
@@ -896,17 +879,17 @@ impl LocalBufferStore {
|
||||
(false, true) => Some(DiffBasesChange::SetHead(head_text)),
|
||||
(false, false) => None,
|
||||
};
|
||||
Some((buffer_snapshot, diff_bases_change))
|
||||
},
|
||||
)
|
||||
|
||||
Some((buffer, diff_bases_change))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for (buffer_snapshot, diff_bases_change) in diff_bases_changes_by_buffer {
|
||||
for (buffer, diff_bases_change) in diff_bases_changes_by_buffer {
|
||||
let Some(OpenBuffer::Complete { diff_state, .. }) =
|
||||
this.opened_buffers.get_mut(&buffer_snapshot.remote_id())
|
||||
this.opened_buffers.get_mut(&buffer.read(cx).remote_id())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@@ -917,8 +900,9 @@ impl LocalBufferStore {
|
||||
diff_state.update(cx, |diff_state, cx| {
|
||||
use proto::update_diff_bases::Mode;
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
if let Some((client, project_id)) = this.downstream_client.as_ref() {
|
||||
let buffer_id = buffer_snapshot.remote_id().to_proto();
|
||||
let buffer_id = buffer.remote_id().to_proto();
|
||||
let (staged_text, committed_text, mode) = match diff_bases_change
|
||||
.clone()
|
||||
{
|
||||
@@ -942,8 +926,11 @@ impl LocalBufferStore {
|
||||
client.send(message).log_err();
|
||||
}
|
||||
|
||||
let _ =
|
||||
diff_state.diff_bases_changed(buffer_snapshot, diff_bases_change, cx);
|
||||
let _ = diff_state.diff_bases_changed(
|
||||
buffer.text_snapshot(),
|
||||
diff_bases_change,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -1705,11 +1692,17 @@ impl BufferStore {
|
||||
Err(e) => return Task::ready(Err(e)),
|
||||
};
|
||||
|
||||
let remote = repo_entry
|
||||
.branch()
|
||||
.and_then(|b| b.upstream.as_ref())
|
||||
.and_then(|b| b.remote_name())
|
||||
.unwrap_or("origin")
|
||||
.to_string();
|
||||
|
||||
cx.spawn(|cx| async move {
|
||||
const REMOTE_NAME: &str = "origin";
|
||||
let origin_url = repo
|
||||
.remote_url(REMOTE_NAME)
|
||||
.ok_or_else(|| anyhow!("remote \"{REMOTE_NAME}\" not found"))?;
|
||||
.remote_url(&remote)
|
||||
.ok_or_else(|| anyhow!("remote \"{remote}\" not found"))?;
|
||||
|
||||
let sha = repo
|
||||
.head_sha()
|
||||
@@ -2787,20 +2780,10 @@ fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::B
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let permalinks = blame
|
||||
.permalinks
|
||||
.into_iter()
|
||||
.map(|(oid, url)| proto::CommitPermalink {
|
||||
oid: oid.as_bytes().into(),
|
||||
permalink: url.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
proto::BlameBufferResponse {
|
||||
blame_response: Some(proto::blame_buffer_response::BlameResponse {
|
||||
entries,
|
||||
messages,
|
||||
permalinks,
|
||||
remote_url: blame.remote_url,
|
||||
}),
|
||||
}
|
||||
@@ -2839,20 +2822,8 @@ fn deserialize_blame_buffer_response(
|
||||
.filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let permalinks = response
|
||||
.permalinks
|
||||
.into_iter()
|
||||
.filter_map(|permalink| {
|
||||
Some((
|
||||
git::Oid::from_bytes(&permalink.oid).ok()?,
|
||||
Url::from_str(&permalink.permalink).ok()?,
|
||||
))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Some(Blame {
|
||||
entries,
|
||||
permalinks,
|
||||
messages,
|
||||
remote_url: response.remote_url,
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user