mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
Remove 2 suffix for workspace
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
492805af9c
commit
789ce8dd75
100
Cargo.lock
generated
100
Cargo.lock
generated
@ -18,7 +18,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -403,7 +403,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"uuid 1.4.1",
|
"uuid 1.4.1",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -770,7 +770,7 @@ dependencies = [
|
|||||||
"tempdir",
|
"tempdir",
|
||||||
"theme2",
|
"theme2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1099,7 +1099,7 @@ dependencies = [
|
|||||||
"settings2",
|
"settings2",
|
||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1767,7 +1767,7 @@ dependencies = [
|
|||||||
"unindent",
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
"uuid 1.4.1",
|
"uuid 1.4.1",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1812,7 +1812,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"vcs_menu",
|
"vcs_menu",
|
||||||
"workspace2",
|
"workspace",
|
||||||
"zed_actions2",
|
"zed_actions2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1867,7 +1867,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
"zed_actions2",
|
"zed_actions2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1986,7 +1986,7 @@ dependencies = [
|
|||||||
"smol",
|
"smol",
|
||||||
"theme2",
|
"theme2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
"zed_actions2",
|
"zed_actions2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2520,7 +2520,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"unindent",
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2706,7 +2706,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"unindent",
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2924,7 +2924,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2949,7 +2949,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3497,7 +3497,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4223,7 +4223,7 @@ dependencies = [
|
|||||||
"settings2",
|
"settings2",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4417,7 +4417,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4441,7 +4441,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"unindent",
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5680,7 +5680,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5901,7 +5901,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6330,7 +6330,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"unicase",
|
"unicase",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6353,7 +6353,7 @@ dependencies = [
|
|||||||
"text2",
|
"text2",
|
||||||
"theme2",
|
"theme2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6521,7 +6521,7 @@ dependencies = [
|
|||||||
"gpui2",
|
"gpui2",
|
||||||
"search",
|
"search",
|
||||||
"ui2",
|
"ui2",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6703,7 +6703,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7550,7 +7550,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"unindent",
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -7678,7 +7678,7 @@ dependencies = [
|
|||||||
"tree-sitter-typescript",
|
"tree-sitter-typescript",
|
||||||
"unindent",
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -8843,7 +8843,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -8980,7 +8980,7 @@ dependencies = [
|
|||||||
"theme2",
|
"theme2",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -10044,7 +10044,7 @@ dependencies = [
|
|||||||
"picker",
|
"picker",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -10083,7 +10083,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"workspace2",
|
"workspace",
|
||||||
"zed_actions2",
|
"zed_actions2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -10497,7 +10497,7 @@ dependencies = [
|
|||||||
"ui2",
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
"vim",
|
"vim",
|
||||||
"workspace2",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -10742,46 +10742,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "workspace"
|
name = "workspace"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-recursion 1.0.5",
|
|
||||||
"bincode",
|
|
||||||
"call",
|
|
||||||
"client",
|
|
||||||
"collections",
|
|
||||||
"context_menu",
|
|
||||||
"db",
|
|
||||||
"drag_and_drop",
|
|
||||||
"env_logger",
|
|
||||||
"fs",
|
|
||||||
"futures 0.3.28",
|
|
||||||
"gpui",
|
|
||||||
"indoc",
|
|
||||||
"install_cli",
|
|
||||||
"itertools 0.10.5",
|
|
||||||
"language",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"menu",
|
|
||||||
"node_runtime",
|
|
||||||
"parking_lot 0.11.2",
|
|
||||||
"postage",
|
|
||||||
"project",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"settings",
|
|
||||||
"smallvec",
|
|
||||||
"terminal",
|
|
||||||
"theme",
|
|
||||||
"util",
|
|
||||||
"uuid 1.4.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "workspace2"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-recursion 1.0.5",
|
"async-recursion 1.0.5",
|
||||||
@ -11021,7 +10981,7 @@ dependencies = [
|
|||||||
"uuid 1.4.1",
|
"uuid 1.4.1",
|
||||||
"vim",
|
"vim",
|
||||||
"welcome",
|
"welcome",
|
||||||
"workspace2",
|
"workspace",
|
||||||
"zed_actions2",
|
"zed_actions2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ members = [
|
|||||||
"crates/story",
|
"crates/story",
|
||||||
"crates/vim",
|
"crates/vim",
|
||||||
"crates/vcs_menu",
|
"crates/vcs_menu",
|
||||||
"crates/workspace2",
|
"crates/workspace",
|
||||||
"crates/welcome",
|
"crates/welcome",
|
||||||
"crates/xtask",
|
"crates/xtask",
|
||||||
"crates/zed",
|
"crates/zed",
|
||||||
|
@ -18,7 +18,7 @@ settings = { path = "../settings2", package = "settings2" }
|
|||||||
ui = { path = "../ui2", package = "ui2" }
|
ui = { path = "../ui2", package = "ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
theme = { path = "../theme2", package = "theme2" }
|
theme = { path = "../theme2", package = "theme2" }
|
||||||
workspace = { path = "../workspace2", package = "workspace2" }
|
workspace = { path = "../workspace", package = "workspace" }
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
@ -25,7 +25,7 @@ settings = { package = "settings2", path = "../settings2" }
|
|||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
|
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
@ -16,7 +16,7 @@ menu = { package = "menu2", path = "../menu2" }
|
|||||||
project = { package = "project2", path = "../project2" }
|
project = { package = "project2", path = "../project2" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
isahc.workspace = true
|
isahc.workspace = true
|
||||||
|
@ -18,11 +18,11 @@ project = { package = "project2", path = "../project2" }
|
|||||||
search = { path = "../search" }
|
search = { path = "../search" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
outline = { path = "../outline" }
|
outline = { path = "../outline" }
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
|
@ -79,7 +79,7 @@ project = { package = "project2", path = "../project2", features = ["test-suppor
|
|||||||
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
||||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
|
|
||||||
collab_ui = { path = "../collab_ui", features = ["test-support"] }
|
collab_ui = { path = "../collab_ui", features = ["test-support"] }
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ theme_selector = { path = "../theme_selector" }
|
|||||||
vcs_menu = { path = "../vcs_menu" }
|
vcs_menu = { path = "../vcs_menu" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
zed-actions = { package="zed_actions2", path = "../zed_actions2"}
|
zed-actions = { package="zed_actions2", path = "../zed_actions2"}
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
@ -75,7 +75,7 @@ project = { package = "project2", path = "../project2", features = ["test-suppor
|
|||||||
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
||||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
|
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
tree-sitter-markdown.workspace = true
|
tree-sitter-markdown.workspace = true
|
||||||
|
@ -19,7 +19,7 @@ settings = { package = "settings2", path = "../settings2" }
|
|||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
workspace = { package="workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
|
zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
@ -32,6 +32,6 @@ project = { package="project2", path = "../project2", features = ["test-support"
|
|||||||
menu = { package = "menu2", path = "../menu2" }
|
menu = { package = "menu2", path = "../menu2" }
|
||||||
go_to_line = { path = "../go_to_line" }
|
go_to_line = { path = "../go_to_line" }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
@ -31,6 +31,6 @@ project = { package="project2", path = "../project2", features = ["test-support"
|
|||||||
menu = { package = "menu2", path = "../menu2" }
|
menu = { package = "menu2", path = "../menu2" }
|
||||||
go_to_line = { package = "go_to_line2", path = "../go_to_line2" }
|
go_to_line = { package = "go_to_line2", path = "../go_to_line2" }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
@ -18,7 +18,7 @@ language = { package = "language2", path = "../language2" }
|
|||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = {path = "../workspace" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
@ -19,7 +19,7 @@ project = { package = "project2", path = "../project2" }
|
|||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = {path = "../workspace" }
|
||||||
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
@ -36,7 +36,7 @@ editor = { path = "../editor", features = ["test-support"] }
|
|||||||
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
||||||
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = {path = "../workspace", features = ["test-support"] }
|
||||||
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
|
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
|
||||||
|
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
@ -46,7 +46,7 @@ theme = { package="theme2", path = "../theme2" }
|
|||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
sqlez = { path = "../sqlez" }
|
sqlez = { path = "../sqlez" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
|
|
||||||
aho-corasick = "1.1"
|
aho-corasick = "1.1"
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
@ -80,7 +80,7 @@ gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
|||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
||||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
multi_buffer = { path = "../multi_buffer", features = ["test-support"] }
|
multi_buffer = { path = "../multi_buffer", features = ["test-support"] }
|
||||||
|
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
|
@ -23,7 +23,7 @@ settings = { package = "settings2", path = "../settings2" }
|
|||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2"}
|
workspace = { path = "../workspace"}
|
||||||
|
|
||||||
bitflags = "2.4.1"
|
bitflags = "2.4.1"
|
||||||
human_bytes = "0.4.1"
|
human_bytes = "0.4.1"
|
||||||
|
@ -21,7 +21,7 @@ text = { package = "text2", path = "../text2" }
|
|||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ serde.workspace = true
|
|||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
|
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
|
||||||
|
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
@ -15,7 +15,7 @@ menu = { package = "menu2", path = "../menu2" }
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
text = { package = "text2", path = "../text2" }
|
text = { package = "text2", path = "../text2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
|
@ -12,7 +12,7 @@ doctest = false
|
|||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace2 = { path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
settings2 = { path = "../settings2" }
|
settings2 = { path = "../settings2" }
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
@ -9,7 +9,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use workspace2::{AppState, Workspace};
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
actions!(journal, [NewJournalEntry]);
|
actions!(journal, [NewJournalEntry]);
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let (journal_dir, entry_path) = create_entry.await?;
|
let (journal_dir, entry_path) = create_entry.await?;
|
||||||
let (workspace, _) = cx
|
let (workspace, _) = cx
|
||||||
.update(|cx| workspace2::open_paths(&[journal_dir], &app_state, None, cx))?
|
.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let _opened = workspace
|
let _opened = workspace
|
||||||
|
@ -19,7 +19,7 @@ theme = { package = "theme2", path = "../theme2" }
|
|||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -15,7 +15,7 @@ settings = { package = "settings2", path = "../settings2" }
|
|||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
language = { package = "language2", path = "../language2" }
|
language = { package = "language2", path = "../language2" }
|
||||||
project = { package = "project2", path = "../project2" }
|
project = { package = "project2", path = "../project2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
@ -18,7 +18,7 @@ picker = { path = "../picker" }
|
|||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
text = { package = "text2", path = "../text2" }
|
text = { package = "text2", path = "../text2" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
||||||
ordered-float.workspace = true
|
ordered-float.workspace = true
|
||||||
|
@ -16,7 +16,7 @@ menu = { package = "menu2", path = "../menu2" }
|
|||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2"}
|
workspace = { path = "../workspace"}
|
||||||
|
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ settings = { path = "../settings2", package = "settings2" }
|
|||||||
theme = { path = "../theme2", package = "theme2" }
|
theme = { path = "../theme2", package = "theme2" }
|
||||||
ui = { path = "../ui2", package = "ui2" }
|
ui = { path = "../ui2", package = "ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace2", package = "workspace2" }
|
workspace = { path = "../workspace", package = "workspace" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
@ -37,5 +37,5 @@ client = { path = "../client2", package = "client2", features = ["test-support"]
|
|||||||
language = { path = "../language2", package = "language2", features = ["test-support"] }
|
language = { path = "../language2", package = "language2", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
|
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
|
||||||
workspace = { path = "../workspace2", package = "workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
@ -10,13 +10,13 @@ doctest = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
fuzzy = {package = "fuzzy2", path = "../fuzzy2" }
|
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||||
gpui = {package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
picker = {path = "../picker" }
|
picker = { path = "../picker" }
|
||||||
project = { package = "project2", path = "../project2" }
|
project = { package = "project2", path = "../project2" }
|
||||||
text = {package = "text2", path = "../text2" }
|
text = { package = "text2", path = "../text2" }
|
||||||
settings = {package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
workspace = {package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
||||||
@ -34,4 +34,4 @@ language = { package = "language2", path = "../language2", features = ["test-sup
|
|||||||
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
||||||
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
||||||
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
|
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
|
@ -13,10 +13,10 @@ assistant = { package = "assistant2", path = "../assistant2" }
|
|||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
search = { path = "../search" }
|
search = { path = "../search" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
|
@ -19,7 +19,7 @@ text = { package = "text2", path = "../text2" }
|
|||||||
util = { path = "../util"}
|
util = { path = "../util"}
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
ordered-float.workspace = true
|
ordered-float.workspace = true
|
||||||
|
@ -20,7 +20,7 @@ settings = { package = "settings2", path = "../settings2" }
|
|||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
ui = {package = "ui2", path = "../ui2"}
|
ui = {package = "ui2", path = "../ui2"}
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
|
semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
@ -36,5 +36,5 @@ client = { package = "client2", path = "../client2", features = ["test-support"]
|
|||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
|
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
unindent.workspace = true
|
unindent.workspace = true
|
||||||
|
@ -14,7 +14,7 @@ collections = { path = "../collections" }
|
|||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
language = { package = "language2", path = "../language2" }
|
language = { package = "language2", path = "../language2" }
|
||||||
project = { package = "project2", path = "../project2" }
|
project = { package = "project2", path = "../project2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
rpc = { package = "rpc2", path = "../rpc2" }
|
rpc = { package = "rpc2", path = "../rpc2" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
@ -45,7 +45,7 @@ gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
|||||||
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
||||||
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
||||||
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"]}
|
settings = { package = "settings2", path = "../settings2", features = ["test-support"]}
|
||||||
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { package = "client2", path = "../client2" }
|
||||||
|
@ -17,7 +17,7 @@ project = { package = "project2", path = "../project2" }
|
|||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
|
procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
|
||||||
terminal = { package = "terminal2", path = "../terminal2" }
|
terminal = { package = "terminal2", path = "../terminal2" }
|
||||||
@ -42,5 +42,5 @@ editor = { path = "../editor", features = ["test-support"] }
|
|||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"]}
|
client = { package = "client2", path = "../client2", features = ["test-support"]}
|
||||||
project = { package = "project2", path = "../project2", features = ["test-support"]}
|
project = { package = "project2", path = "../project2", features = ["test-support"]}
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
|
@ -20,7 +20,7 @@ settings = { package = "settings2", path = "../settings2" }
|
|||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
|
@ -12,6 +12,6 @@ gpui = {package = "gpui2", path = "../gpui2"}
|
|||||||
picker = {path = "../picker"}
|
picker = {path = "../picker"}
|
||||||
util = {path = "../util"}
|
util = {path = "../util"}
|
||||||
ui = {package = "ui2", path = "../ui2"}
|
ui = {package = "ui2", path = "../ui2"}
|
||||||
workspace = {package = "workspace2", path = "../workspace2"}
|
workspace = { path = "../workspace" }
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
@ -31,7 +31,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
|
|||||||
language = { package = "language2", path = "../language2" }
|
language = { package = "language2", path = "../language2" }
|
||||||
search = { path = "../search" }
|
search = { path = "../search" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
ui = { package = "ui2", path = "../ui2"}
|
ui = { package = "ui2", path = "../ui2"}
|
||||||
diagnostics = { path = "../diagnostics" }
|
diagnostics = { path = "../diagnostics" }
|
||||||
@ -48,6 +48,6 @@ language = { package = "language2", path = "../language2", features = ["test-sup
|
|||||||
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
project = { package = "project2", path = "../project2", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
|
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
|
||||||
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
||||||
|
@ -25,7 +25,7 @@ theme = { package = "theme2", path = "../theme2" }
|
|||||||
theme_selector = { path = "../theme_selector" }
|
theme_selector = { path = "../theme_selector" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
picker = { path = "../picker" }
|
picker = { path = "../picker" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
vim = { path = "../vim" }
|
vim = { path = "../vim" }
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
@ -19,23 +19,23 @@ test-support = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
db = { path = "../db" }
|
db = { path = "../db2", package = "db2" }
|
||||||
call = { path = "../call" }
|
call = { path = "../call2", package = "call2" }
|
||||||
client = { path = "../client" }
|
client = { path = "../client2", package = "client2" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
context_menu = { path = "../context_menu" }
|
# context_menu = { path = "../context_menu" }
|
||||||
drag_and_drop = { path = "../drag_and_drop" }
|
fs = { path = "../fs2", package = "fs2" }
|
||||||
fs = { path = "../fs" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
gpui = { path = "../gpui" }
|
install_cli = { path = "../install_cli2", package = "install_cli2" }
|
||||||
install_cli = { path = "../install_cli" }
|
language = { path = "../language2", package = "language2" }
|
||||||
language = { path = "../language" }
|
#menu = { path = "../menu" }
|
||||||
menu = { path = "../menu" }
|
|
||||||
node_runtime = { path = "../node_runtime" }
|
node_runtime = { path = "../node_runtime" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project2", package = "project2" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings2", package = "settings2" }
|
||||||
terminal = { path = "../terminal" }
|
terminal = { path = "../terminal2", package = "terminal2" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme2", package = "theme2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
|
|
||||||
async-recursion = "1.0.0"
|
async-recursion = "1.0.0"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
@ -54,13 +54,13 @@ smallvec.workspace = true
|
|||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
call = { path = "../call", features = ["test-support"] }
|
call = { path = "../call2", package = "call2", features = ["test-support"] }
|
||||||
client = { path = "../client", features = ["test-support"] }
|
client = { path = "../client2", package = "client2", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
|
||||||
project = { path = "../project", features = ["test-support"] }
|
project = { path = "../project2", package = "project2", features = ["test-support"] }
|
||||||
settings = { path = "../settings", features = ["test-support"] }
|
settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
|
||||||
fs = { path = "../fs", features = ["test-support"] }
|
fs = { path = "../fs2", package = "fs2", features = ["test-support"] }
|
||||||
db = { path = "../db", features = ["test-support"] }
|
db = { path = "../db2", package = "db2", features = ["test-support"] }
|
||||||
|
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,35 +1,39 @@
|
|||||||
use crate::{Toast, Workspace};
|
use crate::{Toast, Workspace};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle};
|
use gpui::{
|
||||||
|
AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render,
|
||||||
|
View, ViewContext, VisualContext,
|
||||||
|
};
|
||||||
use std::{any::TypeId, ops::DerefMut};
|
use std::{any::TypeId, ops::DerefMut};
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.set_global(NotificationTracker::new());
|
cx.set_global(NotificationTracker::new());
|
||||||
simple_message_notification::init(cx);
|
// todo!()
|
||||||
|
// simple_message_notification::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Notification: View {
|
pub trait Notification: EventEmitter<DismissEvent> + Render {}
|
||||||
fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
|
|
||||||
|
impl<V: EventEmitter<DismissEvent> + Render> Notification for V {}
|
||||||
|
|
||||||
|
pub trait NotificationHandle: Send {
|
||||||
|
fn id(&self) -> EntityId;
|
||||||
|
fn to_any(&self) -> AnyView;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NotificationHandle {
|
impl<T: Notification> NotificationHandle for View<T> {
|
||||||
fn id(&self) -> usize;
|
fn id(&self) -> EntityId {
|
||||||
fn as_any(&self) -> &AnyViewHandle;
|
self.entity_id()
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Notification> NotificationHandle for ViewHandle<T> {
|
|
||||||
fn id(&self) -> usize {
|
|
||||||
self.id()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &AnyViewHandle {
|
fn to_any(&self) -> AnyView {
|
||||||
self
|
self.clone().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&dyn NotificationHandle> for AnyViewHandle {
|
impl From<&dyn NotificationHandle> for AnyView {
|
||||||
fn from(val: &dyn NotificationHandle) -> Self {
|
fn from(val: &dyn NotificationHandle) -> Self {
|
||||||
val.as_any().clone()
|
val.to_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,14 +79,12 @@ impl Workspace {
|
|||||||
&mut self,
|
&mut self,
|
||||||
id: usize,
|
id: usize,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
|
build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
|
||||||
) {
|
) {
|
||||||
if !self.has_shown_notification_once::<V>(id, cx) {
|
if !self.has_shown_notification_once::<V>(id, cx) {
|
||||||
cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
|
let tracker = cx.global_mut::<NotificationTracker>();
|
||||||
let entry = tracker.entry(TypeId::of::<V>()).or_default();
|
let entry = tracker.entry(TypeId::of::<V>()).or_default();
|
||||||
entry.push(id);
|
entry.push(id);
|
||||||
});
|
|
||||||
|
|
||||||
self.show_notification::<V>(id, cx, build_notification)
|
self.show_notification::<V>(id, cx, build_notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +93,7 @@ impl Workspace {
|
|||||||
&mut self,
|
&mut self,
|
||||||
id: usize,
|
id: usize,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
|
build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
|
||||||
) {
|
) {
|
||||||
let type_id = TypeId::of::<V>();
|
let type_id = TypeId::of::<V>();
|
||||||
if self
|
if self
|
||||||
@ -102,10 +104,8 @@ impl Workspace {
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
let notification = build_notification(cx);
|
let notification = build_notification(cx);
|
||||||
cx.subscribe(¬ification, move |this, handle, event, cx| {
|
cx.subscribe(¬ification, move |this, _, _: &DismissEvent, cx| {
|
||||||
if handle.read(cx).should_dismiss_notification_on_event(event) {
|
this.dismiss_notification_internal(type_id, id, cx);
|
||||||
this.dismiss_notification_internal(type_id, id, cx);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
self.notifications
|
self.notifications
|
||||||
@ -114,6 +114,17 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn show_error<E>(&mut self, err: &E, cx: &mut ViewContext<Self>)
|
||||||
|
where
|
||||||
|
E: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
self.show_notification(0, cx, |cx| {
|
||||||
|
cx.new_view(|_cx| {
|
||||||
|
simple_message_notification::MessageNotification::new(format!("Error: {err:?}"))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
|
pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
|
||||||
let type_id = TypeId::of::<V>();
|
let type_id = TypeId::of::<V>();
|
||||||
|
|
||||||
@ -123,7 +134,7 @@ impl Workspace {
|
|||||||
pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
|
pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
|
||||||
self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
|
self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
|
||||||
self.show_notification(toast.id, cx, |cx| {
|
self.show_notification(toast.id, cx, |cx| {
|
||||||
cx.add_view(|_cx| match toast.on_click.as_ref() {
|
cx.new_view(|_cx| match toast.on_click.as_ref() {
|
||||||
Some((click_msg, on_click)) => {
|
Some((click_msg, on_click)) => {
|
||||||
let on_click = on_click.clone();
|
let on_click = on_click.clone();
|
||||||
simple_message_notification::MessageNotification::new(toast.msg.clone())
|
simple_message_notification::MessageNotification::new(toast.msg.clone())
|
||||||
@ -158,78 +169,29 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod simple_message_notification {
|
pub mod simple_message_notification {
|
||||||
use super::Notification;
|
|
||||||
use crate::Workspace;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,
|
||||||
elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
|
StatefulInteractiveElement, Styled, ViewContext,
|
||||||
fonts::TextStyle,
|
|
||||||
impl_actions,
|
|
||||||
platform::{CursorStyle, MouseButton},
|
|
||||||
AnyElement, AppContext, Element, Entity, View, ViewContext,
|
|
||||||
};
|
};
|
||||||
use menu::Cancel;
|
use std::sync::Arc;
|
||||||
use serde::Deserialize;
|
use ui::prelude::*;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
|
||||||
|
|
||||||
actions!(message_notifications, [CancelMessageNotification]);
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Deserialize, PartialEq)]
|
|
||||||
pub struct OsOpen(pub Cow<'static, str>);
|
|
||||||
|
|
||||||
impl OsOpen {
|
|
||||||
pub fn new<I: Into<Cow<'static, str>>>(url: I) -> Self {
|
|
||||||
OsOpen(url.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_actions!(message_notifications, [OsOpen]);
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
|
||||||
cx.add_action(MessageNotification::dismiss);
|
|
||||||
cx.add_action(
|
|
||||||
|_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext<Workspace>| {
|
|
||||||
cx.platform().open_url(open_action.0.as_ref());
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NotificationMessage {
|
|
||||||
Text(Cow<'static, str>),
|
|
||||||
Element(fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MessageNotification {
|
pub struct MessageNotification {
|
||||||
message: NotificationMessage,
|
message: SharedString,
|
||||||
on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
|
on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
|
||||||
click_message: Option<Cow<'static, str>>,
|
click_message: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MessageNotificationEvent {
|
impl EventEmitter<DismissEvent> for MessageNotification {}
|
||||||
Dismiss,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entity for MessageNotification {
|
|
||||||
type Event = MessageNotificationEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageNotification {
|
impl MessageNotification {
|
||||||
pub fn new<S>(message: S) -> MessageNotification
|
pub fn new<S>(message: S) -> MessageNotification
|
||||||
where
|
where
|
||||||
S: Into<Cow<'static, str>>,
|
S: Into<SharedString>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
message: NotificationMessage::Text(message.into()),
|
message: message.into(),
|
||||||
on_click: None,
|
|
||||||
click_message: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_element(
|
|
||||||
message: fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>,
|
|
||||||
) -> MessageNotification {
|
|
||||||
Self {
|
|
||||||
message: NotificationMessage::Element(message),
|
|
||||||
on_click: None,
|
on_click: None,
|
||||||
click_message: None,
|
click_message: None,
|
||||||
}
|
}
|
||||||
@ -237,7 +199,7 @@ pub mod simple_message_notification {
|
|||||||
|
|
||||||
pub fn with_click_message<S>(mut self, message: S) -> Self
|
pub fn with_click_message<S>(mut self, message: S) -> Self
|
||||||
where
|
where
|
||||||
S: Into<Cow<'static, str>>,
|
S: Into<SharedString>,
|
||||||
{
|
{
|
||||||
self.click_message = Some(message.into());
|
self.click_message = Some(message.into());
|
||||||
self
|
self
|
||||||
@ -251,117 +213,139 @@ pub mod simple_message_notification {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
|
pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
cx.emit(MessageNotificationEvent::Dismiss);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for MessageNotification {
|
impl Render for MessageNotification {
|
||||||
fn ui_name() -> &'static str {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
"MessageNotification"
|
v_stack()
|
||||||
}
|
.elevation_3(cx)
|
||||||
|
.p_4()
|
||||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
|
.child(
|
||||||
let theme = theme::current(cx).clone();
|
h_stack()
|
||||||
let theme = &theme.simple_message_notification;
|
.justify_between()
|
||||||
|
.child(div().max_w_80().child(Label::new(self.message.clone())))
|
||||||
enum MessageNotificationTag {}
|
.child(
|
||||||
|
div()
|
||||||
let click_message = self.click_message.clone();
|
.id("cancel")
|
||||||
let message = match &self.message {
|
.child(IconElement::new(Icon::Close))
|
||||||
NotificationMessage::Text(text) => {
|
.cursor_pointer()
|
||||||
Text::new(text.to_owned(), theme.message.text.clone()).into_any()
|
.on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
|
||||||
}
|
|
||||||
NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
|
|
||||||
};
|
|
||||||
let on_click = self.on_click.clone();
|
|
||||||
let has_click_action = on_click.is_some();
|
|
||||||
|
|
||||||
Flex::column()
|
|
||||||
.with_child(
|
|
||||||
Flex::row()
|
|
||||||
.with_child(
|
|
||||||
message
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.message.container)
|
|
||||||
.aligned()
|
|
||||||
.top()
|
|
||||||
.left()
|
|
||||||
.flex(1., true),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
|
|
||||||
let style = theme.dismiss_button.style_for(state);
|
|
||||||
Svg::new("icons/x.svg")
|
|
||||||
.with_color(style.color)
|
|
||||||
.constrained()
|
|
||||||
.with_width(style.icon_width)
|
|
||||||
.aligned()
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container)
|
|
||||||
.constrained()
|
|
||||||
.with_width(style.button_width)
|
|
||||||
.with_height(style.button_width)
|
|
||||||
})
|
|
||||||
.with_padding(Padding::uniform(5.))
|
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
this.dismiss(&Default::default(), cx);
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.aligned()
|
|
||||||
.constrained()
|
|
||||||
.with_height(cx.font_cache().line_height(theme.message.text.font_size))
|
|
||||||
.aligned()
|
|
||||||
.top()
|
|
||||||
.flex_float(),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.with_children({
|
.children(self.click_message.iter().map(|message| {
|
||||||
click_message
|
Button::new(message.clone(), message.clone()).on_click(cx.listener(
|
||||||
.map(|click_message| {
|
|this, _, cx| {
|
||||||
MouseEventHandler::new::<MessageNotificationTag, _>(
|
if let Some(on_click) = this.on_click.as_ref() {
|
||||||
0,
|
(on_click)(cx)
|
||||||
cx,
|
};
|
||||||
|state, _| {
|
this.dismiss(cx)
|
||||||
let style = theme.action_message.style_for(state);
|
},
|
||||||
|
))
|
||||||
Flex::row()
|
}))
|
||||||
.with_child(
|
|
||||||
Text::new(click_message, style.text.clone())
|
|
||||||
.contained()
|
|
||||||
.with_style(style.container),
|
|
||||||
)
|
|
||||||
.contained()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
if let Some(on_click) = on_click.as_ref() {
|
|
||||||
on_click(cx);
|
|
||||||
this.dismiss(&Default::default(), cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Since we're not using a proper overlay, we have to capture these extra events
|
|
||||||
.on_down(MouseButton::Left, |_, _, _| {})
|
|
||||||
.on_up(MouseButton::Left, |_, _, _| {})
|
|
||||||
.with_cursor_style(if has_click_action {
|
|
||||||
CursorStyle::PointingHand
|
|
||||||
} else {
|
|
||||||
CursorStyle::Arrow
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.into_iter()
|
|
||||||
})
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// todo!()
|
||||||
|
// impl View for MessageNotification {
|
||||||
|
// fn ui_name() -> &'static str {
|
||||||
|
// "MessageNotification"
|
||||||
|
// }
|
||||||
|
|
||||||
impl Notification for MessageNotification {
|
// fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
|
||||||
fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
|
// let theme = theme2::current(cx).clone();
|
||||||
match event {
|
// let theme = &theme.simple_message_notification;
|
||||||
MessageNotificationEvent::Dismiss => true,
|
|
||||||
}
|
// enum MessageNotificationTag {}
|
||||||
}
|
|
||||||
}
|
// let click_message = self.click_message.clone();
|
||||||
|
// let message = match &self.message {
|
||||||
|
// NotificationMessage::Text(text) => {
|
||||||
|
// Text::new(text.to_owned(), theme.message.text.clone()).into_any()
|
||||||
|
// }
|
||||||
|
// NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
|
||||||
|
// };
|
||||||
|
// let on_click = self.on_click.clone();
|
||||||
|
// let has_click_action = on_click.is_some();
|
||||||
|
|
||||||
|
// Flex::column()
|
||||||
|
// .with_child(
|
||||||
|
// Flex::row()
|
||||||
|
// .with_child(
|
||||||
|
// message
|
||||||
|
// .contained()
|
||||||
|
// .with_style(theme.message.container)
|
||||||
|
// .aligned()
|
||||||
|
// .top()
|
||||||
|
// .left()
|
||||||
|
// .flex(1., true),
|
||||||
|
// )
|
||||||
|
// .with_child(
|
||||||
|
// MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
|
||||||
|
// let style = theme.dismiss_button.style_for(state);
|
||||||
|
// Svg::new("icons/x.svg")
|
||||||
|
// .with_color(style.color)
|
||||||
|
// .constrained()
|
||||||
|
// .with_width(style.icon_width)
|
||||||
|
// .aligned()
|
||||||
|
// .contained()
|
||||||
|
// .with_style(style.container)
|
||||||
|
// .constrained()
|
||||||
|
// .with_width(style.button_width)
|
||||||
|
// .with_height(style.button_width)
|
||||||
|
// })
|
||||||
|
// .with_padding(Padding::uniform(5.))
|
||||||
|
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
// this.dismiss(&Default::default(), cx);
|
||||||
|
// })
|
||||||
|
// .with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
// .aligned()
|
||||||
|
// .constrained()
|
||||||
|
// .with_height(cx.font_cache().line_height(theme.message.text.font_size))
|
||||||
|
// .aligned()
|
||||||
|
// .top()
|
||||||
|
// .flex_float(),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .with_children({
|
||||||
|
// click_message
|
||||||
|
// .map(|click_message| {
|
||||||
|
// MouseEventHandler::new::<MessageNotificationTag, _>(
|
||||||
|
// 0,
|
||||||
|
// cx,
|
||||||
|
// |state, _| {
|
||||||
|
// let style = theme.action_message.style_for(state);
|
||||||
|
|
||||||
|
// Flex::row()
|
||||||
|
// .with_child(
|
||||||
|
// Text::new(click_message, style.text.clone())
|
||||||
|
// .contained()
|
||||||
|
// .with_style(style.container),
|
||||||
|
// )
|
||||||
|
// .contained()
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
// if let Some(on_click) = on_click.as_ref() {
|
||||||
|
// on_click(cx);
|
||||||
|
// this.dismiss(&Default::default(), cx);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// // Since we're not using a proper overlay, we have to capture these extra events
|
||||||
|
// .on_down(MouseButton::Left, |_, _, _| {})
|
||||||
|
// .on_up(MouseButton::Left, |_, _, _| {})
|
||||||
|
// .with_cursor_style(if has_click_action {
|
||||||
|
// CursorStyle::PointingHand
|
||||||
|
// } else {
|
||||||
|
// CursorStyle::Arrow
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// .into_iter()
|
||||||
|
// })
|
||||||
|
// .into_any()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NotifyResultExt {
|
pub trait NotifyResultExt {
|
||||||
@ -372,6 +356,8 @@ pub trait NotifyResultExt {
|
|||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) -> Option<Self::Ok>;
|
) -> Option<Self::Ok>;
|
||||||
|
|
||||||
|
fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E> NotifyResultExt for Result<T, E>
|
impl<T, E> NotifyResultExt for Result<T, E>
|
||||||
@ -384,15 +370,24 @@ where
|
|||||||
match self {
|
match self {
|
||||||
Ok(value) => Some(value),
|
Ok(value) => Some(value),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
workspace.show_notification(0, cx, |cx| {
|
log::error!("TODO {err:?}");
|
||||||
cx.add_view(|_cx| {
|
workspace.show_error(&err, cx);
|
||||||
simple_message_notification::MessageNotification::new(format!(
|
None
|
||||||
"Error: {:?}",
|
}
|
||||||
err,
|
}
|
||||||
))
|
}
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<T> {
|
||||||
|
match self {
|
||||||
|
Ok(value) => Some(value),
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("TODO {err:?}");
|
||||||
|
cx.update(|view, cx| {
|
||||||
|
if let Ok(workspace) = view.downcast::<Workspace>() {
|
||||||
|
workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,14 @@
|
|||||||
use super::DraggedItem;
|
use super::DraggedItem;
|
||||||
use crate::{Pane, SplitDirection, Workspace};
|
use crate::{Pane, SplitDirection, Workspace};
|
||||||
use drag_and_drop::DragAndDrop;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::{Canvas, MouseEventHandler, ParentElement, Stack},
|
elements::{Canvas, MouseEventHandler, ParentComponent, Stack},
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
platform::MouseButton,
|
platform::MouseButton,
|
||||||
scene::MouseUp,
|
scene::MouseUp,
|
||||||
AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
|
AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use project::ProjectEntryId;
|
use project2::ProjectEntryId;
|
||||||
|
|
||||||
pub fn dragged_item_receiver<Tag, D, F>(
|
pub fn dragged_item_receiver<Tag, D, F>(
|
||||||
pane: &Pane,
|
pane: &Pane,
|
||||||
@ -236,5 +235,5 @@ fn drop_split_direction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn overlay_color(cx: &AppContext) -> Color {
|
fn overlay_color(cx: &AppContext) -> Color {
|
||||||
theme::current(cx).workspace.drop_target_overlay_color
|
theme2::current(cx).workspace.drop_target_overlay_color
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
#![allow(dead_code)]
|
//#![allow(dead_code)]
|
||||||
|
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
|
||||||
@ -6,7 +6,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
||||||
use gpui::{platform::WindowBounds, Axis};
|
use gpui::{Axis, WindowBounds};
|
||||||
|
|
||||||
use util::{unzip_option, ResultExt};
|
use util::{unzip_option, ResultExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -403,7 +403,7 @@ impl WorkspaceDb {
|
|||||||
.map(|(group_id, axis, pane_id, active, flexes)| {
|
.map(|(group_id, axis, pane_id, active, flexes)| {
|
||||||
if let Some((group_id, axis)) = group_id.zip(axis) {
|
if let Some((group_id, axis)) = group_id.zip(axis) {
|
||||||
let flexes = flexes
|
let flexes = flexes
|
||||||
.map(|flexes| serde_json::from_str::<Vec<f32>>(&flexes))
|
.map(|flexes: String| serde_json::from_str::<Vec<f32>>(&flexes))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
Ok(SerializedPaneGroup::Group {
|
Ok(SerializedPaneGroup::Group {
|
||||||
@ -553,6 +553,7 @@ impl WorkspaceDb {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use db::open_test_db;
|
use db::open_test_db;
|
||||||
|
use gpui;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_next_id_stability() {
|
async fn test_next_id_stability() {
|
||||||
@ -612,13 +613,13 @@ mod tests {
|
|||||||
conn.migrate(
|
conn.migrate(
|
||||||
"test_table",
|
"test_table",
|
||||||
&[sql!(
|
&[sql!(
|
||||||
CREATE TABLE test_table(
|
CREATE TABLE test_table(
|
||||||
text TEXT,
|
text TEXT,
|
||||||
workspace_id INTEGER,
|
workspace_id INTEGER,
|
||||||
FOREIGN KEY(workspace_id)
|
FOREIGN KEY(workspace_id)
|
||||||
REFERENCES workspaces(workspace_id)
|
REFERENCES workspaces(workspace_id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
) STRICT;)],
|
) STRICT;)],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -680,7 +681,7 @@ mod tests {
|
|||||||
assert_eq!(test_text_1, "test-text-1");
|
assert_eq!(test_text_1, "test-text-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group(axis: gpui::Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
|
fn group(axis: Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
|
||||||
SerializedPaneGroup::Group {
|
SerializedPaneGroup::Group {
|
||||||
axis,
|
axis,
|
||||||
flexes: None,
|
flexes: None,
|
||||||
@ -700,10 +701,10 @@ mod tests {
|
|||||||
// | 3,4 | |
|
// | 3,4 | |
|
||||||
// -----------------
|
// -----------------
|
||||||
let center_group = group(
|
let center_group = group(
|
||||||
gpui::Axis::Horizontal,
|
Axis::Horizontal,
|
||||||
vec![
|
vec![
|
||||||
group(
|
group(
|
||||||
gpui::Axis::Vertical,
|
Axis::Vertical,
|
||||||
vec![
|
vec![
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
SerializedPaneGroup::Pane(SerializedPane::new(
|
||||||
vec![
|
vec![
|
||||||
@ -859,10 +860,10 @@ mod tests {
|
|||||||
// | 3,4 | |
|
// | 3,4 | |
|
||||||
// -----------------
|
// -----------------
|
||||||
let center_pane = group(
|
let center_pane = group(
|
||||||
gpui::Axis::Horizontal,
|
Axis::Horizontal,
|
||||||
vec![
|
vec![
|
||||||
group(
|
group(
|
||||||
gpui::Axis::Vertical,
|
Axis::Vertical,
|
||||||
vec![
|
vec![
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
SerializedPaneGroup::Pane(SerializedPane::new(
|
||||||
vec![
|
vec![
|
||||||
@ -906,10 +907,10 @@ mod tests {
|
|||||||
let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
|
let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
|
||||||
|
|
||||||
let center_pane = group(
|
let center_pane = group(
|
||||||
gpui::Axis::Horizontal,
|
Axis::Horizontal,
|
||||||
vec![
|
vec![
|
||||||
group(
|
group(
|
||||||
gpui::Axis::Vertical,
|
Axis::Vertical,
|
||||||
vec![
|
vec![
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
SerializedPaneGroup::Pane(SerializedPane::new(
|
||||||
vec![
|
vec![
|
||||||
@ -944,7 +945,7 @@ mod tests {
|
|||||||
db.save_workspace(workspace.clone()).await;
|
db.save_workspace(workspace.clone()).await;
|
||||||
|
|
||||||
workspace.center_group = group(
|
workspace.center_group = group(
|
||||||
gpui::Axis::Vertical,
|
Axis::Vertical,
|
||||||
vec![
|
vec![
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
SerializedPaneGroup::Pane(SerializedPane::new(
|
||||||
vec![
|
vec![
|
||||||
|
@ -5,9 +5,7 @@ use db::sqlez::{
|
|||||||
bindable::{Bind, Column, StaticColumnCount},
|
bindable::{Bind, Column, StaticColumnCount},
|
||||||
statement::Statement,
|
statement::Statement,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView, WindowBounds};
|
||||||
platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle,
|
|
||||||
};
|
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -151,15 +149,11 @@ impl SerializedPaneGroup {
|
|||||||
#[async_recursion(?Send)]
|
#[async_recursion(?Send)]
|
||||||
pub(crate) async fn deserialize(
|
pub(crate) async fn deserialize(
|
||||||
self,
|
self,
|
||||||
project: &ModelHandle<Project>,
|
project: &Model<Project>,
|
||||||
workspace_id: WorkspaceId,
|
workspace_id: WorkspaceId,
|
||||||
workspace: &WeakViewHandle<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncWindowContext,
|
||||||
) -> Option<(
|
) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
|
||||||
Member,
|
|
||||||
Option<ViewHandle<Pane>>,
|
|
||||||
Vec<Option<Box<dyn ItemHandle>>>,
|
|
||||||
)> {
|
|
||||||
match self {
|
match self {
|
||||||
SerializedPaneGroup::Group {
|
SerializedPaneGroup::Group {
|
||||||
axis,
|
axis,
|
||||||
@ -171,7 +165,7 @@ impl SerializedPaneGroup {
|
|||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
for child in children {
|
for child in children {
|
||||||
if let Some((new_member, active_pane, new_items)) = child
|
if let Some((new_member, active_pane, new_items)) = child
|
||||||
.deserialize(project, workspace_id, workspace, cx)
|
.deserialize(project, workspace_id, workspace.clone(), cx)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
members.push(new_member);
|
members.push(new_member);
|
||||||
@ -200,18 +194,15 @@ impl SerializedPaneGroup {
|
|||||||
.log_err()?;
|
.log_err()?;
|
||||||
let active = serialized_pane.active;
|
let active = serialized_pane.active;
|
||||||
let new_items = serialized_pane
|
let new_items = serialized_pane
|
||||||
.deserialize_to(project, &pane, workspace_id, workspace, cx)
|
.deserialize_to(project, &pane, workspace_id, workspace.clone(), cx)
|
||||||
.await
|
.await
|
||||||
.log_err()?;
|
.log_err()?;
|
||||||
|
|
||||||
if pane
|
if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? {
|
||||||
.read_with(cx, |pane, _| pane.items_len() != 0)
|
let pane = pane.upgrade()?;
|
||||||
.log_err()?
|
|
||||||
{
|
|
||||||
let pane = pane.upgrade(cx)?;
|
|
||||||
Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
|
Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
|
||||||
} else {
|
} else {
|
||||||
let pane = pane.upgrade(cx)?;
|
let pane = pane.upgrade()?;
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx))
|
.update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx))
|
||||||
.log_err()?;
|
.log_err()?;
|
||||||
@ -235,11 +226,11 @@ impl SerializedPane {
|
|||||||
|
|
||||||
pub async fn deserialize_to(
|
pub async fn deserialize_to(
|
||||||
&self,
|
&self,
|
||||||
project: &ModelHandle<Project>,
|
project: &Model<Project>,
|
||||||
pane: &WeakViewHandle<Pane>,
|
pane: &WeakView<Pane>,
|
||||||
workspace_id: WorkspaceId,
|
workspace_id: WorkspaceId,
|
||||||
workspace: &WeakViewHandle<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncWindowContext,
|
||||||
) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
|
) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
let mut active_item_index = None;
|
let mut active_item_index = None;
|
||||||
@ -284,7 +275,7 @@ impl SerializedPane {
|
|||||||
|
|
||||||
pub type GroupId = i64;
|
pub type GroupId = i64;
|
||||||
pub type PaneId = i64;
|
pub type PaneId = i64;
|
||||||
pub type ItemId = usize;
|
pub type ItemId = u64;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct SerializedItem {
|
pub struct SerializedItem {
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle,
|
AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
|
||||||
WeakViewHandle, WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
|
|
||||||
use crate::{item::WeakItemHandle, Item, ItemHandle};
|
use crate::{
|
||||||
|
item::{Item, WeakItemHandle},
|
||||||
|
ItemHandle,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SearchEvent {
|
pub enum SearchEvent {
|
||||||
@ -29,7 +32,7 @@ pub struct SearchOptions {
|
|||||||
pub replacement: bool,
|
pub replacement: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SearchableItem: Item {
|
pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
|
||||||
type Match: Any + Sync + Send + Clone;
|
type Match: Any + Sync + Send + Clone;
|
||||||
|
|
||||||
fn supported_options() -> SearchOptions {
|
fn supported_options() -> SearchOptions {
|
||||||
@ -40,11 +43,7 @@ pub trait SearchableItem: Item {
|
|||||||
replacement: true,
|
replacement: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn to_search_event(
|
|
||||||
&mut self,
|
|
||||||
event: &Self::Event,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Option<SearchEvent>;
|
|
||||||
fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
|
fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
|
||||||
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
|
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
|
||||||
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
|
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
|
||||||
@ -95,7 +94,7 @@ pub trait SearchableItemHandle: ItemHandle {
|
|||||||
fn subscribe_to_search_events(
|
fn subscribe_to_search_events(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
|
handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
|
||||||
) -> Subscription;
|
) -> Subscription;
|
||||||
fn clear_matches(&self, cx: &mut WindowContext);
|
fn clear_matches(&self, cx: &mut WindowContext);
|
||||||
fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
|
fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
|
||||||
@ -128,7 +127,8 @@ pub trait SearchableItemHandle: ItemHandle {
|
|||||||
) -> Option<usize>;
|
) -> Option<usize>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
|
// todo!("here is where we need to use AnyWeakView");
|
||||||
|
impl<T: SearchableItem> SearchableItemHandle for View<T> {
|
||||||
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
|
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
|
||||||
Box::new(self.downgrade())
|
Box::new(self.downgrade())
|
||||||
}
|
}
|
||||||
@ -144,14 +144,9 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
|
|||||||
fn subscribe_to_search_events(
|
fn subscribe_to_search_events(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
|
handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
|
||||||
) -> Subscription {
|
) -> Subscription {
|
||||||
cx.subscribe(self, move |handle, event, cx| {
|
cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
|
||||||
let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx));
|
|
||||||
if let Some(search_event) = search_event {
|
|
||||||
handler(search_event, cx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_matches(&self, cx: &mut WindowContext) {
|
fn clear_matches(&self, cx: &mut WindowContext) {
|
||||||
@ -198,7 +193,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
|
|||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Vec<Box<dyn Any + Send>>> {
|
) -> Task<Vec<Box<dyn Any + Send>>> {
|
||||||
let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
|
let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
|
||||||
cx.foreground().spawn(async {
|
cx.spawn(|_| async {
|
||||||
let matches = matches.await;
|
let matches = matches.await;
|
||||||
matches
|
matches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -231,21 +226,21 @@ fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
|
impl From<Box<dyn SearchableItemHandle>> for AnyView {
|
||||||
fn from(this: Box<dyn SearchableItemHandle>) -> Self {
|
fn from(this: Box<dyn SearchableItemHandle>) -> Self {
|
||||||
this.as_any().clone()
|
this.to_any().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
|
impl From<&Box<dyn SearchableItemHandle>> for AnyView {
|
||||||
fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
|
fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
|
||||||
this.as_any().clone()
|
this.to_any().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Box<dyn SearchableItemHandle> {
|
impl PartialEq for Box<dyn SearchableItemHandle> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.id() == other.id() && self.window() == other.window()
|
self.item_id() == other.item_id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,22 +249,22 @@ impl Eq for Box<dyn SearchableItemHandle> {}
|
|||||||
pub trait WeakSearchableItemHandle: WeakItemHandle {
|
pub trait WeakSearchableItemHandle: WeakItemHandle {
|
||||||
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
|
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
|
||||||
|
|
||||||
fn into_any(self) -> AnyWeakViewHandle;
|
// fn into_any(self) -> AnyWeakView;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
|
impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
|
||||||
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
|
fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
Some(Box::new(self.upgrade(cx)?))
|
Some(Box::new(self.upgrade()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_any(self) -> AnyWeakViewHandle {
|
// fn into_any(self) -> AnyView {
|
||||||
self.into_any()
|
// self.into_any()
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Box<dyn WeakSearchableItemHandle> {
|
impl PartialEq for Box<dyn WeakSearchableItemHandle> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.id() == other.id() && self.window() == other.window()
|
self.id() == other.id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,6 +272,6 @@ impl Eq for Box<dyn WeakSearchableItemHandle> {}
|
|||||||
|
|
||||||
impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
|
impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
(self.id(), self.window().id()).hash(state)
|
self.id().hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,12 @@ use call::participant::{Frame, RemoteVideoTrack};
|
|||||||
use client::{proto::PeerId, User};
|
use client::{proto::PeerId, User};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*,
|
div, img, AppContext, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
|
||||||
platform::MouseButton,
|
WindowContext,
|
||||||
AppContext, Entity, Task, View, ViewContext,
|
|
||||||
};
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::{
|
|
||||||
borrow::Cow,
|
|
||||||
sync::{Arc, Weak},
|
|
||||||
};
|
};
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
|
use ui::{h_stack, prelude::*, Icon, IconElement, Label};
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Close,
|
Close,
|
||||||
@ -29,6 +25,7 @@ pub struct SharedScreen {
|
|||||||
user: Arc<User>,
|
user: Arc<User>,
|
||||||
nav_history: Option<ItemNavHistory>,
|
nav_history: Option<ItemNavHistory>,
|
||||||
_maintain_frame: Task<Result<()>>,
|
_maintain_frame: Task<Result<()>>,
|
||||||
|
focus: FocusHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedScreen {
|
impl SharedScreen {
|
||||||
@ -38,6 +35,7 @@ impl SharedScreen {
|
|||||||
user: Arc<User>,
|
user: Arc<User>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
cx.focus_handle();
|
||||||
let mut frames = track.frames();
|
let mut frames = track.frames();
|
||||||
Self {
|
Self {
|
||||||
track: Arc::downgrade(track),
|
track: Arc::downgrade(track),
|
||||||
@ -55,77 +53,56 @@ impl SharedScreen {
|
|||||||
this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
|
this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}),
|
}),
|
||||||
|
focus: cx.focus_handle(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for SharedScreen {
|
impl EventEmitter<Event> for SharedScreen {}
|
||||||
type Event = Event;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for SharedScreen {
|
impl FocusableView for SharedScreen {
|
||||||
fn ui_name() -> &'static str {
|
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||||
"SharedScreen"
|
self.focus.clone()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
impl Render for SharedScreen {
|
||||||
enum Focus {}
|
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
div().track_focus(&self.focus).size_full().children(
|
||||||
let frame = self.frame.clone();
|
self.frame
|
||||||
MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
|
.as_ref()
|
||||||
Canvas::new(move |bounds, _, _, cx| {
|
.map(|frame| img(frame.image()).size_full()),
|
||||||
if let Some(frame) = frame.clone() {
|
)
|
||||||
let size = constrain_size_preserving_aspect_ratio(
|
|
||||||
bounds.size(),
|
|
||||||
vec2f(frame.width() as f32, frame.height() as f32),
|
|
||||||
);
|
|
||||||
let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
|
|
||||||
cx.scene().push_surface(gpui::platform::mac::Surface {
|
|
||||||
bounds: RectF::new(origin, size),
|
|
||||||
image_buffer: frame.image(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.contained()
|
|
||||||
.with_style(theme::current(cx).shared_screen)
|
|
||||||
})
|
|
||||||
.on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item for SharedScreen {
|
impl Item for SharedScreen {
|
||||||
fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
|
type Event = Event;
|
||||||
|
|
||||||
|
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
|
||||||
Some(format!("{}'s screen", self.user.github_login).into())
|
Some(format!("{}'s screen", self.user.github_login).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
if let Some(nav_history) = self.nav_history.as_mut() {
|
||||||
nav_history.push::<()>(None, cx);
|
nav_history.push::<()>(None, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content<V: 'static>(
|
fn tab_content(
|
||||||
&self,
|
&self,
|
||||||
_: Option<usize>,
|
_: Option<usize>,
|
||||||
style: &theme::Tab,
|
selected: bool,
|
||||||
_: &AppContext,
|
_: &WindowContext<'_>,
|
||||||
) -> gpui::AnyElement<V> {
|
) -> gpui::AnyElement {
|
||||||
Flex::row()
|
h_stack()
|
||||||
.with_child(
|
.gap_1()
|
||||||
Svg::new("icons/desktop.svg")
|
.child(IconElement::new(Icon::Screen))
|
||||||
.with_color(style.label.text.color)
|
.child(
|
||||||
.constrained()
|
Label::new(format!("{}'s screen", self.user.github_login)).color(if selected {
|
||||||
.with_width(style.type_icon_width)
|
Color::Default
|
||||||
.aligned()
|
} else {
|
||||||
.contained()
|
Color::Muted
|
||||||
.with_margin_right(style.spacing),
|
}),
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Label::new(
|
|
||||||
format!("{}'s screen", self.user.github_login),
|
|
||||||
style.label.clone(),
|
|
||||||
)
|
|
||||||
.aligned(),
|
|
||||||
)
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
@ -138,14 +115,14 @@ impl Item for SharedScreen {
|
|||||||
&self,
|
&self,
|
||||||
_workspace_id: WorkspaceId,
|
_workspace_id: WorkspaceId,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<Self> {
|
) -> Option<View<Self>> {
|
||||||
let track = self.track.upgrade()?;
|
let track = self.track.upgrade()?;
|
||||||
Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
|
Some(cx.new_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
|
fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
|
||||||
match event {
|
match event {
|
||||||
Event::Close => smallvec::smallvec!(ItemEvent::CloseItem),
|
Event::Close => f(ItemEvent::CloseItem),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
use std::ops::Range;
|
|
||||||
|
|
||||||
use crate::{ItemHandle, Pane};
|
use crate::{ItemHandle, Pane};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*,
|
div, AnyView, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
|
||||||
geometry::{
|
|
||||||
rect::RectF,
|
|
||||||
vector::{vec2f, Vector2F},
|
|
||||||
},
|
|
||||||
json::{json, ToJson},
|
|
||||||
AnyElement, AnyViewHandle, Entity, SizeConstraint, Subscription, View, ViewContext, ViewHandle,
|
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
use std::any::TypeId;
|
||||||
|
use ui::{h_stack, prelude::*};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
pub trait StatusItemView: View {
|
pub trait StatusItemView: Render {
|
||||||
fn set_active_pane_item(
|
fn set_active_pane_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
active_pane_item: Option<&dyn crate::ItemHandle>,
|
active_pane_item: Option<&dyn crate::ItemHandle>,
|
||||||
@ -20,63 +15,57 @@ pub trait StatusItemView: View {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
trait StatusItemViewHandle {
|
trait StatusItemViewHandle: Send {
|
||||||
fn as_any(&self) -> &AnyViewHandle;
|
fn to_any(&self) -> AnyView;
|
||||||
fn set_active_pane_item(
|
fn set_active_pane_item(
|
||||||
&self,
|
&self,
|
||||||
active_pane_item: Option<&dyn ItemHandle>,
|
active_pane_item: Option<&dyn ItemHandle>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
);
|
);
|
||||||
fn ui_name(&self) -> &'static str;
|
fn item_type(&self) -> TypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StatusBar {
|
pub struct StatusBar {
|
||||||
left_items: Vec<Box<dyn StatusItemViewHandle>>,
|
left_items: Vec<Box<dyn StatusItemViewHandle>>,
|
||||||
right_items: Vec<Box<dyn StatusItemViewHandle>>,
|
right_items: Vec<Box<dyn StatusItemViewHandle>>,
|
||||||
active_pane: ViewHandle<Pane>,
|
active_pane: View<Pane>,
|
||||||
_observe_active_pane: Subscription,
|
_observe_active_pane: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for StatusBar {
|
impl Render for StatusBar {
|
||||||
type Event = ();
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
}
|
div()
|
||||||
|
.py_0p5()
|
||||||
impl View for StatusBar {
|
.px_1()
|
||||||
fn ui_name() -> &'static str {
|
.flex()
|
||||||
"StatusBar"
|
.items_center()
|
||||||
}
|
.justify_between()
|
||||||
|
.w_full()
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
.h_8()
|
||||||
let theme = &theme::current(cx).workspace.status_bar;
|
.bg(cx.theme().colors().status_bar_background)
|
||||||
|
.child(self.render_left_tools(cx))
|
||||||
StatusBarElement {
|
.child(self.render_right_tools(cx))
|
||||||
left: Flex::row()
|
|
||||||
.with_children(self.left_items.iter().map(|i| {
|
|
||||||
ChildView::new(i.as_any(), cx)
|
|
||||||
.aligned()
|
|
||||||
.contained()
|
|
||||||
.with_margin_right(theme.item_spacing)
|
|
||||||
}))
|
|
||||||
.into_any(),
|
|
||||||
right: Flex::row()
|
|
||||||
.with_children(self.right_items.iter().rev().map(|i| {
|
|
||||||
ChildView::new(i.as_any(), cx)
|
|
||||||
.aligned()
|
|
||||||
.contained()
|
|
||||||
.with_margin_left(theme.item_spacing)
|
|
||||||
}))
|
|
||||||
.into_any(),
|
|
||||||
}
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.container)
|
|
||||||
.constrained()
|
|
||||||
.with_height(theme.height)
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatusBar {
|
impl StatusBar {
|
||||||
pub fn new(active_pane: &ViewHandle<Pane>, cx: &mut ViewContext<Self>) -> Self {
|
fn render_left_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
h_stack()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.children(self.left_items.iter().map(|item| item.to_any()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_right_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
h_stack()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.children(self.right_items.iter().rev().map(|item| item.to_any()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusBar {
|
||||||
|
pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
left_items: Default::default(),
|
left_items: Default::default(),
|
||||||
right_items: Default::default(),
|
right_items: Default::default(),
|
||||||
@ -88,19 +77,22 @@ impl StatusBar {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_left_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
|
pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
|
||||||
where
|
where
|
||||||
T: 'static + StatusItemView,
|
T: 'static + StatusItemView,
|
||||||
{
|
{
|
||||||
|
let active_pane_item = self.active_pane.read(cx).active_item();
|
||||||
|
item.set_active_pane_item(active_pane_item.as_deref(), cx);
|
||||||
|
|
||||||
self.left_items.push(Box::new(item));
|
self.left_items.push(Box::new(item));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn item_of_type<T: StatusItemView>(&self) -> Option<ViewHandle<T>> {
|
pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
|
||||||
self.left_items
|
self.left_items
|
||||||
.iter()
|
.iter()
|
||||||
.chain(self.right_items.iter())
|
.chain(self.right_items.iter())
|
||||||
.find_map(|item| item.as_any().clone().downcast())
|
.find_map(|item| item.to_any().clone().downcast().log_err())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position_of_item<T>(&self) -> Option<usize>
|
pub fn position_of_item<T>(&self) -> Option<usize>
|
||||||
@ -108,12 +100,12 @@ impl StatusBar {
|
|||||||
T: StatusItemView,
|
T: StatusItemView,
|
||||||
{
|
{
|
||||||
for (index, item) in self.left_items.iter().enumerate() {
|
for (index, item) in self.left_items.iter().enumerate() {
|
||||||
if item.as_ref().ui_name() == T::ui_name() {
|
if item.item_type() == TypeId::of::<T>() {
|
||||||
return Some(index);
|
return Some(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (index, item) in self.right_items.iter().enumerate() {
|
for (index, item) in self.right_items.iter().enumerate() {
|
||||||
if item.as_ref().ui_name() == T::ui_name() {
|
if item.item_type() == TypeId::of::<T>() {
|
||||||
return Some(index + self.left_items.len());
|
return Some(index + self.left_items.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,11 +115,14 @@ impl StatusBar {
|
|||||||
pub fn insert_item_after<T>(
|
pub fn insert_item_after<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
position: usize,
|
position: usize,
|
||||||
item: ViewHandle<T>,
|
item: View<T>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) where
|
) where
|
||||||
T: 'static + StatusItemView,
|
T: 'static + StatusItemView,
|
||||||
{
|
{
|
||||||
|
let active_pane_item = self.active_pane.read(cx).active_item();
|
||||||
|
item.set_active_pane_item(active_pane_item.as_deref(), cx);
|
||||||
|
|
||||||
if position < self.left_items.len() {
|
if position < self.left_items.len() {
|
||||||
self.left_items.insert(position + 1, Box::new(item))
|
self.left_items.insert(position + 1, Box::new(item))
|
||||||
} else {
|
} else {
|
||||||
@ -146,15 +141,18 @@ impl StatusBar {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_right_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
|
pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
|
||||||
where
|
where
|
||||||
T: 'static + StatusItemView,
|
T: 'static + StatusItemView,
|
||||||
{
|
{
|
||||||
|
let active_pane_item = self.active_pane.read(cx).active_item();
|
||||||
|
item.set_active_pane_item(active_pane_item.as_deref(), cx);
|
||||||
|
|
||||||
self.right_items.push(Box::new(item));
|
self.right_items.push(Box::new(item));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_active_pane(&mut self, active_pane: &ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
|
pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
|
||||||
self.active_pane = active_pane.clone();
|
self.active_pane = active_pane.clone();
|
||||||
self._observe_active_pane =
|
self._observe_active_pane =
|
||||||
cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
|
cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
|
||||||
@ -169,9 +167,9 @@ impl StatusBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: StatusItemView> StatusItemViewHandle for ViewHandle<T> {
|
impl<T: StatusItemView> StatusItemViewHandle for View<T> {
|
||||||
fn as_any(&self) -> &AnyViewHandle {
|
fn to_any(&self) -> AnyView {
|
||||||
self
|
self.clone().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_active_pane_item(
|
fn set_active_pane_item(
|
||||||
@ -184,88 +182,13 @@ impl<T: StatusItemView> StatusItemViewHandle for ViewHandle<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui_name(&self) -> &'static str {
|
fn item_type(&self) -> TypeId {
|
||||||
T::ui_name()
|
TypeId::of::<T>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&dyn StatusItemViewHandle> for AnyViewHandle {
|
impl From<&dyn StatusItemViewHandle> for AnyView {
|
||||||
fn from(val: &dyn StatusItemViewHandle) -> Self {
|
fn from(val: &dyn StatusItemViewHandle) -> Self {
|
||||||
val.as_any().clone()
|
val.to_any().clone()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StatusBarElement {
|
|
||||||
left: AnyElement<StatusBar>,
|
|
||||||
right: AnyElement<StatusBar>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element<StatusBar> for StatusBarElement {
|
|
||||||
type LayoutState = ();
|
|
||||||
type PaintState = ();
|
|
||||||
|
|
||||||
fn layout(
|
|
||||||
&mut self,
|
|
||||||
mut constraint: SizeConstraint,
|
|
||||||
view: &mut StatusBar,
|
|
||||||
cx: &mut ViewContext<StatusBar>,
|
|
||||||
) -> (Vector2F, Self::LayoutState) {
|
|
||||||
let max_width = constraint.max.x();
|
|
||||||
constraint.min = vec2f(0., constraint.min.y());
|
|
||||||
|
|
||||||
let right_size = self.right.layout(constraint, view, cx);
|
|
||||||
let constraint = SizeConstraint::new(
|
|
||||||
vec2f(0., constraint.min.y()),
|
|
||||||
vec2f(max_width - right_size.x(), constraint.max.y()),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.left.layout(constraint, view, cx);
|
|
||||||
|
|
||||||
(vec2f(max_width, right_size.y()), ())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(
|
|
||||||
&mut self,
|
|
||||||
bounds: RectF,
|
|
||||||
visible_bounds: RectF,
|
|
||||||
_: &mut Self::LayoutState,
|
|
||||||
view: &mut StatusBar,
|
|
||||||
cx: &mut ViewContext<StatusBar>,
|
|
||||||
) -> Self::PaintState {
|
|
||||||
let origin_y = bounds.upper_right().y();
|
|
||||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
|
||||||
|
|
||||||
let left_origin = vec2f(bounds.lower_left().x(), origin_y);
|
|
||||||
self.left.paint(left_origin, visible_bounds, view, cx);
|
|
||||||
|
|
||||||
let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
|
|
||||||
self.right.paint(right_origin, visible_bounds, view, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rect_for_text_range(
|
|
||||||
&self,
|
|
||||||
_: Range<usize>,
|
|
||||||
_: RectF,
|
|
||||||
_: RectF,
|
|
||||||
_: &Self::LayoutState,
|
|
||||||
_: &Self::PaintState,
|
|
||||||
_: &StatusBar,
|
|
||||||
_: &ViewContext<StatusBar>,
|
|
||||||
) -> Option<RectF> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug(
|
|
||||||
&self,
|
|
||||||
bounds: RectF,
|
|
||||||
_: &Self::LayoutState,
|
|
||||||
_: &Self::PaintState,
|
|
||||||
_: &StatusBar,
|
|
||||||
_: &ViewContext<StatusBar>,
|
|
||||||
) -> serde_json::Value {
|
|
||||||
json!({
|
|
||||||
"type": "StatusBarElement",
|
|
||||||
"bounds": bounds.to_json()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,35 @@
|
|||||||
use crate::ItemHandle;
|
use crate::ItemHandle;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle,
|
AnyView, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View, ViewContext,
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use ui::{h_stack, v_stack};
|
||||||
|
|
||||||
pub trait ToolbarItemView: View {
|
pub enum ToolbarItemEvent {
|
||||||
|
ChangeLocation(ToolbarItemLocation),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ToolbarItemView: Render + EventEmitter<ToolbarItemEvent> {
|
||||||
fn set_active_pane_item(
|
fn set_active_pane_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
active_pane_item: Option<&dyn crate::ItemHandle>,
|
active_pane_item: Option<&dyn crate::ItemHandle>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> ToolbarItemLocation;
|
) -> ToolbarItemLocation;
|
||||||
|
|
||||||
fn location_for_event(
|
|
||||||
&self,
|
|
||||||
_event: &Self::Event,
|
|
||||||
current_location: ToolbarItemLocation,
|
|
||||||
_cx: &AppContext,
|
|
||||||
) -> ToolbarItemLocation {
|
|
||||||
current_location
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
|
fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
|
||||||
|
|
||||||
/// Number of times toolbar's height will be repeated to get the effective height.
|
/// Number of times toolbar's height will be repeated to get the effective height.
|
||||||
/// Useful when multiple rows one under each other are needed.
|
/// Useful when multiple rows one under each other are needed.
|
||||||
/// The rows have the same width and act as a whole when reacting to resizes and similar events.
|
/// The rows have the same width and act as a whole when reacting to resizes and similar events.
|
||||||
fn row_count(&self, _cx: &ViewContext<Self>) -> usize {
|
fn row_count(&self, _cx: &WindowContext) -> usize {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ToolbarItemViewHandle {
|
trait ToolbarItemViewHandle: Send {
|
||||||
fn id(&self) -> usize;
|
fn id(&self) -> EntityId;
|
||||||
fn as_any(&self) -> &AnyViewHandle;
|
fn to_any(&self) -> AnyView;
|
||||||
fn set_active_pane_item(
|
fn set_active_pane_item(
|
||||||
&self,
|
&self,
|
||||||
active_pane_item: Option<&dyn ItemHandle>,
|
active_pane_item: Option<&dyn ItemHandle>,
|
||||||
@ -45,8 +42,8 @@ trait ToolbarItemViewHandle {
|
|||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum ToolbarItemLocation {
|
pub enum ToolbarItemLocation {
|
||||||
Hidden,
|
Hidden,
|
||||||
PrimaryLeft { flex: Option<(f32, bool)> },
|
PrimaryLeft,
|
||||||
PrimaryRight { flex: Option<(f32, bool)> },
|
PrimaryRight,
|
||||||
Secondary,
|
Secondary,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,140 +54,161 @@ pub struct Toolbar {
|
|||||||
items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
|
items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Toolbar {
|
impl Toolbar {
|
||||||
type Event = ();
|
fn has_any_visible_items(&self) -> bool {
|
||||||
}
|
self.items
|
||||||
|
.iter()
|
||||||
impl View for Toolbar {
|
.any(|(_item, location)| *location != ToolbarItemLocation::Hidden)
|
||||||
fn ui_name() -> &'static str {
|
|
||||||
"Toolbar"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn left_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
|
||||||
let theme = &theme::current(cx).workspace.toolbar;
|
self.items.iter().filter_map(|(item, location)| {
|
||||||
|
if *location == ToolbarItemLocation::PrimaryLeft {
|
||||||
let mut primary_left_items = Vec::new();
|
Some(item.as_ref())
|
||||||
let mut primary_right_items = Vec::new();
|
} else {
|
||||||
let mut secondary_item = None;
|
None
|
||||||
let spacing = theme.item_spacing;
|
|
||||||
let mut primary_items_row_count = 1;
|
|
||||||
|
|
||||||
for (item, position) in &self.items {
|
|
||||||
match *position {
|
|
||||||
ToolbarItemLocation::Hidden => {}
|
|
||||||
|
|
||||||
ToolbarItemLocation::PrimaryLeft { flex } => {
|
|
||||||
primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
|
|
||||||
let left_item = ChildView::new(item.as_any(), cx).aligned();
|
|
||||||
if let Some((flex, expanded)) = flex {
|
|
||||||
primary_left_items.push(left_item.flex(flex, expanded).into_any());
|
|
||||||
} else {
|
|
||||||
primary_left_items.push(left_item.into_any());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolbarItemLocation::PrimaryRight { flex } => {
|
|
||||||
primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
|
|
||||||
let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
|
|
||||||
if let Some((flex, expanded)) = flex {
|
|
||||||
primary_right_items.push(right_item.flex(flex, expanded).into_any());
|
|
||||||
} else {
|
|
||||||
primary_right_items.push(right_item.into_any());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolbarItemLocation::Secondary => {
|
|
||||||
secondary_item = Some(
|
|
||||||
ChildView::new(item.as_any(), cx)
|
|
||||||
.constrained()
|
|
||||||
.with_height(theme.height * item.row_count(cx) as f32)
|
|
||||||
.into_any(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let container_style = theme.container;
|
fn right_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
|
||||||
let height = theme.height * primary_items_row_count as f32;
|
self.items.iter().filter_map(|(item, location)| {
|
||||||
|
if *location == ToolbarItemLocation::PrimaryRight {
|
||||||
|
Some(item.as_ref())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let mut primary_items = Flex::row().with_spacing(spacing);
|
fn secondary_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
|
||||||
primary_items.extend(primary_left_items);
|
self.items.iter().filter_map(|(item, location)| {
|
||||||
primary_items.extend(primary_right_items);
|
if *location == ToolbarItemLocation::Secondary {
|
||||||
|
Some(item.as_ref())
|
||||||
let mut toolbar = Flex::column();
|
} else {
|
||||||
if !primary_items.is_empty() {
|
None
|
||||||
toolbar.add_child(primary_items.constrained().with_height(height));
|
}
|
||||||
}
|
})
|
||||||
if let Some(secondary_item) = secondary_item {
|
|
||||||
toolbar.add_child(secondary_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
if toolbar.is_empty() {
|
|
||||||
toolbar.into_any_named("toolbar")
|
|
||||||
} else {
|
|
||||||
toolbar
|
|
||||||
.contained()
|
|
||||||
.with_style(container_style)
|
|
||||||
.into_any_named("toolbar")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// <<<<<<< HEAD
|
impl Render for Toolbar {
|
||||||
// =======
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
// #[allow(clippy::too_many_arguments)]
|
if !self.has_any_visible_items() {
|
||||||
// fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
|
return div();
|
||||||
// svg_path: &'static str,
|
}
|
||||||
// style: theme::Interactive<theme::IconButton>,
|
|
||||||
// nav_button_height: f32,
|
let secondary_item = self.secondary_items().next().map(|item| item.to_any());
|
||||||
// tooltip_style: TooltipStyle,
|
|
||||||
// enabled: bool,
|
let has_left_items = self.left_items().count() > 0;
|
||||||
// spacing: f32,
|
let has_right_items = self.right_items().count() > 0;
|
||||||
// on_click: F,
|
|
||||||
// tooltip_action: A,
|
v_stack()
|
||||||
// action_name: &'static str,
|
.p_2()
|
||||||
// cx: &mut ViewContext<Toolbar>,
|
.when(has_left_items || has_right_items, |this| this.gap_2())
|
||||||
// ) -> AnyElement<Toolbar> {
|
.border_b()
|
||||||
// MouseEventHandler::new::<A, _>(0, cx, |state, _| {
|
.border_color(cx.theme().colors().border_variant)
|
||||||
// let style = if enabled {
|
.bg(cx.theme().colors().toolbar_background)
|
||||||
// style.style_for(state)
|
.child(
|
||||||
|
h_stack()
|
||||||
|
.justify_between()
|
||||||
|
.when(has_left_items, |this| {
|
||||||
|
this.child(
|
||||||
|
h_stack()
|
||||||
|
.flex_1()
|
||||||
|
.justify_start()
|
||||||
|
.children(self.left_items().map(|item| item.to_any())),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(has_right_items, |this| {
|
||||||
|
this.child(
|
||||||
|
h_stack()
|
||||||
|
.flex_1()
|
||||||
|
.justify_end()
|
||||||
|
.children(self.right_items().map(|item| item.to_any())),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.children(secondary_item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo!()
|
||||||
|
// impl View for Toolbar {
|
||||||
|
// fn ui_name() -> &'static str {
|
||||||
|
// "Toolbar"
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
|
// let theme = &theme::current(cx).workspace.toolbar;
|
||||||
|
|
||||||
|
// let mut primary_left_items = Vec::new();
|
||||||
|
// let mut primary_right_items = Vec::new();
|
||||||
|
// let mut secondary_item = None;
|
||||||
|
// let spacing = theme.item_spacing;
|
||||||
|
// let mut primary_items_row_count = 1;
|
||||||
|
|
||||||
|
// for (item, position) in &self.items {
|
||||||
|
// match *position {
|
||||||
|
// ToolbarItemLocation::Hidden => {}
|
||||||
|
|
||||||
|
// ToolbarItemLocation::PrimaryLeft { flex } => {
|
||||||
|
// primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
|
||||||
|
// let left_item = ChildView::new(item.as_any(), cx).aligned();
|
||||||
|
// if let Some((flex, expanded)) = flex {
|
||||||
|
// primary_left_items.push(left_item.flex(flex, expanded).into_any());
|
||||||
|
// } else {
|
||||||
|
// primary_left_items.push(left_item.into_any());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ToolbarItemLocation::PrimaryRight { flex } => {
|
||||||
|
// primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
|
||||||
|
// let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
|
||||||
|
// if let Some((flex, expanded)) = flex {
|
||||||
|
// primary_right_items.push(right_item.flex(flex, expanded).into_any());
|
||||||
|
// } else {
|
||||||
|
// primary_right_items.push(right_item.into_any());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ToolbarItemLocation::Secondary => {
|
||||||
|
// secondary_item = Some(
|
||||||
|
// ChildView::new(item.as_any(), cx)
|
||||||
|
// .constrained()
|
||||||
|
// .with_height(theme.height * item.row_count(cx) as f32)
|
||||||
|
// .into_any(),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let container_style = theme.container;
|
||||||
|
// let height = theme.height * primary_items_row_count as f32;
|
||||||
|
|
||||||
|
// let mut primary_items = Flex::row().with_spacing(spacing);
|
||||||
|
// primary_items.extend(primary_left_items);
|
||||||
|
// primary_items.extend(primary_right_items);
|
||||||
|
|
||||||
|
// let mut toolbar = Flex::column();
|
||||||
|
// if !primary_items.is_empty() {
|
||||||
|
// toolbar.add_child(primary_items.constrained().with_height(height));
|
||||||
|
// }
|
||||||
|
// if let Some(secondary_item) = secondary_item {
|
||||||
|
// toolbar.add_child(secondary_item);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if toolbar.is_empty() {
|
||||||
|
// toolbar.into_any_named("toolbar")
|
||||||
// } else {
|
// } else {
|
||||||
// style.disabled_style()
|
// toolbar
|
||||||
// };
|
// .contained()
|
||||||
// Svg::new(svg_path)
|
// .with_style(container_style)
|
||||||
// .with_color(style.color)
|
// .into_any_named("toolbar")
|
||||||
// .constrained()
|
// }
|
||||||
// .with_width(style.icon_width)
|
// }
|
||||||
// .aligned()
|
|
||||||
// .contained()
|
|
||||||
// .with_style(style.container)
|
|
||||||
// .constrained()
|
|
||||||
// .with_width(style.button_width)
|
|
||||||
// .with_height(nav_button_height)
|
|
||||||
// .aligned()
|
|
||||||
// .top()
|
|
||||||
// })
|
|
||||||
// .with_cursor_style(if enabled {
|
|
||||||
// CursorStyle::PointingHand
|
|
||||||
// } else {
|
|
||||||
// CursorStyle::default()
|
|
||||||
// })
|
|
||||||
// .on_click(MouseButton::Left, move |_, toolbar, cx| {
|
|
||||||
// on_click(toolbar, cx)
|
|
||||||
// })
|
|
||||||
// .with_tooltip::<A>(
|
|
||||||
// 0,
|
|
||||||
// action_name,
|
|
||||||
// Some(Box::new(tooltip_action)),
|
|
||||||
// tooltip_style,
|
|
||||||
// cx,
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// .with_margin_right(spacing)
|
|
||||||
// .into_any_named("nav button")
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// >>>>>>> 139cbbfd3aebd0863a7d51b0c12d748764cf0b2e
|
|
||||||
impl Toolbar {
|
impl Toolbar {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -206,7 +224,7 @@ impl Toolbar {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
|
pub fn add_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
|
||||||
where
|
where
|
||||||
T: 'static + ToolbarItemView,
|
T: 'static + ToolbarItemView,
|
||||||
{
|
{
|
||||||
@ -215,12 +233,13 @@ impl Toolbar {
|
|||||||
if let Some((_, current_location)) =
|
if let Some((_, current_location)) =
|
||||||
this.items.iter_mut().find(|(i, _)| i.id() == item.id())
|
this.items.iter_mut().find(|(i, _)| i.id() == item.id())
|
||||||
{
|
{
|
||||||
let new_location = item
|
match event {
|
||||||
.read(cx)
|
ToolbarItemEvent::ChangeLocation(new_location) => {
|
||||||
.location_for_event(event, *current_location, cx);
|
if new_location != current_location {
|
||||||
if new_location != *current_location {
|
*current_location = *new_location;
|
||||||
*current_location = new_location;
|
cx.notify();
|
||||||
cx.notify();
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -252,10 +271,10 @@ impl Toolbar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
|
pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<View<T>> {
|
||||||
self.items
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|(item, _)| item.as_any().clone().downcast())
|
.find_map(|(item, _)| item.to_any().downcast().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hidden(&self) -> bool {
|
pub fn hidden(&self) -> bool {
|
||||||
@ -263,13 +282,13 @@ impl Toolbar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
|
impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
|
||||||
fn id(&self) -> usize {
|
fn id(&self) -> EntityId {
|
||||||
self.id()
|
self.entity_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_any(&self) -> &AnyViewHandle {
|
fn to_any(&self) -> AnyView {
|
||||||
self
|
self.clone().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_active_pane_item(
|
fn set_active_pane_item(
|
||||||
@ -290,12 +309,13 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn row_count(&self, cx: &WindowContext) -> usize {
|
fn row_count(&self, cx: &WindowContext) -> usize {
|
||||||
self.read_with(cx, |this, cx| this.row_count(cx))
|
self.read(cx).row_count(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
|
// todo!()
|
||||||
fn from(val: &dyn ToolbarItemViewHandle) -> Self {
|
// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
|
||||||
val.as_any().clone()
|
// fn from(val: &dyn ToolbarItemViewHandle) -> Self {
|
||||||
}
|
// val.as_any().clone()
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Setting;
|
use settings::Settings;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct WorkspaceSettings {
|
pub struct WorkspaceSettings {
|
||||||
@ -41,7 +41,7 @@ pub enum GitGutterSetting {
|
|||||||
Hide,
|
Hide,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Setting for WorkspaceSettings {
|
impl Settings for WorkspaceSettings {
|
||||||
const KEY: Option<&'static str> = None;
|
const KEY: Option<&'static str> = None;
|
||||||
|
|
||||||
type FileContent = WorkspaceSettingsContent;
|
type FileContent = WorkspaceSettingsContent;
|
||||||
@ -49,7 +49,7 @@ impl Setting for WorkspaceSettings {
|
|||||||
fn load(
|
fn load(
|
||||||
default_value: &Self::FileContent,
|
default_value: &Self::FileContent,
|
||||||
user_values: &[&Self::FileContent],
|
user_values: &[&Self::FileContent],
|
||||||
_: &gpui::AppContext,
|
_: &mut gpui::AppContext,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
Self::load_via_json_merge(default_value, user_values)
|
Self::load_via_json_merge(default_value, user_values)
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -1,66 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "workspace2"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/workspace2.rs"
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[features]
|
|
||||||
test-support = [
|
|
||||||
"call/test-support",
|
|
||||||
"client/test-support",
|
|
||||||
"project/test-support",
|
|
||||||
"settings/test-support",
|
|
||||||
"gpui/test-support",
|
|
||||||
"fs/test-support"
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
db = { path = "../db2", package = "db2" }
|
|
||||||
call = { path = "../call2", package = "call2" }
|
|
||||||
client = { path = "../client2", package = "client2" }
|
|
||||||
collections = { path = "../collections" }
|
|
||||||
# context_menu = { path = "../context_menu" }
|
|
||||||
fs = { path = "../fs2", package = "fs2" }
|
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
|
||||||
install_cli = { path = "../install_cli2", package = "install_cli2" }
|
|
||||||
language = { path = "../language2", package = "language2" }
|
|
||||||
#menu = { path = "../menu" }
|
|
||||||
node_runtime = { path = "../node_runtime" }
|
|
||||||
project = { path = "../project2", package = "project2" }
|
|
||||||
settings = { path = "../settings2", package = "settings2" }
|
|
||||||
terminal = { path = "../terminal2", package = "terminal2" }
|
|
||||||
theme = { path = "../theme2", package = "theme2" }
|
|
||||||
util = { path = "../util" }
|
|
||||||
ui = { package = "ui2", path = "../ui2" }
|
|
||||||
|
|
||||||
async-recursion = "1.0.0"
|
|
||||||
itertools = "0.10"
|
|
||||||
bincode = "1.2.1"
|
|
||||||
anyhow.workspace = true
|
|
||||||
futures.workspace = true
|
|
||||||
lazy_static.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
parking_lot.workspace = true
|
|
||||||
postage.workspace = true
|
|
||||||
schemars.workspace = true
|
|
||||||
serde.workspace = true
|
|
||||||
serde_derive.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
smallvec.workspace = true
|
|
||||||
uuid.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
call = { path = "../call2", package = "call2", features = ["test-support"] }
|
|
||||||
client = { path = "../client2", package = "client2", features = ["test-support"] }
|
|
||||||
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
|
|
||||||
project = { path = "../project2", package = "project2", features = ["test-support"] }
|
|
||||||
settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
|
|
||||||
fs = { path = "../fs2", package = "fs2", features = ["test-support"] }
|
|
||||||
db = { path = "../db2", package = "db2", features = ["test-support"] }
|
|
||||||
|
|
||||||
indoc.workspace = true
|
|
||||||
env_logger.workspace = true
|
|
@ -1,783 +0,0 @@
|
|||||||
use crate::DraggedDock;
|
|
||||||
use crate::{status_bar::StatusItemView, Workspace};
|
|
||||||
use gpui::{
|
|
||||||
div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
|
|
||||||
EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render,
|
|
||||||
SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
|
|
||||||
};
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use ui::{h_stack, ContextMenu, IconButton, Tooltip};
|
|
||||||
use ui::{prelude::*, right_click_menu};
|
|
||||||
|
|
||||||
const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.);
|
|
||||||
|
|
||||||
pub enum PanelEvent {
|
|
||||||
ChangePosition,
|
|
||||||
ZoomIn,
|
|
||||||
ZoomOut,
|
|
||||||
Activate,
|
|
||||||
Close,
|
|
||||||
Focus,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
|
|
||||||
fn persistent_name() -> &'static str;
|
|
||||||
fn position(&self, cx: &WindowContext) -> DockPosition;
|
|
||||||
fn position_is_valid(&self, position: DockPosition) -> bool;
|
|
||||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
|
|
||||||
fn size(&self, cx: &WindowContext) -> Pixels;
|
|
||||||
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);
|
|
||||||
fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
|
|
||||||
fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
|
|
||||||
fn toggle_action(&self) -> Box<dyn Action>;
|
|
||||||
fn icon_label(&self, _: &WindowContext) -> Option<String> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
fn is_zoomed(&self, _cx: &WindowContext) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
|
|
||||||
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait PanelHandle: Send + Sync {
|
|
||||||
fn panel_id(&self) -> EntityId;
|
|
||||||
fn persistent_name(&self) -> &'static str;
|
|
||||||
fn position(&self, cx: &WindowContext) -> DockPosition;
|
|
||||||
fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
|
|
||||||
fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
|
|
||||||
fn is_zoomed(&self, cx: &WindowContext) -> bool;
|
|
||||||
fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
|
|
||||||
fn set_active(&self, active: bool, cx: &mut WindowContext);
|
|
||||||
fn size(&self, cx: &WindowContext) -> Pixels;
|
|
||||||
fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
|
|
||||||
fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
|
|
||||||
fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
|
|
||||||
fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
|
|
||||||
fn icon_label(&self, cx: &WindowContext) -> Option<String>;
|
|
||||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
|
|
||||||
fn to_any(&self) -> AnyView;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PanelHandle for View<T>
|
|
||||||
where
|
|
||||||
T: Panel,
|
|
||||||
{
|
|
||||||
fn panel_id(&self) -> EntityId {
|
|
||||||
Entity::entity_id(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn persistent_name(&self) -> &'static str {
|
|
||||||
T::persistent_name()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position(&self, cx: &WindowContext) -> DockPosition {
|
|
||||||
self.read(cx).position(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool {
|
|
||||||
self.read(cx).position_is_valid(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_position(&self, position: DockPosition, cx: &mut WindowContext) {
|
|
||||||
self.update(cx, |this, cx| this.set_position(position, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_zoomed(&self, cx: &WindowContext) -> bool {
|
|
||||||
self.read(cx).is_zoomed(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) {
|
|
||||||
self.update(cx, |this, cx| this.set_zoomed(zoomed, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_active(&self, active: bool, cx: &mut WindowContext) {
|
|
||||||
self.update(cx, |this, cx| this.set_active(active, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self, cx: &WindowContext) -> Pixels {
|
|
||||||
self.read(cx).size(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext) {
|
|
||||||
self.update(cx, |this, cx| this.set_size(size, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
|
|
||||||
self.read(cx).icon(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str> {
|
|
||||||
self.read(cx).icon_tooltip(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action> {
|
|
||||||
self.read(cx).toggle_action()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn icon_label(&self, cx: &WindowContext) -> Option<String> {
|
|
||||||
self.read(cx).icon_label(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_any(&self) -> AnyView {
|
|
||||||
self.clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
|
||||||
self.read(cx).focus_handle(cx).clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&dyn PanelHandle> for AnyView {
|
|
||||||
fn from(val: &dyn PanelHandle) -> Self {
|
|
||||||
val.to_any()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Dock {
|
|
||||||
position: DockPosition,
|
|
||||||
panel_entries: Vec<PanelEntry>,
|
|
||||||
is_open: bool,
|
|
||||||
active_panel_index: usize,
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
_focus_subscription: Subscription,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusableView for Dock {
|
|
||||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum DockPosition {
|
|
||||||
Left,
|
|
||||||
Bottom,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DockPosition {
|
|
||||||
fn to_label(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Left => "left",
|
|
||||||
Self::Bottom => "bottom",
|
|
||||||
Self::Right => "right",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo!()
|
|
||||||
// fn to_resize_handle_side(self) -> HandleSide {
|
|
||||||
// match self {
|
|
||||||
// Self::Left => HandleSide::Right,
|
|
||||||
// Self::Bottom => HandleSide::Top,
|
|
||||||
// Self::Right => HandleSide::Left,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn axis(&self) -> Axis {
|
|
||||||
match self {
|
|
||||||
Self::Left | Self::Right => Axis::Horizontal,
|
|
||||||
Self::Bottom => Axis::Vertical,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PanelEntry {
|
|
||||||
panel: Arc<dyn PanelHandle>,
|
|
||||||
// todo!()
|
|
||||||
// context_menu: View<ContextMenu>,
|
|
||||||
_subscriptions: [Subscription; 2],
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PanelButtons {
|
|
||||||
dock: View<Dock>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dock {
|
|
||||||
pub fn new(position: DockPosition, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
|
||||||
let focus_handle = cx.focus_handle();
|
|
||||||
|
|
||||||
let dock = cx.new_view(|cx: &mut ViewContext<Self>| {
|
|
||||||
let focus_subscription = cx.on_focus(&focus_handle, |dock, cx| {
|
|
||||||
if let Some(active_entry) = dock.panel_entries.get(dock.active_panel_index) {
|
|
||||||
active_entry.panel.focus_handle(cx).focus(cx)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Self {
|
|
||||||
position,
|
|
||||||
panel_entries: Default::default(),
|
|
||||||
active_panel_index: 0,
|
|
||||||
is_open: false,
|
|
||||||
focus_handle: focus_handle.clone(),
|
|
||||||
_focus_subscription: focus_subscription,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.observe(&dock, move |workspace, dock, cx| {
|
|
||||||
if dock.read(cx).is_open() {
|
|
||||||
if let Some(panel) = dock.read(cx).active_panel() {
|
|
||||||
if panel.is_zoomed(cx) {
|
|
||||||
workspace.zoomed = Some(panel.to_any().downgrade());
|
|
||||||
workspace.zoomed_position = Some(position);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if workspace.zoomed_position == Some(position) {
|
|
||||||
workspace.zoomed = None;
|
|
||||||
workspace.zoomed_position = None;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
dock
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(&self) -> DockPosition {
|
|
||||||
self.position
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_open(&self) -> bool {
|
|
||||||
self.is_open
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo!()
|
|
||||||
// pub fn has_focus(&self, cx: &WindowContext) -> bool {
|
|
||||||
// self.visible_panel()
|
|
||||||
// .map_or(false, |panel| panel.has_focus(cx))
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn panel<T: Panel>(&self) -> Option<View<T>> {
|
|
||||||
self.panel_entries
|
|
||||||
.iter()
|
|
||||||
.find_map(|entry| entry.panel.to_any().clone().downcast().ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn panel_index_for_type<T: Panel>(&self) -> Option<usize> {
|
|
||||||
self.panel_entries
|
|
||||||
.iter()
|
|
||||||
.position(|entry| entry.panel.to_any().downcast::<T>().is_ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn panel_index_for_persistent_name(
|
|
||||||
&self,
|
|
||||||
ui_name: &str,
|
|
||||||
_cx: &AppContext,
|
|
||||||
) -> Option<usize> {
|
|
||||||
self.panel_entries
|
|
||||||
.iter()
|
|
||||||
.position(|entry| entry.panel.persistent_name() == ui_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_panel_index(&self) -> usize {
|
|
||||||
self.active_panel_index
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
|
|
||||||
if open != self.is_open {
|
|
||||||
self.is_open = open;
|
|
||||||
if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
|
|
||||||
active_panel.panel.set_active(open, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
|
|
||||||
for entry in &mut self.panel_entries {
|
|
||||||
if entry.panel.panel_id() == panel.entity_id() {
|
|
||||||
if zoomed != entry.panel.is_zoomed(cx) {
|
|
||||||
entry.panel.set_zoomed(zoomed, cx);
|
|
||||||
}
|
|
||||||
} else if entry.panel.is_zoomed(cx) {
|
|
||||||
entry.panel.set_zoomed(false, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
for entry in &mut self.panel_entries {
|
|
||||||
if entry.panel.is_zoomed(cx) {
|
|
||||||
entry.panel.set_zoomed(false, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn add_panel<T: Panel>(
|
|
||||||
&mut self,
|
|
||||||
panel: View<T>,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
let subscriptions = [
|
|
||||||
cx.observe(&panel, |_, _, cx| cx.notify()),
|
|
||||||
cx.subscribe(&panel, move |this, panel, event, cx| match event {
|
|
||||||
PanelEvent::ChangePosition => {
|
|
||||||
let new_position = panel.read(cx).position(cx);
|
|
||||||
|
|
||||||
let Ok(new_dock) = workspace.update(cx, |workspace, cx| {
|
|
||||||
if panel.is_zoomed(cx) {
|
|
||||||
workspace.zoomed_position = Some(new_position);
|
|
||||||
}
|
|
||||||
match new_position {
|
|
||||||
DockPosition::Left => &workspace.left_dock,
|
|
||||||
DockPosition::Bottom => &workspace.bottom_dock,
|
|
||||||
DockPosition::Right => &workspace.right_dock,
|
|
||||||
}
|
|
||||||
.clone()
|
|
||||||
}) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let was_visible = this.is_open()
|
|
||||||
&& this.visible_panel().map_or(false, |active_panel| {
|
|
||||||
active_panel.panel_id() == Entity::entity_id(&panel)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.remove_panel(&panel, cx);
|
|
||||||
|
|
||||||
new_dock.update(cx, |new_dock, cx| {
|
|
||||||
new_dock.add_panel(panel.clone(), workspace.clone(), cx);
|
|
||||||
if was_visible {
|
|
||||||
new_dock.set_open(true, cx);
|
|
||||||
new_dock.activate_panel(new_dock.panels_len() - 1, cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
PanelEvent::ZoomIn => {
|
|
||||||
this.set_panel_zoomed(&panel.to_any(), true, cx);
|
|
||||||
if !panel.focus_handle(cx).contains_focused(cx) {
|
|
||||||
cx.focus_view(&panel);
|
|
||||||
}
|
|
||||||
workspace
|
|
||||||
.update(cx, |workspace, cx| {
|
|
||||||
workspace.zoomed = Some(panel.downgrade().into());
|
|
||||||
workspace.zoomed_position = Some(panel.read(cx).position(cx));
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
PanelEvent::ZoomOut => {
|
|
||||||
this.set_panel_zoomed(&panel.to_any(), false, cx);
|
|
||||||
workspace
|
|
||||||
.update(cx, |workspace, cx| {
|
|
||||||
if workspace.zoomed_position == Some(this.position) {
|
|
||||||
workspace.zoomed = None;
|
|
||||||
workspace.zoomed_position = None;
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
// todo!() we do not use this event in the production code (even in zed1), remove it
|
|
||||||
PanelEvent::Activate => {
|
|
||||||
if let Some(ix) = this
|
|
||||||
.panel_entries
|
|
||||||
.iter()
|
|
||||||
.position(|entry| entry.panel.panel_id() == Entity::entity_id(&panel))
|
|
||||||
{
|
|
||||||
this.set_open(true, cx);
|
|
||||||
this.activate_panel(ix, cx);
|
|
||||||
cx.focus_view(&panel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PanelEvent::Close => {
|
|
||||||
if this
|
|
||||||
.visible_panel()
|
|
||||||
.map_or(false, |p| p.panel_id() == Entity::entity_id(&panel))
|
|
||||||
{
|
|
||||||
this.set_open(false, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PanelEvent::Focus => {}
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
// todo!()
|
|
||||||
// let dock_view_id = cx.view_id();
|
|
||||||
self.panel_entries.push(PanelEntry {
|
|
||||||
panel: Arc::new(panel),
|
|
||||||
// todo!()
|
|
||||||
// context_menu: cx.add_view(|cx| {
|
|
||||||
// let mut menu = ContextMenu::new(dock_view_id, cx);
|
|
||||||
// menu.set_position_mode(OverlayPositionMode::Local);
|
|
||||||
// menu
|
|
||||||
// }),
|
|
||||||
_subscriptions: subscriptions,
|
|
||||||
});
|
|
||||||
cx.notify()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_panel<T: Panel>(&mut self, panel: &View<T>, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some(panel_ix) = self
|
|
||||||
.panel_entries
|
|
||||||
.iter()
|
|
||||||
.position(|entry| entry.panel.panel_id() == Entity::entity_id(panel))
|
|
||||||
{
|
|
||||||
if panel_ix == self.active_panel_index {
|
|
||||||
self.active_panel_index = 0;
|
|
||||||
self.set_open(false, cx);
|
|
||||||
} else if panel_ix < self.active_panel_index {
|
|
||||||
self.active_panel_index -= 1;
|
|
||||||
}
|
|
||||||
self.panel_entries.remove(panel_ix);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn panels_len(&self) -> usize {
|
|
||||||
self.panel_entries.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext<Self>) {
|
|
||||||
if panel_ix != self.active_panel_index {
|
|
||||||
if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
|
|
||||||
active_panel.panel.set_active(false, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.active_panel_index = panel_ix;
|
|
||||||
if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
|
|
||||||
active_panel.panel.set_active(true, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn visible_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
|
|
||||||
let entry = self.visible_entry()?;
|
|
||||||
Some(&entry.panel)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
|
|
||||||
Some(&self.panel_entries.get(self.active_panel_index)?.panel)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visible_entry(&self) -> Option<&PanelEntry> {
|
|
||||||
if self.is_open {
|
|
||||||
self.panel_entries.get(self.active_panel_index)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Arc<dyn PanelHandle>> {
|
|
||||||
let entry = self.visible_entry()?;
|
|
||||||
if entry.panel.is_zoomed(cx) {
|
|
||||||
Some(entry.panel.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<Pixels> {
|
|
||||||
self.panel_entries
|
|
||||||
.iter()
|
|
||||||
.find(|entry| entry.panel.panel_id() == panel.panel_id())
|
|
||||||
.map(|entry| entry.panel.size(cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_panel_size(&self, cx: &WindowContext) -> Option<Pixels> {
|
|
||||||
if self.is_open {
|
|
||||||
self.panel_entries
|
|
||||||
.get(self.active_panel_index)
|
|
||||||
.map(|entry| entry.panel.size(cx))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
|
|
||||||
let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
|
|
||||||
entry.panel.set_size(size, cx);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_action(&self) -> Box<dyn Action> {
|
|
||||||
match self.position {
|
|
||||||
DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
|
|
||||||
DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
|
|
||||||
DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Dock {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
if let Some(entry) = self.visible_entry() {
|
|
||||||
let size = entry.panel.size(cx);
|
|
||||||
|
|
||||||
let position = self.position;
|
|
||||||
let mut handle = div()
|
|
||||||
.id("resize-handle")
|
|
||||||
.on_drag(DraggedDock(position), |dock, cx| {
|
|
||||||
cx.stop_propagation();
|
|
||||||
cx.new_view(|_| dock.clone())
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|v, e: &ClickEvent, cx| {
|
|
||||||
if e.down.button == MouseButton::Left && e.down.click_count == 2 {
|
|
||||||
v.resize_active_panel(None, cx);
|
|
||||||
cx.stop_propagation();
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.z_index(1)
|
|
||||||
.block_mouse();
|
|
||||||
|
|
||||||
match self.position() {
|
|
||||||
DockPosition::Left => {
|
|
||||||
handle = handle
|
|
||||||
.absolute()
|
|
||||||
.right(px(0.))
|
|
||||||
.top(px(0.))
|
|
||||||
.h_full()
|
|
||||||
.w(RESIZE_HANDLE_SIZE)
|
|
||||||
.cursor_col_resize();
|
|
||||||
}
|
|
||||||
DockPosition::Bottom => {
|
|
||||||
handle = handle
|
|
||||||
.absolute()
|
|
||||||
.top(px(0.))
|
|
||||||
.left(px(0.))
|
|
||||||
.w_full()
|
|
||||||
.h(RESIZE_HANDLE_SIZE)
|
|
||||||
.cursor_row_resize();
|
|
||||||
}
|
|
||||||
DockPosition::Right => {
|
|
||||||
handle = handle
|
|
||||||
.absolute()
|
|
||||||
.top(px(0.))
|
|
||||||
.left(px(0.))
|
|
||||||
.h_full()
|
|
||||||
.w(RESIZE_HANDLE_SIZE)
|
|
||||||
.cursor_col_resize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.bg(cx.theme().colors().panel_background)
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.overflow_hidden()
|
|
||||||
.map(|this| match self.position().axis() {
|
|
||||||
Axis::Horizontal => this.w(size).h_full().flex_row(),
|
|
||||||
Axis::Vertical => this.h(size).w_full().flex_col(),
|
|
||||||
})
|
|
||||||
.map(|this| match self.position() {
|
|
||||||
DockPosition::Left => this.border_r(),
|
|
||||||
DockPosition::Right => this.border_l(),
|
|
||||||
DockPosition::Bottom => this.border_t(),
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.map(|this| match self.position().axis() {
|
|
||||||
Axis::Horizontal => this.min_w(size).h_full(),
|
|
||||||
Axis::Vertical => this.min_h(size).w_full(),
|
|
||||||
})
|
|
||||||
.child(entry.panel.to_any()),
|
|
||||||
)
|
|
||||||
.child(handle)
|
|
||||||
} else {
|
|
||||||
div()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PanelButtons {
|
|
||||||
pub fn new(dock: View<Dock>, cx: &mut ViewContext<Self>) -> Self {
|
|
||||||
cx.observe(&dock, |_, _, cx| cx.notify()).detach();
|
|
||||||
Self { dock }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for PanelButtons {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
// todo!()
|
|
||||||
let dock = self.dock.read(cx);
|
|
||||||
let active_index = dock.active_panel_index;
|
|
||||||
let is_open = dock.is_open;
|
|
||||||
let dock_position = dock.position;
|
|
||||||
|
|
||||||
let (menu_anchor, menu_attach) = match dock.position {
|
|
||||||
DockPosition::Left => (AnchorCorner::BottomLeft, AnchorCorner::TopLeft),
|
|
||||||
DockPosition::Bottom | DockPosition::Right => {
|
|
||||||
(AnchorCorner::BottomRight, AnchorCorner::TopRight)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let buttons = dock
|
|
||||||
.panel_entries
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, entry)| {
|
|
||||||
let icon = entry.panel.icon(cx)?;
|
|
||||||
let icon_tooltip = entry.panel.icon_tooltip(cx)?;
|
|
||||||
let name = entry.panel.persistent_name();
|
|
||||||
let panel = entry.panel.clone();
|
|
||||||
|
|
||||||
let is_active_button = i == active_index && is_open;
|
|
||||||
|
|
||||||
let (action, tooltip) = if is_active_button {
|
|
||||||
let action = dock.toggle_action();
|
|
||||||
|
|
||||||
let tooltip: SharedString =
|
|
||||||
format!("Close {} dock", dock.position.to_label()).into();
|
|
||||||
|
|
||||||
(action, tooltip)
|
|
||||||
} else {
|
|
||||||
let action = entry.panel.toggle_action(cx);
|
|
||||||
|
|
||||||
(action, icon_tooltip.into())
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(
|
|
||||||
right_click_menu(name)
|
|
||||||
.menu(move |cx| {
|
|
||||||
const POSITIONS: [DockPosition; 3] = [
|
|
||||||
DockPosition::Left,
|
|
||||||
DockPosition::Right,
|
|
||||||
DockPosition::Bottom,
|
|
||||||
];
|
|
||||||
|
|
||||||
ContextMenu::build(cx, |mut menu, cx| {
|
|
||||||
for position in POSITIONS {
|
|
||||||
if position != dock_position
|
|
||||||
&& panel.position_is_valid(position, cx)
|
|
||||||
{
|
|
||||||
let panel = panel.clone();
|
|
||||||
menu = menu.entry(position.to_label(), None, move |cx| {
|
|
||||||
panel.set_position(position, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
menu
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.anchor(menu_anchor)
|
|
||||||
.attach(menu_attach)
|
|
||||||
.trigger(
|
|
||||||
IconButton::new(name, icon)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.selected(is_active_button)
|
|
||||||
.on_click({
|
|
||||||
let action = action.boxed_clone();
|
|
||||||
move |_, cx| cx.dispatch_action(action.boxed_clone())
|
|
||||||
})
|
|
||||||
.tooltip(move |cx| {
|
|
||||||
Tooltip::for_action(tooltip.clone(), &*action, cx)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
h_stack().gap_0p5().children(buttons)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusItemView for PanelButtons {
|
|
||||||
fn set_active_pane_item(
|
|
||||||
&mut self,
|
|
||||||
_active_pane_item: Option<&dyn crate::ItemHandle>,
|
|
||||||
_cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
// Nothing to do, panel buttons don't depend on the active center item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub mod test {
|
|
||||||
use super::*;
|
|
||||||
use gpui::{actions, div, ViewContext, WindowContext};
|
|
||||||
|
|
||||||
pub struct TestPanel {
|
|
||||||
pub position: DockPosition,
|
|
||||||
pub zoomed: bool,
|
|
||||||
pub active: bool,
|
|
||||||
pub focus_handle: FocusHandle,
|
|
||||||
pub size: Pixels,
|
|
||||||
}
|
|
||||||
actions!(test, [ToggleTestPanel]);
|
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for TestPanel {}
|
|
||||||
|
|
||||||
impl TestPanel {
|
|
||||||
pub fn new(position: DockPosition, cx: &mut WindowContext) -> Self {
|
|
||||||
Self {
|
|
||||||
position,
|
|
||||||
zoomed: false,
|
|
||||||
active: false,
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
size: px(300.),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for TestPanel {
|
|
||||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
div()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Panel for TestPanel {
|
|
||||||
fn persistent_name() -> &'static str {
|
|
||||||
"TestPanel"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
|
|
||||||
self.position
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position_is_valid(&self, _: super::DockPosition) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
|
|
||||||
self.position = position;
|
|
||||||
cx.emit(PanelEvent::ChangePosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self, _: &WindowContext) -> Pixels {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_size(&mut self, size: Option<Pixels>, _: &mut ViewContext<Self>) {
|
|
||||||
self.size = size.unwrap_or(px(300.));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_action(&self) -> Box<dyn Action> {
|
|
||||||
ToggleTestPanel.boxed_clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_zoomed(&self, _: &WindowContext) -> bool {
|
|
||||||
self.zoomed
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
|
|
||||||
self.zoomed = zoomed;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
|
|
||||||
self.active = active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusableView for TestPanel {
|
|
||||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,395 +0,0 @@
|
|||||||
use crate::{Toast, Workspace};
|
|
||||||
use collections::HashMap;
|
|
||||||
use gpui::{
|
|
||||||
AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render,
|
|
||||||
View, ViewContext, VisualContext,
|
|
||||||
};
|
|
||||||
use std::{any::TypeId, ops::DerefMut};
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
|
||||||
cx.set_global(NotificationTracker::new());
|
|
||||||
// todo!()
|
|
||||||
// simple_message_notification::init(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Notification: EventEmitter<DismissEvent> + Render {}
|
|
||||||
|
|
||||||
impl<V: EventEmitter<DismissEvent> + Render> Notification for V {}
|
|
||||||
|
|
||||||
pub trait NotificationHandle: Send {
|
|
||||||
fn id(&self) -> EntityId;
|
|
||||||
fn to_any(&self) -> AnyView;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Notification> NotificationHandle for View<T> {
|
|
||||||
fn id(&self) -> EntityId {
|
|
||||||
self.entity_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_any(&self) -> AnyView {
|
|
||||||
self.clone().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&dyn NotificationHandle> for AnyView {
|
|
||||||
fn from(val: &dyn NotificationHandle) -> Self {
|
|
||||||
val.to_any()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct NotificationTracker {
|
|
||||||
notifications_sent: HashMap<TypeId, Vec<usize>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for NotificationTracker {
|
|
||||||
type Target = HashMap<TypeId, Vec<usize>>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.notifications_sent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for NotificationTracker {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.notifications_sent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NotificationTracker {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
notifications_sent: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Workspace {
|
|
||||||
pub fn has_shown_notification_once<V: Notification>(
|
|
||||||
&self,
|
|
||||||
id: usize,
|
|
||||||
cx: &ViewContext<Self>,
|
|
||||||
) -> bool {
|
|
||||||
cx.global::<NotificationTracker>()
|
|
||||||
.get(&TypeId::of::<V>())
|
|
||||||
.map(|ids| ids.contains(&id))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_notification_once<V: Notification>(
|
|
||||||
&mut self,
|
|
||||||
id: usize,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
|
|
||||||
) {
|
|
||||||
if !self.has_shown_notification_once::<V>(id, cx) {
|
|
||||||
let tracker = cx.global_mut::<NotificationTracker>();
|
|
||||||
let entry = tracker.entry(TypeId::of::<V>()).or_default();
|
|
||||||
entry.push(id);
|
|
||||||
self.show_notification::<V>(id, cx, build_notification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_notification<V: Notification>(
|
|
||||||
&mut self,
|
|
||||||
id: usize,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
|
|
||||||
) {
|
|
||||||
let type_id = TypeId::of::<V>();
|
|
||||||
if self
|
|
||||||
.notifications
|
|
||||||
.iter()
|
|
||||||
.all(|(existing_type_id, existing_id, _)| {
|
|
||||||
(*existing_type_id, *existing_id) != (type_id, id)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
let notification = build_notification(cx);
|
|
||||||
cx.subscribe(¬ification, move |this, _, _: &DismissEvent, cx| {
|
|
||||||
this.dismiss_notification_internal(type_id, id, cx);
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
self.notifications
|
|
||||||
.push((type_id, id, Box::new(notification)));
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_error<E>(&mut self, err: &E, cx: &mut ViewContext<Self>)
|
|
||||||
where
|
|
||||||
E: std::fmt::Debug,
|
|
||||||
{
|
|
||||||
self.show_notification(0, cx, |cx| {
|
|
||||||
cx.new_view(|_cx| {
|
|
||||||
simple_message_notification::MessageNotification::new(format!("Error: {err:?}"))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
|
|
||||||
let type_id = TypeId::of::<V>();
|
|
||||||
|
|
||||||
self.dismiss_notification_internal(type_id, id, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
|
|
||||||
self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
|
|
||||||
self.show_notification(toast.id, cx, |cx| {
|
|
||||||
cx.new_view(|_cx| match toast.on_click.as_ref() {
|
|
||||||
Some((click_msg, on_click)) => {
|
|
||||||
let on_click = on_click.clone();
|
|
||||||
simple_message_notification::MessageNotification::new(toast.msg.clone())
|
|
||||||
.with_click_message(click_msg.clone())
|
|
||||||
.on_click(move |cx| on_click(cx))
|
|
||||||
}
|
|
||||||
None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
|
|
||||||
self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dismiss_notification_internal(
|
|
||||||
&mut self,
|
|
||||||
type_id: TypeId,
|
|
||||||
id: usize,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
self.notifications
|
|
||||||
.retain(|(existing_type_id, existing_id, _)| {
|
|
||||||
if (*existing_type_id, *existing_id) == (type_id, id) {
|
|
||||||
cx.notify();
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod simple_message_notification {
|
|
||||||
use gpui::{
|
|
||||||
div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,
|
|
||||||
StatefulInteractiveElement, Styled, ViewContext,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use ui::prelude::*;
|
|
||||||
use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
|
|
||||||
|
|
||||||
pub struct MessageNotification {
|
|
||||||
message: SharedString,
|
|
||||||
on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
|
|
||||||
click_message: Option<SharedString>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for MessageNotification {}
|
|
||||||
|
|
||||||
impl MessageNotification {
|
|
||||||
pub fn new<S>(message: S) -> MessageNotification
|
|
||||||
where
|
|
||||||
S: Into<SharedString>,
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
message: message.into(),
|
|
||||||
on_click: None,
|
|
||||||
click_message: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_click_message<S>(mut self, message: S) -> Self
|
|
||||||
where
|
|
||||||
S: Into<SharedString>,
|
|
||||||
{
|
|
||||||
self.click_message = Some(message.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_click<F>(mut self, on_click: F) -> Self
|
|
||||||
where
|
|
||||||
F: 'static + Fn(&mut ViewContext<Self>),
|
|
||||||
{
|
|
||||||
self.on_click = Some(Arc::new(on_click));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for MessageNotification {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
v_stack()
|
|
||||||
.elevation_3(cx)
|
|
||||||
.p_4()
|
|
||||||
.child(
|
|
||||||
h_stack()
|
|
||||||
.justify_between()
|
|
||||||
.child(div().max_w_80().child(Label::new(self.message.clone())))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.id("cancel")
|
|
||||||
.child(IconElement::new(Icon::Close))
|
|
||||||
.cursor_pointer()
|
|
||||||
.on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.children(self.click_message.iter().map(|message| {
|
|
||||||
Button::new(message.clone(), message.clone()).on_click(cx.listener(
|
|
||||||
|this, _, cx| {
|
|
||||||
if let Some(on_click) = this.on_click.as_ref() {
|
|
||||||
(on_click)(cx)
|
|
||||||
};
|
|
||||||
this.dismiss(cx)
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// todo!()
|
|
||||||
// impl View for MessageNotification {
|
|
||||||
// fn ui_name() -> &'static str {
|
|
||||||
// "MessageNotification"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
|
|
||||||
// let theme = theme2::current(cx).clone();
|
|
||||||
// let theme = &theme.simple_message_notification;
|
|
||||||
|
|
||||||
// enum MessageNotificationTag {}
|
|
||||||
|
|
||||||
// let click_message = self.click_message.clone();
|
|
||||||
// let message = match &self.message {
|
|
||||||
// NotificationMessage::Text(text) => {
|
|
||||||
// Text::new(text.to_owned(), theme.message.text.clone()).into_any()
|
|
||||||
// }
|
|
||||||
// NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
|
|
||||||
// };
|
|
||||||
// let on_click = self.on_click.clone();
|
|
||||||
// let has_click_action = on_click.is_some();
|
|
||||||
|
|
||||||
// Flex::column()
|
|
||||||
// .with_child(
|
|
||||||
// Flex::row()
|
|
||||||
// .with_child(
|
|
||||||
// message
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.message.container)
|
|
||||||
// .aligned()
|
|
||||||
// .top()
|
|
||||||
// .left()
|
|
||||||
// .flex(1., true),
|
|
||||||
// )
|
|
||||||
// .with_child(
|
|
||||||
// MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
|
|
||||||
// let style = theme.dismiss_button.style_for(state);
|
|
||||||
// Svg::new("icons/x.svg")
|
|
||||||
// .with_color(style.color)
|
|
||||||
// .constrained()
|
|
||||||
// .with_width(style.icon_width)
|
|
||||||
// .aligned()
|
|
||||||
// .contained()
|
|
||||||
// .with_style(style.container)
|
|
||||||
// .constrained()
|
|
||||||
// .with_width(style.button_width)
|
|
||||||
// .with_height(style.button_width)
|
|
||||||
// })
|
|
||||||
// .with_padding(Padding::uniform(5.))
|
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
// this.dismiss(&Default::default(), cx);
|
|
||||||
// })
|
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
// .aligned()
|
|
||||||
// .constrained()
|
|
||||||
// .with_height(cx.font_cache().line_height(theme.message.text.font_size))
|
|
||||||
// .aligned()
|
|
||||||
// .top()
|
|
||||||
// .flex_float(),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// .with_children({
|
|
||||||
// click_message
|
|
||||||
// .map(|click_message| {
|
|
||||||
// MouseEventHandler::new::<MessageNotificationTag, _>(
|
|
||||||
// 0,
|
|
||||||
// cx,
|
|
||||||
// |state, _| {
|
|
||||||
// let style = theme.action_message.style_for(state);
|
|
||||||
|
|
||||||
// Flex::row()
|
|
||||||
// .with_child(
|
|
||||||
// Text::new(click_message, style.text.clone())
|
|
||||||
// .contained()
|
|
||||||
// .with_style(style.container),
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
// if let Some(on_click) = on_click.as_ref() {
|
|
||||||
// on_click(cx);
|
|
||||||
// this.dismiss(&Default::default(), cx);
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// // Since we're not using a proper overlay, we have to capture these extra events
|
|
||||||
// .on_down(MouseButton::Left, |_, _, _| {})
|
|
||||||
// .on_up(MouseButton::Left, |_, _, _| {})
|
|
||||||
// .with_cursor_style(if has_click_action {
|
|
||||||
// CursorStyle::PointingHand
|
|
||||||
// } else {
|
|
||||||
// CursorStyle::Arrow
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// .into_iter()
|
|
||||||
// })
|
|
||||||
// .into_any()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NotifyResultExt {
|
|
||||||
type Ok;
|
|
||||||
|
|
||||||
fn notify_err(
|
|
||||||
self,
|
|
||||||
workspace: &mut Workspace,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
) -> Option<Self::Ok>;
|
|
||||||
|
|
||||||
fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E> NotifyResultExt for Result<T, E>
|
|
||||||
where
|
|
||||||
E: std::fmt::Debug,
|
|
||||||
{
|
|
||||||
type Ok = T;
|
|
||||||
|
|
||||||
fn notify_err(self, workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<T> {
|
|
||||||
match self {
|
|
||||||
Ok(value) => Some(value),
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("TODO {err:?}");
|
|
||||||
workspace.show_error(&err, cx);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<T> {
|
|
||||||
match self {
|
|
||||||
Ok(value) => Some(value),
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("TODO {err:?}");
|
|
||||||
cx.update(|view, cx| {
|
|
||||||
if let Ok(workspace) = view.downcast::<Workspace>() {
|
|
||||||
workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,239 +0,0 @@
|
|||||||
use super::DraggedItem;
|
|
||||||
use crate::{Pane, SplitDirection, Workspace};
|
|
||||||
use gpui::{
|
|
||||||
color::Color,
|
|
||||||
elements::{Canvas, MouseEventHandler, ParentComponent, Stack},
|
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
|
||||||
platform::MouseButton,
|
|
||||||
scene::MouseUp,
|
|
||||||
AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
|
|
||||||
};
|
|
||||||
use project2::ProjectEntryId;
|
|
||||||
|
|
||||||
pub fn dragged_item_receiver<Tag, D, F>(
|
|
||||||
pane: &Pane,
|
|
||||||
region_id: usize,
|
|
||||||
drop_index: usize,
|
|
||||||
allow_same_pane: bool,
|
|
||||||
split_margin: Option<f32>,
|
|
||||||
cx: &mut ViewContext<Pane>,
|
|
||||||
render_child: F,
|
|
||||||
) -> MouseEventHandler<Pane>
|
|
||||||
where
|
|
||||||
Tag: 'static,
|
|
||||||
D: Element<Pane>,
|
|
||||||
F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
|
|
||||||
{
|
|
||||||
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
|
||||||
let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
|
|
||||||
drag_and_drop
|
|
||||||
.currently_dragged::<DraggedItem>(cx.window())
|
|
||||||
.map(|(drag_position, _)| drag_position)
|
|
||||||
.or_else(|| {
|
|
||||||
drag_and_drop
|
|
||||||
.currently_dragged::<ProjectEntryId>(cx.window())
|
|
||||||
.map(|(drag_position, _)| drag_position)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
|
|
||||||
// Observing hovered will cause a render when the mouse enters regardless
|
|
||||||
// of if mouse position was accessed before
|
|
||||||
let drag_position = if state.dragging() {
|
|
||||||
drag_position
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Stack::new()
|
|
||||||
.with_child(render_child(state, cx))
|
|
||||||
.with_children(drag_position.map(|drag_position| {
|
|
||||||
Canvas::new(move |bounds, _, _, cx| {
|
|
||||||
if bounds.contains_point(drag_position) {
|
|
||||||
let overlay_region = split_margin
|
|
||||||
.and_then(|split_margin| {
|
|
||||||
drop_split_direction(drag_position, bounds, split_margin)
|
|
||||||
.map(|dir| (dir, split_margin))
|
|
||||||
})
|
|
||||||
.map(|(dir, margin)| dir.along_edge(bounds, margin))
|
|
||||||
.unwrap_or(bounds);
|
|
||||||
|
|
||||||
cx.scene().push_stacking_context(None, None);
|
|
||||||
let background = overlay_color(cx);
|
|
||||||
cx.scene().push_quad(Quad {
|
|
||||||
bounds: overlay_region,
|
|
||||||
background: Some(background),
|
|
||||||
border: Default::default(),
|
|
||||||
corner_radii: Default::default(),
|
|
||||||
});
|
|
||||||
cx.scene().pop_stacking_context();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
if drag_position.is_some() {
|
|
||||||
handler = handler
|
|
||||||
.on_up(MouseButton::Left, {
|
|
||||||
move |event, pane, cx| {
|
|
||||||
let workspace = pane.workspace.clone();
|
|
||||||
let pane = cx.weak_handle();
|
|
||||||
handle_dropped_item(
|
|
||||||
event,
|
|
||||||
workspace,
|
|
||||||
&pane,
|
|
||||||
drop_index,
|
|
||||||
allow_same_pane,
|
|
||||||
split_margin,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_move(|_, _, cx| {
|
|
||||||
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
|
||||||
|
|
||||||
if drag_and_drop
|
|
||||||
.currently_dragged::<DraggedItem>(cx.window())
|
|
||||||
.is_some()
|
|
||||||
|| drag_and_drop
|
|
||||||
.currently_dragged::<ProjectEntryId>(cx.window())
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
cx.notify();
|
|
||||||
} else {
|
|
||||||
cx.propagate_event();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handler
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_dropped_item<V: 'static>(
|
|
||||||
event: MouseUp,
|
|
||||||
workspace: WeakViewHandle<Workspace>,
|
|
||||||
pane: &WeakViewHandle<Pane>,
|
|
||||||
index: usize,
|
|
||||||
allow_same_pane: bool,
|
|
||||||
split_margin: Option<f32>,
|
|
||||||
cx: &mut EventContext<V>,
|
|
||||||
) {
|
|
||||||
enum Action {
|
|
||||||
Move(WeakViewHandle<Pane>, usize),
|
|
||||||
Open(ProjectEntryId),
|
|
||||||
}
|
|
||||||
let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
|
|
||||||
let action = if let Some((_, dragged_item)) =
|
|
||||||
drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
|
|
||||||
{
|
|
||||||
Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
|
|
||||||
} else if let Some((_, project_entry)) =
|
|
||||||
drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
|
|
||||||
{
|
|
||||||
Action::Open(*project_entry)
|
|
||||||
} else {
|
|
||||||
cx.propagate_event();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(split_direction) =
|
|
||||||
split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
|
|
||||||
{
|
|
||||||
let pane_to_split = pane.clone();
|
|
||||||
match action {
|
|
||||||
Action::Move(from, item_id_to_move) => {
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(workspace) = workspace.upgrade(cx) {
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
workspace.split_pane_with_item(
|
|
||||||
pane_to_split,
|
|
||||||
split_direction,
|
|
||||||
from,
|
|
||||||
item_id_to_move,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Action::Open(project_entry) => {
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(workspace) = workspace.upgrade(cx) {
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
if let Some(task) = workspace.split_pane_with_project_entry(
|
|
||||||
pane_to_split,
|
|
||||||
split_direction,
|
|
||||||
project_entry,
|
|
||||||
cx,
|
|
||||||
) {
|
|
||||||
task.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
match action {
|
|
||||||
Action::Move(from, item_id) => {
|
|
||||||
if pane != &from || allow_same_pane {
|
|
||||||
let pane = pane.clone();
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(((workspace, from), to)) = workspace
|
|
||||||
.upgrade(cx)
|
|
||||||
.zip(from.upgrade(cx))
|
|
||||||
.zip(pane.upgrade(cx))
|
|
||||||
{
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
workspace.move_item(from, to, item_id, index, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cx.propagate_event();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::Open(project_entry) => {
|
|
||||||
let pane = pane.clone();
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(workspace) = workspace.upgrade(cx) {
|
|
||||||
workspace.update(cx, |workspace, cx| {
|
|
||||||
if let Some(path) =
|
|
||||||
workspace.project.read(cx).path_for_entry(project_entry, cx)
|
|
||||||
{
|
|
||||||
workspace
|
|
||||||
.open_path(path, Some(pane), true, cx)
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drop_split_direction(
|
|
||||||
position: Vector2F,
|
|
||||||
region: RectF,
|
|
||||||
split_margin: f32,
|
|
||||||
) -> Option<SplitDirection> {
|
|
||||||
let mut min_direction = None;
|
|
||||||
let mut min_distance = split_margin;
|
|
||||||
for direction in SplitDirection::all() {
|
|
||||||
let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
|
|
||||||
|
|
||||||
if edge_distance < min_distance {
|
|
||||||
min_direction = Some(direction);
|
|
||||||
min_distance = edge_distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
min_direction
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overlay_color(cx: &AppContext) -> Color {
|
|
||||||
theme2::current(cx).workspace.drop_target_overlay_color
|
|
||||||
}
|
|
@ -1,865 +0,0 @@
|
|||||||
use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use call::{ActiveCall, ParticipantLocation};
|
|
||||||
use collections::HashMap;
|
|
||||||
use gpui::{
|
|
||||||
point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View,
|
|
||||||
ViewContext,
|
|
||||||
};
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use project::Project;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use ui::{prelude::*, Button};
|
|
||||||
|
|
||||||
const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4)
|
|
||||||
const HORIZONTAL_MIN_SIZE: f32 = 80.;
|
|
||||||
const VERTICAL_MIN_SIZE: f32 = 100.;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PaneGroup {
|
|
||||||
pub(crate) root: Member,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PaneGroup {
|
|
||||||
pub(crate) fn with_root(root: Member) -> Self {
|
|
||||||
Self { root }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(pane: View<Pane>) -> Self {
|
|
||||||
Self {
|
|
||||||
root: Member::Pane(pane),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn split(
|
|
||||||
&mut self,
|
|
||||||
old_pane: &View<Pane>,
|
|
||||||
new_pane: &View<Pane>,
|
|
||||||
direction: SplitDirection,
|
|
||||||
) -> Result<()> {
|
|
||||||
match &mut self.root {
|
|
||||||
Member::Pane(pane) => {
|
|
||||||
if pane == old_pane {
|
|
||||||
self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Pane not found"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
|
|
||||||
match &self.root {
|
|
||||||
Member::Pane(_) => None,
|
|
||||||
Member::Axis(axis) => axis.bounding_box_for_pane(pane),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
|
|
||||||
match &self.root {
|
|
||||||
Member::Pane(pane) => Some(pane),
|
|
||||||
Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns:
|
|
||||||
/// - Ok(true) if it found and removed a pane
|
|
||||||
/// - Ok(false) if it found but did not remove the pane
|
|
||||||
/// - Err(_) if it did not find the pane
|
|
||||||
pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
|
|
||||||
match &mut self.root {
|
|
||||||
Member::Pane(_) => Ok(false),
|
|
||||||
Member::Axis(axis) => {
|
|
||||||
if let Some(last_pane) = axis.remove(pane)? {
|
|
||||||
self.root = last_pane;
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
|
|
||||||
match &mut self.root {
|
|
||||||
Member::Pane(_) => {}
|
|
||||||
Member::Axis(axis) => axis.swap(from, to),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn render(
|
|
||||||
&self,
|
|
||||||
project: &Model<Project>,
|
|
||||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
|
||||||
active_call: Option<&Model<ActiveCall>>,
|
|
||||||
active_pane: &View<Pane>,
|
|
||||||
zoomed: Option<&AnyWeakView>,
|
|
||||||
app_state: &Arc<AppState>,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
) -> impl IntoElement {
|
|
||||||
self.root.render(
|
|
||||||
project,
|
|
||||||
0,
|
|
||||||
follower_states,
|
|
||||||
active_call,
|
|
||||||
active_pane,
|
|
||||||
zoomed,
|
|
||||||
app_state,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
|
|
||||||
let mut panes = Vec::new();
|
|
||||||
self.root.collect_panes(&mut panes);
|
|
||||||
panes
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn first_pane(&self) -> View<Pane> {
|
|
||||||
self.root.first_pane()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) enum Member {
|
|
||||||
Axis(PaneAxis),
|
|
||||||
Pane(View<Pane>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Member {
|
|
||||||
fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
|
|
||||||
use Axis::*;
|
|
||||||
use SplitDirection::*;
|
|
||||||
|
|
||||||
let axis = match direction {
|
|
||||||
Up | Down => Vertical,
|
|
||||||
Left | Right => Horizontal,
|
|
||||||
};
|
|
||||||
|
|
||||||
let members = match direction {
|
|
||||||
Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
|
|
||||||
Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
|
|
||||||
};
|
|
||||||
|
|
||||||
Member::Axis(PaneAxis::new(axis, members))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains(&self, needle: &View<Pane>) -> bool {
|
|
||||||
match self {
|
|
||||||
Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
|
|
||||||
Member::Pane(pane) => pane == needle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn first_pane(&self) -> View<Pane> {
|
|
||||||
match self {
|
|
||||||
Member::Axis(axis) => axis.members[0].first_pane(),
|
|
||||||
Member::Pane(pane) => pane.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(
|
|
||||||
&self,
|
|
||||||
project: &Model<Project>,
|
|
||||||
basis: usize,
|
|
||||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
|
||||||
active_call: Option<&Model<ActiveCall>>,
|
|
||||||
active_pane: &View<Pane>,
|
|
||||||
zoomed: Option<&AnyWeakView>,
|
|
||||||
app_state: &Arc<AppState>,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
) -> impl IntoElement {
|
|
||||||
match self {
|
|
||||||
Member::Pane(pane) => {
|
|
||||||
if zoomed == Some(&pane.downgrade().into()) {
|
|
||||||
return div().into_any();
|
|
||||||
}
|
|
||||||
|
|
||||||
let leader = follower_states.get(pane).and_then(|state| {
|
|
||||||
let room = active_call?.read(cx).room()?.read(cx);
|
|
||||||
room.remote_participant_for_peer_id(state.leader_id)
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut leader_border = None;
|
|
||||||
let mut leader_status_box = None;
|
|
||||||
if let Some(leader) = &leader {
|
|
||||||
let mut leader_color = cx
|
|
||||||
.theme()
|
|
||||||
.players()
|
|
||||||
.color_for_participant(leader.participant_index.0)
|
|
||||||
.cursor;
|
|
||||||
leader_color.fade_out(0.3);
|
|
||||||
leader_border = Some(leader_color);
|
|
||||||
|
|
||||||
leader_status_box = match leader.location {
|
|
||||||
ParticipantLocation::SharedProject {
|
|
||||||
project_id: leader_project_id,
|
|
||||||
} => {
|
|
||||||
if Some(leader_project_id) == project.read(cx).remote_id() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let leader_user = leader.user.clone();
|
|
||||||
let leader_user_id = leader.user.id;
|
|
||||||
Some(
|
|
||||||
Button::new(
|
|
||||||
("leader-status", pane.entity_id()),
|
|
||||||
format!(
|
|
||||||
"Follow {} to their active project",
|
|
||||||
leader_user.github_login,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.on_click(cx.listener(
|
|
||||||
move |this, _, cx| {
|
|
||||||
crate::join_remote_project(
|
|
||||||
leader_project_id,
|
|
||||||
leader_user_id,
|
|
||||||
this.app_state().clone(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ParticipantLocation::UnsharedProject => Some(Button::new(
|
|
||||||
("leader-status", pane.entity_id()),
|
|
||||||
format!(
|
|
||||||
"{} is viewing an unshared Zed project",
|
|
||||||
leader.user.github_login
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
ParticipantLocation::External => Some(Button::new(
|
|
||||||
("leader-status", pane.entity_id()),
|
|
||||||
format!(
|
|
||||||
"{} is viewing a window outside of Zed",
|
|
||||||
leader.user.github_login
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
div()
|
|
||||||
.relative()
|
|
||||||
.flex_1()
|
|
||||||
.size_full()
|
|
||||||
.child(pane.clone())
|
|
||||||
.when_some(leader_border, |this, color| {
|
|
||||||
this.border_2().border_color(color)
|
|
||||||
})
|
|
||||||
.when_some(leader_status_box, |this, status_box| {
|
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.w_96()
|
|
||||||
.bottom_3()
|
|
||||||
.right_3()
|
|
||||||
.z_index(1)
|
|
||||||
.child(status_box),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.into_any()
|
|
||||||
|
|
||||||
// let el = div()
|
|
||||||
// .flex()
|
|
||||||
// .flex_1()
|
|
||||||
// .gap_px()
|
|
||||||
// .w_full()
|
|
||||||
// .h_full()
|
|
||||||
// .bg(cx.theme().colors().editor)
|
|
||||||
// .children();
|
|
||||||
}
|
|
||||||
Member::Axis(axis) => axis
|
|
||||||
.render(
|
|
||||||
project,
|
|
||||||
basis + 1,
|
|
||||||
follower_states,
|
|
||||||
active_call,
|
|
||||||
active_pane,
|
|
||||||
zoomed,
|
|
||||||
app_state,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.into_any(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
|
|
||||||
match self {
|
|
||||||
Member::Axis(axis) => {
|
|
||||||
for member in &axis.members {
|
|
||||||
member.collect_panes(panes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Member::Pane(pane) => panes.push(pane),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct PaneAxis {
|
|
||||||
pub axis: Axis,
|
|
||||||
pub members: Vec<Member>,
|
|
||||||
pub flexes: Arc<Mutex<Vec<f32>>>,
|
|
||||||
pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PaneAxis {
|
|
||||||
pub fn new(axis: Axis, members: Vec<Member>) -> Self {
|
|
||||||
let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
|
|
||||||
let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
|
|
||||||
Self {
|
|
||||||
axis,
|
|
||||||
members,
|
|
||||||
flexes,
|
|
||||||
bounding_boxes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
|
|
||||||
let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
|
|
||||||
debug_assert!(members.len() == flexes.len());
|
|
||||||
|
|
||||||
let flexes = Arc::new(Mutex::new(flexes));
|
|
||||||
let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
|
|
||||||
Self {
|
|
||||||
axis,
|
|
||||||
members,
|
|
||||||
flexes,
|
|
||||||
bounding_boxes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split(
|
|
||||||
&mut self,
|
|
||||||
old_pane: &View<Pane>,
|
|
||||||
new_pane: &View<Pane>,
|
|
||||||
direction: SplitDirection,
|
|
||||||
) -> Result<()> {
|
|
||||||
for (mut idx, member) in self.members.iter_mut().enumerate() {
|
|
||||||
match member {
|
|
||||||
Member::Axis(axis) => {
|
|
||||||
if axis.split(old_pane, new_pane, direction).is_ok() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Member::Pane(pane) => {
|
|
||||||
if pane == old_pane {
|
|
||||||
if direction.axis() == self.axis {
|
|
||||||
if direction.increasing() {
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.members.insert(idx, Member::Pane(new_pane.clone()));
|
|
||||||
*self.flexes.lock() = vec![1.; self.members.len()];
|
|
||||||
} else {
|
|
||||||
*member =
|
|
||||||
Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(anyhow!("Pane not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
|
|
||||||
let mut found_pane = false;
|
|
||||||
let mut remove_member = None;
|
|
||||||
for (idx, member) in self.members.iter_mut().enumerate() {
|
|
||||||
match member {
|
|
||||||
Member::Axis(axis) => {
|
|
||||||
if let Ok(last_pane) = axis.remove(pane_to_remove) {
|
|
||||||
if let Some(last_pane) = last_pane {
|
|
||||||
*member = last_pane;
|
|
||||||
}
|
|
||||||
found_pane = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Member::Pane(pane) => {
|
|
||||||
if pane == pane_to_remove {
|
|
||||||
found_pane = true;
|
|
||||||
remove_member = Some(idx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if found_pane {
|
|
||||||
if let Some(idx) = remove_member {
|
|
||||||
self.members.remove(idx);
|
|
||||||
*self.flexes.lock() = vec![1.; self.members.len()];
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.members.len() == 1 {
|
|
||||||
let result = self.members.pop();
|
|
||||||
*self.flexes.lock() = vec![1.; self.members.len()];
|
|
||||||
Ok(result)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Pane not found"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
|
|
||||||
for member in self.members.iter_mut() {
|
|
||||||
match member {
|
|
||||||
Member::Axis(axis) => axis.swap(from, to),
|
|
||||||
Member::Pane(pane) => {
|
|
||||||
if pane == from {
|
|
||||||
*member = Member::Pane(to.clone());
|
|
||||||
} else if pane == to {
|
|
||||||
*member = Member::Pane(from.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
|
|
||||||
debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
|
|
||||||
|
|
||||||
for (idx, member) in self.members.iter().enumerate() {
|
|
||||||
match member {
|
|
||||||
Member::Pane(found) => {
|
|
||||||
if pane == found {
|
|
||||||
return self.bounding_boxes.lock()[idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Member::Axis(axis) => {
|
|
||||||
if let Some(rect) = axis.bounding_box_for_pane(pane) {
|
|
||||||
return Some(rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
|
|
||||||
debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
|
|
||||||
|
|
||||||
let bounding_boxes = self.bounding_boxes.lock();
|
|
||||||
|
|
||||||
for (idx, member) in self.members.iter().enumerate() {
|
|
||||||
if let Some(coordinates) = bounding_boxes[idx] {
|
|
||||||
if coordinates.contains(&coordinate) {
|
|
||||||
return match member {
|
|
||||||
Member::Pane(found) => Some(found),
|
|
||||||
Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(
|
|
||||||
&self,
|
|
||||||
project: &Model<Project>,
|
|
||||||
basis: usize,
|
|
||||||
follower_states: &HashMap<View<Pane>, FollowerState>,
|
|
||||||
active_call: Option<&Model<ActiveCall>>,
|
|
||||||
active_pane: &View<Pane>,
|
|
||||||
zoomed: Option<&AnyWeakView>,
|
|
||||||
app_state: &Arc<AppState>,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
) -> gpui::AnyElement {
|
|
||||||
debug_assert!(self.members.len() == self.flexes.lock().len());
|
|
||||||
let mut active_pane_ix = None;
|
|
||||||
|
|
||||||
pane_axis(
|
|
||||||
self.axis,
|
|
||||||
basis,
|
|
||||||
self.flexes.clone(),
|
|
||||||
self.bounding_boxes.clone(),
|
|
||||||
)
|
|
||||||
.children(self.members.iter().enumerate().map(|(ix, member)| {
|
|
||||||
if member.contains(active_pane) {
|
|
||||||
active_pane_ix = Some(ix);
|
|
||||||
}
|
|
||||||
member
|
|
||||||
.render(
|
|
||||||
project,
|
|
||||||
(basis + ix) * 10,
|
|
||||||
follower_states,
|
|
||||||
active_call,
|
|
||||||
active_pane,
|
|
||||||
zoomed,
|
|
||||||
app_state,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}))
|
|
||||||
.with_active_pane(active_pane_ix)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
|
|
||||||
pub enum SplitDirection {
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SplitDirection {
|
|
||||||
pub fn all() -> [Self; 4] {
|
|
||||||
[Self::Up, Self::Down, Self::Left, Self::Right]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
|
|
||||||
match self {
|
|
||||||
Self::Up => rect.origin.y,
|
|
||||||
Self::Down => rect.lower_left().y,
|
|
||||||
Self::Left => rect.lower_left().x,
|
|
||||||
Self::Right => rect.lower_right().x,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
|
|
||||||
match self {
|
|
||||||
Self::Up => Bounds {
|
|
||||||
origin: bounds.origin,
|
|
||||||
size: size(bounds.size.width, length),
|
|
||||||
},
|
|
||||||
Self::Down => Bounds {
|
|
||||||
origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
|
|
||||||
size: size(bounds.size.width, length),
|
|
||||||
},
|
|
||||||
Self::Left => Bounds {
|
|
||||||
origin: bounds.origin,
|
|
||||||
size: size(length, bounds.size.height),
|
|
||||||
},
|
|
||||||
Self::Right => Bounds {
|
|
||||||
origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
|
|
||||||
size: size(length, bounds.size.height),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis(&self) -> Axis {
|
|
||||||
match self {
|
|
||||||
Self::Up | Self::Down => Axis::Vertical,
|
|
||||||
Self::Left | Self::Right => Axis::Horizontal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn increasing(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Left | Self::Up => false,
|
|
||||||
Self::Down | Self::Right => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod element {
|
|
||||||
|
|
||||||
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
|
||||||
|
|
||||||
use gpui::{
|
|
||||||
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
|
|
||||||
IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
|
|
||||||
Size, Style, WindowContext,
|
|
||||||
};
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use ui::prelude::*;
|
|
||||||
|
|
||||||
use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
|
|
||||||
|
|
||||||
const DIVIDER_SIZE: f32 = 1.0;
|
|
||||||
|
|
||||||
pub fn pane_axis(
|
|
||||||
axis: Axis,
|
|
||||||
basis: usize,
|
|
||||||
flexes: Arc<Mutex<Vec<f32>>>,
|
|
||||||
bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
|
|
||||||
) -> PaneAxisElement {
|
|
||||||
PaneAxisElement {
|
|
||||||
axis,
|
|
||||||
basis,
|
|
||||||
flexes,
|
|
||||||
bounding_boxes,
|
|
||||||
children: SmallVec::new(),
|
|
||||||
active_pane_ix: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PaneAxisElement {
|
|
||||||
axis: Axis,
|
|
||||||
basis: usize,
|
|
||||||
flexes: Arc<Mutex<Vec<f32>>>,
|
|
||||||
bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
|
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
|
||||||
active_pane_ix: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PaneAxisElement {
|
|
||||||
pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
|
|
||||||
self.active_pane_ix = active_pane_ix;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_resize(
|
|
||||||
flexes: &Arc<Mutex<Vec<f32>>>,
|
|
||||||
e: &MouseMoveEvent,
|
|
||||||
ix: usize,
|
|
||||||
axis: Axis,
|
|
||||||
child_start: Point<Pixels>,
|
|
||||||
container_size: Size<Pixels>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) {
|
|
||||||
let min_size = match axis {
|
|
||||||
Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
|
|
||||||
Axis::Vertical => px(VERTICAL_MIN_SIZE),
|
|
||||||
};
|
|
||||||
let mut flexes = flexes.lock();
|
|
||||||
debug_assert!(flex_values_in_bounds(flexes.as_slice()));
|
|
||||||
|
|
||||||
let size = move |ix, flexes: &[f32]| {
|
|
||||||
container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Don't allow resizing to less than the minimum size, if elements are already too small
|
|
||||||
if min_size - px(1.) > size(ix, flexes.as_slice()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut proposed_current_pixel_change =
|
|
||||||
(e.position - child_start).along(axis) - size(ix, flexes.as_slice());
|
|
||||||
|
|
||||||
let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
|
|
||||||
let flex_change = pixel_dx / container_size.along(axis);
|
|
||||||
let current_target_flex = flexes[target_ix] + flex_change;
|
|
||||||
let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
|
|
||||||
(current_target_flex, next_target_flex)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut successors = iter::from_fn({
|
|
||||||
let forward = proposed_current_pixel_change > px(0.);
|
|
||||||
let mut ix_offset = 0;
|
|
||||||
let len = flexes.len();
|
|
||||||
move || {
|
|
||||||
let result = if forward {
|
|
||||||
(ix + 1 + ix_offset < len).then(|| ix + ix_offset)
|
|
||||||
} else {
|
|
||||||
(ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
|
|
||||||
};
|
|
||||||
|
|
||||||
ix_offset += 1;
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
while proposed_current_pixel_change.abs() > px(0.) {
|
|
||||||
let Some(current_ix) = successors.next() else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
let next_target_size = Pixels::max(
|
|
||||||
size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
|
|
||||||
min_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
let current_target_size = Pixels::max(
|
|
||||||
size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
|
|
||||||
- next_target_size,
|
|
||||||
min_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
let current_pixel_change =
|
|
||||||
current_target_size - size(current_ix, flexes.as_slice());
|
|
||||||
|
|
||||||
let (current_target_flex, next_target_flex) =
|
|
||||||
flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
|
|
||||||
|
|
||||||
flexes[current_ix] = current_target_flex;
|
|
||||||
flexes[current_ix + 1] = next_target_flex;
|
|
||||||
|
|
||||||
proposed_current_pixel_change -= current_pixel_change;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo!(schedule serialize)
|
|
||||||
// workspace.schedule_serialize(cx);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_handle(
|
|
||||||
flexes: Arc<Mutex<Vec<f32>>>,
|
|
||||||
dragged_handle: Rc<RefCell<Option<usize>>>,
|
|
||||||
axis: Axis,
|
|
||||||
ix: usize,
|
|
||||||
pane_bounds: Bounds<Pixels>,
|
|
||||||
axis_bounds: Bounds<Pixels>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) {
|
|
||||||
let handle_bounds = Bounds {
|
|
||||||
origin: pane_bounds.origin.apply_along(axis, |origin| {
|
|
||||||
origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
|
|
||||||
}),
|
|
||||||
size: pane_bounds
|
|
||||||
.size
|
|
||||||
.apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
|
|
||||||
};
|
|
||||||
let divider_bounds = Bounds {
|
|
||||||
origin: pane_bounds
|
|
||||||
.origin
|
|
||||||
.apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
|
|
||||||
size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.with_z_index(3, |cx| {
|
|
||||||
let interactive_handle_bounds = InteractiveBounds {
|
|
||||||
bounds: handle_bounds,
|
|
||||||
stacking_order: cx.stacking_order().clone(),
|
|
||||||
};
|
|
||||||
if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
|
|
||||||
cx.set_cursor_style(match axis {
|
|
||||||
Axis::Vertical => CursorStyle::ResizeUpDown,
|
|
||||||
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.add_opaque_layer(handle_bounds);
|
|
||||||
cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
|
|
||||||
|
|
||||||
cx.on_mouse_event({
|
|
||||||
let dragged_handle = dragged_handle.clone();
|
|
||||||
move |e: &MouseDownEvent, phase, _cx| {
|
|
||||||
if phase.bubble() && handle_bounds.contains(&e.position) {
|
|
||||||
dragged_handle.replace(Some(ix));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| {
|
|
||||||
let dragged_handle = dragged_handle.borrow();
|
|
||||||
if phase.bubble() && *dragged_handle == Some(ix) {
|
|
||||||
Self::compute_resize(
|
|
||||||
&flexes,
|
|
||||||
e,
|
|
||||||
ix,
|
|
||||||
axis,
|
|
||||||
pane_bounds.origin,
|
|
||||||
axis_bounds.size,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoElement for PaneAxisElement {
|
|
||||||
type Element = Self;
|
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ui::prelude::ElementId> {
|
|
||||||
Some(self.basis.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element for PaneAxisElement {
|
|
||||||
type State = Rc<RefCell<Option<usize>>>;
|
|
||||||
|
|
||||||
fn request_layout(
|
|
||||||
&mut self,
|
|
||||||
state: Option<Self::State>,
|
|
||||||
cx: &mut ui::prelude::WindowContext,
|
|
||||||
) -> (gpui::LayoutId, Self::State) {
|
|
||||||
let mut style = Style::default();
|
|
||||||
style.flex_grow = 1.;
|
|
||||||
style.flex_shrink = 1.;
|
|
||||||
style.flex_basis = relative(0.).into();
|
|
||||||
style.size.width = relative(1.).into();
|
|
||||||
style.size.height = relative(1.).into();
|
|
||||||
let layout_id = cx.request_layout(&style, None);
|
|
||||||
let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
|
|
||||||
(layout_id, dragged_pane)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(
|
|
||||||
&mut self,
|
|
||||||
bounds: gpui::Bounds<ui::prelude::Pixels>,
|
|
||||||
state: &mut Self::State,
|
|
||||||
cx: &mut ui::prelude::WindowContext,
|
|
||||||
) {
|
|
||||||
let flexes = self.flexes.lock().clone();
|
|
||||||
let len = self.children.len();
|
|
||||||
debug_assert!(flexes.len() == len);
|
|
||||||
debug_assert!(flex_values_in_bounds(flexes.as_slice()));
|
|
||||||
|
|
||||||
let mut origin = bounds.origin;
|
|
||||||
let space_per_flex = bounds.size.along(self.axis) / len as f32;
|
|
||||||
|
|
||||||
let mut bounding_boxes = self.bounding_boxes.lock();
|
|
||||||
bounding_boxes.clear();
|
|
||||||
|
|
||||||
for (ix, child) in self.children.iter_mut().enumerate() {
|
|
||||||
//todo!(active_pane_magnification)
|
|
||||||
// If using active pane magnification, need to switch to using
|
|
||||||
// 1 for all non-active panes, and then the magnification for the
|
|
||||||
// active pane.
|
|
||||||
let child_size = bounds
|
|
||||||
.size
|
|
||||||
.apply_along(self.axis, |_| space_per_flex * flexes[ix]);
|
|
||||||
|
|
||||||
let child_bounds = Bounds {
|
|
||||||
origin,
|
|
||||||
size: child_size,
|
|
||||||
};
|
|
||||||
bounding_boxes.push(Some(child_bounds));
|
|
||||||
cx.with_z_index(0, |cx| {
|
|
||||||
child.draw(origin, child_size.into(), cx);
|
|
||||||
});
|
|
||||||
cx.with_z_index(1, |cx| {
|
|
||||||
if ix < len - 1 {
|
|
||||||
Self::push_handle(
|
|
||||||
self.flexes.clone(),
|
|
||||||
state.clone(),
|
|
||||||
self.axis,
|
|
||||||
ix,
|
|
||||||
child_bounds,
|
|
||||||
bounds,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.with_z_index(1, |cx| {
|
|
||||||
cx.on_mouse_event({
|
|
||||||
let state = state.clone();
|
|
||||||
move |_: &MouseUpEvent, phase, _cx| {
|
|
||||||
if phase.bubble() {
|
|
||||||
state.replace(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParentElement for PaneAxisElement {
|
|
||||||
fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
|
|
||||||
&mut self.children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flex_values_in_bounds(flexes: &[f32]) -> bool {
|
|
||||||
(flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,973 +0,0 @@
|
|||||||
//#![allow(dead_code)]
|
|
||||||
|
|
||||||
pub mod model;
|
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
|
||||||
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
|
||||||
use gpui::{Axis, WindowBounds};
|
|
||||||
|
|
||||||
use util::{unzip_option, ResultExt};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::WorkspaceId;
|
|
||||||
|
|
||||||
use model::{
|
|
||||||
GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
|
|
||||||
WorkspaceLocation,
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::model::DockStructure;
|
|
||||||
|
|
||||||
define_connection! {
|
|
||||||
// Current schema shape using pseudo-rust syntax:
|
|
||||||
//
|
|
||||||
// workspaces(
|
|
||||||
// workspace_id: usize, // Primary key for workspaces
|
|
||||||
// workspace_location: Bincode<Vec<PathBuf>>,
|
|
||||||
// dock_visible: bool, // Deprecated
|
|
||||||
// dock_anchor: DockAnchor, // Deprecated
|
|
||||||
// dock_pane: Option<usize>, // Deprecated
|
|
||||||
// left_sidebar_open: boolean,
|
|
||||||
// timestamp: String, // UTC YYYY-MM-DD HH:MM:SS
|
|
||||||
// window_state: String, // WindowBounds Discriminant
|
|
||||||
// window_x: Option<f32>, // WindowBounds::Fixed RectF x
|
|
||||||
// window_y: Option<f32>, // WindowBounds::Fixed RectF y
|
|
||||||
// window_width: Option<f32>, // WindowBounds::Fixed RectF width
|
|
||||||
// window_height: Option<f32>, // WindowBounds::Fixed RectF height
|
|
||||||
// display: Option<Uuid>, // Display id
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// pane_groups(
|
|
||||||
// group_id: usize, // Primary key for pane_groups
|
|
||||||
// workspace_id: usize, // References workspaces table
|
|
||||||
// parent_group_id: Option<usize>, // None indicates that this is the root node
|
|
||||||
// position: Optiopn<usize>, // None indicates that this is the root node
|
|
||||||
// axis: Option<Axis>, // 'Vertical', 'Horizontal'
|
|
||||||
// flexes: Option<Vec<f32>>, // A JSON array of floats
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// panes(
|
|
||||||
// pane_id: usize, // Primary key for panes
|
|
||||||
// workspace_id: usize, // References workspaces table
|
|
||||||
// active: bool,
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// center_panes(
|
|
||||||
// pane_id: usize, // Primary key for center_panes
|
|
||||||
// parent_group_id: Option<usize>, // References pane_groups. If none, this is the root
|
|
||||||
// position: Option<usize>, // None indicates this is the root
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// CREATE TABLE items(
|
|
||||||
// item_id: usize, // This is the item's view id, so this is not unique
|
|
||||||
// workspace_id: usize, // References workspaces table
|
|
||||||
// pane_id: usize, // References panes table
|
|
||||||
// kind: String, // Indicates which view this connects to. This is the key in the item_deserializers global
|
|
||||||
// position: usize, // Position of the item in the parent pane. This is equivalent to panes' position column
|
|
||||||
// active: bool, // Indicates if this item is the active one in the pane
|
|
||||||
// )
|
|
||||||
pub static ref DB: WorkspaceDb<()> =
|
|
||||||
&[sql!(
|
|
||||||
CREATE TABLE workspaces(
|
|
||||||
workspace_id INTEGER PRIMARY KEY,
|
|
||||||
workspace_location BLOB UNIQUE,
|
|
||||||
dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
|
|
||||||
dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
|
|
||||||
dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
|
|
||||||
left_sidebar_open INTEGER, // Boolean
|
|
||||||
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
|
|
||||||
) STRICT;
|
|
||||||
|
|
||||||
CREATE TABLE pane_groups(
|
|
||||||
group_id INTEGER PRIMARY KEY,
|
|
||||||
workspace_id INTEGER NOT NULL,
|
|
||||||
parent_group_id INTEGER, // NULL indicates that this is a root node
|
|
||||||
position INTEGER, // NULL indicates that this is a root node
|
|
||||||
axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal'
|
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE,
|
|
||||||
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
|
||||||
) STRICT;
|
|
||||||
|
|
||||||
CREATE TABLE panes(
|
|
||||||
pane_id INTEGER PRIMARY KEY,
|
|
||||||
workspace_id INTEGER NOT NULL,
|
|
||||||
active INTEGER NOT NULL, // Boolean
|
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE
|
|
||||||
) STRICT;
|
|
||||||
|
|
||||||
CREATE TABLE center_panes(
|
|
||||||
pane_id INTEGER PRIMARY KEY,
|
|
||||||
parent_group_id INTEGER, // NULL means that this is a root pane
|
|
||||||
position INTEGER, // NULL means that this is a root pane
|
|
||||||
FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
|
|
||||||
ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
|
||||||
) STRICT;
|
|
||||||
|
|
||||||
CREATE TABLE items(
|
|
||||||
item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique
|
|
||||||
workspace_id INTEGER NOT NULL,
|
|
||||||
pane_id INTEGER NOT NULL,
|
|
||||||
kind TEXT NOT NULL,
|
|
||||||
position INTEGER NOT NULL,
|
|
||||||
active INTEGER NOT NULL,
|
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE,
|
|
||||||
FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
|
|
||||||
ON DELETE CASCADE,
|
|
||||||
PRIMARY KEY(item_id, workspace_id)
|
|
||||||
) STRICT;
|
|
||||||
),
|
|
||||||
sql!(
|
|
||||||
ALTER TABLE workspaces ADD COLUMN window_state TEXT;
|
|
||||||
ALTER TABLE workspaces ADD COLUMN window_x REAL;
|
|
||||||
ALTER TABLE workspaces ADD COLUMN window_y REAL;
|
|
||||||
ALTER TABLE workspaces ADD COLUMN window_width REAL;
|
|
||||||
ALTER TABLE workspaces ADD COLUMN window_height REAL;
|
|
||||||
ALTER TABLE workspaces ADD COLUMN display BLOB;
|
|
||||||
),
|
|
||||||
// Drop foreign key constraint from workspaces.dock_pane to panes table.
|
|
||||||
sql!(
|
|
||||||
CREATE TABLE workspaces_2(
|
|
||||||
workspace_id INTEGER PRIMARY KEY,
|
|
||||||
workspace_location BLOB UNIQUE,
|
|
||||||
dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
|
|
||||||
dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
|
|
||||||
dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
|
|
||||||
left_sidebar_open INTEGER, // Boolean
|
|
||||||
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
window_state TEXT,
|
|
||||||
window_x REAL,
|
|
||||||
window_y REAL,
|
|
||||||
window_width REAL,
|
|
||||||
window_height REAL,
|
|
||||||
display BLOB
|
|
||||||
) STRICT;
|
|
||||||
INSERT INTO workspaces_2 SELECT * FROM workspaces;
|
|
||||||
DROP TABLE workspaces;
|
|
||||||
ALTER TABLE workspaces_2 RENAME TO workspaces;
|
|
||||||
),
|
|
||||||
// Add panels related information
|
|
||||||
sql!(
|
|
||||||
ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool
|
|
||||||
ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT;
|
|
||||||
ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool
|
|
||||||
ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT;
|
|
||||||
ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool
|
|
||||||
ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT;
|
|
||||||
),
|
|
||||||
// Add panel zoom persistence
|
|
||||||
sql!(
|
|
||||||
ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool
|
|
||||||
ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool
|
|
||||||
ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool
|
|
||||||
),
|
|
||||||
// Add pane group flex data
|
|
||||||
sql!(
|
|
||||||
ALTER TABLE pane_groups ADD COLUMN flexes TEXT;
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkspaceDb {
|
|
||||||
/// Returns a serialized workspace for the given worktree_roots. If the passed array
|
|
||||||
/// is empty, the most recent workspace is returned instead. If no workspace for the
|
|
||||||
/// passed roots is stored, returns none.
|
|
||||||
pub fn workspace_for_roots<P: AsRef<Path>>(
|
|
||||||
&self,
|
|
||||||
worktree_roots: &[P],
|
|
||||||
) -> Option<SerializedWorkspace> {
|
|
||||||
let workspace_location: WorkspaceLocation = worktree_roots.into();
|
|
||||||
|
|
||||||
// Note that we re-assign the workspace_id here in case it's empty
|
|
||||||
// and we've grabbed the most recent workspace
|
|
||||||
let (workspace_id, workspace_location, bounds, display, docks): (
|
|
||||||
WorkspaceId,
|
|
||||||
WorkspaceLocation,
|
|
||||||
Option<WindowBounds>,
|
|
||||||
Option<Uuid>,
|
|
||||||
DockStructure,
|
|
||||||
) = self
|
|
||||||
.select_row_bound(sql! {
|
|
||||||
SELECT
|
|
||||||
workspace_id,
|
|
||||||
workspace_location,
|
|
||||||
window_state,
|
|
||||||
window_x,
|
|
||||||
window_y,
|
|
||||||
window_width,
|
|
||||||
window_height,
|
|
||||||
display,
|
|
||||||
left_dock_visible,
|
|
||||||
left_dock_active_panel,
|
|
||||||
left_dock_zoom,
|
|
||||||
right_dock_visible,
|
|
||||||
right_dock_active_panel,
|
|
||||||
right_dock_zoom,
|
|
||||||
bottom_dock_visible,
|
|
||||||
bottom_dock_active_panel,
|
|
||||||
bottom_dock_zoom
|
|
||||||
FROM workspaces
|
|
||||||
WHERE workspace_location = ?
|
|
||||||
})
|
|
||||||
.and_then(|mut prepared_statement| (prepared_statement)(&workspace_location))
|
|
||||||
.context("No workspaces found")
|
|
||||||
.warn_on_err()
|
|
||||||
.flatten()?;
|
|
||||||
|
|
||||||
Some(SerializedWorkspace {
|
|
||||||
id: workspace_id,
|
|
||||||
location: workspace_location.clone(),
|
|
||||||
center_group: self
|
|
||||||
.get_center_pane_group(workspace_id)
|
|
||||||
.context("Getting center group")
|
|
||||||
.log_err()?,
|
|
||||||
bounds,
|
|
||||||
display,
|
|
||||||
docks,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Saves a workspace using the worktree roots. Will garbage collect any workspaces
|
|
||||||
/// that used this workspace previously
|
|
||||||
pub async fn save_workspace(&self, workspace: SerializedWorkspace) {
|
|
||||||
self.write(move |conn| {
|
|
||||||
conn.with_savepoint("update_worktrees", || {
|
|
||||||
// Clear out panes and pane_groups
|
|
||||||
conn.exec_bound(sql!(
|
|
||||||
DELETE FROM pane_groups WHERE workspace_id = ?1;
|
|
||||||
DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
|
|
||||||
.expect("Clearing old panes");
|
|
||||||
|
|
||||||
conn.exec_bound(sql!(
|
|
||||||
DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ?
|
|
||||||
))?((&workspace.location, workspace.id.clone()))
|
|
||||||
.context("clearing out old locations")?;
|
|
||||||
|
|
||||||
// Upsert
|
|
||||||
conn.exec_bound(sql!(
|
|
||||||
INSERT INTO workspaces(
|
|
||||||
workspace_id,
|
|
||||||
workspace_location,
|
|
||||||
left_dock_visible,
|
|
||||||
left_dock_active_panel,
|
|
||||||
left_dock_zoom,
|
|
||||||
right_dock_visible,
|
|
||||||
right_dock_active_panel,
|
|
||||||
right_dock_zoom,
|
|
||||||
bottom_dock_visible,
|
|
||||||
bottom_dock_active_panel,
|
|
||||||
bottom_dock_zoom,
|
|
||||||
timestamp
|
|
||||||
)
|
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP)
|
|
||||||
ON CONFLICT DO
|
|
||||||
UPDATE SET
|
|
||||||
workspace_location = ?2,
|
|
||||||
left_dock_visible = ?3,
|
|
||||||
left_dock_active_panel = ?4,
|
|
||||||
left_dock_zoom = ?5,
|
|
||||||
right_dock_visible = ?6,
|
|
||||||
right_dock_active_panel = ?7,
|
|
||||||
right_dock_zoom = ?8,
|
|
||||||
bottom_dock_visible = ?9,
|
|
||||||
bottom_dock_active_panel = ?10,
|
|
||||||
bottom_dock_zoom = ?11,
|
|
||||||
timestamp = CURRENT_TIMESTAMP
|
|
||||||
))?((workspace.id, &workspace.location, workspace.docks))
|
|
||||||
.context("Updating workspace")?;
|
|
||||||
|
|
||||||
// Save center pane group
|
|
||||||
Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
|
|
||||||
.context("save pane group in save workspace")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
query! {
|
|
||||||
pub async fn next_id() -> Result<WorkspaceId> {
|
|
||||||
INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query! {
|
|
||||||
fn recent_workspaces() -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
|
|
||||||
SELECT workspace_id, workspace_location
|
|
||||||
FROM workspaces
|
|
||||||
WHERE workspace_location IS NOT NULL
|
|
||||||
ORDER BY timestamp DESC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query! {
|
|
||||||
async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> {
|
|
||||||
DELETE FROM workspaces
|
|
||||||
WHERE workspace_id IS ?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the recent locations which are still valid on disk and deletes ones which no longer
|
|
||||||
// exist.
|
|
||||||
pub async fn recent_workspaces_on_disk(&self) -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
let mut delete_tasks = Vec::new();
|
|
||||||
for (id, location) in self.recent_workspaces()? {
|
|
||||||
if location.paths().iter().all(|path| path.exists())
|
|
||||||
&& location.paths().iter().any(|path| path.is_dir())
|
|
||||||
{
|
|
||||||
result.push((id, location));
|
|
||||||
} else {
|
|
||||||
delete_tasks.push(self.delete_stale_workspace(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
futures::future::join_all(delete_tasks).await;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn last_workspace(&self) -> Result<Option<WorkspaceLocation>> {
|
|
||||||
Ok(self
|
|
||||||
.recent_workspaces_on_disk()
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.map(|(_, location)| location))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
|
|
||||||
Ok(self
|
|
||||||
.get_pane_group(workspace_id, None)?
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane {
|
|
||||||
active: true,
|
|
||||||
children: vec![],
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pane_group(
|
|
||||||
&self,
|
|
||||||
workspace_id: WorkspaceId,
|
|
||||||
group_id: Option<GroupId>,
|
|
||||||
) -> Result<Vec<SerializedPaneGroup>> {
|
|
||||||
type GroupKey = (Option<GroupId>, WorkspaceId);
|
|
||||||
type GroupOrPane = (
|
|
||||||
Option<GroupId>,
|
|
||||||
Option<Axis>,
|
|
||||||
Option<PaneId>,
|
|
||||||
Option<bool>,
|
|
||||||
Option<String>,
|
|
||||||
);
|
|
||||||
self.select_bound::<GroupKey, GroupOrPane>(sql!(
|
|
||||||
SELECT group_id, axis, pane_id, active, flexes
|
|
||||||
FROM (SELECT
|
|
||||||
group_id,
|
|
||||||
axis,
|
|
||||||
NULL as pane_id,
|
|
||||||
NULL as active,
|
|
||||||
position,
|
|
||||||
parent_group_id,
|
|
||||||
workspace_id,
|
|
||||||
flexes
|
|
||||||
FROM pane_groups
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
center_panes.pane_id,
|
|
||||||
panes.active as active,
|
|
||||||
position,
|
|
||||||
parent_group_id,
|
|
||||||
panes.workspace_id as workspace_id,
|
|
||||||
NULL
|
|
||||||
FROM center_panes
|
|
||||||
JOIN panes ON center_panes.pane_id = panes.pane_id)
|
|
||||||
WHERE parent_group_id IS ? AND workspace_id = ?
|
|
||||||
ORDER BY position
|
|
||||||
))?((group_id, workspace_id))?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(group_id, axis, pane_id, active, flexes)| {
|
|
||||||
if let Some((group_id, axis)) = group_id.zip(axis) {
|
|
||||||
let flexes = flexes
|
|
||||||
.map(|flexes: String| serde_json::from_str::<Vec<f32>>(&flexes))
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
Ok(SerializedPaneGroup::Group {
|
|
||||||
axis,
|
|
||||||
children: self.get_pane_group(workspace_id, Some(group_id))?,
|
|
||||||
flexes,
|
|
||||||
})
|
|
||||||
} else if let Some((pane_id, active)) = pane_id.zip(active) {
|
|
||||||
Ok(SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
self.get_items(pane_id)?,
|
|
||||||
active,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
bail!("Pane Group Child was neither a pane group or a pane");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Filter out panes and pane groups which don't have any children or items
|
|
||||||
.filter(|pane_group| match pane_group {
|
|
||||||
Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(),
|
|
||||||
Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(),
|
|
||||||
_ => true,
|
|
||||||
})
|
|
||||||
.collect::<Result<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_pane_group(
|
|
||||||
conn: &Connection,
|
|
||||||
workspace_id: WorkspaceId,
|
|
||||||
pane_group: &SerializedPaneGroup,
|
|
||||||
parent: Option<(GroupId, usize)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
match pane_group {
|
|
||||||
SerializedPaneGroup::Group {
|
|
||||||
axis,
|
|
||||||
children,
|
|
||||||
flexes,
|
|
||||||
} => {
|
|
||||||
let (parent_id, position) = unzip_option(parent);
|
|
||||||
|
|
||||||
let flex_string = flexes
|
|
||||||
.as_ref()
|
|
||||||
.map(|flexes| serde_json::json!(flexes).to_string());
|
|
||||||
|
|
||||||
let group_id = conn.select_row_bound::<_, i64>(sql!(
|
|
||||||
INSERT INTO pane_groups(
|
|
||||||
workspace_id,
|
|
||||||
parent_group_id,
|
|
||||||
position,
|
|
||||||
axis,
|
|
||||||
flexes
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
RETURNING group_id
|
|
||||||
))?((
|
|
||||||
workspace_id,
|
|
||||||
parent_id,
|
|
||||||
position,
|
|
||||||
*axis,
|
|
||||||
flex_string,
|
|
||||||
))?
|
|
||||||
.ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
|
|
||||||
|
|
||||||
for (position, group) in children.iter().enumerate() {
|
|
||||||
Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))?
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
SerializedPaneGroup::Pane(pane) => {
|
|
||||||
Self::save_pane(conn, workspace_id, &pane, parent)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_pane(
|
|
||||||
conn: &Connection,
|
|
||||||
workspace_id: WorkspaceId,
|
|
||||||
pane: &SerializedPane,
|
|
||||||
parent: Option<(GroupId, usize)>,
|
|
||||||
) -> Result<PaneId> {
|
|
||||||
let pane_id = conn.select_row_bound::<_, i64>(sql!(
|
|
||||||
INSERT INTO panes(workspace_id, active)
|
|
||||||
VALUES (?, ?)
|
|
||||||
RETURNING pane_id
|
|
||||||
))?((workspace_id, pane.active))?
|
|
||||||
.ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
|
|
||||||
|
|
||||||
let (parent_id, order) = unzip_option(parent);
|
|
||||||
conn.exec_bound(sql!(
|
|
||||||
INSERT INTO center_panes(pane_id, parent_group_id, position)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
))?((pane_id, parent_id, order))?;
|
|
||||||
|
|
||||||
Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?;
|
|
||||||
|
|
||||||
Ok(pane_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> {
|
|
||||||
Ok(self.select_bound(sql!(
|
|
||||||
SELECT kind, item_id, active FROM items
|
|
||||||
WHERE pane_id = ?
|
|
||||||
ORDER BY position
|
|
||||||
))?(pane_id)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_items(
|
|
||||||
conn: &Connection,
|
|
||||||
workspace_id: WorkspaceId,
|
|
||||||
pane_id: PaneId,
|
|
||||||
items: &[SerializedItem],
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut insert = conn.exec_bound(sql!(
|
|
||||||
INSERT INTO items(workspace_id, pane_id, position, kind, item_id, active) VALUES (?, ?, ?, ?, ?, ?)
|
|
||||||
)).context("Preparing insertion")?;
|
|
||||||
for (position, item) in items.iter().enumerate() {
|
|
||||||
insert((workspace_id, pane_id, position, item))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
query! {
|
|
||||||
pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> {
|
|
||||||
UPDATE workspaces
|
|
||||||
SET timestamp = CURRENT_TIMESTAMP
|
|
||||||
WHERE workspace_id = ?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query! {
|
|
||||||
pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> {
|
|
||||||
UPDATE workspaces
|
|
||||||
SET window_state = ?2,
|
|
||||||
window_x = ?3,
|
|
||||||
window_y = ?4,
|
|
||||||
window_width = ?5,
|
|
||||||
window_height = ?6,
|
|
||||||
display = ?7
|
|
||||||
WHERE workspace_id = ?1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use db::open_test_db;
|
|
||||||
use gpui;
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_next_id_stability() {
|
|
||||||
env_logger::try_init().ok();
|
|
||||||
|
|
||||||
let db = WorkspaceDb(open_test_db("test_next_id_stability").await);
|
|
||||||
|
|
||||||
db.write(|conn| {
|
|
||||||
conn.migrate(
|
|
||||||
"test_table",
|
|
||||||
&[sql!(
|
|
||||||
CREATE TABLE test_table(
|
|
||||||
text TEXT,
|
|
||||||
workspace_id INTEGER,
|
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
) STRICT;
|
|
||||||
)],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let id = db.next_id().await.unwrap();
|
|
||||||
// Assert the empty row got inserted
|
|
||||||
assert_eq!(
|
|
||||||
Some(id),
|
|
||||||
db.select_row_bound::<WorkspaceId, WorkspaceId>(sql!(
|
|
||||||
SELECT workspace_id FROM workspaces WHERE workspace_id = ?
|
|
||||||
))
|
|
||||||
.unwrap()(id)
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
db.write(move |conn| {
|
|
||||||
conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
|
|
||||||
.unwrap()(("test-text-1", id))
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let test_text_1 = db
|
|
||||||
.select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
|
|
||||||
.unwrap()(1)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(test_text_1, "test-text-1");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_workspace_id_stability() {
|
|
||||||
env_logger::try_init().ok();
|
|
||||||
|
|
||||||
let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await);
|
|
||||||
|
|
||||||
db.write(|conn| {
|
|
||||||
conn.migrate(
|
|
||||||
"test_table",
|
|
||||||
&[sql!(
|
|
||||||
CREATE TABLE test_table(
|
|
||||||
text TEXT,
|
|
||||||
workspace_id INTEGER,
|
|
||||||
FOREIGN KEY(workspace_id)
|
|
||||||
REFERENCES workspaces(workspace_id)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
) STRICT;)],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut workspace_1 = SerializedWorkspace {
|
|
||||||
id: 1,
|
|
||||||
location: (["/tmp", "/tmp2"]).into(),
|
|
||||||
center_group: Default::default(),
|
|
||||||
bounds: Default::default(),
|
|
||||||
display: Default::default(),
|
|
||||||
docks: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let workspace_2 = SerializedWorkspace {
|
|
||||||
id: 2,
|
|
||||||
location: (["/tmp"]).into(),
|
|
||||||
center_group: Default::default(),
|
|
||||||
bounds: Default::default(),
|
|
||||||
display: Default::default(),
|
|
||||||
docks: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
db.save_workspace(workspace_1.clone()).await;
|
|
||||||
|
|
||||||
db.write(|conn| {
|
|
||||||
conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
|
|
||||||
.unwrap()(("test-text-1", 1))
|
|
||||||
.unwrap();
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
db.save_workspace(workspace_2.clone()).await;
|
|
||||||
|
|
||||||
db.write(|conn| {
|
|
||||||
conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
|
|
||||||
.unwrap()(("test-text-2", 2))
|
|
||||||
.unwrap();
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
workspace_1.location = (["/tmp", "/tmp3"]).into();
|
|
||||||
db.save_workspace(workspace_1.clone()).await;
|
|
||||||
db.save_workspace(workspace_1).await;
|
|
||||||
db.save_workspace(workspace_2).await;
|
|
||||||
|
|
||||||
let test_text_2 = db
|
|
||||||
.select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
|
|
||||||
.unwrap()(2)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(test_text_2, "test-text-2");
|
|
||||||
|
|
||||||
let test_text_1 = db
|
|
||||||
.select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
|
|
||||||
.unwrap()(1)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(test_text_1, "test-text-1");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn group(axis: Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
|
|
||||||
SerializedPaneGroup::Group {
|
|
||||||
axis,
|
|
||||||
flexes: None,
|
|
||||||
children,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_full_workspace_serialization() {
|
|
||||||
env_logger::try_init().ok();
|
|
||||||
|
|
||||||
let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
|
|
||||||
|
|
||||||
// -----------------
|
|
||||||
// | 1,2 | 5,6 |
|
|
||||||
// | - - - | |
|
|
||||||
// | 3,4 | |
|
|
||||||
// -----------------
|
|
||||||
let center_group = group(
|
|
||||||
Axis::Horizontal,
|
|
||||||
vec![
|
|
||||||
group(
|
|
||||||
Axis::Vertical,
|
|
||||||
vec![
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 5, false),
|
|
||||||
SerializedItem::new("Terminal", 6, true),
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 7, true),
|
|
||||||
SerializedItem::new("Terminal", 8, false),
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 9, false),
|
|
||||||
SerializedItem::new("Terminal", 10, true),
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
let workspace = SerializedWorkspace {
|
|
||||||
id: 5,
|
|
||||||
location: (["/tmp", "/tmp2"]).into(),
|
|
||||||
center_group,
|
|
||||||
bounds: Default::default(),
|
|
||||||
display: Default::default(),
|
|
||||||
docks: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
db.save_workspace(workspace.clone()).await;
|
|
||||||
let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]);
|
|
||||||
|
|
||||||
assert_eq!(workspace, round_trip_workspace.unwrap());
|
|
||||||
|
|
||||||
// Test guaranteed duplicate IDs
|
|
||||||
db.save_workspace(workspace.clone()).await;
|
|
||||||
db.save_workspace(workspace.clone()).await;
|
|
||||||
|
|
||||||
let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]);
|
|
||||||
assert_eq!(workspace, round_trip_workspace.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_workspace_assignment() {
|
|
||||||
env_logger::try_init().ok();
|
|
||||||
|
|
||||||
let db = WorkspaceDb(open_test_db("test_basic_functionality").await);
|
|
||||||
|
|
||||||
let workspace_1 = SerializedWorkspace {
|
|
||||||
id: 1,
|
|
||||||
location: (["/tmp", "/tmp2"]).into(),
|
|
||||||
center_group: Default::default(),
|
|
||||||
bounds: Default::default(),
|
|
||||||
display: Default::default(),
|
|
||||||
docks: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut workspace_2 = SerializedWorkspace {
|
|
||||||
id: 2,
|
|
||||||
location: (["/tmp"]).into(),
|
|
||||||
center_group: Default::default(),
|
|
||||||
bounds: Default::default(),
|
|
||||||
display: Default::default(),
|
|
||||||
docks: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
db.save_workspace(workspace_1.clone()).await;
|
|
||||||
db.save_workspace(workspace_2.clone()).await;
|
|
||||||
|
|
||||||
// Test that paths are treated as a set
|
|
||||||
assert_eq!(
|
|
||||||
db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
|
|
||||||
workspace_1
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(),
|
|
||||||
workspace_1
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make sure that other keys work
|
|
||||||
assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2);
|
|
||||||
assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
|
|
||||||
|
|
||||||
// Test 'mutate' case of updating a pre-existing id
|
|
||||||
workspace_2.location = (["/tmp", "/tmp2"]).into();
|
|
||||||
|
|
||||||
db.save_workspace(workspace_2.clone()).await;
|
|
||||||
assert_eq!(
|
|
||||||
db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
|
|
||||||
workspace_2
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test other mechanism for mutating
|
|
||||||
let mut workspace_3 = SerializedWorkspace {
|
|
||||||
id: 3,
|
|
||||||
location: (&["/tmp", "/tmp2"]).into(),
|
|
||||||
center_group: Default::default(),
|
|
||||||
bounds: Default::default(),
|
|
||||||
display: Default::default(),
|
|
||||||
docks: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
db.save_workspace(workspace_3.clone()).await;
|
|
||||||
assert_eq!(
|
|
||||||
db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
|
|
||||||
workspace_3
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make sure that updating paths differently also works
|
|
||||||
workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
|
|
||||||
db.save_workspace(workspace_3.clone()).await;
|
|
||||||
assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
|
|
||||||
assert_eq!(
|
|
||||||
db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
|
|
||||||
.unwrap(),
|
|
||||||
workspace_3
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::persistence::model::SerializedWorkspace;
|
|
||||||
use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
|
|
||||||
|
|
||||||
fn default_workspace<P: AsRef<Path>>(
|
|
||||||
workspace_id: &[P],
|
|
||||||
center_group: &SerializedPaneGroup,
|
|
||||||
) -> SerializedWorkspace {
|
|
||||||
SerializedWorkspace {
|
|
||||||
id: 4,
|
|
||||||
location: workspace_id.into(),
|
|
||||||
center_group: center_group.clone(),
|
|
||||||
bounds: Default::default(),
|
|
||||||
display: Default::default(),
|
|
||||||
docks: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_simple_split() {
|
|
||||||
env_logger::try_init().ok();
|
|
||||||
|
|
||||||
let db = WorkspaceDb(open_test_db("simple_split").await);
|
|
||||||
|
|
||||||
// -----------------
|
|
||||||
// | 1,2 | 5,6 |
|
|
||||||
// | - - - | |
|
|
||||||
// | 3,4 | |
|
|
||||||
// -----------------
|
|
||||||
let center_pane = group(
|
|
||||||
Axis::Horizontal,
|
|
||||||
vec![
|
|
||||||
group(
|
|
||||||
Axis::Vertical,
|
|
||||||
vec![
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 1, false),
|
|
||||||
SerializedItem::new("Terminal", 2, true),
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 4, false),
|
|
||||||
SerializedItem::new("Terminal", 3, true),
|
|
||||||
],
|
|
||||||
true,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 5, true),
|
|
||||||
SerializedItem::new("Terminal", 6, false),
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
let workspace = default_workspace(&["/tmp"], ¢er_pane);
|
|
||||||
|
|
||||||
db.save_workspace(workspace.clone()).await;
|
|
||||||
|
|
||||||
let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(workspace.center_group, new_workspace.center_group);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_cleanup_panes() {
|
|
||||||
env_logger::try_init().ok();
|
|
||||||
|
|
||||||
let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
|
|
||||||
|
|
||||||
let center_pane = group(
|
|
||||||
Axis::Horizontal,
|
|
||||||
vec![
|
|
||||||
group(
|
|
||||||
Axis::Vertical,
|
|
||||||
vec![
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 1, false),
|
|
||||||
SerializedItem::new("Terminal", 2, true),
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 4, false),
|
|
||||||
SerializedItem::new("Terminal", 3, true),
|
|
||||||
],
|
|
||||||
true,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 5, false),
|
|
||||||
SerializedItem::new("Terminal", 6, true),
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
let id = &["/tmp"];
|
|
||||||
|
|
||||||
let mut workspace = default_workspace(id, ¢er_pane);
|
|
||||||
|
|
||||||
db.save_workspace(workspace.clone()).await;
|
|
||||||
|
|
||||||
workspace.center_group = group(
|
|
||||||
Axis::Vertical,
|
|
||||||
vec![
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 1, false),
|
|
||||||
SerializedItem::new("Terminal", 2, true),
|
|
||||||
],
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane::new(
|
|
||||||
vec![
|
|
||||||
SerializedItem::new("Terminal", 4, true),
|
|
||||||
SerializedItem::new("Terminal", 3, false),
|
|
||||||
],
|
|
||||||
true,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
db.save_workspace(workspace.clone()).await;
|
|
||||||
|
|
||||||
let new_workspace = db.workspace_for_roots(id).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(workspace.center_group, new_workspace.center_group);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,335 +0,0 @@
|
|||||||
use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use async_recursion::async_recursion;
|
|
||||||
use db::sqlez::{
|
|
||||||
bindable::{Bind, Column, StaticColumnCount},
|
|
||||||
statement::Statement,
|
|
||||||
};
|
|
||||||
use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView, WindowBounds};
|
|
||||||
use project::Project;
|
|
||||||
use std::{
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
use util::ResultExt;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct WorkspaceLocation(Arc<Vec<PathBuf>>);
|
|
||||||
|
|
||||||
impl WorkspaceLocation {
|
|
||||||
pub fn paths(&self) -> Arc<Vec<PathBuf>> {
|
|
||||||
self.0.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {
|
|
||||||
fn from(iterator: T) -> Self {
|
|
||||||
let mut roots = iterator
|
|
||||||
.into_iter()
|
|
||||||
.map(|p| p.as_ref().to_path_buf())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
roots.sort();
|
|
||||||
Self(Arc::new(roots))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StaticColumnCount for WorkspaceLocation {}
|
|
||||||
impl Bind for &WorkspaceLocation {
|
|
||||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
|
||||||
bincode::serialize(&self.0)
|
|
||||||
.expect("Bincode serialization of paths should not fail")
|
|
||||||
.bind(statement, start_index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Column for WorkspaceLocation {
|
|
||||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
|
||||||
let blob = statement.column_blob(start_index)?;
|
|
||||||
Ok((
|
|
||||||
WorkspaceLocation(bincode::deserialize(blob).context("Bincode failed")?),
|
|
||||||
start_index + 1,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub struct SerializedWorkspace {
|
|
||||||
pub id: WorkspaceId,
|
|
||||||
pub location: WorkspaceLocation,
|
|
||||||
pub center_group: SerializedPaneGroup,
|
|
||||||
pub bounds: Option<WindowBounds>,
|
|
||||||
pub display: Option<Uuid>,
|
|
||||||
pub docks: DockStructure,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Default)]
|
|
||||||
pub struct DockStructure {
|
|
||||||
pub(crate) left: DockData,
|
|
||||||
pub(crate) right: DockData,
|
|
||||||
pub(crate) bottom: DockData,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Column for DockStructure {
|
|
||||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
|
||||||
let (left, next_index) = DockData::column(statement, start_index)?;
|
|
||||||
let (right, next_index) = DockData::column(statement, next_index)?;
|
|
||||||
let (bottom, next_index) = DockData::column(statement, next_index)?;
|
|
||||||
Ok((
|
|
||||||
DockStructure {
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
bottom,
|
|
||||||
},
|
|
||||||
next_index,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bind for DockStructure {
|
|
||||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
|
||||||
let next_index = statement.bind(&self.left, start_index)?;
|
|
||||||
let next_index = statement.bind(&self.right, next_index)?;
|
|
||||||
statement.bind(&self.bottom, next_index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Default)]
|
|
||||||
pub struct DockData {
|
|
||||||
pub(crate) visible: bool,
|
|
||||||
pub(crate) active_panel: Option<String>,
|
|
||||||
pub(crate) zoom: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Column for DockData {
|
|
||||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
|
||||||
let (visible, next_index) = Option::<bool>::column(statement, start_index)?;
|
|
||||||
let (active_panel, next_index) = Option::<String>::column(statement, next_index)?;
|
|
||||||
let (zoom, next_index) = Option::<bool>::column(statement, next_index)?;
|
|
||||||
Ok((
|
|
||||||
DockData {
|
|
||||||
visible: visible.unwrap_or(false),
|
|
||||||
active_panel,
|
|
||||||
zoom: zoom.unwrap_or(false),
|
|
||||||
},
|
|
||||||
next_index,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bind for DockData {
|
|
||||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
|
||||||
let next_index = statement.bind(&self.visible, start_index)?;
|
|
||||||
let next_index = statement.bind(&self.active_panel, next_index)?;
|
|
||||||
statement.bind(&self.zoom, next_index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub enum SerializedPaneGroup {
|
|
||||||
Group {
|
|
||||||
axis: Axis,
|
|
||||||
flexes: Option<Vec<f32>>,
|
|
||||||
children: Vec<SerializedPaneGroup>,
|
|
||||||
},
|
|
||||||
Pane(SerializedPane),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl Default for SerializedPaneGroup {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Pane(SerializedPane {
|
|
||||||
children: vec![SerializedItem::default()],
|
|
||||||
active: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SerializedPaneGroup {
|
|
||||||
#[async_recursion(?Send)]
|
|
||||||
pub(crate) async fn deserialize(
|
|
||||||
self,
|
|
||||||
project: &Model<Project>,
|
|
||||||
workspace_id: WorkspaceId,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
cx: &mut AsyncWindowContext,
|
|
||||||
) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
|
|
||||||
match self {
|
|
||||||
SerializedPaneGroup::Group {
|
|
||||||
axis,
|
|
||||||
children,
|
|
||||||
flexes,
|
|
||||||
} => {
|
|
||||||
let mut current_active_pane = None;
|
|
||||||
let mut members = Vec::new();
|
|
||||||
let mut items = Vec::new();
|
|
||||||
for child in children {
|
|
||||||
if let Some((new_member, active_pane, new_items)) = child
|
|
||||||
.deserialize(project, workspace_id, workspace.clone(), cx)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
members.push(new_member);
|
|
||||||
items.extend(new_items);
|
|
||||||
current_active_pane = current_active_pane.or(active_pane);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if members.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if members.len() == 1 {
|
|
||||||
return Some((members.remove(0), current_active_pane, items));
|
|
||||||
}
|
|
||||||
|
|
||||||
Some((
|
|
||||||
Member::Axis(PaneAxis::load(axis, members, flexes)),
|
|
||||||
current_active_pane,
|
|
||||||
items,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
SerializedPaneGroup::Pane(serialized_pane) => {
|
|
||||||
let pane = workspace
|
|
||||||
.update(cx, |workspace, cx| workspace.add_pane(cx).downgrade())
|
|
||||||
.log_err()?;
|
|
||||||
let active = serialized_pane.active;
|
|
||||||
let new_items = serialized_pane
|
|
||||||
.deserialize_to(project, &pane, workspace_id, workspace.clone(), cx)
|
|
||||||
.await
|
|
||||||
.log_err()?;
|
|
||||||
|
|
||||||
if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? {
|
|
||||||
let pane = pane.upgrade()?;
|
|
||||||
Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
|
|
||||||
} else {
|
|
||||||
let pane = pane.upgrade()?;
|
|
||||||
workspace
|
|
||||||
.update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx))
|
|
||||||
.log_err()?;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Default, Clone)]
|
|
||||||
pub struct SerializedPane {
|
|
||||||
pub(crate) active: bool,
|
|
||||||
pub(crate) children: Vec<SerializedItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SerializedPane {
|
|
||||||
pub fn new(children: Vec<SerializedItem>, active: bool) -> Self {
|
|
||||||
SerializedPane { children, active }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn deserialize_to(
|
|
||||||
&self,
|
|
||||||
project: &Model<Project>,
|
|
||||||
pane: &WeakView<Pane>,
|
|
||||||
workspace_id: WorkspaceId,
|
|
||||||
workspace: WeakView<Workspace>,
|
|
||||||
cx: &mut AsyncWindowContext,
|
|
||||||
) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
|
|
||||||
let mut items = Vec::new();
|
|
||||||
let mut active_item_index = None;
|
|
||||||
for (index, item) in self.children.iter().enumerate() {
|
|
||||||
let project = project.clone();
|
|
||||||
let item_handle = pane
|
|
||||||
.update(cx, |_, cx| {
|
|
||||||
if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) {
|
|
||||||
deserializer(project, workspace.clone(), workspace_id, item.item_id, cx)
|
|
||||||
} else {
|
|
||||||
Task::ready(Err(anyhow::anyhow!(
|
|
||||||
"Deserializer does not exist for item kind: {}",
|
|
||||||
item.kind
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
})?
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
|
|
||||||
items.push(item_handle.clone());
|
|
||||||
|
|
||||||
if let Some(item_handle) = item_handle {
|
|
||||||
pane.update(cx, |pane, cx| {
|
|
||||||
pane.add_item(item_handle.clone(), true, true, None, cx);
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.active {
|
|
||||||
active_item_index = Some(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(active_item_index) = active_item_index {
|
|
||||||
pane.update(cx, |pane, cx| {
|
|
||||||
pane.activate_item(active_item_index, false, false, cx);
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
anyhow::Ok(items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type GroupId = i64;
|
|
||||||
pub type PaneId = i64;
|
|
||||||
pub type ItemId = u64;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct SerializedItem {
|
|
||||||
pub kind: Arc<str>,
|
|
||||||
pub item_id: ItemId,
|
|
||||||
pub active: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SerializedItem {
|
|
||||||
pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
kind: Arc::from(kind.as_ref()),
|
|
||||||
item_id,
|
|
||||||
active,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl Default for SerializedItem {
|
|
||||||
fn default() -> Self {
|
|
||||||
SerializedItem {
|
|
||||||
kind: Arc::from("Terminal"),
|
|
||||||
item_id: 100000,
|
|
||||||
active: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StaticColumnCount for SerializedItem {
|
|
||||||
fn column_count() -> usize {
|
|
||||||
3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Bind for &SerializedItem {
|
|
||||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
|
||||||
let next_index = statement.bind(&self.kind, start_index)?;
|
|
||||||
let next_index = statement.bind(&self.item_id, next_index)?;
|
|
||||||
statement.bind(&self.active, next_index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Column for SerializedItem {
|
|
||||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
|
||||||
let (kind, next_index) = Arc::<str>::column(statement, start_index)?;
|
|
||||||
let (item_id, next_index) = ItemId::column(statement, next_index)?;
|
|
||||||
let (active, next_index) = bool::column(statement, next_index)?;
|
|
||||||
Ok((
|
|
||||||
SerializedItem {
|
|
||||||
kind,
|
|
||||||
item_id,
|
|
||||||
active,
|
|
||||||
},
|
|
||||||
next_index,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,277 +0,0 @@
|
|||||||
use std::{any::Any, sync::Arc};
|
|
||||||
|
|
||||||
use gpui::{
|
|
||||||
AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
|
|
||||||
WindowContext,
|
|
||||||
};
|
|
||||||
use project::search::SearchQuery;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
item::{Item, WeakItemHandle},
|
|
||||||
ItemHandle,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum SearchEvent {
|
|
||||||
MatchesInvalidated,
|
|
||||||
ActiveMatchChanged,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
||||||
pub enum Direction {
|
|
||||||
Prev,
|
|
||||||
Next,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub struct SearchOptions {
|
|
||||||
pub case: bool,
|
|
||||||
pub word: bool,
|
|
||||||
pub regex: bool,
|
|
||||||
/// Specifies whether the item supports search & replace.
|
|
||||||
pub replacement: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
|
|
||||||
type Match: Any + Sync + Send + Clone;
|
|
||||||
|
|
||||||
fn supported_options() -> SearchOptions {
|
|
||||||
SearchOptions {
|
|
||||||
case: true,
|
|
||||||
word: true,
|
|
||||||
regex: true,
|
|
||||||
replacement: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
|
|
||||||
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
|
|
||||||
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
|
|
||||||
fn activate_match(
|
|
||||||
&mut self,
|
|
||||||
index: usize,
|
|
||||||
matches: Vec<Self::Match>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
);
|
|
||||||
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
|
|
||||||
fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
|
|
||||||
fn match_index_for_direction(
|
|
||||||
&mut self,
|
|
||||||
matches: &Vec<Self::Match>,
|
|
||||||
current_index: usize,
|
|
||||||
direction: Direction,
|
|
||||||
count: usize,
|
|
||||||
_: &mut ViewContext<Self>,
|
|
||||||
) -> usize {
|
|
||||||
match direction {
|
|
||||||
Direction::Prev => {
|
|
||||||
let count = count % matches.len();
|
|
||||||
if current_index >= count {
|
|
||||||
current_index - count
|
|
||||||
} else {
|
|
||||||
matches.len() - (count - current_index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Direction::Next => (current_index + count) % matches.len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn find_matches(
|
|
||||||
&mut self,
|
|
||||||
query: Arc<SearchQuery>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Task<Vec<Self::Match>>;
|
|
||||||
fn active_match_index(
|
|
||||||
&mut self,
|
|
||||||
matches: Vec<Self::Match>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Option<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait SearchableItemHandle: ItemHandle {
|
|
||||||
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
|
|
||||||
fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
|
|
||||||
fn supported_options(&self) -> SearchOptions;
|
|
||||||
fn subscribe_to_search_events(
|
|
||||||
&self,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
|
|
||||||
) -> Subscription;
|
|
||||||
fn clear_matches(&self, cx: &mut WindowContext);
|
|
||||||
fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
|
|
||||||
fn query_suggestion(&self, cx: &mut WindowContext) -> String;
|
|
||||||
fn activate_match(
|
|
||||||
&self,
|
|
||||||
index: usize,
|
|
||||||
matches: &Vec<Box<dyn Any + Send>>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
);
|
|
||||||
fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
|
|
||||||
fn replace(&self, _: &Box<dyn Any + Send>, _: &SearchQuery, _: &mut WindowContext);
|
|
||||||
fn match_index_for_direction(
|
|
||||||
&self,
|
|
||||||
matches: &Vec<Box<dyn Any + Send>>,
|
|
||||||
current_index: usize,
|
|
||||||
direction: Direction,
|
|
||||||
count: usize,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> usize;
|
|
||||||
fn find_matches(
|
|
||||||
&self,
|
|
||||||
query: Arc<SearchQuery>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> Task<Vec<Box<dyn Any + Send>>>;
|
|
||||||
fn active_match_index(
|
|
||||||
&self,
|
|
||||||
matches: &Vec<Box<dyn Any + Send>>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> Option<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo!("here is where we need to use AnyWeakView");
|
|
||||||
impl<T: SearchableItem> SearchableItemHandle for View<T> {
|
|
||||||
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
|
|
||||||
Box::new(self.downgrade())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn supported_options(&self) -> SearchOptions {
|
|
||||||
T::supported_options()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subscribe_to_search_events(
|
|
||||||
&self,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
|
|
||||||
) -> Subscription {
|
|
||||||
cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_matches(&self, cx: &mut WindowContext) {
|
|
||||||
self.update(cx, |this, cx| this.clear_matches(cx));
|
|
||||||
}
|
|
||||||
fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
|
|
||||||
let matches = downcast_matches(matches);
|
|
||||||
self.update(cx, |this, cx| this.update_matches(matches, cx));
|
|
||||||
}
|
|
||||||
fn query_suggestion(&self, cx: &mut WindowContext) -> String {
|
|
||||||
self.update(cx, |this, cx| this.query_suggestion(cx))
|
|
||||||
}
|
|
||||||
fn activate_match(
|
|
||||||
&self,
|
|
||||||
index: usize,
|
|
||||||
matches: &Vec<Box<dyn Any + Send>>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) {
|
|
||||||
let matches = downcast_matches(matches);
|
|
||||||
self.update(cx, |this, cx| this.activate_match(index, matches, cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
|
|
||||||
let matches = downcast_matches(matches);
|
|
||||||
self.update(cx, |this, cx| this.select_matches(matches, cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_index_for_direction(
|
|
||||||
&self,
|
|
||||||
matches: &Vec<Box<dyn Any + Send>>,
|
|
||||||
current_index: usize,
|
|
||||||
direction: Direction,
|
|
||||||
count: usize,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> usize {
|
|
||||||
let matches = downcast_matches(matches);
|
|
||||||
self.update(cx, |this, cx| {
|
|
||||||
this.match_index_for_direction(&matches, current_index, direction, count, cx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn find_matches(
|
|
||||||
&self,
|
|
||||||
query: Arc<SearchQuery>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> Task<Vec<Box<dyn Any + Send>>> {
|
|
||||||
let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
|
|
||||||
cx.spawn(|_| async {
|
|
||||||
let matches = matches.await;
|
|
||||||
matches
|
|
||||||
.into_iter()
|
|
||||||
.map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn active_match_index(
|
|
||||||
&self,
|
|
||||||
matches: &Vec<Box<dyn Any + Send>>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> Option<usize> {
|
|
||||||
let matches = downcast_matches(matches);
|
|
||||||
self.update(cx, |this, cx| this.active_match_index(matches, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace(&self, matches: &Box<dyn Any + Send>, query: &SearchQuery, cx: &mut WindowContext) {
|
|
||||||
let matches = matches.downcast_ref().unwrap();
|
|
||||||
self.update(cx, |this, cx| this.replace(matches, query, cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
|
|
||||||
matches
|
|
||||||
.iter()
|
|
||||||
.map(|range| range.downcast_ref::<T>().cloned())
|
|
||||||
.collect::<Option<Vec<_>>>()
|
|
||||||
.expect(
|
|
||||||
"SearchableItemHandle function called with vec of matches of a different type than expected",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Box<dyn SearchableItemHandle>> for AnyView {
|
|
||||||
fn from(this: Box<dyn SearchableItemHandle>) -> Self {
|
|
||||||
this.to_any().clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Box<dyn SearchableItemHandle>> for AnyView {
|
|
||||||
fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
|
|
||||||
this.to_any().clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Box<dyn SearchableItemHandle> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.item_id() == other.item_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Box<dyn SearchableItemHandle> {}
|
|
||||||
|
|
||||||
pub trait WeakSearchableItemHandle: WeakItemHandle {
|
|
||||||
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
|
|
||||||
|
|
||||||
// fn into_any(self) -> AnyWeakView;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
|
|
||||||
fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
|
|
||||||
Some(Box::new(self.upgrade()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn into_any(self) -> AnyView {
|
|
||||||
// self.into_any()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Box<dyn WeakSearchableItemHandle> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.id() == other.id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Box<dyn WeakSearchableItemHandle> {}
|
|
||||||
|
|
||||||
impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.id().hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
item::{Item, ItemEvent},
|
|
||||||
ItemNavHistory, WorkspaceId,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
|
||||||
use call::participant::{Frame, RemoteVideoTrack};
|
|
||||||
use client::{proto::PeerId, User};
|
|
||||||
use futures::StreamExt;
|
|
||||||
use gpui::{
|
|
||||||
div, img, AppContext, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
|
||||||
ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
|
|
||||||
WindowContext,
|
|
||||||
};
|
|
||||||
use std::sync::{Arc, Weak};
|
|
||||||
use ui::{h_stack, prelude::*, Icon, IconElement, Label};
|
|
||||||
|
|
||||||
pub enum Event {
|
|
||||||
Close,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SharedScreen {
|
|
||||||
track: Weak<RemoteVideoTrack>,
|
|
||||||
frame: Option<Frame>,
|
|
||||||
pub peer_id: PeerId,
|
|
||||||
user: Arc<User>,
|
|
||||||
nav_history: Option<ItemNavHistory>,
|
|
||||||
_maintain_frame: Task<Result<()>>,
|
|
||||||
focus: FocusHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SharedScreen {
|
|
||||||
pub fn new(
|
|
||||||
track: &Arc<RemoteVideoTrack>,
|
|
||||||
peer_id: PeerId,
|
|
||||||
user: Arc<User>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
cx.focus_handle();
|
|
||||||
let mut frames = track.frames();
|
|
||||||
Self {
|
|
||||||
track: Arc::downgrade(track),
|
|
||||||
frame: None,
|
|
||||||
peer_id,
|
|
||||||
user,
|
|
||||||
nav_history: Default::default(),
|
|
||||||
_maintain_frame: cx.spawn(|this, mut cx| async move {
|
|
||||||
while let Some(frame) = frames.next().await {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.frame = Some(frame);
|
|
||||||
cx.notify();
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
|
|
||||||
Ok(())
|
|
||||||
}),
|
|
||||||
focus: cx.focus_handle(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<Event> for SharedScreen {}
|
|
||||||
|
|
||||||
impl FocusableView for SharedScreen {
|
|
||||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
|
||||||
self.focus.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Render for SharedScreen {
|
|
||||||
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
div().track_focus(&self.focus).size_full().children(
|
|
||||||
self.frame
|
|
||||||
.as_ref()
|
|
||||||
.map(|frame| img(frame.image()).size_full()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Item for SharedScreen {
|
|
||||||
type Event = Event;
|
|
||||||
|
|
||||||
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
|
|
||||||
Some(format!("{}'s screen", self.user.github_login).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
|
||||||
nav_history.push::<()>(None, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tab_content(
|
|
||||||
&self,
|
|
||||||
_: Option<usize>,
|
|
||||||
selected: bool,
|
|
||||||
_: &WindowContext<'_>,
|
|
||||||
) -> gpui::AnyElement {
|
|
||||||
h_stack()
|
|
||||||
.gap_1()
|
|
||||||
.child(IconElement::new(Icon::Screen))
|
|
||||||
.child(
|
|
||||||
Label::new(format!("{}'s screen", self.user.github_login)).color(if selected {
|
|
||||||
Color::Default
|
|
||||||
} else {
|
|
||||||
Color::Muted
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.into_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
|
|
||||||
self.nav_history = Some(history);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_on_split(
|
|
||||||
&self,
|
|
||||||
_workspace_id: WorkspaceId,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Option<View<Self>> {
|
|
||||||
let track = self.track.upgrade()?;
|
|
||||||
Some(cx.new_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
|
|
||||||
match event {
|
|
||||||
Event::Close => f(ItemEvent::CloseItem),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,194 +0,0 @@
|
|||||||
use crate::{ItemHandle, Pane};
|
|
||||||
use gpui::{
|
|
||||||
div, AnyView, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
|
|
||||||
WindowContext,
|
|
||||||
};
|
|
||||||
use std::any::TypeId;
|
|
||||||
use ui::{h_stack, prelude::*};
|
|
||||||
use util::ResultExt;
|
|
||||||
|
|
||||||
pub trait StatusItemView: Render {
|
|
||||||
fn set_active_pane_item(
|
|
||||||
&mut self,
|
|
||||||
active_pane_item: Option<&dyn crate::ItemHandle>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
trait StatusItemViewHandle: Send {
|
|
||||||
fn to_any(&self) -> AnyView;
|
|
||||||
fn set_active_pane_item(
|
|
||||||
&self,
|
|
||||||
active_pane_item: Option<&dyn ItemHandle>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
);
|
|
||||||
fn item_type(&self) -> TypeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StatusBar {
|
|
||||||
left_items: Vec<Box<dyn StatusItemViewHandle>>,
|
|
||||||
right_items: Vec<Box<dyn StatusItemViewHandle>>,
|
|
||||||
active_pane: View<Pane>,
|
|
||||||
_observe_active_pane: Subscription,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for StatusBar {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
div()
|
|
||||||
.py_0p5()
|
|
||||||
.px_1()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.w_full()
|
|
||||||
.h_8()
|
|
||||||
.bg(cx.theme().colors().status_bar_background)
|
|
||||||
.child(self.render_left_tools(cx))
|
|
||||||
.child(self.render_right_tools(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusBar {
|
|
||||||
fn render_left_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
h_stack()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.children(self.left_items.iter().map(|item| item.to_any()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_right_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
h_stack()
|
|
||||||
.items_center()
|
|
||||||
.gap_2()
|
|
||||||
.children(self.right_items.iter().rev().map(|item| item.to_any()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusBar {
|
|
||||||
pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
|
|
||||||
let mut this = Self {
|
|
||||||
left_items: Default::default(),
|
|
||||||
right_items: Default::default(),
|
|
||||||
active_pane: active_pane.clone(),
|
|
||||||
_observe_active_pane: cx
|
|
||||||
.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)),
|
|
||||||
};
|
|
||||||
this.update_active_pane_item(cx);
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
|
|
||||||
where
|
|
||||||
T: 'static + StatusItemView,
|
|
||||||
{
|
|
||||||
let active_pane_item = self.active_pane.read(cx).active_item();
|
|
||||||
item.set_active_pane_item(active_pane_item.as_deref(), cx);
|
|
||||||
|
|
||||||
self.left_items.push(Box::new(item));
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
|
|
||||||
self.left_items
|
|
||||||
.iter()
|
|
||||||
.chain(self.right_items.iter())
|
|
||||||
.find_map(|item| item.to_any().clone().downcast().log_err())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position_of_item<T>(&self) -> Option<usize>
|
|
||||||
where
|
|
||||||
T: StatusItemView,
|
|
||||||
{
|
|
||||||
for (index, item) in self.left_items.iter().enumerate() {
|
|
||||||
if item.item_type() == TypeId::of::<T>() {
|
|
||||||
return Some(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (index, item) in self.right_items.iter().enumerate() {
|
|
||||||
if item.item_type() == TypeId::of::<T>() {
|
|
||||||
return Some(index + self.left_items.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_item_after<T>(
|
|
||||||
&mut self,
|
|
||||||
position: usize,
|
|
||||||
item: View<T>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) where
|
|
||||||
T: 'static + StatusItemView,
|
|
||||||
{
|
|
||||||
let active_pane_item = self.active_pane.read(cx).active_item();
|
|
||||||
item.set_active_pane_item(active_pane_item.as_deref(), cx);
|
|
||||||
|
|
||||||
if position < self.left_items.len() {
|
|
||||||
self.left_items.insert(position + 1, Box::new(item))
|
|
||||||
} else {
|
|
||||||
self.right_items
|
|
||||||
.insert(position + 1 - self.left_items.len(), Box::new(item))
|
|
||||||
}
|
|
||||||
cx.notify()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_item_at(&mut self, position: usize, cx: &mut ViewContext<Self>) {
|
|
||||||
if position < self.left_items.len() {
|
|
||||||
self.left_items.remove(position);
|
|
||||||
} else {
|
|
||||||
self.right_items.remove(position - self.left_items.len());
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
|
|
||||||
where
|
|
||||||
T: 'static + StatusItemView,
|
|
||||||
{
|
|
||||||
let active_pane_item = self.active_pane.read(cx).active_item();
|
|
||||||
item.set_active_pane_item(active_pane_item.as_deref(), cx);
|
|
||||||
|
|
||||||
self.right_items.push(Box::new(item));
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
|
|
||||||
self.active_pane = active_pane.clone();
|
|
||||||
self._observe_active_pane =
|
|
||||||
cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
|
|
||||||
self.update_active_pane_item(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_active_pane_item(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
let active_pane_item = self.active_pane.read(cx).active_item();
|
|
||||||
for item in self.left_items.iter().chain(&self.right_items) {
|
|
||||||
item.set_active_pane_item(active_pane_item.as_deref(), cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: StatusItemView> StatusItemViewHandle for View<T> {
|
|
||||||
fn to_any(&self) -> AnyView {
|
|
||||||
self.clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_active_pane_item(
|
|
||||||
&self,
|
|
||||||
active_pane_item: Option<&dyn ItemHandle>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) {
|
|
||||||
self.update(cx, |this, cx| {
|
|
||||||
this.set_active_pane_item(active_pane_item, cx)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn item_type(&self) -> TypeId {
|
|
||||||
TypeId::of::<T>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&dyn StatusItemViewHandle> for AnyView {
|
|
||||||
fn from(val: &dyn StatusItemViewHandle) -> Self {
|
|
||||||
val.to_any().clone()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,321 +0,0 @@
|
|||||||
use crate::ItemHandle;
|
|
||||||
use gpui::{
|
|
||||||
AnyView, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View, ViewContext,
|
|
||||||
WindowContext,
|
|
||||||
};
|
|
||||||
use ui::prelude::*;
|
|
||||||
use ui::{h_stack, v_stack};
|
|
||||||
|
|
||||||
pub enum ToolbarItemEvent {
|
|
||||||
ChangeLocation(ToolbarItemLocation),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ToolbarItemView: Render + EventEmitter<ToolbarItemEvent> {
|
|
||||||
fn set_active_pane_item(
|
|
||||||
&mut self,
|
|
||||||
active_pane_item: Option<&dyn crate::ItemHandle>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> ToolbarItemLocation;
|
|
||||||
|
|
||||||
fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
|
|
||||||
|
|
||||||
/// Number of times toolbar's height will be repeated to get the effective height.
|
|
||||||
/// Useful when multiple rows one under each other are needed.
|
|
||||||
/// The rows have the same width and act as a whole when reacting to resizes and similar events.
|
|
||||||
fn row_count(&self, _cx: &WindowContext) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait ToolbarItemViewHandle: Send {
|
|
||||||
fn id(&self) -> EntityId;
|
|
||||||
fn to_any(&self) -> AnyView;
|
|
||||||
fn set_active_pane_item(
|
|
||||||
&self,
|
|
||||||
active_pane_item: Option<&dyn ItemHandle>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> ToolbarItemLocation;
|
|
||||||
fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext);
|
|
||||||
fn row_count(&self, cx: &WindowContext) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub enum ToolbarItemLocation {
|
|
||||||
Hidden,
|
|
||||||
PrimaryLeft,
|
|
||||||
PrimaryRight,
|
|
||||||
Secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Toolbar {
|
|
||||||
active_item: Option<Box<dyn ItemHandle>>,
|
|
||||||
hidden: bool,
|
|
||||||
can_navigate: bool,
|
|
||||||
items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Toolbar {
|
|
||||||
fn has_any_visible_items(&self) -> bool {
|
|
||||||
self.items
|
|
||||||
.iter()
|
|
||||||
.any(|(_item, location)| *location != ToolbarItemLocation::Hidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn left_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
|
|
||||||
self.items.iter().filter_map(|(item, location)| {
|
|
||||||
if *location == ToolbarItemLocation::PrimaryLeft {
|
|
||||||
Some(item.as_ref())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn right_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
|
|
||||||
self.items.iter().filter_map(|(item, location)| {
|
|
||||||
if *location == ToolbarItemLocation::PrimaryRight {
|
|
||||||
Some(item.as_ref())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn secondary_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
|
|
||||||
self.items.iter().filter_map(|(item, location)| {
|
|
||||||
if *location == ToolbarItemLocation::Secondary {
|
|
||||||
Some(item.as_ref())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Toolbar {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
if !self.has_any_visible_items() {
|
|
||||||
return div();
|
|
||||||
}
|
|
||||||
|
|
||||||
let secondary_item = self.secondary_items().next().map(|item| item.to_any());
|
|
||||||
|
|
||||||
let has_left_items = self.left_items().count() > 0;
|
|
||||||
let has_right_items = self.right_items().count() > 0;
|
|
||||||
|
|
||||||
v_stack()
|
|
||||||
.p_2()
|
|
||||||
.when(has_left_items || has_right_items, |this| this.gap_2())
|
|
||||||
.border_b()
|
|
||||||
.border_color(cx.theme().colors().border_variant)
|
|
||||||
.bg(cx.theme().colors().toolbar_background)
|
|
||||||
.child(
|
|
||||||
h_stack()
|
|
||||||
.justify_between()
|
|
||||||
.when(has_left_items, |this| {
|
|
||||||
this.child(
|
|
||||||
h_stack()
|
|
||||||
.flex_1()
|
|
||||||
.justify_start()
|
|
||||||
.children(self.left_items().map(|item| item.to_any())),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when(has_right_items, |this| {
|
|
||||||
this.child(
|
|
||||||
h_stack()
|
|
||||||
.flex_1()
|
|
||||||
.justify_end()
|
|
||||||
.children(self.right_items().map(|item| item.to_any())),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.children(secondary_item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo!()
|
|
||||||
// impl View for Toolbar {
|
|
||||||
// fn ui_name() -> &'static str {
|
|
||||||
// "Toolbar"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
|
||||||
// let theme = &theme::current(cx).workspace.toolbar;
|
|
||||||
|
|
||||||
// let mut primary_left_items = Vec::new();
|
|
||||||
// let mut primary_right_items = Vec::new();
|
|
||||||
// let mut secondary_item = None;
|
|
||||||
// let spacing = theme.item_spacing;
|
|
||||||
// let mut primary_items_row_count = 1;
|
|
||||||
|
|
||||||
// for (item, position) in &self.items {
|
|
||||||
// match *position {
|
|
||||||
// ToolbarItemLocation::Hidden => {}
|
|
||||||
|
|
||||||
// ToolbarItemLocation::PrimaryLeft { flex } => {
|
|
||||||
// primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
|
|
||||||
// let left_item = ChildView::new(item.as_any(), cx).aligned();
|
|
||||||
// if let Some((flex, expanded)) = flex {
|
|
||||||
// primary_left_items.push(left_item.flex(flex, expanded).into_any());
|
|
||||||
// } else {
|
|
||||||
// primary_left_items.push(left_item.into_any());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ToolbarItemLocation::PrimaryRight { flex } => {
|
|
||||||
// primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
|
|
||||||
// let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
|
|
||||||
// if let Some((flex, expanded)) = flex {
|
|
||||||
// primary_right_items.push(right_item.flex(flex, expanded).into_any());
|
|
||||||
// } else {
|
|
||||||
// primary_right_items.push(right_item.into_any());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ToolbarItemLocation::Secondary => {
|
|
||||||
// secondary_item = Some(
|
|
||||||
// ChildView::new(item.as_any(), cx)
|
|
||||||
// .constrained()
|
|
||||||
// .with_height(theme.height * item.row_count(cx) as f32)
|
|
||||||
// .into_any(),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let container_style = theme.container;
|
|
||||||
// let height = theme.height * primary_items_row_count as f32;
|
|
||||||
|
|
||||||
// let mut primary_items = Flex::row().with_spacing(spacing);
|
|
||||||
// primary_items.extend(primary_left_items);
|
|
||||||
// primary_items.extend(primary_right_items);
|
|
||||||
|
|
||||||
// let mut toolbar = Flex::column();
|
|
||||||
// if !primary_items.is_empty() {
|
|
||||||
// toolbar.add_child(primary_items.constrained().with_height(height));
|
|
||||||
// }
|
|
||||||
// if let Some(secondary_item) = secondary_item {
|
|
||||||
// toolbar.add_child(secondary_item);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if toolbar.is_empty() {
|
|
||||||
// toolbar.into_any_named("toolbar")
|
|
||||||
// } else {
|
|
||||||
// toolbar
|
|
||||||
// .contained()
|
|
||||||
// .with_style(container_style)
|
|
||||||
// .into_any_named("toolbar")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl Toolbar {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
active_item: None,
|
|
||||||
items: Default::default(),
|
|
||||||
hidden: false,
|
|
||||||
can_navigate: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
|
|
||||||
self.can_navigate = can_navigate;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
|
|
||||||
where
|
|
||||||
T: 'static + ToolbarItemView,
|
|
||||||
{
|
|
||||||
let location = item.set_active_pane_item(self.active_item.as_deref(), cx);
|
|
||||||
cx.subscribe(&item, |this, item, event, cx| {
|
|
||||||
if let Some((_, current_location)) =
|
|
||||||
this.items.iter_mut().find(|(i, _)| i.id() == item.id())
|
|
||||||
{
|
|
||||||
match event {
|
|
||||||
ToolbarItemEvent::ChangeLocation(new_location) => {
|
|
||||||
if new_location != current_location {
|
|
||||||
*current_location = *new_location;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
self.items.push((Box::new(item), location));
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
|
|
||||||
self.active_item = item.map(|item| item.boxed_clone());
|
|
||||||
self.hidden = self
|
|
||||||
.active_item
|
|
||||||
.as_ref()
|
|
||||||
.map(|item| !item.show_toolbar(cx))
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
for (toolbar_item, current_location) in self.items.iter_mut() {
|
|
||||||
let new_location = toolbar_item.set_active_pane_item(item, cx);
|
|
||||||
if new_location != *current_location {
|
|
||||||
*current_location = new_location;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
|
|
||||||
for (toolbar_item, _) in self.items.iter_mut() {
|
|
||||||
toolbar_item.focus_changed(focused, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<View<T>> {
|
|
||||||
self.items
|
|
||||||
.iter()
|
|
||||||
.find_map(|(item, _)| item.to_any().downcast().ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hidden(&self) -> bool {
|
|
||||||
self.hidden
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
|
|
||||||
fn id(&self) -> EntityId {
|
|
||||||
self.entity_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_any(&self) -> AnyView {
|
|
||||||
self.clone().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_active_pane_item(
|
|
||||||
&self,
|
|
||||||
active_pane_item: Option<&dyn ItemHandle>,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> ToolbarItemLocation {
|
|
||||||
self.update(cx, |this, cx| {
|
|
||||||
this.set_active_pane_item(active_pane_item, cx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) {
|
|
||||||
self.update(cx, |this, cx| {
|
|
||||||
this.pane_focus_update(pane_focused, cx);
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn row_count(&self, cx: &WindowContext) -> usize {
|
|
||||||
self.read(cx).row_count(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo!()
|
|
||||||
// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
|
|
||||||
// fn from(val: &dyn ToolbarItemViewHandle) -> Self {
|
|
||||||
// val.as_any().clone()
|
|
||||||
// }
|
|
||||||
// }
|
|
File diff suppressed because it is too large
Load Diff
@ -1,56 +0,0 @@
|
|||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use settings::Settings;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct WorkspaceSettings {
|
|
||||||
pub active_pane_magnification: f32,
|
|
||||||
pub confirm_quit: bool,
|
|
||||||
pub show_call_status_icon: bool,
|
|
||||||
pub autosave: AutosaveSetting,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct WorkspaceSettingsContent {
|
|
||||||
pub active_pane_magnification: Option<f32>,
|
|
||||||
pub confirm_quit: Option<bool>,
|
|
||||||
pub show_call_status_icon: Option<bool>,
|
|
||||||
pub autosave: Option<AutosaveSetting>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum AutosaveSetting {
|
|
||||||
Off,
|
|
||||||
AfterDelay { milliseconds: u64 },
|
|
||||||
OnFocusChange,
|
|
||||||
OnWindowChange,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct GitSettings {
|
|
||||||
pub git_gutter: Option<GitGutterSetting>,
|
|
||||||
pub gutter_debounce: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum GitGutterSetting {
|
|
||||||
#[default]
|
|
||||||
TrackedFiles,
|
|
||||||
Hide,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Settings for WorkspaceSettings {
|
|
||||||
const KEY: Option<&'static str> = None;
|
|
||||||
|
|
||||||
type FileContent = WorkspaceSettingsContent;
|
|
||||||
|
|
||||||
fn load(
|
|
||||||
default_value: &Self::FileContent,
|
|
||||||
user_values: &[&Self::FileContent],
|
|
||||||
_: &mut gpui::AppContext,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
Self::load_via_json_merge(default_value, user_values)
|
|
||||||
}
|
|
||||||
}
|
|
@ -71,7 +71,7 @@ theme_selector = { path = "../theme_selector" }
|
|||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
|
semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
|
||||||
vim = { path = "../vim" }
|
vim = { path = "../vim" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { path = "../workspace" }
|
||||||
welcome = { path = "../welcome" }
|
welcome = { path = "../welcome" }
|
||||||
zed_actions = {package = "zed_actions2", path = "../zed_actions2"}
|
zed_actions = {package = "zed_actions2", path = "../zed_actions2"}
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
Loading…
Reference in New Issue
Block a user