Compare commits
2 Commits
pixel-type
...
x11_debug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
804b00c12a | ||
|
|
f724b2c171 |
@@ -4,7 +4,3 @@ rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]
|
||||
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "/usr/bin/clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -74,8 +74,8 @@ jobs:
|
||||
version: v1.29.0
|
||||
- uses: bufbuild/buf-breaking-action@v1
|
||||
with:
|
||||
input: "crates/proto/proto/"
|
||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"
|
||||
input: "crates/rpc/proto/"
|
||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/rpc/proto/"
|
||||
|
||||
macos_tests:
|
||||
timeout-minutes: 60
|
||||
@@ -305,6 +305,9 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
- name: Create and upload Linux .tar.gz bundle
|
||||
run: script/bundle-linux
|
||||
|
||||
|
||||
114
Cargo.lock
generated
114
Cargo.lock
generated
@@ -359,7 +359,6 @@ dependencies = [
|
||||
"log",
|
||||
"menu",
|
||||
"multi_buffer",
|
||||
"ollama",
|
||||
"open_ai",
|
||||
"ordered-float 2.10.0",
|
||||
"parking_lot",
|
||||
@@ -368,14 +367,12 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rope",
|
||||
"rustdoc",
|
||||
"schemars",
|
||||
"search",
|
||||
"semantic_index",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"similar",
|
||||
"smol",
|
||||
"strsim 0.11.1",
|
||||
"strum",
|
||||
@@ -1514,7 +1511,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
|
||||
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -1544,7 +1541,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
|
||||
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1554,7 +1551,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
|
||||
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
|
||||
dependencies = [
|
||||
"blade-graphics",
|
||||
"bytemuck",
|
||||
@@ -2151,6 +2148,7 @@ dependencies = [
|
||||
"exec",
|
||||
"fork",
|
||||
"ipc-channel",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"plist",
|
||||
"release_channel",
|
||||
@@ -2211,10 +2209,8 @@ dependencies = [
|
||||
"async-tungstenite",
|
||||
"chrono",
|
||||
"clock",
|
||||
"cocoa",
|
||||
"collections",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"http 0.1.0",
|
||||
@@ -2241,7 +2237,6 @@ dependencies = [
|
||||
"tiny_http",
|
||||
"url",
|
||||
"util",
|
||||
"windows 0.56.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2368,7 +2363,6 @@ dependencies = [
|
||||
"prometheus",
|
||||
"prost",
|
||||
"rand 0.8.5",
|
||||
"recent_projects",
|
||||
"release_channel",
|
||||
"reqwest",
|
||||
"rpc",
|
||||
@@ -2459,6 +2453,13 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"palette",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
@@ -3451,7 +3452,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
"assets",
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
@@ -3833,7 +3833,6 @@ dependencies = [
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"num-format",
|
||||
"picker",
|
||||
"project",
|
||||
"release_channel",
|
||||
@@ -6736,16 +6735,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-format"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
@@ -6920,19 +6909,6 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ollama"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.28",
|
||||
"http 0.1.0",
|
||||
"isahc",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
@@ -7132,6 +7108,7 @@ dependencies = [
|
||||
"project",
|
||||
"rope",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"tree-sitter-rust",
|
||||
@@ -7141,31 +7118,6 @@ dependencies = [
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outline_panel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"db",
|
||||
"editor",
|
||||
"file_icons",
|
||||
"git",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"unicase",
|
||||
"util",
|
||||
"workspace",
|
||||
"worktree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outref"
|
||||
version = "0.5.1"
|
||||
@@ -7968,17 +7920,6 @@ dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proto"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "2.28.0"
|
||||
@@ -8562,7 +8503,8 @@ dependencies = [
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"parking_lot",
|
||||
"proto",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"rand 0.8.5",
|
||||
"rsa 0.4.0",
|
||||
"serde",
|
||||
@@ -8687,26 +8629,6 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustdoc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"html_to_markdown",
|
||||
"http 0.1.0",
|
||||
"indexmap 1.9.3",
|
||||
"indoc",
|
||||
"parking_lot",
|
||||
"pretty_assertions",
|
||||
"strum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.23"
|
||||
@@ -10485,6 +10407,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"color",
|
||||
"derive_more",
|
||||
"fs",
|
||||
"futures 0.3.28",
|
||||
@@ -12964,6 +12887,7 @@ name = "worktree"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"env_logger",
|
||||
@@ -12975,8 +12899,10 @@ dependencies = [
|
||||
"gpui",
|
||||
"http 0.1.0",
|
||||
"ignore",
|
||||
"itertools 0.11.0",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
@@ -13225,11 +13151,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.141.0"
|
||||
version = "0.140.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
"assets",
|
||||
"assistant",
|
||||
"audio",
|
||||
@@ -13282,7 +13207,6 @@ dependencies = [
|
||||
"node_runtime",
|
||||
"notifications",
|
||||
"outline",
|
||||
"outline_panel",
|
||||
"parking_lot",
|
||||
"profiling",
|
||||
"project",
|
||||
|
||||
25
Cargo.toml
25
Cargo.toml
@@ -61,16 +61,13 @@ members = [
|
||||
"crates/multi_buffer",
|
||||
"crates/node_runtime",
|
||||
"crates/notifications",
|
||||
"crates/ollama",
|
||||
"crates/open_ai",
|
||||
"crates/outline",
|
||||
"crates/outline_panel",
|
||||
"crates/picker",
|
||||
"crates/prettier",
|
||||
"crates/project",
|
||||
"crates/project_panel",
|
||||
"crates/project_symbols",
|
||||
"crates/proto",
|
||||
"crates/quick_action_bar",
|
||||
"crates/recent_projects",
|
||||
"crates/refineable",
|
||||
@@ -80,7 +77,6 @@ members = [
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/rustdoc",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
"crates/search",
|
||||
@@ -167,6 +163,7 @@ clock = { path = "crates/clock" }
|
||||
collab = { path = "crates/collab" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
color = { path = "crates/color" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
@@ -210,16 +207,13 @@ menu = { path = "crates/menu" }
|
||||
multi_buffer = { path = "crates/multi_buffer" }
|
||||
node_runtime = { path = "crates/node_runtime" }
|
||||
notifications = { path = "crates/notifications" }
|
||||
ollama = { path = "crates/ollama" }
|
||||
open_ai = { path = "crates/open_ai" }
|
||||
outline = { path = "crates/outline" }
|
||||
outline_panel = { path = "crates/outline_panel" }
|
||||
picker = { path = "crates/picker" }
|
||||
plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
proto = { path = "crates/proto" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
@@ -230,7 +224,6 @@ dev_server_projects = { path = "crates/dev_server_projects" }
|
||||
rich_text = { path = "crates/rich_text" }
|
||||
rope = { path = "crates/rope" }
|
||||
rpc = { path = "crates/rpc" }
|
||||
rustdoc = { path = "crates/rustdoc" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
search = { path = "crates/search" }
|
||||
@@ -274,15 +267,14 @@ async-tar = "0.4.2"
|
||||
async-trait = "0.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
bitflags = "2.4.2"
|
||||
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
|
||||
blade-macros = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
|
||||
blade-util = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
|
||||
cap-std = "3.0"
|
||||
cargo_toml = "0.20"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
clickhouse = { version = "0.11.6" }
|
||||
cocoa = "0.25"
|
||||
ctor = "0.2.6"
|
||||
signal-hook = "0.3.17"
|
||||
core-foundation = { version = "0.9.3" }
|
||||
@@ -301,7 +293,6 @@ heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
|
||||
hex = "0.4.3"
|
||||
html5ever = "0.27.0"
|
||||
ignore = "0.4.22"
|
||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
indoc = "1"
|
||||
# We explicitly disable http2 support in isahc.
|
||||
isahc = { version = "1.7.2", default-features = false, features = [
|
||||
@@ -316,7 +307,6 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
nix = "0.28"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
ordered-float = "2.1.1"
|
||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
@@ -348,7 +338,6 @@ serde_repr = "0.1"
|
||||
sha2 = "0.10"
|
||||
shellexpand = "2.1.0"
|
||||
shlex = "1.3.0"
|
||||
similar = "1.3"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
smol = "1.2"
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
@@ -474,6 +463,12 @@ codegen-units = 1
|
||||
[profile.release.package]
|
||||
zed = { codegen-units = 16 }
|
||||
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
lto = false
|
||||
codegen-units = 16
|
||||
|
||||
[workspace.lints.clippy]
|
||||
dbg_macro = "deny"
|
||||
todo = "deny"
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 5H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.5 8H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 10.9502H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 683 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-tree"><path d="M21 12h-8"/><path d="M21 6H8"/><path d="M21 18h-8"/><path d="M3 6v4c0 1.1.9 2 2 2h3"/><path d="M3 10v6c0 1.1.9 2 2 2h3"/></svg>
|
||||
|
Before Width: | Height: | Size: 349 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-cw"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg>
|
||||
|
Before Width: | Height: | Size: 303 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.88889 1H2.11111C1.49746 1 1 1.49746 1 2.11111V9.88889C1 10.5025 1.49746 11 2.11111 11H9.88889C10.5025 11 11 10.5025 11 9.88889V2.11111C11 1.49746 10.5025 1 9.88889 1Z" stroke="#C56757" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 369 B |
@@ -439,7 +439,6 @@
|
||||
"ctrl-shift-p": "command_palette::Toggle",
|
||||
"ctrl-shift-m": "diagnostics::Deploy",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
||||
"ctrl-?": "assistant::ToggleFocus",
|
||||
"ctrl-alt-s": "workspace::SaveAll",
|
||||
"ctrl-k m": "language_selector::Toggle",
|
||||
@@ -546,7 +545,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextEditor > Editor",
|
||||
"context": "ConversationEditor > Editor",
|
||||
"bindings": {
|
||||
"ctrl-enter": "assistant::Assist",
|
||||
"ctrl-s": "workspace::Save",
|
||||
@@ -563,18 +562,6 @@
|
||||
"ctrl-enter": "project_search::SearchInNew"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "OutlinePanel",
|
||||
"bindings": {
|
||||
"left": "project_panel::CollapseSelectedEntry",
|
||||
"right": "project_panel::ExpandSelectedEntry",
|
||||
"ctrl-alt-c": "project_panel::CopyPath",
|
||||
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
|
||||
"alt-ctrl-r": "project_panel::RevealInFinder",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
@@ -596,10 +583,7 @@
|
||||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFinder",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
"escape": "menu::Cancel"
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextEditor > Editor",
|
||||
"context": "ConversationEditor > Editor",
|
||||
"bindings": {
|
||||
"cmd-enter": "assistant::Assist",
|
||||
"cmd-s": "workspace::Save",
|
||||
@@ -475,7 +475,6 @@
|
||||
"cmd-shift-p": "command_palette::Toggle",
|
||||
"cmd-shift-m": "diagnostics::Deploy",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-shift-b": "outline_panel::ToggleFocus",
|
||||
"cmd-?": "assistant::ToggleFocus",
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
"cmd-k m": "language_selector::Toggle",
|
||||
@@ -585,18 +584,6 @@
|
||||
"cmd-enter": "project_search::SearchInNew"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "OutlinePanel",
|
||||
"bindings": {
|
||||
"left": "outline_panel::CollapseSelectedEntry",
|
||||
"right": "outline_panel::ExpandSelectedEntry",
|
||||
"cmd-alt-c": "outline_panel::CopyPath",
|
||||
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
|
||||
"alt-cmd-r": "outline_panel::RevealInFinder",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||
|
||||
"/": "vim::Search",
|
||||
"g /": "pane::DeploySearch",
|
||||
"?": [
|
||||
"vim::Search",
|
||||
{
|
||||
@@ -382,10 +381,6 @@
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": ["vim::PushOperator", "Indent"],
|
||||
"<": ["vim::PushOperator", "Outdent"],
|
||||
"g u": ["vim::PushOperator", "Lowercase"],
|
||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
// tree-sitter related commands
|
||||
@@ -400,7 +395,6 @@
|
||||
{
|
||||
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
|
||||
"bindings": {
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode"
|
||||
@@ -436,27 +430,6 @@
|
||||
"d": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == gu",
|
||||
"bindings": {
|
||||
"g u": "vim::CurrentLine",
|
||||
"u": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == gU",
|
||||
"bindings": {
|
||||
"g shift-u": "vim::CurrentLine",
|
||||
"shift-u": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == g~",
|
||||
"bindings": {
|
||||
"g ~": "vim::CurrentLine",
|
||||
"~": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == normal && vim_operator == d",
|
||||
"bindings": {
|
||||
|
||||
@@ -131,14 +131,7 @@
|
||||
// The default number of lines to expand excerpts in the multibuffer by.
|
||||
"expand_excerpt_lines": 3,
|
||||
// Globs to match against file paths to determine if a file is private.
|
||||
"private_files": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
"**/*.key",
|
||||
"**/*.cert",
|
||||
"**/*.crt",
|
||||
"**/secrets.yml"
|
||||
],
|
||||
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
@@ -302,29 +295,6 @@
|
||||
/// when a directory has only one directory inside.
|
||||
"auto_fold_dirs": false
|
||||
},
|
||||
"outline_panel": {
|
||||
// Whether to show the outline panel button in the status bar
|
||||
"button": true,
|
||||
// Default width of the outline panel.
|
||||
"default_width": 240,
|
||||
// Where to dock the outline panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Whether to show file icons in the outline panel.
|
||||
"file_icons": true,
|
||||
// Whether to show folder icons or chevrons for directories in the outline panel.
|
||||
"folder_icons": true,
|
||||
// Whether to show the git status in the outline panel.
|
||||
"git_status": true,
|
||||
// Amount of indentation for nested items.
|
||||
"indent_size": 20,
|
||||
// Whether to reveal it in the outline panel automatically,
|
||||
// when a corresponding outline entry becomes active.
|
||||
// Gitignored entries are never auto revealed.
|
||||
"auto_reveal_entries": true,
|
||||
/// Whether to fold directories automatically
|
||||
/// when a directory has only one directory inside.
|
||||
"auto_fold_dirs": true
|
||||
},
|
||||
"collaboration_panel": {
|
||||
// Whether to show the collaboration panel button in the status bar.
|
||||
"button": true,
|
||||
@@ -384,9 +354,6 @@
|
||||
"show_call_status_icon": true,
|
||||
// Whether to use language servers to provide code intelligence.
|
||||
"enable_language_server": true,
|
||||
// Whether to perform linked edits of associated ranges, if the language server supports it.
|
||||
// For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.
|
||||
"linked_edits": true,
|
||||
// The list of language servers to use (or disable) for all languages.
|
||||
//
|
||||
// This is typically customized on a per-language basis.
|
||||
|
||||
@@ -62,16 +62,16 @@ impl ActivityIndicator {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.statuses.retain(|s| s.name != name);
|
||||
this.statuses.push(LspStatus { name, status });
|
||||
cx.notify();
|
||||
cx.notify(); // commented back in
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
||||
cx.observe(&project, |_, _, cx| cx.notify()).detach(); // commented back in
|
||||
|
||||
if let Some(auto_updater) = auto_updater.as_ref() {
|
||||
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
|
||||
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach(); // commented back in
|
||||
}
|
||||
|
||||
Self {
|
||||
@@ -285,10 +285,10 @@ impl ActivityIndicator {
|
||||
icon: None,
|
||||
message: "Click to restart and update Zed".to_string(),
|
||||
on_click: Some(Arc::new({
|
||||
let reload = workspace::Reload {
|
||||
let restart = workspace::Restart {
|
||||
binary_path: Some(binary_path.clone()),
|
||||
};
|
||||
move |_, cx| workspace::reload(&reload, cx)
|
||||
move |_, cx| workspace::restart(&restart, cx)
|
||||
})),
|
||||
},
|
||||
AutoUpdateStatus::Errored => Content {
|
||||
|
||||
@@ -35,21 +35,18 @@ language.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
ollama = { workspace = true, features = ["schemars"] }
|
||||
open_ai = { workspace = true, features = ["schemars"] }
|
||||
ordered-float.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
rustdoc.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
semantic_index.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
similar.workspace = true
|
||||
smol.workspace = true
|
||||
strsim = "0.11"
|
||||
strum.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod assistant_panel;
|
||||
pub mod assistant_settings;
|
||||
mod completion_provider;
|
||||
mod context_store;
|
||||
mod conversation_store;
|
||||
mod inline_assistant;
|
||||
mod model_selector;
|
||||
mod prompt_library;
|
||||
@@ -12,22 +12,21 @@ mod streaming_diff;
|
||||
|
||||
pub use assistant_panel::AssistantPanel;
|
||||
|
||||
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaModel, OpenAiModel};
|
||||
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OpenAiModel};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::{proto, Client};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
pub(crate) use completion_provider::*;
|
||||
pub(crate) use context_store::*;
|
||||
pub(crate) use conversation_store::*;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
pub(crate) use inline_assistant::*;
|
||||
pub(crate) use model_selector::*;
|
||||
use rustdoc::RustdocStore;
|
||||
use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use slash_command::{
|
||||
active_command, default_command, fetch_command, file_command, now_command, project_command,
|
||||
prompt_command, rustdoc_command, search_command, tabs_command,
|
||||
active_command, default_command, fetch_command, file_command, project_command, prompt_command,
|
||||
rustdoc_command, search_command, tabs_command,
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
@@ -92,7 +91,6 @@ pub enum LanguageModel {
|
||||
Cloud(CloudModel),
|
||||
OpenAi(OpenAiModel),
|
||||
Anthropic(AnthropicModel),
|
||||
Ollama(OllamaModel),
|
||||
}
|
||||
|
||||
impl Default for LanguageModel {
|
||||
@@ -107,7 +105,6 @@ impl LanguageModel {
|
||||
LanguageModel::OpenAi(model) => format!("openai/{}", model.id()),
|
||||
LanguageModel::Anthropic(model) => format!("anthropic/{}", model.id()),
|
||||
LanguageModel::Cloud(model) => format!("zed.dev/{}", model.id()),
|
||||
LanguageModel::Ollama(model) => format!("ollama/{}", model.id()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +113,6 @@ impl LanguageModel {
|
||||
LanguageModel::OpenAi(model) => model.display_name().into(),
|
||||
LanguageModel::Anthropic(model) => model.display_name().into(),
|
||||
LanguageModel::Cloud(model) => model.display_name().into(),
|
||||
LanguageModel::Ollama(model) => model.display_name().into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +121,6 @@ impl LanguageModel {
|
||||
LanguageModel::OpenAi(model) => model.max_token_count(),
|
||||
LanguageModel::Anthropic(model) => model.max_token_count(),
|
||||
LanguageModel::Cloud(model) => model.max_token_count(),
|
||||
LanguageModel::Ollama(model) => model.max_token_count(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +129,6 @@ impl LanguageModel {
|
||||
LanguageModel::OpenAi(model) => model.id(),
|
||||
LanguageModel::Anthropic(model) => model.id(),
|
||||
LanguageModel::Cloud(model) => model.id(),
|
||||
LanguageModel::Ollama(model) => model.id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,7 +179,6 @@ impl LanguageModelRequest {
|
||||
match &self.model {
|
||||
LanguageModel::OpenAi(_) => {}
|
||||
LanguageModel::Anthropic(_) => {}
|
||||
LanguageModel::Ollama(_) => {}
|
||||
LanguageModel::Cloud(model) => match model {
|
||||
CloudModel::Claude3Opus | CloudModel::Claude3Sonnet | CloudModel::Claude3Haiku => {
|
||||
preprocess_anthropic_request(self);
|
||||
@@ -287,7 +280,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
register_slash_commands(cx);
|
||||
assistant_panel::init(cx);
|
||||
inline_assistant::init(client.telemetry().clone(), cx);
|
||||
RustdocStore::init_global(cx);
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(Assistant::NAMESPACE);
|
||||
@@ -315,7 +307,6 @@ fn register_slash_commands(cx: &mut AppContext) {
|
||||
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
||||
slash_command_registry.register_command(now_command::NowSlashCommand, true);
|
||||
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
||||
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ use std::fmt;
|
||||
|
||||
pub use anthropic::Model as AnthropicModel;
|
||||
use gpui::Pixels;
|
||||
pub use ollama::Model as OllamaModel;
|
||||
pub use open_ai::Model as OpenAiModel;
|
||||
use schemars::{
|
||||
schema::{InstanceType, Metadata, Schema, SchemaObject},
|
||||
@@ -169,11 +168,6 @@ pub enum AssistantProvider {
|
||||
api_url: String,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
},
|
||||
Ollama {
|
||||
model: OllamaModel,
|
||||
api_url: String,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for AssistantProvider {
|
||||
@@ -203,12 +197,6 @@ pub enum AssistantProviderContent {
|
||||
api_url: Option<String>,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
},
|
||||
#[serde(rename = "ollama")]
|
||||
Ollama {
|
||||
default_model: Option<OllamaModel>,
|
||||
api_url: Option<String>,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -340,13 +328,6 @@ impl AssistantSettingsContent {
|
||||
low_speed_timeout_in_seconds: None,
|
||||
})
|
||||
}
|
||||
LanguageModel::Ollama(model) => {
|
||||
*provider = Some(AssistantProviderContent::Ollama {
|
||||
default_model: Some(model),
|
||||
api_url: None,
|
||||
low_speed_timeout_in_seconds: None,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -491,27 +472,6 @@ impl Settings for AssistantSettings {
|
||||
Some(low_speed_timeout_in_seconds_override);
|
||||
}
|
||||
}
|
||||
(
|
||||
AssistantProvider::Ollama {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
},
|
||||
AssistantProviderContent::Ollama {
|
||||
default_model: model_override,
|
||||
api_url: api_url_override,
|
||||
low_speed_timeout_in_seconds: low_speed_timeout_in_seconds_override,
|
||||
},
|
||||
) => {
|
||||
merge(model, model_override);
|
||||
merge(api_url, api_url_override);
|
||||
if let Some(low_speed_timeout_in_seconds_override) =
|
||||
low_speed_timeout_in_seconds_override
|
||||
{
|
||||
*low_speed_timeout_in_seconds =
|
||||
Some(low_speed_timeout_in_seconds_override);
|
||||
}
|
||||
}
|
||||
(
|
||||
AssistantProvider::Anthropic {
|
||||
model,
|
||||
@@ -559,15 +519,6 @@ impl Settings for AssistantSettings {
|
||||
.unwrap_or_else(|| anthropic::ANTHROPIC_API_URL.into()),
|
||||
low_speed_timeout_in_seconds,
|
||||
},
|
||||
AssistantProviderContent::Ollama {
|
||||
default_model: model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
} => AssistantProvider::Ollama {
|
||||
model: model.unwrap_or_default(),
|
||||
api_url: api_url.unwrap_or_else(|| ollama::OLLAMA_API_URL.into()),
|
||||
low_speed_timeout_in_seconds,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ mod anthropic;
|
||||
mod cloud;
|
||||
#[cfg(test)]
|
||||
mod fake;
|
||||
mod ollama;
|
||||
mod open_ai;
|
||||
|
||||
pub use anthropic::*;
|
||||
pub use cloud::*;
|
||||
#[cfg(test)]
|
||||
pub use fake::*;
|
||||
pub use ollama::*;
|
||||
pub use open_ai::*;
|
||||
|
||||
use crate::{
|
||||
@@ -52,18 +50,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
settings_version,
|
||||
)),
|
||||
AssistantProvider::Ollama {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
} => CompletionProvider::Ollama(OllamaCompletionProvider::new(
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
client.http_client(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
settings_version,
|
||||
cx,
|
||||
)),
|
||||
};
|
||||
cx.set_global(provider);
|
||||
|
||||
@@ -101,24 +87,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
settings_version,
|
||||
);
|
||||
}
|
||||
|
||||
(
|
||||
CompletionProvider::Ollama(provider),
|
||||
AssistantProvider::Ollama {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
},
|
||||
) => {
|
||||
provider.update(
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
settings_version,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
(CompletionProvider::Cloud(provider), AssistantProvider::ZedDotDev { model }) => {
|
||||
provider.update(model.clone(), settings_version);
|
||||
}
|
||||
@@ -162,23 +130,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
settings_version,
|
||||
));
|
||||
}
|
||||
(
|
||||
_,
|
||||
AssistantProvider::Ollama {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
},
|
||||
) => {
|
||||
*provider = CompletionProvider::Ollama(OllamaCompletionProvider::new(
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
client.http_client(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
settings_version,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -191,7 +142,6 @@ pub enum CompletionProvider {
|
||||
Cloud(CloudCompletionProvider),
|
||||
#[cfg(test)]
|
||||
Fake(FakeCompletionProvider),
|
||||
Ollama(OllamaCompletionProvider),
|
||||
}
|
||||
|
||||
impl gpui::Global for CompletionProvider {}
|
||||
@@ -215,10 +165,6 @@ impl CompletionProvider {
|
||||
.available_models()
|
||||
.map(LanguageModel::Cloud)
|
||||
.collect(),
|
||||
CompletionProvider::Ollama(provider) => provider
|
||||
.available_models()
|
||||
.map(|model| LanguageModel::Ollama(model.clone()))
|
||||
.collect(),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(_) => unimplemented!(),
|
||||
}
|
||||
@@ -229,7 +175,6 @@ impl CompletionProvider {
|
||||
CompletionProvider::OpenAi(provider) => provider.settings_version(),
|
||||
CompletionProvider::Anthropic(provider) => provider.settings_version(),
|
||||
CompletionProvider::Cloud(provider) => provider.settings_version(),
|
||||
CompletionProvider::Ollama(provider) => provider.settings_version(),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(_) => unimplemented!(),
|
||||
}
|
||||
@@ -240,7 +185,6 @@ impl CompletionProvider {
|
||||
CompletionProvider::OpenAi(provider) => provider.is_authenticated(),
|
||||
CompletionProvider::Anthropic(provider) => provider.is_authenticated(),
|
||||
CompletionProvider::Cloud(provider) => provider.is_authenticated(),
|
||||
CompletionProvider::Ollama(provider) => provider.is_authenticated(),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(_) => true,
|
||||
}
|
||||
@@ -251,7 +195,6 @@ impl CompletionProvider {
|
||||
CompletionProvider::OpenAi(provider) => provider.authenticate(cx),
|
||||
CompletionProvider::Anthropic(provider) => provider.authenticate(cx),
|
||||
CompletionProvider::Cloud(provider) => provider.authenticate(cx),
|
||||
CompletionProvider::Ollama(provider) => provider.authenticate(cx),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(_) => Task::ready(Ok(())),
|
||||
}
|
||||
@@ -262,7 +205,6 @@ impl CompletionProvider {
|
||||
CompletionProvider::OpenAi(provider) => provider.authentication_prompt(cx),
|
||||
CompletionProvider::Anthropic(provider) => provider.authentication_prompt(cx),
|
||||
CompletionProvider::Cloud(provider) => provider.authentication_prompt(cx),
|
||||
CompletionProvider::Ollama(provider) => provider.authentication_prompt(cx),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(_) => unimplemented!(),
|
||||
}
|
||||
@@ -273,7 +215,6 @@ impl CompletionProvider {
|
||||
CompletionProvider::OpenAi(provider) => provider.reset_credentials(cx),
|
||||
CompletionProvider::Anthropic(provider) => provider.reset_credentials(cx),
|
||||
CompletionProvider::Cloud(_) => Task::ready(Ok(())),
|
||||
CompletionProvider::Ollama(provider) => provider.reset_credentials(cx),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(_) => Task::ready(Ok(())),
|
||||
}
|
||||
@@ -284,7 +225,6 @@ impl CompletionProvider {
|
||||
CompletionProvider::OpenAi(provider) => LanguageModel::OpenAi(provider.model()),
|
||||
CompletionProvider::Anthropic(provider) => LanguageModel::Anthropic(provider.model()),
|
||||
CompletionProvider::Cloud(provider) => LanguageModel::Cloud(provider.model()),
|
||||
CompletionProvider::Ollama(provider) => LanguageModel::Ollama(provider.model()),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(_) => LanguageModel::default(),
|
||||
}
|
||||
@@ -299,7 +239,6 @@ impl CompletionProvider {
|
||||
CompletionProvider::OpenAi(provider) => provider.count_tokens(request, cx),
|
||||
CompletionProvider::Anthropic(provider) => provider.count_tokens(request, cx),
|
||||
CompletionProvider::Cloud(provider) => provider.count_tokens(request, cx),
|
||||
CompletionProvider::Ollama(provider) => provider.count_tokens(request, cx),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(_) => futures::FutureExt::boxed(futures::future::ready(Ok(0))),
|
||||
}
|
||||
@@ -313,7 +252,6 @@ impl CompletionProvider {
|
||||
CompletionProvider::OpenAi(provider) => provider.complete(request),
|
||||
CompletionProvider::Anthropic(provider) => provider.complete(request),
|
||||
CompletionProvider::Cloud(provider) => provider.complete(request),
|
||||
CompletionProvider::Ollama(provider) => provider.complete(request),
|
||||
#[cfg(test)]
|
||||
CompletionProvider::Fake(provider) => provider.complete(),
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ impl Render for AuthenticationPrompt {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Label::new("Click on").size(LabelSize::Small))
|
||||
.child(Icon::new(IconName::ZedAssistant).size(IconSize::XSmall))
|
||||
.child(Icon::new(IconName::Ai).size(IconSize::XSmall))
|
||||
.child(
|
||||
Label::new("in the status bar to close this panel.").size(LabelSize::Small),
|
||||
),
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
use crate::{
|
||||
assistant_settings::OllamaModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use futures::StreamExt as _;
|
||||
use futures::{future::BoxFuture, stream::BoxStream, FutureExt};
|
||||
use gpui::{AnyView, AppContext, Task};
|
||||
use http::HttpClient;
|
||||
use ollama::{
|
||||
get_models, preload_model, stream_chat_completion, ChatMessage, ChatOptions, ChatRequest,
|
||||
Role as OllamaRole,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
|
||||
const OLLAMA_DOWNLOAD_URL: &str = "https://ollama.com/download";
|
||||
|
||||
pub struct OllamaCompletionProvider {
|
||||
api_url: String,
|
||||
model: OllamaModel,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
low_speed_timeout: Option<Duration>,
|
||||
settings_version: usize,
|
||||
available_models: Vec<OllamaModel>,
|
||||
}
|
||||
|
||||
impl OllamaCompletionProvider {
|
||||
pub fn new(
|
||||
model: OllamaModel,
|
||||
api_url: String,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
low_speed_timeout: Option<Duration>,
|
||||
settings_version: usize,
|
||||
cx: &AppContext,
|
||||
) -> Self {
|
||||
cx.spawn({
|
||||
let api_url = api_url.clone();
|
||||
let client = http_client.clone();
|
||||
let model = model.name.clone();
|
||||
|
||||
|_| async move { preload_model(client.as_ref(), &api_url, &model).await }
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
Self {
|
||||
api_url,
|
||||
model,
|
||||
http_client,
|
||||
low_speed_timeout,
|
||||
settings_version,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
model: OllamaModel,
|
||||
api_url: String,
|
||||
low_speed_timeout: Option<Duration>,
|
||||
settings_version: usize,
|
||||
cx: &AppContext,
|
||||
) {
|
||||
cx.spawn({
|
||||
let api_url = api_url.clone();
|
||||
let client = self.http_client.clone();
|
||||
let model = model.name.clone();
|
||||
|
||||
|_| async move { preload_model(client.as_ref(), &api_url, &model).await }
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
self.model = model;
|
||||
self.api_url = api_url;
|
||||
self.low_speed_timeout = low_speed_timeout;
|
||||
self.settings_version = settings_version;
|
||||
}
|
||||
|
||||
pub fn available_models(&self) -> impl Iterator<Item = &OllamaModel> {
|
||||
self.available_models.iter()
|
||||
}
|
||||
|
||||
pub fn settings_version(&self) -> usize {
|
||||
self.settings_version
|
||||
}
|
||||
|
||||
pub fn is_authenticated(&self) -> bool {
|
||||
!self.available_models.is_empty()
|
||||
}
|
||||
|
||||
pub fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
|
||||
if self.is_authenticated() {
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
self.fetch_models(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
|
||||
self.fetch_models(cx)
|
||||
}
|
||||
|
||||
pub fn fetch_models(&self, cx: &AppContext) -> Task<Result<()>> {
|
||||
let http_client = self.http_client.clone();
|
||||
let api_url = self.api_url.clone();
|
||||
|
||||
// As a proxy for the server being "authenticated", we'll check if its up by fetching the models
|
||||
cx.spawn(|mut cx| async move {
|
||||
let models = get_models(http_client.as_ref(), &api_url, None).await?;
|
||||
|
||||
let mut models: Vec<OllamaModel> = models
|
||||
.into_iter()
|
||||
// Since there is no metadata from the Ollama API
|
||||
// indicating which models are embedding models,
|
||||
// simply filter out models with "-embed" in their name
|
||||
.filter(|model| !model.name.contains("-embed"))
|
||||
.map(|model| OllamaModel::new(&model.name))
|
||||
.collect();
|
||||
|
||||
models.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
|
||||
if let CompletionProvider::Ollama(provider) = provider {
|
||||
provider.available_models = models;
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
|
||||
cx.new_view(|cx| DownloadOllamaMessage::new(cx)).into()
|
||||
}
|
||||
|
||||
pub fn model(&self) -> OllamaModel {
|
||||
self.model.clone()
|
||||
}
|
||||
|
||||
pub fn count_tokens(
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
_cx: &AppContext,
|
||||
) -> BoxFuture<'static, Result<usize>> {
|
||||
// There is no endpoint for this _yet_ in Ollama
|
||||
// see: https://github.com/ollama/ollama/issues/1716 and https://github.com/ollama/ollama/issues/3582
|
||||
let token_count = request
|
||||
.messages
|
||||
.iter()
|
||||
.map(|msg| msg.content.chars().count())
|
||||
.sum::<usize>()
|
||||
/ 4;
|
||||
|
||||
async move { Ok(token_count) }.boxed()
|
||||
}
|
||||
|
||||
pub fn complete(
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
||||
let request = self.to_ollama_request(request);
|
||||
|
||||
let http_client = self.http_client.clone();
|
||||
let api_url = self.api_url.clone();
|
||||
let low_speed_timeout = self.low_speed_timeout;
|
||||
async move {
|
||||
let request =
|
||||
stream_chat_completion(http_client.as_ref(), &api_url, request, low_speed_timeout);
|
||||
let response = request.await?;
|
||||
let stream = response
|
||||
.filter_map(|response| async move {
|
||||
match response {
|
||||
Ok(delta) => {
|
||||
let content = match delta.message {
|
||||
ChatMessage::User { content } => content,
|
||||
ChatMessage::Assistant { content } => content,
|
||||
ChatMessage::System { content } => content,
|
||||
};
|
||||
Some(Ok(content))
|
||||
}
|
||||
Err(error) => Some(Err(error)),
|
||||
}
|
||||
})
|
||||
.boxed();
|
||||
Ok(stream)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn to_ollama_request(&self, request: LanguageModelRequest) -> ChatRequest {
|
||||
let model = match request.model {
|
||||
LanguageModel::Ollama(model) => model,
|
||||
_ => self.model(),
|
||||
};
|
||||
|
||||
ChatRequest {
|
||||
model: model.name,
|
||||
messages: request
|
||||
.messages
|
||||
.into_iter()
|
||||
.map(|msg| match msg.role {
|
||||
Role::User => ChatMessage::User {
|
||||
content: msg.content,
|
||||
},
|
||||
Role::Assistant => ChatMessage::Assistant {
|
||||
content: msg.content,
|
||||
},
|
||||
Role::System => ChatMessage::System {
|
||||
content: msg.content,
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
keep_alive: model.keep_alive,
|
||||
stream: true,
|
||||
options: Some(ChatOptions {
|
||||
num_ctx: Some(model.max_tokens),
|
||||
stop: Some(request.stop),
|
||||
temperature: Some(request.temperature),
|
||||
..Default::default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Role> for ollama::Role {
|
||||
fn from(val: Role) -> Self {
|
||||
match val {
|
||||
Role::User => OllamaRole::User,
|
||||
Role::Assistant => OllamaRole::Assistant,
|
||||
Role::System => OllamaRole::System,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DownloadOllamaMessage {}
|
||||
|
||||
impl DownloadOllamaMessage {
|
||||
pub fn new(_cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn render_download_button(&self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
ButtonLike::new("download_ollama_button")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Get Ollama"))
|
||||
.on_click(move |_, cx| cx.open_url(OLLAMA_DOWNLOAD_URL))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DownloadOllamaMessage {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.p_4()
|
||||
.size_full()
|
||||
.child(Label::new("To use Ollama models via the assistant, Ollama must be running on your machine.").size(LabelSize::Large))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.p_4()
|
||||
.justify_center()
|
||||
.child(
|
||||
self.render_download_button(cx)
|
||||
)
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
@@ -336,7 +336,7 @@ impl Render for AuthenticationPrompt {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Label::new("Click on").size(LabelSize::Small))
|
||||
.child(Icon::new(IconName::ZedAssistant).size(IconSize::XSmall))
|
||||
.child(Icon::new(IconName::Ai).size(IconSize::XSmall))
|
||||
.child(
|
||||
Label::new("in the status bar to close this panel.").size(LabelSize::Small),
|
||||
),
|
||||
|
||||
@@ -9,7 +9,7 @@ use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
|
||||
use ui::Context;
|
||||
use util::{paths::CONTEXTS_DIR, ResultExt, TryFutureExt};
|
||||
use util::{paths::CONVERSATIONS_DIR, ResultExt, TryFutureExt};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedMessage {
|
||||
@@ -18,7 +18,7 @@ pub struct SavedMessage {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SavedContext {
|
||||
pub struct SavedConversation {
|
||||
pub id: Option<String>,
|
||||
pub zed: String,
|
||||
pub version: String,
|
||||
@@ -28,12 +28,12 @@ pub struct SavedContext {
|
||||
pub summary: String,
|
||||
}
|
||||
|
||||
impl SavedContext {
|
||||
impl SavedConversation {
|
||||
pub const VERSION: &'static str = "0.2.0";
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedContextV0_1_0 {
|
||||
struct SavedConversationV0_1_0 {
|
||||
id: Option<String>,
|
||||
zed: String,
|
||||
version: String,
|
||||
@@ -46,26 +46,28 @@ struct SavedContextV0_1_0 {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SavedContextMetadata {
|
||||
pub struct SavedConversationMetadata {
|
||||
pub title: String,
|
||||
pub path: PathBuf,
|
||||
pub mtime: chrono::DateTime<chrono::Local>,
|
||||
}
|
||||
|
||||
pub struct ContextStore {
|
||||
contexts_metadata: Vec<SavedContextMetadata>,
|
||||
pub struct ConversationStore {
|
||||
conversations_metadata: Vec<SavedConversationMetadata>,
|
||||
fs: Arc<dyn Fs>,
|
||||
_watch_updates: Task<Option<()>>,
|
||||
}
|
||||
|
||||
impl ContextStore {
|
||||
impl ConversationStore {
|
||||
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||
let (mut events, _) = fs.watch(&CONTEXTS_DIR, CONTEXT_WATCH_DURATION).await;
|
||||
const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||
let (mut events, _) = fs
|
||||
.watch(&CONVERSATIONS_DIR, CONVERSATION_WATCH_DURATION)
|
||||
.await;
|
||||
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| Self {
|
||||
contexts_metadata: Vec::new(),
|
||||
conversations_metadata: Vec::new(),
|
||||
fs,
|
||||
_watch_updates: cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
@@ -86,41 +88,46 @@ impl ContextStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load(&self, path: PathBuf, cx: &AppContext) -> Task<Result<SavedContext>> {
|
||||
pub fn load(&self, path: PathBuf, cx: &AppContext) -> Task<Result<SavedConversation>> {
|
||||
let fs = self.fs.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let saved_context = fs.load(&path).await?;
|
||||
let saved_context_json = serde_json::from_str::<serde_json::Value>(&saved_context)?;
|
||||
match saved_context_json
|
||||
let saved_conversation = fs.load(&path).await?;
|
||||
let saved_conversation_json =
|
||||
serde_json::from_str::<serde_json::Value>(&saved_conversation)?;
|
||||
match saved_conversation_json
|
||||
.get("version")
|
||||
.ok_or_else(|| anyhow!("version not found"))?
|
||||
{
|
||||
serde_json::Value::String(version) => match version.as_str() {
|
||||
SavedContext::VERSION => {
|
||||
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
||||
}
|
||||
SavedConversation::VERSION => Ok(serde_json::from_value::<SavedConversation>(
|
||||
saved_conversation_json,
|
||||
)?),
|
||||
"0.1.0" => {
|
||||
let saved_context =
|
||||
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
||||
Ok(SavedContext {
|
||||
id: saved_context.id,
|
||||
zed: saved_context.zed,
|
||||
version: saved_context.version,
|
||||
text: saved_context.text,
|
||||
messages: saved_context.messages,
|
||||
message_metadata: saved_context.message_metadata,
|
||||
summary: saved_context.summary,
|
||||
let saved_conversation = serde_json::from_value::<SavedConversationV0_1_0>(
|
||||
saved_conversation_json,
|
||||
)?;
|
||||
Ok(SavedConversation {
|
||||
id: saved_conversation.id,
|
||||
zed: saved_conversation.zed,
|
||||
version: saved_conversation.version,
|
||||
text: saved_conversation.text,
|
||||
messages: saved_conversation.messages,
|
||||
message_metadata: saved_conversation.message_metadata,
|
||||
summary: saved_conversation.summary,
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
||||
_ => Err(anyhow!(
|
||||
"unrecognized saved conversation version: {}",
|
||||
version
|
||||
)),
|
||||
},
|
||||
_ => Err(anyhow!("version not found on saved context")),
|
||||
_ => Err(anyhow!("version not found on saved conversation")),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn search(&self, query: String, cx: &AppContext) -> Task<Vec<SavedContextMetadata>> {
|
||||
let metadata = self.contexts_metadata.clone();
|
||||
pub fn search(&self, query: String, cx: &AppContext) -> Task<Vec<SavedConversationMetadata>> {
|
||||
let metadata = self.conversations_metadata.clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
if query.is_empty() {
|
||||
@@ -152,10 +159,10 @@ impl ContextStore {
|
||||
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
fs.create_dir(&CONTEXTS_DIR).await?;
|
||||
fs.create_dir(&CONVERSATIONS_DIR).await?;
|
||||
|
||||
let mut paths = fs.read_dir(&CONTEXTS_DIR).await?;
|
||||
let mut contexts = Vec::<SavedContextMetadata>::new();
|
||||
let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
|
||||
let mut conversations = Vec::<SavedConversationMetadata>::new();
|
||||
while let Some(path) = paths.next().await {
|
||||
let path = path?;
|
||||
if path.extension() != Some(OsStr::new("json")) {
|
||||
@@ -171,13 +178,13 @@ impl ContextStore {
|
||||
.and_then(|name| name.to_str())
|
||||
.zip(metadata)
|
||||
{
|
||||
// This is used to filter out contexts saved by the new assistant.
|
||||
// This is used to filter out conversations saved by the new assistant.
|
||||
if !re.is_match(file_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(title) = re.replace(file_name, "").lines().next() {
|
||||
contexts.push(SavedContextMetadata {
|
||||
conversations.push(SavedConversationMetadata {
|
||||
title: title.to_string(),
|
||||
path,
|
||||
mtime: metadata.mtime.into(),
|
||||
@@ -185,10 +192,10 @@ impl ContextStore {
|
||||
}
|
||||
}
|
||||
}
|
||||
contexts.sort_unstable_by_key(|context| Reverse(context.mtime));
|
||||
conversations.sort_unstable_by_key(|conversation| Reverse(conversation.mtime));
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.contexts_metadata = contexts;
|
||||
this.conversations_metadata = conversations;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,10 @@ use futures::{
|
||||
};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
actions, percentage, point, size, Animation, AnimationExt, AppContext, BackgroundExecutor,
|
||||
Bounds, EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions,
|
||||
Transformation, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
||||
actions, percentage, point, size, Animation, AnimationExt, AnyElement, AppContext,
|
||||
BackgroundExecutor, Bounds, DevicePixels, EventEmitter, Global, PromptLevel, ReadGlobal,
|
||||
Subscription, Task, TitlebarOptions, Transformation, UpdateGlobal, View, WindowBounds,
|
||||
WindowHandle, WindowOptions,
|
||||
};
|
||||
use heed::{types::SerdeBincode, Database, RoTxn};
|
||||
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
|
||||
@@ -25,7 +26,6 @@ use rope::Rope;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
future::Future,
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
@@ -33,8 +33,8 @@ use std::{
|
||||
};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
|
||||
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
||||
div, prelude::*, IconButtonShape, ListHeader, ListItem, ListItemSpacing, ListSubHeader,
|
||||
ParentElement, Render, SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
||||
};
|
||||
use util::{paths::PROMPTS_DIR, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
@@ -80,7 +80,11 @@ pub fn open_prompt_library(
|
||||
cx.spawn(|cx| async move {
|
||||
let store = store.await?;
|
||||
cx.update(|cx| {
|
||||
let bounds = Bounds::centered(None, size(px(1024.0), px(768.0)), cx);
|
||||
let bounds = Bounds::centered(
|
||||
None,
|
||||
size(DevicePixels::from(1024), DevicePixels::from(768)),
|
||||
cx,
|
||||
);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
titlebar: Some(TitlebarOptions {
|
||||
@@ -93,7 +97,7 @@ pub fn open_prompt_library(
|
||||
},
|
||||
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
|
||||
)
|
||||
})?
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -120,23 +124,41 @@ struct PromptEditor {
|
||||
struct PromptPickerDelegate {
|
||||
store: Arc<PromptStore>,
|
||||
selected_index: usize,
|
||||
matches: Vec<PromptMetadata>,
|
||||
entries: Vec<PromptPickerEntry>,
|
||||
}
|
||||
|
||||
enum PromptPickerEvent {
|
||||
Selected { prompt_id: PromptId },
|
||||
Selected { prompt_id: Option<PromptId> },
|
||||
Confirmed { prompt_id: PromptId },
|
||||
Deleted { prompt_id: PromptId },
|
||||
ToggledDefault { prompt_id: PromptId },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PromptPickerEntry {
|
||||
DefaultPromptsHeader,
|
||||
DefaultPromptsEmpty,
|
||||
AllPromptsHeader,
|
||||
AllPromptsEmpty,
|
||||
Prompt(PromptMetadata),
|
||||
}
|
||||
|
||||
impl PromptPickerEntry {
|
||||
fn prompt_id(&self) -> Option<PromptId> {
|
||||
match self {
|
||||
PromptPickerEntry::Prompt(metadata) => Some(metadata.id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PromptPickerEvent> for Picker<PromptPickerDelegate> {}
|
||||
|
||||
impl PickerDelegate for PromptPickerDelegate {
|
||||
type ListItem = ListItem;
|
||||
type ListItem = AnyElement;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.matches.len()
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
@@ -145,11 +167,14 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix;
|
||||
if let Some(prompt) = self.matches.get(self.selected_index) {
|
||||
cx.emit(PromptPickerEvent::Selected {
|
||||
prompt_id: prompt.id,
|
||||
});
|
||||
}
|
||||
let prompt_id = if let Some(PromptPickerEntry::Prompt(prompt)) =
|
||||
self.entries.get(self.selected_index)
|
||||
{
|
||||
Some(prompt.id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
cx.emit(PromptPickerEvent::Selected { prompt_id });
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
@@ -158,24 +183,48 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let search = self.store.search(query);
|
||||
let prev_prompt_id = self.matches.get(self.selected_index).map(|mat| mat.id);
|
||||
let prev_prompt_id = self
|
||||
.entries
|
||||
.get(self.selected_index)
|
||||
.and_then(|mat| mat.prompt_id());
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (matches, selected_index) = cx
|
||||
let (entries, selected_index) = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
let matches = search.await;
|
||||
let prompts = search.await;
|
||||
let (default_prompts, prompts) = prompts
|
||||
.into_iter()
|
||||
.partition::<Vec<_>, _>(|prompt| prompt.default);
|
||||
|
||||
let mut entries = Vec::new();
|
||||
entries.push(PromptPickerEntry::DefaultPromptsHeader);
|
||||
if default_prompts.is_empty() {
|
||||
entries.push(PromptPickerEntry::DefaultPromptsEmpty);
|
||||
} else {
|
||||
entries.extend(default_prompts.into_iter().map(PromptPickerEntry::Prompt));
|
||||
}
|
||||
|
||||
entries.push(PromptPickerEntry::AllPromptsHeader);
|
||||
if prompts.is_empty() {
|
||||
entries.push(PromptPickerEntry::AllPromptsEmpty);
|
||||
} else {
|
||||
entries.extend(prompts.into_iter().map(PromptPickerEntry::Prompt));
|
||||
}
|
||||
|
||||
let selected_index = prev_prompt_id
|
||||
.and_then(|prev_prompt_id| {
|
||||
matches.iter().position(|entry| entry.id == prev_prompt_id)
|
||||
entries
|
||||
.iter()
|
||||
.position(|entry| entry.prompt_id() == Some(prev_prompt_id))
|
||||
})
|
||||
.or_else(|| entries.iter().position(|entry| entry.prompt_id().is_some()))
|
||||
.unwrap_or(0);
|
||||
(matches, selected_index)
|
||||
(entries, selected_index)
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate.matches = matches;
|
||||
this.delegate.entries = entries;
|
||||
this.delegate.set_selected_index(selected_index, cx);
|
||||
cx.notify();
|
||||
})
|
||||
@@ -184,7 +233,7 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(prompt) = self.matches.get(self.selected_index) {
|
||||
if let Some(PromptPickerEntry::Prompt(prompt)) = self.entries.get(self.selected_index) {
|
||||
cx.emit(PromptPickerEvent::Confirmed {
|
||||
prompt_id: prompt.id,
|
||||
});
|
||||
@@ -199,59 +248,82 @@ impl PickerDelegate for PromptPickerDelegate {
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let prompt = self.matches.get(ix)?;
|
||||
let default = prompt.default;
|
||||
let prompt_id = prompt.id;
|
||||
let element = ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.selected(selected)
|
||||
.child(h_flex().h_5().line_height(relative(1.)).child(Label::new(
|
||||
prompt.title.clone().unwrap_or("Untitled".into()),
|
||||
)))
|
||||
.end_slot::<IconButton>(default.then(|| {
|
||||
IconButton::new("toggle-default-prompt", IconName::SparkleFilled)
|
||||
.selected(true)
|
||||
.icon_color(Color::Accent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| Tooltip::text("Remove from Default Prompt", cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
|
||||
}))
|
||||
}))
|
||||
.end_hover_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
IconButton::new("delete-prompt", IconName::Trash)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(PromptPickerEvent::Deleted { prompt_id })
|
||||
})),
|
||||
let prompt = self.entries.get(ix)?;
|
||||
let element = match prompt {
|
||||
PromptPickerEntry::DefaultPromptsHeader => ListHeader::new("Default Prompts")
|
||||
.inset(true)
|
||||
.start_slot(
|
||||
Icon::new(IconName::Sparkle)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.selected(selected)
|
||||
.into_any_element(),
|
||||
PromptPickerEntry::DefaultPromptsEmpty => {
|
||||
ListSubHeader::new("Star a prompt to add it to the default context")
|
||||
.inset(true)
|
||||
.selected(selected)
|
||||
.into_any_element()
|
||||
}
|
||||
PromptPickerEntry::AllPromptsHeader => ListHeader::new("All Prompts")
|
||||
.inset(true)
|
||||
.start_slot(
|
||||
Icon::new(IconName::Library)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.selected(selected)
|
||||
.into_any_element(),
|
||||
PromptPickerEntry::AllPromptsEmpty => ListSubHeader::new("No prompts")
|
||||
.inset(true)
|
||||
.selected(selected)
|
||||
.into_any_element(),
|
||||
PromptPickerEntry::Prompt(prompt) => {
|
||||
let default = prompt.default;
|
||||
let prompt_id = prompt.id;
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.selected(selected)
|
||||
.child(h_flex().h_5().line_height(relative(1.)).child(Label::new(
|
||||
prompt.title.clone().unwrap_or("Untitled".into()),
|
||||
)))
|
||||
.end_hover_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
IconButton::new("delete-prompt", IconName::Trash)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(PromptPickerEvent::Deleted { prompt_id })
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
||||
.selected(default)
|
||||
.selected_icon(IconName::SparkleFilled)
|
||||
.icon_color(if default { Color::Accent } else { Color::Muted })
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if default {
|
||||
"Remove from Default Prompt"
|
||||
} else {
|
||||
"Add to Default Prompt"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
||||
.selected(default)
|
||||
.selected_icon(IconName::SparkleFilled)
|
||||
.icon_color(if default { Color::Accent } else { Color::Muted })
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if default {
|
||||
"Remove from Default Prompt"
|
||||
} else {
|
||||
"Add to Default Prompt"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
|
||||
})),
|
||||
),
|
||||
);
|
||||
.into_any_element()
|
||||
}
|
||||
};
|
||||
Some(element)
|
||||
}
|
||||
|
||||
@@ -277,13 +349,11 @@ impl PromptLibrary {
|
||||
let delegate = PromptPickerDelegate {
|
||||
store: store.clone(),
|
||||
selected_index: 0,
|
||||
matches: Vec::new(),
|
||||
entries: Vec::new(),
|
||||
};
|
||||
|
||||
let picker = cx.new_view(|cx| {
|
||||
let picker = Picker::uniform_list(delegate, cx)
|
||||
.modal(false)
|
||||
.max_height(None);
|
||||
let picker = Picker::list(delegate, cx).modal(false).max_height(None);
|
||||
picker.focus(cx);
|
||||
picker
|
||||
});
|
||||
@@ -306,7 +376,11 @@ impl PromptLibrary {
|
||||
) {
|
||||
match event {
|
||||
PromptPickerEvent::Selected { prompt_id } => {
|
||||
self.load_prompt(*prompt_id, false, cx);
|
||||
if let Some(prompt_id) = *prompt_id {
|
||||
self.load_prompt(prompt_id, false, cx);
|
||||
} else {
|
||||
self.focus_picker(&Default::default(), cx);
|
||||
}
|
||||
}
|
||||
PromptPickerEvent::Confirmed { prompt_id } => {
|
||||
self.load_prompt(*prompt_id, true, cx);
|
||||
@@ -493,23 +567,21 @@ impl PromptLibrary {
|
||||
if let Some(prompt_id) = prompt_id {
|
||||
if picker
|
||||
.delegate
|
||||
.matches
|
||||
.entries
|
||||
.get(picker.delegate.selected_index())
|
||||
.map_or(true, |old_selected_prompt| {
|
||||
old_selected_prompt.id != prompt_id
|
||||
old_selected_prompt.prompt_id() != Some(prompt_id)
|
||||
})
|
||||
{
|
||||
if let Some(ix) = picker
|
||||
.delegate
|
||||
.matches
|
||||
.entries
|
||||
.iter()
|
||||
.position(|mat| mat.id == prompt_id)
|
||||
.position(|mat| mat.prompt_id() == Some(prompt_id))
|
||||
{
|
||||
picker.set_selected_index(ix, true, cx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
picker.focus(cx);
|
||||
}
|
||||
});
|
||||
cx.notify();
|
||||
@@ -1033,7 +1105,7 @@ impl PromptStore {
|
||||
let cached_metadata = self.metadata_cache.read().metadata.clone();
|
||||
let executor = self.executor.clone();
|
||||
self.executor.spawn(async move {
|
||||
let mut matches = if query.is_empty() {
|
||||
if query.is_empty() {
|
||||
cached_metadata
|
||||
} else {
|
||||
let candidates = cached_metadata
|
||||
@@ -1059,9 +1131,7 @@ impl PromptStore {
|
||||
.into_iter()
|
||||
.map(|mat| cached_metadata[mat.candidate_id].clone())
|
||||
.collect()
|
||||
};
|
||||
matches.sort_by_key(|metadata| Reverse(metadata.default));
|
||||
matches
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
use crate::assistant_panel::ConversationEditor;
|
||||
use anyhow::Result;
|
||||
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
|
||||
use editor::{CompletionProvider, Editor};
|
||||
@@ -20,7 +20,6 @@ pub mod active_command;
|
||||
pub mod default_command;
|
||||
pub mod fetch_command;
|
||||
pub mod file_command;
|
||||
pub mod now_command;
|
||||
pub mod project_command;
|
||||
pub mod prompt_command;
|
||||
pub mod rustdoc_command;
|
||||
@@ -30,7 +29,7 @@ pub mod tabs_command;
|
||||
pub(crate) struct SlashCommandCompletionProvider {
|
||||
commands: Arc<SlashCommandRegistry>,
|
||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
editor: Option<WeakView<ConversationEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
}
|
||||
|
||||
@@ -44,7 +43,7 @@ pub(crate) struct SlashCommandLine {
|
||||
impl SlashCommandCompletionProvider {
|
||||
pub fn new(
|
||||
commands: Arc<SlashCommandRegistry>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
||||
editor: Option<WeakView<ConversationEditor>>,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -7,19 +5,12 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||
use html_to_markdown::convert_html_to_markdown;
|
||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use language::LspAdapterDelegate;
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use workspace::Workspace;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
enum ContentType {
|
||||
Html,
|
||||
Plaintext,
|
||||
Json,
|
||||
}
|
||||
|
||||
pub(crate) struct FetchSlashCommand;
|
||||
|
||||
impl FetchSlashCommand {
|
||||
@@ -46,52 +37,7 @@ impl FetchSlashCommand {
|
||||
);
|
||||
}
|
||||
|
||||
let Some(content_type) = response.headers().get("content-type") else {
|
||||
bail!("missing Content-Type header");
|
||||
};
|
||||
let content_type = content_type
|
||||
.to_str()
|
||||
.context("invalid Content-Type header")?;
|
||||
let content_type = match content_type {
|
||||
"text/html" => ContentType::Html,
|
||||
"text/plain" => ContentType::Plaintext,
|
||||
"application/json" => ContentType::Json,
|
||||
_ => ContentType::Html,
|
||||
};
|
||||
|
||||
match content_type {
|
||||
ContentType::Html => {
|
||||
let mut handlers: Vec<TagHandler> = vec![
|
||||
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
||||
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
||||
Rc::new(RefCell::new(markdown::ListHandler)),
|
||||
Rc::new(RefCell::new(markdown::TableHandler::new())),
|
||||
Rc::new(RefCell::new(markdown::StyledTextHandler)),
|
||||
];
|
||||
if url.contains("wikipedia.org") {
|
||||
use html_to_markdown::structure::wikipedia;
|
||||
|
||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaChromeRemover)));
|
||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaInfoboxHandler)));
|
||||
handlers.push(Rc::new(
|
||||
RefCell::new(wikipedia::WikipediaCodeHandler::new()),
|
||||
));
|
||||
} else {
|
||||
handlers.push(Rc::new(RefCell::new(markdown::CodeHandler)));
|
||||
}
|
||||
|
||||
convert_html_to_markdown(&body[..], &mut handlers)
|
||||
}
|
||||
ContentType::Plaintext => Ok(std::str::from_utf8(&body)?.to_owned()),
|
||||
ContentType::Json => {
|
||||
let json: serde_json::Value = serde_json::from_slice(&body)?;
|
||||
|
||||
Ok(format!(
|
||||
"```json\n{}\n```",
|
||||
serde_json::to_string_pretty(&json)?
|
||||
))
|
||||
}
|
||||
}
|
||||
convert_html_to_markdown(&body[..])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||
use chrono::{DateTime, Local};
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::LspAdapterDelegate;
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct NowSlashCommand;
|
||||
|
||||
impl SlashCommand for NowSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
"now".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"insert the current date and time".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Insert current date and time".into()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
_query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut AppContext,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_argument: Option<&str>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Arc<dyn LspAdapterDelegate>,
|
||||
_cx: &mut WindowContext,
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let now = Local::now();
|
||||
let text = format!("Today is {now}.", now = now.to_rfc3339());
|
||||
let range = 0..text.len();
|
||||
|
||||
Task::ready(Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
NowPlaceholder { id, unfold, now }.into_any_element()
|
||||
}),
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct NowPlaceholder {
|
||||
pub id: ElementId,
|
||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||
pub now: DateTime<Local>,
|
||||
}
|
||||
|
||||
impl RenderOnce for NowPlaceholder {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
let unfold = self.unfold;
|
||||
|
||||
ButtonLike::new(self.id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(Icon::new(IconName::CountdownTimer))
|
||||
.child(Label::new(self.now.to_rfc3339()))
|
||||
.on_click(move |_, cx| unfold(cx))
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,10 @@ use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutp
|
||||
use fs::Fs;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::{AppContext, Model, Task, WeakView};
|
||||
use html_to_markdown::convert_rustdoc_to_markdown;
|
||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use language::LspAdapterDelegate;
|
||||
use project::{Project, ProjectPath};
|
||||
use rustdoc::crawler::LocalProvider;
|
||||
use rustdoc::{convert_rustdoc_to_markdown, RustdocStore};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -43,9 +42,10 @@ impl RustdocSlashCommand {
|
||||
local_cargo_doc_path.push("index.html");
|
||||
|
||||
if let Ok(contents) = fs.load(&local_cargo_doc_path).await {
|
||||
let (markdown, _items) = convert_rustdoc_to_markdown(contents.as_bytes())?;
|
||||
|
||||
return Ok((RustdocSource::Local, markdown));
|
||||
return Ok((
|
||||
RustdocSource::Local,
|
||||
convert_rustdoc_to_markdown(contents.as_bytes())?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,9 +78,10 @@ impl RustdocSlashCommand {
|
||||
);
|
||||
}
|
||||
|
||||
let (markdown, _items) = convert_rustdoc_to_markdown(&body[..])?;
|
||||
|
||||
Ok((RustdocSource::DocsDotRs, markdown))
|
||||
Ok((
|
||||
RustdocSource::DocsDotRs,
|
||||
convert_rustdoc_to_markdown(&body[..])?,
|
||||
))
|
||||
}
|
||||
|
||||
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
|
||||
@@ -116,19 +117,12 @@ impl SlashCommand for RustdocSlashCommand {
|
||||
|
||||
fn complete_argument(
|
||||
&self,
|
||||
query: String,
|
||||
_query: String,
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut AppContext,
|
||||
_cx: &mut AppContext,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
let store = RustdocStore::global(cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
let items = store.search(query).await;
|
||||
Ok(items
|
||||
.into_iter()
|
||||
.map(|(crate_name, item)| format!("{crate_name}::{}", item.display()))
|
||||
.collect())
|
||||
})
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
|
||||
fn run(
|
||||
@@ -148,67 +142,7 @@ impl SlashCommand for RustdocSlashCommand {
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let http_client = workspace.read(cx).client().http_client();
|
||||
let path_to_cargo_toml = Self::path_to_cargo_toml(project, cx);
|
||||
|
||||
let mut item_path = String::new();
|
||||
let mut crate_name_to_index = None;
|
||||
|
||||
let mut args = argument.split(' ').map(|word| word.trim());
|
||||
while let Some(arg) = args.next() {
|
||||
if arg == "--index" {
|
||||
let Some(crate_name) = args.next() else {
|
||||
return Task::ready(Err(anyhow!("no crate name provided to --index")));
|
||||
};
|
||||
crate_name_to_index = Some(crate_name.to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
item_path.push_str(arg);
|
||||
}
|
||||
|
||||
if let Some(crate_name_to_index) = crate_name_to_index {
|
||||
let index_task = cx.background_executor().spawn({
|
||||
let rustdoc_store = RustdocStore::global(cx);
|
||||
let fs = fs.clone();
|
||||
let crate_name_to_index = crate_name_to_index.clone();
|
||||
async move {
|
||||
let cargo_workspace_root = path_to_cargo_toml
|
||||
.and_then(|path| path.parent().map(|path| path.to_path_buf()))
|
||||
.ok_or_else(|| anyhow!("no Cargo workspace root found"))?;
|
||||
|
||||
let provider = Box::new(LocalProvider::new(fs, cargo_workspace_root));
|
||||
|
||||
rustdoc_store
|
||||
.index(crate_name_to_index.clone(), provider)
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(format!("Indexed {crate_name_to_index}"))
|
||||
}
|
||||
});
|
||||
|
||||
return cx.foreground_executor().spawn(async move {
|
||||
let text = index_task.await?;
|
||||
let range = 0..text.len();
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
||||
RustdocIndexPlaceholder {
|
||||
id,
|
||||
unfold,
|
||||
source: RustdocSource::Local,
|
||||
crate_name: SharedString::from(crate_name_to_index.clone()),
|
||||
}
|
||||
.into_any_element()
|
||||
}),
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
let mut path_components = item_path.split("::");
|
||||
let mut path_components = argument.split("::");
|
||||
let crate_name = match path_components
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("missing crate name"))
|
||||
@@ -216,37 +150,29 @@ impl SlashCommand for RustdocSlashCommand {
|
||||
Ok(crate_name) => crate_name.to_string(),
|
||||
Err(err) => return Task::ready(Err(err)),
|
||||
};
|
||||
let item_path = path_components.map(ToString::to_string).collect::<Vec<_>>();
|
||||
let module_path = path_components.map(ToString::to_string).collect::<Vec<_>>();
|
||||
let path_to_cargo_toml = Self::path_to_cargo_toml(project, cx);
|
||||
|
||||
let text = cx.background_executor().spawn({
|
||||
let rustdoc_store = RustdocStore::global(cx);
|
||||
let crate_name = crate_name.clone();
|
||||
let item_path = item_path.clone();
|
||||
let module_path = module_path.clone();
|
||||
async move {
|
||||
let item_docs = rustdoc_store
|
||||
.load(crate_name.clone(), Some(item_path.join("::")))
|
||||
.await;
|
||||
|
||||
if let Ok(item_docs) = item_docs {
|
||||
anyhow::Ok((RustdocSource::Local, item_docs))
|
||||
} else {
|
||||
Self::build_message(
|
||||
fs,
|
||||
http_client,
|
||||
crate_name,
|
||||
item_path,
|
||||
path_to_cargo_toml.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Self::build_message(
|
||||
fs,
|
||||
http_client,
|
||||
crate_name,
|
||||
module_path,
|
||||
path_to_cargo_toml.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
});
|
||||
|
||||
let crate_name = SharedString::from(crate_name);
|
||||
let module_path = if item_path.is_empty() {
|
||||
let module_path = if module_path.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SharedString::from(item_path.join("::")))
|
||||
Some(SharedString::from(module_path.join("::")))
|
||||
};
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let (source, text) = text.await?;
|
||||
@@ -304,31 +230,3 @@ impl RenderOnce for RustdocPlaceholder {
|
||||
.on_click(move |_, cx| unfold(cx))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct RustdocIndexPlaceholder {
|
||||
pub id: ElementId,
|
||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
||||
pub source: RustdocSource,
|
||||
pub crate_name: SharedString,
|
||||
}
|
||||
|
||||
impl RenderOnce for RustdocIndexPlaceholder {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
let unfold = self.unfold;
|
||||
|
||||
ButtonLike::new(self.id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(Icon::new(IconName::FileRust))
|
||||
.child(Label::new(format!(
|
||||
"rustdoc index ({source}): {crate_name}",
|
||||
crate_name = self.crate_name,
|
||||
source = match self.source {
|
||||
RustdocSource::Local => "local",
|
||||
RustdocSource::DocsDotRs => "docs.rs",
|
||||
}
|
||||
)))
|
||||
.on_click(move |_, cx| unfold(cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::channel_chat::ChannelChatEvent;
|
||||
use super::*;
|
||||
use client::{test::FakeServer, Client, UserStore};
|
||||
use clock::FakeSystemClock;
|
||||
use gpui::{AppContext, Context, Model, SemanticVersion, TestAppContext};
|
||||
use gpui::{AppContext, Context, Model, TestAppContext};
|
||||
use http::FakeHttpClient;
|
||||
use rpc::proto::{self};
|
||||
use settings::SettingsStore;
|
||||
@@ -340,7 +340,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
|
||||
let clock = Arc::new(FakeSystemClock::default());
|
||||
|
||||
@@ -19,6 +19,7 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
libc.workspace = true
|
||||
ipc-channel = "0.18"
|
||||
once_cell.workspace = true
|
||||
release_channel.workspace = true
|
||||
|
||||
@@ -161,7 +161,10 @@ mod linux {
|
||||
env,
|
||||
ffi::OsString,
|
||||
io,
|
||||
os::unix::net::{SocketAddr, UnixDatagram},
|
||||
os::{
|
||||
linux::net::SocketAddrExt,
|
||||
unix::net::{SocketAddr, UnixDatagram},
|
||||
},
|
||||
path::{Path, PathBuf},
|
||||
process::{self, ExitStatus},
|
||||
thread,
|
||||
@@ -172,7 +175,6 @@ mod linux {
|
||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||
use fork::Fork;
|
||||
use once_cell::sync::Lazy;
|
||||
use util::paths;
|
||||
|
||||
use crate::{Detect, InstalledApp};
|
||||
|
||||
@@ -221,9 +223,12 @@ mod linux {
|
||||
}
|
||||
|
||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
||||
let sock_path = paths::SUPPORT_DIR.join(format!("zed-{}.sock", *RELEASE_CHANNEL));
|
||||
let uid: u32 = unsafe { libc::getuid() };
|
||||
let sock_addr =
|
||||
SocketAddr::from_abstract_name(format!("zed-{}-{}", *RELEASE_CHANNEL, uid))?;
|
||||
|
||||
let sock = UnixDatagram::unbound()?;
|
||||
if sock.connect(&sock_path).is_err() {
|
||||
if sock.connect_addr(&sock_addr).is_err() {
|
||||
self.boot_background(ipc_url)?;
|
||||
} else {
|
||||
sock.send(ipc_url.as_bytes())?;
|
||||
|
||||
@@ -24,7 +24,6 @@ chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http.workspace = true
|
||||
@@ -61,12 +60,6 @@ settings = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
http = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
async-native-tls = {"version" = "0.5.0", features = ["vendored"]}
|
||||
# This is an indirect dependency of async-tungstenite that is included
|
||||
|
||||
@@ -1429,31 +1429,6 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_dynamic(
|
||||
&self,
|
||||
envelope: proto::Envelope,
|
||||
request_type: &'static str,
|
||||
) -> impl Future<Output = Result<proto::Envelope>> {
|
||||
let client_id = self.id();
|
||||
log::debug!(
|
||||
"rpc request start. client_id:{}. name:{}",
|
||||
client_id,
|
||||
request_type
|
||||
);
|
||||
let response = self
|
||||
.connection_id()
|
||||
.map(|conn_id| self.peer.request_dynamic(conn_id, envelope, request_type));
|
||||
async move {
|
||||
let response = response?.await;
|
||||
log::debug!(
|
||||
"rpc request finish. client_id:{}. name:{}",
|
||||
client_id,
|
||||
request_type
|
||||
);
|
||||
Ok(response?.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn respond<T: RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) -> Result<()> {
|
||||
log::debug!("rpc respond. client_id:{}. name:{}", self.id(), T::NAME);
|
||||
self.peer.respond(receipt, response)
|
||||
@@ -1729,7 +1704,6 @@ mod tests {
|
||||
use gpui::{BackgroundExecutor, Context, TestAppContext};
|
||||
use http::FakeHttpClient;
|
||||
use parking_lot::Mutex;
|
||||
use proto::TypedEnvelope;
|
||||
use settings::SettingsStore;
|
||||
use std::future;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{ChannelId, TelemetrySettings};
|
||||
use chrono::{DateTime, Utc};
|
||||
use clock::SystemClock;
|
||||
use futures::Future;
|
||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
|
||||
use http::{self, HttpClient, HttpClientWithUrl, Method};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
@@ -39,6 +39,7 @@ struct TelemetryState {
|
||||
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
||||
session_id: Option<String>, // Per app launch
|
||||
release_channel: Option<&'static str>,
|
||||
app_metadata: AppMetadata,
|
||||
architecture: &'static str,
|
||||
events_queue: Vec<EventWrapper>,
|
||||
flush_events_task: Option<Task<()>>,
|
||||
@@ -47,10 +48,6 @@ struct TelemetryState {
|
||||
first_event_date_time: Option<DateTime<Utc>>,
|
||||
event_coalescer: EventCoalescer,
|
||||
max_queue_size: usize,
|
||||
|
||||
os_name: String,
|
||||
app_version: String,
|
||||
os_version: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -74,87 +71,6 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
|
||||
})
|
||||
});
|
||||
|
||||
pub fn os_name() -> String {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
"macOS".to_string()
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
format!("Linux {}", gpui::guess_compositor())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
"Windows".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: This might do blocking IO! Only call from background threads
|
||||
pub fn os_version() -> String {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use cocoa::base::nil;
|
||||
use cocoa::foundation::NSProcessInfo;
|
||||
|
||||
unsafe {
|
||||
let process_info = cocoa::foundation::NSProcessInfo::processInfo(nil);
|
||||
let version = process_info.operatingSystemVersion();
|
||||
gpui::SemanticVersion::new(
|
||||
version.majorVersion as usize,
|
||||
version.minorVersion as usize,
|
||||
version.patchVersion as usize,
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::path::Path;
|
||||
|
||||
let content = if let Ok(file) = std::fs::read_to_string(&Path::new("/etc/os-release")) {
|
||||
file
|
||||
} else if let Ok(file) = std::fs::read_to_string(&Path::new("/usr/lib/os-release")) {
|
||||
file
|
||||
} else {
|
||||
log::error!("Failed to load /etc/os-release, /usr/lib/os-release");
|
||||
"".to_string()
|
||||
};
|
||||
let mut name = "unknown".to_string();
|
||||
let mut version = "unknown".to_string();
|
||||
|
||||
for line in content.lines() {
|
||||
if line.starts_with("ID=") {
|
||||
name = line.trim_start_matches("ID=").trim_matches('"').to_string();
|
||||
}
|
||||
if line.starts_with("VERSION_ID=") {
|
||||
version = line
|
||||
.trim_start_matches("VERSION_ID=")
|
||||
.trim_matches('"')
|
||||
.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
format!("{} {}", name, version)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let mut info = unsafe { std::mem::zeroed() };
|
||||
let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut info) };
|
||||
if status.is_ok() {
|
||||
gpui::SemanticVersion::new(
|
||||
info.dwMajorVersion as _,
|
||||
info.dwMinorVersion as _,
|
||||
info.dwBuildNumber as _,
|
||||
)
|
||||
.to_string()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Telemetry {
|
||||
pub fn new(
|
||||
clock: Arc<dyn SystemClock>,
|
||||
@@ -168,6 +84,7 @@ impl Telemetry {
|
||||
|
||||
let state = Arc::new(Mutex::new(TelemetryState {
|
||||
settings: *TelemetrySettings::get_global(cx),
|
||||
app_metadata: cx.app_metadata(),
|
||||
architecture: env::consts::ARCH,
|
||||
release_channel,
|
||||
installation_id: None,
|
||||
@@ -180,10 +97,6 @@ impl Telemetry {
|
||||
first_event_date_time: None,
|
||||
event_coalescer: EventCoalescer::new(clock.clone()),
|
||||
max_queue_size: MAX_QUEUE_LEN,
|
||||
|
||||
os_version: None,
|
||||
os_name: os_name(),
|
||||
app_version: release_channel::AppVersion::global(cx).to_string(),
|
||||
}));
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
@@ -255,9 +168,6 @@ impl Telemetry {
|
||||
let mut state = self.state.lock();
|
||||
state.installation_id = installation_id.map(|id| id.into());
|
||||
state.session_id = Some(session_id);
|
||||
state.app_version = release_channel::AppVersion::global(cx).to_string();
|
||||
state.os_name = os_version();
|
||||
|
||||
drop(state);
|
||||
|
||||
let this = self.clone();
|
||||
@@ -535,14 +445,20 @@ impl Telemetry {
|
||||
|
||||
{
|
||||
let state = this.state.lock();
|
||||
|
||||
let request_body = EventRequestBody {
|
||||
installation_id: state.installation_id.as_deref().map(Into::into),
|
||||
session_id: state.session_id.clone(),
|
||||
is_staff: state.is_staff,
|
||||
app_version: state.app_version.clone(),
|
||||
os_name: state.os_name.clone(),
|
||||
os_version: state.os_version.clone(),
|
||||
app_version: state
|
||||
.app_metadata
|
||||
.app_version
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
os_name: state.app_metadata.os_name.to_string(),
|
||||
os_version: state
|
||||
.app_metadata
|
||||
.os_version
|
||||
.map(|version| version.to_string()),
|
||||
architecture: state.architecture.to_string(),
|
||||
|
||||
release_channel: state.release_channel.map(Into::into),
|
||||
|
||||
@@ -96,7 +96,6 @@ node_runtime.workspace = true
|
||||
notifications = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
recent_projects = { workspace = true }
|
||||
release_channel.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
rpc = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -308,14 +308,6 @@ pub async fn post_panic(
|
||||
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
|
||||
let panic = report.panic;
|
||||
|
||||
// better OS reporting for linux (because linux is hard):
|
||||
// - Remove os_version/app_version/os_name from the gpui platform trait
|
||||
// - Move platform processing data into client/telemetry
|
||||
// - Duplicate some small code in macOS platform for a version check
|
||||
// - Add GPUI API for reporting the selected platform integration
|
||||
// - macos-blade, macos-metal, linux-X11, linux-headless
|
||||
// if cfg(macos( { "Macos" } else { "Linux-{cx.compositor_name()"} ))
|
||||
|
||||
tracing::error!(
|
||||
service = "client",
|
||||
version = %panic.app_version,
|
||||
|
||||
@@ -548,9 +548,6 @@ impl Server {
|
||||
.add_request_handler(user_handler(
|
||||
forward_mutating_project_request::<proto::RestartLanguageServers>,
|
||||
))
|
||||
.add_request_handler(user_handler(
|
||||
forward_mutating_project_request::<proto::LinkedEditingRange>,
|
||||
))
|
||||
.add_message_handler(create_buffer_for_peer)
|
||||
.add_request_handler(update_buffer)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::RefreshInlayHints>)
|
||||
|
||||
@@ -68,7 +68,6 @@ async fn test_dev_server(cx: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppC
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert_eq!(projects[0].path, "/remote");
|
||||
workspace::join_dev_server_project(
|
||||
projects[0].id,
|
||||
projects[0].project_id.unwrap(),
|
||||
client.app_state.clone(),
|
||||
None,
|
||||
@@ -208,7 +207,6 @@ async fn create_dev_server_project(
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert_eq!(projects[0].path, "/remote");
|
||||
workspace::join_dev_server_project(
|
||||
projects[0].id,
|
||||
projects[0].project_id.unwrap(),
|
||||
client_app_state,
|
||||
None,
|
||||
@@ -493,7 +491,6 @@ async fn test_dev_server_reconnect(
|
||||
.update(cx2, |store, cx| {
|
||||
let projects = store.dev_server_projects();
|
||||
workspace::join_dev_server_project(
|
||||
projects[0].id,
|
||||
projects[0].project_id.unwrap(),
|
||||
client2.app_state.clone(),
|
||||
None,
|
||||
|
||||
@@ -30,7 +30,6 @@ use project::{
|
||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
|
||||
};
|
||||
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
||||
use rpc::RECEIVE_TIMEOUT;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
@@ -60,7 +59,6 @@ async fn test_host_disconnect(
|
||||
.await;
|
||||
|
||||
cx_b.update(editor::init);
|
||||
cx_b.update(recent_projects::init);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
@@ -85,7 +83,7 @@ async fn test_host_disconnect(
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
cx_a.background_executor.run_until_parked();
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
|
||||
|
||||
let workspace_b = cx_b
|
||||
.add_window(|cx| Workspace::new(None, project_b.clone(), client_b.app_state.clone(), cx));
|
||||
@@ -122,13 +120,14 @@ async fn test_host_disconnect(
|
||||
|
||||
project_b.read_with(cx_b, |project, _| project.is_read_only());
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
|
||||
|
||||
// Ensure client B's edited state is reset and that the whole window is blurred.
|
||||
|
||||
workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
|
||||
assert!(!workspace.is_edited());
|
||||
assert_eq!(cx.focused(), None);
|
||||
assert!(!workspace.is_edited())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1378,7 +1378,7 @@ async fn test_unshare_project(
|
||||
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
|
||||
executor.run_until_parked();
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
|
||||
|
||||
project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
@@ -1403,7 +1403,7 @@ async fn test_unshare_project(
|
||||
.unwrap();
|
||||
executor.run_until_parked();
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
|
||||
|
||||
assert!(project_c.read_with(cx_c, |project, _| project.is_disconnected()));
|
||||
|
||||
@@ -1415,7 +1415,7 @@ async fn test_unshare_project(
|
||||
let project_c2 = client_c.build_dev_server_project(project_id, cx_c).await;
|
||||
executor.run_until_parked();
|
||||
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
|
||||
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
|
||||
project_c2
|
||||
.update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
.await
|
||||
@@ -1522,7 +1522,7 @@ async fn test_project_reconnect(
|
||||
executor.run_until_parked();
|
||||
|
||||
let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
|
||||
assert!(worktree.has_update_observer());
|
||||
assert!(worktree.as_local().unwrap().is_shared());
|
||||
worktree.id()
|
||||
});
|
||||
let (worktree_a2, _) = project_a1
|
||||
@@ -1534,7 +1534,7 @@ async fn test_project_reconnect(
|
||||
executor.run_until_parked();
|
||||
|
||||
let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
|
||||
assert!(tree.has_update_observer());
|
||||
assert!(tree.as_local().unwrap().is_shared());
|
||||
tree.id()
|
||||
});
|
||||
executor.run_until_parked();
|
||||
@@ -1567,7 +1567,9 @@ async fn test_project_reconnect(
|
||||
assert_eq!(project.collaborators().len(), 1);
|
||||
});
|
||||
|
||||
worktree_a1.read_with(cx_a, |tree, _| assert!(tree.has_update_observer()));
|
||||
worktree_a1.read_with(cx_a, |tree, _| {
|
||||
assert!(tree.as_local().unwrap().is_shared())
|
||||
});
|
||||
|
||||
// While client A is disconnected, add and remove files from client A's project.
|
||||
client_a
|
||||
@@ -1609,7 +1611,7 @@ async fn test_project_reconnect(
|
||||
.await;
|
||||
|
||||
let worktree3_id = worktree_a3.read_with(cx_a, |tree, _| {
|
||||
assert!(!tree.has_update_observer());
|
||||
assert!(!tree.as_local().unwrap().is_shared());
|
||||
tree.id()
|
||||
});
|
||||
executor.run_until_parked();
|
||||
@@ -1632,7 +1634,7 @@ async fn test_project_reconnect(
|
||||
|
||||
project_a1.read_with(cx_a, |project, cx| {
|
||||
assert!(project.is_shared());
|
||||
assert!(worktree_a1.read(cx).has_update_observer());
|
||||
assert!(worktree_a1.read(cx).as_local().unwrap().is_shared());
|
||||
assert_eq!(
|
||||
worktree_a1
|
||||
.read(cx)
|
||||
@@ -1650,7 +1652,7 @@ async fn test_project_reconnect(
|
||||
"subdir2/i.txt"
|
||||
]
|
||||
);
|
||||
assert!(worktree_a3.read(cx).has_update_observer());
|
||||
assert!(worktree_a3.read(cx).as_local().unwrap().is_shared());
|
||||
assert_eq!(
|
||||
worktree_a3
|
||||
.read(cx)
|
||||
@@ -1731,7 +1733,7 @@ async fn test_project_reconnect(
|
||||
executor.run_until_parked();
|
||||
|
||||
let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
|
||||
assert!(tree.has_update_observer());
|
||||
assert!(tree.as_local().unwrap().is_shared());
|
||||
tree.id()
|
||||
});
|
||||
project_a1.update(cx_a, |project, cx| {
|
||||
|
||||
@@ -69,6 +69,7 @@ struct TestPlan<T: RandomizedTest> {
|
||||
pub struct UserTestPlan {
|
||||
pub user_id: UserId,
|
||||
pub username: String,
|
||||
pub allow_client_reconnection: bool,
|
||||
pub allow_client_disconnection: bool,
|
||||
next_root_id: usize,
|
||||
operation_ix: usize,
|
||||
@@ -236,6 +237,7 @@ impl<T: RandomizedTest> TestPlan<T> {
|
||||
next_root_id: 0,
|
||||
operation_ix: 0,
|
||||
allow_client_disconnection,
|
||||
allow_client_reconnection,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ impl TestServer {
|
||||
}
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
});
|
||||
|
||||
@@ -327,7 +327,7 @@ impl TestServer {
|
||||
}
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
});
|
||||
let (dev_server_id, _) = split_dev_server_token(&access_token).unwrap();
|
||||
|
||||
@@ -413,17 +413,6 @@ impl CollabTitlebarItem {
|
||||
);
|
||||
}
|
||||
|
||||
if self.project.read(cx).is_disconnected() {
|
||||
return Some(
|
||||
Button::new("disconnected", "Disconnected")
|
||||
.disabled(true)
|
||||
.color(Color::Disabled)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
|
||||
let host = self.project.read(cx).host()?;
|
||||
let host_user = self.user_store.read(cx).get_cached_user(host.user_id)?;
|
||||
let participant_index = self
|
||||
@@ -697,7 +686,7 @@ impl CollabTitlebarItem {
|
||||
.on_click(|_, cx| {
|
||||
if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
|
||||
if auto_updater.read(cx).status().is_updated() {
|
||||
workspace::reload(&Default::default(), cx);
|
||||
workspace::restart(&Default::default(), cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ use call::{report_call_event_for_room, ActiveCall};
|
||||
pub use collab_panel::CollabPanel;
|
||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||
use gpui::{
|
||||
actions, point, AppContext, Pixels, PlatformDisplay, Size, Task, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowContext, WindowKind, WindowOptions,
|
||||
actions, point, AppContext, DevicePixels, Pixels, PlatformDisplay, Size, Task,
|
||||
WindowBackgroundAppearance, WindowBounds, WindowContext, WindowKind, WindowOptions,
|
||||
};
|
||||
use panel_settings::MessageEditorSettings;
|
||||
pub use panel_settings::{
|
||||
@@ -22,7 +22,6 @@ pub use panel_settings::{
|
||||
};
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::Settings;
|
||||
use ui::px;
|
||||
use workspace::{notifications::DetachAndPromptErr, AppState};
|
||||
|
||||
actions!(
|
||||
@@ -97,19 +96,22 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
||||
|
||||
fn notification_window_options(
|
||||
screen: Rc<dyn PlatformDisplay>,
|
||||
size: Size<Pixels>,
|
||||
window_size: Size<Pixels>,
|
||||
cx: &AppContext,
|
||||
) -> WindowOptions {
|
||||
let notification_margin_width = px(16.);
|
||||
let notification_margin_height = px(-48.);
|
||||
let notification_margin_width = DevicePixels::from(16);
|
||||
let notification_margin_height = DevicePixels::from(-0) - DevicePixels::from(48);
|
||||
|
||||
let bounds = gpui::Bounds::<Pixels> {
|
||||
origin: screen.bounds().upper_right()
|
||||
let screen_bounds = screen.bounds();
|
||||
let size: Size<DevicePixels> = window_size.into();
|
||||
|
||||
let bounds = gpui::Bounds::<DevicePixels> {
|
||||
origin: screen_bounds.upper_right()
|
||||
- point(
|
||||
size.width + notification_margin_width,
|
||||
notification_margin_height,
|
||||
),
|
||||
size,
|
||||
size: window_size.into(),
|
||||
};
|
||||
|
||||
let app_id = ReleaseChannel::global(cx).app_id();
|
||||
|
||||
@@ -8,7 +8,6 @@ use settings::Settings;
|
||||
use std::sync::{Arc, Weak};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, Button, Label};
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
@@ -28,21 +27,16 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
|
||||
for screen in cx.displays() {
|
||||
let options = notification_window_options(screen, window_size, cx);
|
||||
let Some(window) = cx
|
||||
.open_window(options, |cx| {
|
||||
cx.new_view(|_| {
|
||||
ProjectSharedNotification::new(
|
||||
owner.clone(),
|
||||
*project_id,
|
||||
worktree_root_names.clone(),
|
||||
app_state.clone(),
|
||||
)
|
||||
})
|
||||
let window = cx.open_window(options, |cx| {
|
||||
cx.new_view(|_| {
|
||||
ProjectSharedNotification::new(
|
||||
owner.clone(),
|
||||
*project_id,
|
||||
worktree_root_names.clone(),
|
||||
app_state.clone(),
|
||||
)
|
||||
})
|
||||
.log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
});
|
||||
notification_windows
|
||||
.entry(*project_id)
|
||||
.or_insert(Vec::new())
|
||||
|
||||
19
crates/color/Cargo.toml
Normal file
19
crates/color/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "color"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lib]
|
||||
path = "src/color.rs"
|
||||
doctest = true
|
||||
|
||||
[dependencies]
|
||||
palette.workspace = true
|
||||
227
crates/color/src/color.rs
Normal file
227
crates/color/src/color.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
//! # Color
|
||||
//!
|
||||
//! The `color` crate provides a set utilities for working with colors. It is a wrapper around the [`palette`](https://docs.rs/palette) crate with some additional functionality.
|
||||
//!
|
||||
//! It is used to create a manipulate colors when building themes.
|
||||
//!
|
||||
//! === In development note ===
|
||||
//!
|
||||
//! This crate is meant to sit between gpui and the theme/ui for all the color related stuff.
|
||||
//!
|
||||
//! It could be folded into gpui, ui or theme potentially but for now we'll continue
|
||||
//! to develop it in isolation.
|
||||
//!
|
||||
//! Once we have a good idea of the needs of the theme system and color in gpui in general I see 3 paths:
|
||||
//! 1. Use `palette` (or another color library) directly in gpui and everywhere else, rather than rolling our own color system.
|
||||
//! 2. Keep this crate as a thin wrapper around `palette` and use it everywhere except gpui, and convert to gpui's color system when needed.
|
||||
//! 3. Build the needed functionality into gpui and keep using its color system everywhere.
|
||||
//!
|
||||
//! I'm leaning towards 2 in the short term and 1 in the long term, but we'll need to discuss it more.
|
||||
//!
|
||||
//! === End development note ===
|
||||
use palette::{
|
||||
blend::Blend, convert::FromColorUnclamped, encoding, rgb::Rgb, Clamp, Mix, Srgb, WithAlpha,
|
||||
};
|
||||
|
||||
/// The types of blend modes supported
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum BlendMode {
|
||||
/// Multiplies the colors, resulting in a darker color. This mode is useful for creating shadows.
|
||||
Multiply,
|
||||
/// Lightens the color by adding the source and destination colors. It results in a lighter color.
|
||||
Screen,
|
||||
/// Combines Multiply and Screen blend modes. Parts of the image that are lighter than 50% gray are lightened, and parts that are darker are darkened.
|
||||
Overlay,
|
||||
/// Selects the darker of the base or blend color as the resulting color. Useful for darkening images without affecting the overall contrast.
|
||||
Darken,
|
||||
/// Selects the lighter of the base or blend color as the resulting color. Useful for lightening images without affecting the overall contrast.
|
||||
Lighten,
|
||||
/// Brightens the base color to reflect the blend color. The result is a lightened image.
|
||||
Dodge,
|
||||
/// Darkens the base color to reflect the blend color. The result is a darkened image.
|
||||
Burn,
|
||||
/// Similar to Overlay, but with a stronger effect. Hard Light can either multiply or screen colors, depending on the blend color.
|
||||
HardLight,
|
||||
/// A softer version of Hard Light. Soft Light either darkens or lightens colors, depending on the blend color.
|
||||
SoftLight,
|
||||
/// Subtracts the darker of the two constituent colors from the lighter color. Difference mode is useful for creating more vivid colors.
|
||||
Difference,
|
||||
/// Similar to Difference, but with a lower contrast. Exclusion mode produces an effect similar to Difference but with less intensity.
|
||||
Exclusion,
|
||||
}
|
||||
|
||||
/// Converts a hexadecimal color string to a `palette::Hsla` color.
|
||||
///
|
||||
/// This function supports the following hex formats:
|
||||
/// `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`.
|
||||
pub fn hex_to_hsla(s: &str) -> Result<RGBAColor, String> {
|
||||
let hex = s.trim_start_matches('#');
|
||||
|
||||
// Expand shorthand formats #RGB and #RGBA to #RRGGBB and #RRGGBBAA
|
||||
let h = hex.as_bytes();
|
||||
let arr: [u8; 8] = match h.len() {
|
||||
// #RGB => #RRGGBBAA
|
||||
3 => [h[0], h[0], h[1], h[1], h[2], h[2], b'f', b'f'],
|
||||
// #RGBA => #RRGGBBAA
|
||||
4 => [h[0], h[0], h[1], h[1], h[2], h[2], h[3], h[3]],
|
||||
// #RRGGBB => #RRGGBBAA
|
||||
6 => [h[0], h[1], h[2], h[3], h[4], h[5], b'f', b'f'],
|
||||
// Already in #RRGGBBAA
|
||||
8 => h.try_into().unwrap(),
|
||||
_ => return Err("Invalid hexadecimal string length".to_string()),
|
||||
};
|
||||
|
||||
let hex =
|
||||
std::str::from_utf8(&arr).map_err(|_| format!("Invalid hexadecimal string: {}", s))?;
|
||||
let hex_val =
|
||||
u32::from_str_radix(hex, 16).map_err(|_| format!("Invalid hexadecimal string: {}", s))?;
|
||||
|
||||
Ok(RGBAColor {
|
||||
r: ((hex_val >> 24) & 0xFF) as f32 / 255.0,
|
||||
g: ((hex_val >> 16) & 0xFF) as f32 / 255.0,
|
||||
b: ((hex_val >> 8) & 0xFF) as f32 / 255.0,
|
||||
a: (hex_val & 0xFF) as f32 / 255.0,
|
||||
})
|
||||
}
|
||||
|
||||
// These derives implement to and from palette's color types.
|
||||
#[derive(FromColorUnclamped, WithAlpha, Debug, Clone)]
|
||||
#[palette(skip_derives(Rgb), rgb_standard = "encoding::Srgb")]
|
||||
pub struct RGBAColor {
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
// Let Palette know this is our alpha channel.
|
||||
#[palette(alpha)]
|
||||
a: f32,
|
||||
}
|
||||
|
||||
impl FromColorUnclamped<RGBAColor> for RGBAColor {
|
||||
fn from_color_unclamped(color: RGBAColor) -> RGBAColor {
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> FromColorUnclamped<Rgb<S, f32>> for RGBAColor
|
||||
where
|
||||
Srgb: FromColorUnclamped<Rgb<S, f32>>,
|
||||
{
|
||||
fn from_color_unclamped(color: Rgb<S, f32>) -> RGBAColor {
|
||||
let srgb = Srgb::from_color_unclamped(color);
|
||||
RGBAColor {
|
||||
r: srgb.red,
|
||||
g: srgb.green,
|
||||
b: srgb.blue,
|
||||
a: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> FromColorUnclamped<RGBAColor> for Rgb<S, f32>
|
||||
where
|
||||
Rgb<S, f32>: FromColorUnclamped<Srgb>,
|
||||
{
|
||||
fn from_color_unclamped(color: RGBAColor) -> Self {
|
||||
Self::from_color_unclamped(Srgb::new(color.r, color.g, color.b))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clamp for RGBAColor {
|
||||
fn clamp(self) -> Self {
|
||||
RGBAColor {
|
||||
r: self.r.min(1.0).max(0.0),
|
||||
g: self.g.min(1.0).max(0.0),
|
||||
b: self.b.min(1.0).max(0.0),
|
||||
a: self.a.min(1.0).max(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RGBAColor {
|
||||
/// Creates a new color from the given RGBA values.
|
||||
///
|
||||
/// This color can be used to convert to any [`palette::Color`] type.
|
||||
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
RGBAColor { r, g, b, a }
|
||||
}
|
||||
|
||||
/// Returns a set of states for this color.
|
||||
pub fn states(self, is_light: bool) -> ColorStates {
|
||||
states_for_color(self, is_light)
|
||||
}
|
||||
|
||||
/// Mixes this color with another [`palette::Hsl`] color at the given `mix_ratio`.
|
||||
pub fn mixed(&self, other: RGBAColor, mix_ratio: f32) -> Self {
|
||||
let srgb_self = Srgb::new(self.r, self.g, self.b);
|
||||
let srgb_other = Srgb::new(other.r, other.g, other.b);
|
||||
|
||||
// Directly mix the colors as sRGB values
|
||||
let mixed = srgb_self.mix(srgb_other, mix_ratio);
|
||||
RGBAColor::from_color_unclamped(mixed)
|
||||
}
|
||||
|
||||
pub fn blend(&self, other: RGBAColor, blend_mode: BlendMode) -> Self {
|
||||
let srgb_self = Srgb::new(self.r, self.g, self.b);
|
||||
let srgb_other = Srgb::new(other.r, other.g, other.b);
|
||||
|
||||
let blended = match blend_mode {
|
||||
// replace hsl methods with the respective sRGB methods
|
||||
BlendMode::Multiply => srgb_self.multiply(srgb_other),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
Self {
|
||||
r: blended.red,
|
||||
g: blended.green,
|
||||
b: blended.blue,
|
||||
a: self.a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of colors for different states of an element.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ColorStates {
|
||||
/// The default color.
|
||||
pub default: RGBAColor,
|
||||
/// The color when the mouse is hovering over the element.
|
||||
pub hover: RGBAColor,
|
||||
/// The color when the mouse button is held down on the element.
|
||||
pub active: RGBAColor,
|
||||
/// The color when the element is focused with the keyboard.
|
||||
pub focused: RGBAColor,
|
||||
/// The color when the element is disabled.
|
||||
pub disabled: RGBAColor,
|
||||
}
|
||||
|
||||
/// Returns a set of colors for different states of an element.
|
||||
///
|
||||
/// todo("This should take a theme and use appropriate colors from it")
|
||||
pub fn states_for_color(color: RGBAColor, is_light: bool) -> ColorStates {
|
||||
let adjustment_factor = if is_light { 0.1 } else { -0.1 };
|
||||
let hover_adjustment = 1.0 - adjustment_factor;
|
||||
let active_adjustment = 1.0 - 2.0 * adjustment_factor;
|
||||
let focused_adjustment = 1.0 - 3.0 * adjustment_factor;
|
||||
let disabled_adjustment = 1.0 - 4.0 * adjustment_factor;
|
||||
|
||||
let make_adjustment = |color: RGBAColor, adjustment: f32| -> RGBAColor {
|
||||
// Adjust lightness for each state
|
||||
// Note: Adjustment logic may differ; simplify as needed for sRGB
|
||||
RGBAColor::new(
|
||||
color.r * adjustment,
|
||||
color.g * adjustment,
|
||||
color.b * adjustment,
|
||||
color.a,
|
||||
)
|
||||
};
|
||||
|
||||
let color = color.clamp();
|
||||
|
||||
ColorStates {
|
||||
default: color.clone(),
|
||||
hover: make_adjustment(color.clone(), hover_adjustment),
|
||||
active: make_adjustment(color.clone(), active_adjustment),
|
||||
focused: make_adjustment(color.clone(), focused_adjustment),
|
||||
disabled: make_adjustment(color.clone(), disabled_adjustment),
|
||||
}
|
||||
}
|
||||
@@ -1044,6 +1044,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
use language::BufferId;
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_buffer_management(cx: &mut TestAppContext) {
|
||||
@@ -1257,5 +1258,16 @@ mod tests {
|
||||
fn load(&self, _: &AppContext) -> Task<Result<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn buffer_reloaded(
|
||||
&self,
|
||||
_: BufferId,
|
||||
_: &clock::Global,
|
||||
_: language::LineEnding,
|
||||
_: Option<std::time::SystemTime>,
|
||||
_: &mut AppContext,
|
||||
) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -867,12 +867,10 @@ fn compare_diagnostics(
|
||||
snapshot: &language::BufferSnapshot,
|
||||
) -> Ordering {
|
||||
use language::ToOffset;
|
||||
|
||||
// The diagnostics may point to a previously open Buffer for this file.
|
||||
if !old.range.start.is_valid(snapshot) || !new.range.start.is_valid(snapshot) {
|
||||
// The old diagnostics may point to a previously open Buffer for this file.
|
||||
if !old.range.start.is_valid(snapshot) {
|
||||
return Ordering::Greater;
|
||||
}
|
||||
|
||||
old.range
|
||||
.start
|
||||
.to_offset(snapshot)
|
||||
|
||||
@@ -30,7 +30,6 @@ test-support = [
|
||||
[dependencies]
|
||||
aho-corasick = "1.1"
|
||||
anyhow.workspace = true
|
||||
assets.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
|
||||
@@ -52,14 +52,8 @@ use multi_buffer::{
|
||||
ToOffset, ToPoint,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
borrow::Cow,
|
||||
fmt::Debug,
|
||||
num::NonZeroU32,
|
||||
ops::{Add, Range, Sub},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::ops::Add;
|
||||
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::{TabMap, TabSnapshot};
|
||||
use text::LineIndent;
|
||||
@@ -1033,14 +1027,6 @@ impl Add for DisplayRow {
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for DisplayRow {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
DisplayRow(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayPoint {
|
||||
pub fn new(row: DisplayRow, column: u32) -> Self {
|
||||
Self(BlockPoint(Point::new(row.0, column)))
|
||||
|
||||
@@ -28,7 +28,6 @@ mod indent_guides;
|
||||
mod inlay_hint_cache;
|
||||
mod inline_completion_provider;
|
||||
pub mod items;
|
||||
mod linked_editing_ranges;
|
||||
mod mouse_context_menu;
|
||||
pub mod movement;
|
||||
mod persistence;
|
||||
@@ -89,7 +88,6 @@ use language::{
|
||||
Point, Selection, SelectionGoal, TransactionId,
|
||||
};
|
||||
use language::{BufferRow, Runnable, RunnableRange};
|
||||
use linked_editing_ranges::refresh_linked_ranges;
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
|
||||
@@ -149,9 +147,6 @@ use workspace::{OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
|
||||
|
||||
use crate::hover_links::find_url;
|
||||
|
||||
pub const FILE_HEADER_HEIGHT: u8 = 1;
|
||||
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u8 = 1;
|
||||
pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u8 = 1;
|
||||
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
const MAX_LINE_LEN: usize = 1024;
|
||||
@@ -381,7 +376,6 @@ type CompletionId = usize;
|
||||
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
|
||||
|
||||
type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
|
||||
type GutterHighlight = (fn(&AppContext) -> Hsla, Arc<[Range<Anchor>]>);
|
||||
|
||||
struct ScrollbarMarkerState {
|
||||
scrollbar_size: Size<Pixels>,
|
||||
@@ -470,7 +464,6 @@ pub struct Editor {
|
||||
highlight_order: usize,
|
||||
highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
|
||||
background_highlights: TreeMap<TypeId, BackgroundHighlight>,
|
||||
gutter_highlights: TreeMap<TypeId, GutterHighlight>,
|
||||
scrollbar_marker_state: ScrollbarMarkerState,
|
||||
active_indent_guides_state: ActiveIndentGuidesState,
|
||||
nav_history: Option<ItemNavHistory>,
|
||||
@@ -483,8 +476,6 @@ pub struct Editor {
|
||||
available_code_actions: Option<(Location, Arc<[CodeAction]>)>,
|
||||
code_actions_task: Option<Task<()>>,
|
||||
document_highlights_task: Option<Task<()>>,
|
||||
linked_editing_range_task: Option<Task<Option<()>>>,
|
||||
linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
|
||||
pending_rename: Option<RenameState>,
|
||||
searchable: bool,
|
||||
cursor_shape: CursorShape,
|
||||
@@ -532,7 +523,6 @@ pub struct Editor {
|
||||
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
|
||||
tasks_update_task: Option<Task<()>>,
|
||||
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
|
||||
file_header_size: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -1506,7 +1496,7 @@ struct ActiveDiagnosticGroup {
|
||||
is_valid: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ClipboardSelection {
|
||||
pub len: usize,
|
||||
pub is_entire_line: bool,
|
||||
@@ -1655,8 +1645,9 @@ impl Editor {
|
||||
}),
|
||||
merge_adjacent: true,
|
||||
};
|
||||
let file_header_size = if show_excerpt_controls { 3 } else { 2 };
|
||||
let display_map = cx.new_model(|cx| {
|
||||
let file_header_size = if show_excerpt_controls { 3 } else { 2 };
|
||||
|
||||
DisplayMap::new(
|
||||
buffer.clone(),
|
||||
style.font(),
|
||||
@@ -1664,8 +1655,8 @@ impl Editor {
|
||||
None,
|
||||
show_excerpt_controls,
|
||||
file_header_size,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
|
||||
1,
|
||||
1,
|
||||
fold_placeholder,
|
||||
cx,
|
||||
)
|
||||
@@ -1761,7 +1752,6 @@ impl Editor {
|
||||
highlight_order: 0,
|
||||
highlighted_rows: HashMap::default(),
|
||||
background_highlights: Default::default(),
|
||||
gutter_highlights: TreeMap::default(),
|
||||
scrollbar_marker_state: ScrollbarMarkerState::default(),
|
||||
active_indent_guides_state: ActiveIndentGuidesState::default(),
|
||||
nav_history: None,
|
||||
@@ -1775,7 +1765,6 @@ impl Editor {
|
||||
available_code_actions: Default::default(),
|
||||
code_actions_task: Default::default(),
|
||||
document_highlights_task: Default::default(),
|
||||
linked_editing_range_task: Default::default(),
|
||||
pending_rename: Default::default(),
|
||||
searchable: true,
|
||||
cursor_shape: Default::default(),
|
||||
@@ -1815,7 +1804,6 @@ impl Editor {
|
||||
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
|
||||
blame: None,
|
||||
blame_subscription: None,
|
||||
file_header_size,
|
||||
tasks: Default::default(),
|
||||
_subscriptions: vec![
|
||||
cx.observe(&buffer, Self::on_buffer_changed),
|
||||
@@ -1837,7 +1825,6 @@ impl Editor {
|
||||
}),
|
||||
],
|
||||
tasks_update_task: None,
|
||||
linked_edit_ranges: Default::default(),
|
||||
previous_search_ranges: None,
|
||||
};
|
||||
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
||||
@@ -2218,6 +2205,7 @@ impl Editor {
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let display_map = self
|
||||
.display_map
|
||||
.update(cx, |display_map, cx| display_map.snapshot(cx));
|
||||
@@ -2305,7 +2293,6 @@ impl Editor {
|
||||
self.refresh_document_highlights(cx);
|
||||
refresh_matching_bracket_highlights(self, cx);
|
||||
self.discard_inline_completion(false, cx);
|
||||
linked_editing_ranges::refresh_linked_ranges(self, cx);
|
||||
if self.git_blame_inline_enabled {
|
||||
self.start_inline_blame_timer(cx);
|
||||
}
|
||||
@@ -2317,6 +2304,7 @@ impl Editor {
|
||||
if self.selections.disjoint_anchors().len() == 1 {
|
||||
cx.emit(SearchEvent::ActiveMatchChanged)
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -2786,49 +2774,6 @@ impl Editor {
|
||||
false
|
||||
}
|
||||
|
||||
fn linked_editing_ranges_for(
|
||||
&self,
|
||||
selection: Range<text::Anchor>,
|
||||
cx: &AppContext,
|
||||
) -> Option<HashMap<Model<Buffer>, Vec<Range<text::Anchor>>>> {
|
||||
if self.linked_edit_ranges.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let ((base_range, linked_ranges), buffer_snapshot, buffer) =
|
||||
selection.end.buffer_id.and_then(|end_buffer_id| {
|
||||
if selection.start.buffer_id != Some(end_buffer_id) {
|
||||
return None;
|
||||
}
|
||||
let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
self.linked_edit_ranges
|
||||
.get(end_buffer_id, selection.start..selection.end, &snapshot)
|
||||
.map(|ranges| (ranges, snapshot, buffer))
|
||||
})?;
|
||||
use text::ToOffset as TO;
|
||||
// find offset from the start of current range to current cursor position
|
||||
let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
|
||||
|
||||
let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
|
||||
let start_difference = start_offset - start_byte_offset;
|
||||
let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
|
||||
let end_difference = end_offset - start_byte_offset;
|
||||
// Current range has associated linked ranges.
|
||||
let mut linked_edits = HashMap::<_, Vec<_>>::default();
|
||||
for range in linked_ranges.iter() {
|
||||
let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
|
||||
let end_offset = start_offset + end_difference;
|
||||
let start_offset = start_offset + start_difference;
|
||||
let start = buffer_snapshot.anchor_after(start_offset);
|
||||
let end = buffer_snapshot.anchor_after(end_offset);
|
||||
linked_edits
|
||||
.entry(buffer.clone())
|
||||
.or_default()
|
||||
.push(start..end);
|
||||
}
|
||||
Some(linked_edits)
|
||||
}
|
||||
|
||||
pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||
let text: Arc<str> = text.into();
|
||||
|
||||
@@ -2839,7 +2784,6 @@ impl Editor {
|
||||
let selections = self.selections.all_adjusted(cx);
|
||||
let mut brace_inserted = false;
|
||||
let mut edits = Vec::new();
|
||||
let mut linked_edits = HashMap::<_, Vec<_>>::default();
|
||||
let mut new_selections = Vec::with_capacity(selections.len());
|
||||
let mut new_autoclose_regions = Vec::new();
|
||||
let snapshot = self.buffer.read(cx).read(cx);
|
||||
@@ -3020,46 +2964,16 @@ impl Editor {
|
||||
// text with the given input and move the selection to the end of the
|
||||
// newly inserted text.
|
||||
let anchor = snapshot.anchor_after(selection.end);
|
||||
if !self.linked_edit_ranges.is_empty() {
|
||||
let start_anchor = snapshot.anchor_before(selection.start);
|
||||
if let Some(ranges) =
|
||||
self.linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
|
||||
{
|
||||
for (buffer, edits) in ranges {
|
||||
linked_edits
|
||||
.entry(buffer.clone())
|
||||
.or_default()
|
||||
.extend(edits.into_iter().map(|range| (range, text.clone())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_selections.push((selection.map(|_| anchor), 0));
|
||||
edits.push((selection.start..selection.end, text.clone()));
|
||||
}
|
||||
|
||||
drop(snapshot);
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, this.autoindent_mode.clone(), cx);
|
||||
});
|
||||
for (buffer, edits) in linked_edits {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let edits = edits
|
||||
.into_iter()
|
||||
.map(|(range, text)| {
|
||||
use text::ToPoint as TP;
|
||||
let end_point = TP::to_point(&range.end, &snapshot);
|
||||
let start_point = TP::to_point(&range.start, &snapshot);
|
||||
(start_point..end_point, text)
|
||||
})
|
||||
.sorted_by_key(|(range, _)| range.start)
|
||||
.collect::<Vec<_>>();
|
||||
buffer.edit(edits, None, cx);
|
||||
})
|
||||
}
|
||||
|
||||
let new_anchor_selections = new_selections.iter().map(|e| &e.0);
|
||||
let new_selection_deltas = new_selections.iter().map(|e| e.1);
|
||||
let snapshot = this.buffer.read(cx).read(cx);
|
||||
@@ -3116,7 +3030,6 @@ impl Editor {
|
||||
|
||||
let trigger_in_words = !had_active_inline_completion;
|
||||
this.trigger_completion_on_input(&text, trigger_in_words, cx);
|
||||
linked_editing_ranges::refresh_linked_ranges(this, cx);
|
||||
this.refresh_inline_completion(true, cx);
|
||||
});
|
||||
}
|
||||
@@ -4054,7 +3967,6 @@ impl Editor {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut range_to_replace: Option<Range<isize>> = None;
|
||||
let mut ranges = Vec::new();
|
||||
let mut linked_edits = HashMap::<_, Vec<_>>::default();
|
||||
for selection in &selections {
|
||||
if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
|
||||
let start = selection.start.saturating_sub(lookbehind);
|
||||
@@ -4084,21 +3996,6 @@ impl Editor {
|
||||
}));
|
||||
break;
|
||||
}
|
||||
if !self.linked_edit_ranges.is_empty() {
|
||||
let start_anchor = snapshot.anchor_before(selection.head());
|
||||
let end_anchor = snapshot.anchor_after(selection.tail());
|
||||
if let Some(ranges) = self
|
||||
.linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
|
||||
{
|
||||
for (buffer, edits) in ranges {
|
||||
linked_edits.entry(buffer.clone()).or_default().extend(
|
||||
edits
|
||||
.into_iter()
|
||||
.map(|range| (range, text[common_prefix_len..].to_owned())),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let text = &text[common_prefix_len..];
|
||||
|
||||
@@ -4125,22 +4022,6 @@ impl Editor {
|
||||
);
|
||||
});
|
||||
}
|
||||
for (buffer, edits) in linked_edits {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let edits = edits
|
||||
.into_iter()
|
||||
.map(|(range, text)| {
|
||||
use text::ToPoint as TP;
|
||||
let end_point = TP::to_point(&range.end, &snapshot);
|
||||
let start_point = TP::to_point(&range.start, &snapshot);
|
||||
(start_point..end_point, text)
|
||||
})
|
||||
.sorted_by_key(|(range, _)| range.start)
|
||||
.collect::<Vec<_>>();
|
||||
buffer.edit(edits, None, cx);
|
||||
})
|
||||
}
|
||||
|
||||
this.refresh_inline_completion(true, cx);
|
||||
});
|
||||
@@ -5125,27 +5006,6 @@ impl Editor {
|
||||
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||
self.transact(cx, |this, cx| {
|
||||
this.select_autoclose_pair(cx);
|
||||
let mut linked_ranges = HashMap::<_, Vec<_>>::default();
|
||||
if !this.linked_edit_ranges.is_empty() {
|
||||
let selections = this.selections.all::<MultiBufferPoint>(cx);
|
||||
let snapshot = this.buffer.read(cx).snapshot(cx);
|
||||
|
||||
for selection in selections.iter() {
|
||||
let selection_start = snapshot.anchor_before(selection.start).text_anchor;
|
||||
let selection_end = snapshot.anchor_after(selection.end).text_anchor;
|
||||
if selection_start.buffer_id != selection_end.buffer_id {
|
||||
continue;
|
||||
}
|
||||
if let Some(ranges) =
|
||||
this.linked_editing_ranges_for(selection_start..selection_end, cx)
|
||||
{
|
||||
for (buffer, entries) in ranges {
|
||||
linked_ranges.entry(buffer).or_default().extend(entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut selections = this.selections.all::<MultiBufferPoint>(cx);
|
||||
if !this.selections.line_mode {
|
||||
let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
@@ -5186,33 +5046,7 @@ impl Editor {
|
||||
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
this.insert("", cx);
|
||||
let empty_str: Arc<str> = Arc::from("");
|
||||
for (buffer, edits) in linked_ranges {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
use text::ToPoint as TP;
|
||||
|
||||
let edits = edits
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let end_point = TP::to_point(&range.end, &snapshot);
|
||||
let mut start_point = TP::to_point(&range.start, &snapshot);
|
||||
|
||||
if end_point == start_point {
|
||||
let offset = text::ToOffset::to_offset(&range.start, &snapshot)
|
||||
.saturating_sub(1);
|
||||
start_point = TP::to_point(&offset, &snapshot);
|
||||
};
|
||||
|
||||
(start_point..end_point, empty_str.clone())
|
||||
})
|
||||
.sorted_by_key(|(range, _)| range.start)
|
||||
.collect::<Vec<_>>();
|
||||
buffer.update(cx, |this, cx| {
|
||||
this.edit(edits, None, cx);
|
||||
})
|
||||
}
|
||||
this.refresh_inline_completion(true, cx);
|
||||
linked_editing_ranges::refresh_linked_ranges(this, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6544,8 +6378,6 @@ impl Editor {
|
||||
}
|
||||
|
||||
let text_layout_details = &self.text_layout_details(cx);
|
||||
let selection_count = self.selections.count();
|
||||
let first_selection = self.selections.first_anchor();
|
||||
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
let line_mode = s.line_mode;
|
||||
@@ -6562,12 +6394,7 @@ impl Editor {
|
||||
);
|
||||
selection.collapse_to(cursor, goal);
|
||||
});
|
||||
});
|
||||
|
||||
if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
|
||||
{
|
||||
cx.propagate();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn move_up_by_lines(&mut self, action: &MoveUpByLines, cx: &mut ViewContext<Self>) {
|
||||
@@ -6711,9 +6538,6 @@ impl Editor {
|
||||
}
|
||||
|
||||
let text_layout_details = &self.text_layout_details(cx);
|
||||
let selection_count = self.selections.count();
|
||||
let first_selection = self.selections.first_anchor();
|
||||
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
let line_mode = s.line_mode;
|
||||
s.move_with(|map, selection| {
|
||||
@@ -6730,11 +6554,6 @@ impl Editor {
|
||||
selection.collapse_to(cursor, goal);
|
||||
});
|
||||
});
|
||||
|
||||
if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
|
||||
{
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
|
||||
@@ -10444,25 +10263,6 @@ impl Editor {
|
||||
Some(text_highlights)
|
||||
}
|
||||
|
||||
pub fn highlight_gutter<T: 'static>(
|
||||
&mut self,
|
||||
ranges: &[Range<Anchor>],
|
||||
color_fetcher: fn(&AppContext) -> Hsla,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.gutter_highlights
|
||||
.insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn clear_gutter_highlights<T: 'static>(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<GutterHighlight> {
|
||||
cx.notify();
|
||||
self.gutter_highlights.remove(&TypeId::of::<T>())
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn all_text_background_highlights(
|
||||
&mut self,
|
||||
@@ -10652,44 +10452,6 @@ impl Editor {
|
||||
results
|
||||
}
|
||||
|
||||
pub fn gutter_highlights_in_range(
|
||||
&self,
|
||||
search_range: Range<Anchor>,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
cx: &AppContext,
|
||||
) -> Vec<(Range<DisplayPoint>, Hsla)> {
|
||||
let mut results = Vec::new();
|
||||
for (color_fetcher, ranges) in self.gutter_highlights.values() {
|
||||
let color = color_fetcher(cx);
|
||||
let start_ix = match ranges.binary_search_by(|probe| {
|
||||
let cmp = probe
|
||||
.end
|
||||
.cmp(&search_range.start, &display_snapshot.buffer_snapshot);
|
||||
if cmp.is_gt() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
}) {
|
||||
Ok(i) | Err(i) => i,
|
||||
};
|
||||
for range in &ranges[start_ix..] {
|
||||
if range
|
||||
.start
|
||||
.cmp(&search_range.end, &display_snapshot.buffer_snapshot)
|
||||
.is_ge()
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
let start = range.start.to_display_point(&display_snapshot);
|
||||
let end = range.end.to_display_point(&display_snapshot);
|
||||
results.push((start..end, color))
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
/// Get the text ranges corresponding to the redaction query
|
||||
pub fn redacted_ranges(
|
||||
&self,
|
||||
@@ -10782,6 +10544,7 @@ impl Editor {
|
||||
}
|
||||
cx.emit(EditorEvent::BufferEdited);
|
||||
cx.emit(SearchEvent::MatchesInvalidated);
|
||||
|
||||
if *singleton_buffer_edited {
|
||||
if let Some(project) = &self.project {
|
||||
let project = project.read(cx);
|
||||
@@ -10813,7 +10576,6 @@ impl Editor {
|
||||
|
||||
let Some(project) = &self.project else { return };
|
||||
let telemetry = project.read(cx).client().telemetry().clone();
|
||||
refresh_linked_ranges(self, cx);
|
||||
telemetry.log_edit_event("editor");
|
||||
}
|
||||
multi_buffer::Event::ExcerptsAdded {
|
||||
@@ -10833,19 +10595,12 @@ impl Editor {
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
|
||||
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
|
||||
}
|
||||
multi_buffer::Event::ExcerptsEdited { ids } => {
|
||||
cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
|
||||
}
|
||||
multi_buffer::Event::ExcerptsExpanded { ids } => {
|
||||
cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
|
||||
}
|
||||
multi_buffer::Event::Reparsed => {
|
||||
self.tasks_update_task = Some(self.refresh_runnables(cx));
|
||||
|
||||
cx.emit(EditorEvent::Reparsed);
|
||||
}
|
||||
multi_buffer::Event::LanguageChanged => {
|
||||
linked_editing_ranges::refresh_linked_ranges(self, cx);
|
||||
cx.emit(EditorEvent::Reparsed);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -11309,10 +11064,6 @@ impl Editor {
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn file_header_size(&self) -> u8 {
|
||||
self.file_header_size
|
||||
}
|
||||
}
|
||||
|
||||
fn hunks_for_selections(
|
||||
@@ -11757,12 +11508,6 @@ pub enum EditorEvent {
|
||||
ExcerptsRemoved {
|
||||
ids: Vec<ExcerptId>,
|
||||
},
|
||||
ExcerptsEdited {
|
||||
ids: Vec<ExcerptId>,
|
||||
},
|
||||
ExcerptsExpanded {
|
||||
ids: Vec<ExcerptId>,
|
||||
},
|
||||
BufferEdited,
|
||||
Edited,
|
||||
Reparsed,
|
||||
@@ -11963,6 +11708,7 @@ impl ViewInputHandler for Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if !self.input_enabled {
|
||||
cx.emit(EditorEvent::InputIgnored { text: text.into() });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,7 @@ use crate::{
|
||||
JoinLines,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
|
||||
WindowBounds, WindowOptions,
|
||||
};
|
||||
use gpui::{div, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds, WindowOptions};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings::{
|
||||
@@ -512,7 +509,6 @@ fn test_clone(cx: &mut TestAppContext) {
|
||||
.update(cx, |editor, cx| {
|
||||
cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
|
||||
@@ -7654,14 +7650,13 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
|
||||
gpui::Point::new(px(0.), px(0.)),
|
||||
gpui::Point::new(px(10.), px(80.)),
|
||||
gpui::Point::new(0.into(), 0.into()),
|
||||
gpui::Point::new(10.into(), 80.into()),
|
||||
))),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let is_still_following = Rc::new(RefCell::new(true));
|
||||
@@ -12189,16 +12184,10 @@ pub(crate) fn update_test_project_settings(
|
||||
|
||||
pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
|
||||
_ = cx.update(|cx| {
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
|
||||
@@ -2837,8 +2837,6 @@ impl EditorElement {
|
||||
Self::paint_diff_hunks(layout.gutter_hitbox.bounds, layout, cx)
|
||||
}
|
||||
|
||||
self.paint_gutter_highlights(layout, cx);
|
||||
|
||||
if layout.blamed_display_rows.is_some() {
|
||||
self.paint_blamed_display_rows(layout, cx);
|
||||
}
|
||||
@@ -3008,37 +3006,6 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_gutter_highlights(&self, layout: &EditorLayout, cx: &mut WindowContext) {
|
||||
let highlight_width = 0.275 * layout.position_map.line_height;
|
||||
let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
|
||||
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
||||
for (range, color) in &layout.highlighted_gutter_ranges {
|
||||
let start_row = if range.start.row() < layout.visible_display_row_range.start {
|
||||
layout.visible_display_row_range.start - DisplayRow(1)
|
||||
} else {
|
||||
range.start.row()
|
||||
};
|
||||
let end_row = if range.end.row() > layout.visible_display_row_range.end {
|
||||
layout.visible_display_row_range.end + DisplayRow(1)
|
||||
} else {
|
||||
range.end.row()
|
||||
};
|
||||
|
||||
let start_y = layout.gutter_hitbox.top()
|
||||
+ start_row.0 as f32 * layout.position_map.line_height
|
||||
- layout.position_map.scroll_pixel_position.y;
|
||||
let end_y = layout.gutter_hitbox.top()
|
||||
+ (end_row.0 + 1) as f32 * layout.position_map.line_height
|
||||
- layout.position_map.scroll_pixel_position.y;
|
||||
let bounds = Bounds::from_corners(
|
||||
point(layout.gutter_hitbox.left(), start_y),
|
||||
point(layout.gutter_hitbox.left() + highlight_width, end_y),
|
||||
);
|
||||
cx.paint_quad(fill(bounds, *color).corner_radii(highlight_corner_radii));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
|
||||
return;
|
||||
@@ -4664,12 +4631,6 @@ impl Element for EditorElement {
|
||||
&snapshot.display_snapshot,
|
||||
cx.theme().colors(),
|
||||
);
|
||||
let highlighted_gutter_ranges =
|
||||
self.editor.read(cx).gutter_highlights_in_range(
|
||||
start_anchor..end_anchor,
|
||||
&snapshot.display_snapshot,
|
||||
cx,
|
||||
);
|
||||
|
||||
let redacted_ranges = self.editor.read(cx).redacted_ranges(
|
||||
start_anchor..end_anchor,
|
||||
@@ -5030,7 +4991,6 @@ impl Element for EditorElement {
|
||||
active_rows,
|
||||
highlighted_rows,
|
||||
highlighted_ranges,
|
||||
highlighted_gutter_ranges,
|
||||
redacted_ranges,
|
||||
line_elements,
|
||||
line_numbers,
|
||||
@@ -5161,7 +5121,6 @@ pub struct EditorLayout {
|
||||
inline_blame: Option<AnyElement>,
|
||||
blocks: Vec<BlockLayout>,
|
||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
redacted_ranges: Vec<Range<DisplayPoint>>,
|
||||
cursors: Vec<(DisplayPoint, Hsla)>,
|
||||
visible_cursors: Vec<CursorLayout>,
|
||||
|
||||
@@ -322,6 +322,7 @@ pub fn update_inlay_link_and_hover_points(
|
||||
hover_popover::hover_at_inlay(
|
||||
editor,
|
||||
InlayHover {
|
||||
excerpt: excerpt_id,
|
||||
tooltip: match tooltip {
|
||||
InlayHintTooltip::String(text) => HoverBlock {
|
||||
text,
|
||||
@@ -369,6 +370,7 @@ pub fn update_inlay_link_and_hover_points(
|
||||
hover_popover::hover_at_inlay(
|
||||
editor,
|
||||
InlayHover {
|
||||
excerpt: excerpt_id,
|
||||
tooltip: match tooltip {
|
||||
InlayHintLabelPartTooltip::String(text) => {
|
||||
HoverBlock {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
hover_links::{InlayHighlight, RangeInEditor},
|
||||
scroll::ScrollAmount,
|
||||
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
|
||||
EditorStyle, Hover, RangeToAnchorExt,
|
||||
EditorStyle, ExcerptId, Hover, RangeToAnchorExt,
|
||||
};
|
||||
use futures::{stream::FuturesUnordered, FutureExt};
|
||||
use gpui::{
|
||||
@@ -49,6 +49,7 @@ pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContex
|
||||
}
|
||||
|
||||
pub struct InlayHover {
|
||||
pub excerpt: ExcerptId,
|
||||
pub range: InlayHighlight,
|
||||
pub tooltip: HoverBlock,
|
||||
}
|
||||
|
||||
@@ -1268,7 +1268,7 @@ pub mod tests {
|
||||
ExcerptRange,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{Context, SemanticVersion, TestAppContext, WindowHandle};
|
||||
use gpui::{Context, TestAppContext, WindowHandle};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
|
||||
@@ -3361,7 +3361,7 @@ pub mod tests {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
release_channel::init("0.0.0", cx);
|
||||
client::init_settings(cx);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use collections::HashMap;
|
||||
use itertools::Itertools;
|
||||
use text::{AnchorRangeExt, BufferId, ToPoint};
|
||||
use ui::ViewContext;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::Editor;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(super) struct LinkedEditingRanges(
|
||||
/// Ranges are non-overlapping and sorted by .0 (thus, [x + 1].start > [x].end must hold)
|
||||
pub HashMap<BufferId, Vec<(Range<text::Anchor>, Vec<Range<text::Anchor>>)>>,
|
||||
);
|
||||
|
||||
impl LinkedEditingRanges {
|
||||
pub(super) fn get(
|
||||
&self,
|
||||
id: BufferId,
|
||||
anchor: Range<text::Anchor>,
|
||||
snapshot: &text::BufferSnapshot,
|
||||
) -> Option<&(Range<text::Anchor>, Vec<Range<text::Anchor>>)> {
|
||||
let ranges_for_buffer = self.0.get(&id)?;
|
||||
let lower_bound = ranges_for_buffer
|
||||
.partition_point(|(range, _)| range.start.cmp(&anchor.start, snapshot).is_le());
|
||||
if lower_bound == 0 {
|
||||
// None of the linked ranges contains `anchor`.
|
||||
return None;
|
||||
}
|
||||
ranges_for_buffer
|
||||
.get(lower_bound - 1)
|
||||
.filter(|(range, _)| range.end.cmp(&anchor.end, snapshot).is_ge())
|
||||
}
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Editor>) -> Option<()> {
|
||||
if this.pending_rename.is_some() {
|
||||
return None;
|
||||
}
|
||||
let project = this.project.clone()?;
|
||||
let buffer = this.buffer.read(cx);
|
||||
let mut applicable_selections = vec![];
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
for selection in selections {
|
||||
let cursor_position = selection.head();
|
||||
let start_position = snapshot.anchor_before(cursor_position);
|
||||
let end_position = snapshot.anchor_after(selection.tail());
|
||||
if start_position.buffer_id != end_position.buffer_id || end_position.buffer_id.is_none() {
|
||||
// Throw away selections spanning multiple buffers.
|
||||
continue;
|
||||
}
|
||||
if let Some(buffer) = end_position.buffer_id.and_then(|id| buffer.buffer(id)) {
|
||||
applicable_selections.push((
|
||||
buffer,
|
||||
start_position.text_anchor,
|
||||
end_position.text_anchor,
|
||||
));
|
||||
}
|
||||
}
|
||||
if applicable_selections.is_empty() {
|
||||
return None;
|
||||
}
|
||||
this.linked_editing_range_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let highlights = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let mut linked_edits_tasks = vec![];
|
||||
|
||||
for (buffer, start, end) in &applicable_selections {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
|
||||
let linked_edits_task = project.linked_edit(&buffer, *start, cx);
|
||||
let highlights = move || async move {
|
||||
let edits = linked_edits_task.await.log_err()?;
|
||||
// Find the range containing our current selection.
|
||||
// We might not find one, because the selection contains both the start and end of the contained range
|
||||
// (think of selecting <`html>foo`</html> - even though there's a matching closing tag, the selection goes beyond the range of the opening tag)
|
||||
// or the language server may not have returned any ranges.
|
||||
|
||||
let start_point = start.to_point(&snapshot);
|
||||
let end_point = end.to_point(&snapshot);
|
||||
let _current_selection_contains_range = edits.iter().find(|range| {
|
||||
range.start.to_point(&snapshot) <= start_point
|
||||
&& range.end.to_point(&snapshot) >= end_point
|
||||
});
|
||||
if _current_selection_contains_range.is_none() {
|
||||
return None;
|
||||
}
|
||||
// Now link every range as each-others sibling.
|
||||
let mut siblings: HashMap<Range<text::Anchor>, Vec<_>> = Default::default();
|
||||
let mut insert_sorted_anchor =
|
||||
|key: &Range<text::Anchor>, value: &Range<text::Anchor>| {
|
||||
siblings.entry(key.clone()).or_default().push(value.clone());
|
||||
};
|
||||
for items in edits.into_iter().combinations(2) {
|
||||
let Ok([first, second]): Result<[_; 2], _> = items.try_into() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
insert_sorted_anchor(&first, &second);
|
||||
insert_sorted_anchor(&second, &first);
|
||||
}
|
||||
let mut siblings: Vec<(_, _)> = siblings.into_iter().collect();
|
||||
siblings.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
|
||||
Some((buffer_id, siblings))
|
||||
};
|
||||
linked_edits_tasks.push(highlights());
|
||||
}
|
||||
linked_edits_tasks
|
||||
})
|
||||
.log_err()?;
|
||||
|
||||
let highlights = futures::future::join_all(highlights).await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.linked_edit_ranges.0.clear();
|
||||
if this.pending_rename.is_some() {
|
||||
return;
|
||||
}
|
||||
for (buffer_id, ranges) in highlights.into_iter().flatten() {
|
||||
this.linked_edit_ranges
|
||||
.0
|
||||
.entry(buffer_id)
|
||||
.or_default()
|
||||
.extend(ranges);
|
||||
}
|
||||
for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
|
||||
let Some(snapshot) = this
|
||||
.buffer
|
||||
.read(cx)
|
||||
.buffer(*buffer_id)
|
||||
.map(|buffer| buffer.read(cx).snapshot())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
|
||||
Some(())
|
||||
}));
|
||||
None
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use serde_json::json;
|
||||
use crate::{Editor, ToPoint};
|
||||
use collections::HashSet;
|
||||
use futures::Future;
|
||||
use gpui::{AssetSource, View, ViewContext, VisualTestContext};
|
||||
use gpui::{View, ViewContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,
|
||||
@@ -39,12 +39,6 @@ impl EditorLspTestContext {
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
|
||||
@@ -10,7 +10,7 @@ use async_compression::futures::bufread::GzipEncoder;
|
||||
use collections::BTreeMap;
|
||||
use fs::{FakeFs, Fs, RealFs};
|
||||
use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{Context, SemanticVersion, TestAppContext};
|
||||
use gpui::{Context, TestAppContext};
|
||||
use http::{FakeHttpClient, Response};
|
||||
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
@@ -723,7 +723,7 @@ fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
release_channel::init("0.0.0", cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
Project::init_settings(cx);
|
||||
ExtensionSettings::register(cx);
|
||||
|
||||
@@ -8,10 +8,6 @@ keywords = ["zed", "extension"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
|
||||
# Don't publish v0.0.7 until we're ready to commit to the breaking API changes
|
||||
# Marshall is DRI on this.
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ fs.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
num-format.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
|
||||
@@ -16,7 +16,6 @@ use gpui::{
|
||||
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
use num_format::{Locale, ToFormattedString};
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::Settings;
|
||||
use std::ops::DerefMut;
|
||||
@@ -488,11 +487,8 @@ impl ExtensionsPage {
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Label::new(format!(
|
||||
"Downloads: {}",
|
||||
extension.download_count.to_formatted_string(&Locale::en)
|
||||
))
|
||||
.size(LabelSize::Small),
|
||||
Label::new(format!("Downloads: {}", extension.download_count))
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use gpui::{actions, AppContext, ClipboardItem, PromptLevel};
|
||||
use system_specs::SystemSpecs;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub mod feedback_modal;
|
||||
@@ -39,38 +38,25 @@ pub fn init(cx: &mut AppContext) {
|
||||
feedback_modal::FeedbackModal::register(workspace, cx);
|
||||
workspace
|
||||
.register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| {
|
||||
let specs = SystemSpecs::new(&cx);
|
||||
let specs = SystemSpecs::new(&cx).to_string();
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let specs = specs.await.to_string();
|
||||
|
||||
cx.update(|cx| cx.write_to_clipboard(ClipboardItem::new(specs.clone())))
|
||||
.log_err();
|
||||
|
||||
cx.prompt(
|
||||
PromptLevel::Info,
|
||||
"Copied into clipboard",
|
||||
Some(&specs),
|
||||
&["OK"],
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
let prompt = cx.prompt(
|
||||
PromptLevel::Info,
|
||||
"Copied into clipboard",
|
||||
Some(&specs),
|
||||
&["OK"],
|
||||
);
|
||||
cx.spawn(|_, _cx| async move {
|
||||
prompt.await.ok();
|
||||
})
|
||||
.detach();
|
||||
cx.write_to_clipboard(ClipboardItem::new(specs.clone()));
|
||||
})
|
||||
.register_action(|_, _: &RequestFeature, cx| {
|
||||
cx.open_url(request_feature_url());
|
||||
})
|
||||
.register_action(move |_, _: &FileBugReport, cx| {
|
||||
let specs = SystemSpecs::new(&cx);
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let specs = specs.await;
|
||||
cx.update(|cx| {
|
||||
cx.open_url(&file_bug_report_url(&specs));
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
cx.open_url(&file_bug_report_url(&SystemSpecs::new(&cx)));
|
||||
})
|
||||
.register_action(move |_, _: &OpenZedRepo, cx| {
|
||||
cx.open_url(zed_repo_url());
|
||||
|
||||
@@ -141,15 +141,15 @@ impl FeedbackModal {
|
||||
return;
|
||||
}
|
||||
|
||||
let system_specs = SystemSpecs::new(cx);
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let markdown = markdown.await.log_err();
|
||||
let buffer = project.update(&mut cx, |project, cx| {
|
||||
project.create_local_buffer("", markdown, cx)
|
||||
})?;
|
||||
let system_specs = system_specs.await;
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let system_specs = SystemSpecs::new(cx);
|
||||
|
||||
workspace.toggle_modal(cx, move |cx| {
|
||||
FeedbackModal::new(system_specs, project, buffer, cx)
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use client::telemetry;
|
||||
use gpui::{AppContext, Task};
|
||||
use gpui::AppContext;
|
||||
use human_bytes::human_bytes;
|
||||
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
||||
use serde::Serialize;
|
||||
@@ -10,23 +9,27 @@ use sysinfo::{MemoryRefreshKind, RefreshKind, System};
|
||||
pub struct SystemSpecs {
|
||||
app_version: String,
|
||||
release_channel: &'static str,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
os_name: &'static str,
|
||||
os_version: Option<String>,
|
||||
memory: u64,
|
||||
architecture: &'static str,
|
||||
commit_sha: Option<String>,
|
||||
}
|
||||
|
||||
impl SystemSpecs {
|
||||
pub fn new(cx: &AppContext) -> Task<Self> {
|
||||
pub fn new(cx: &AppContext) -> Self {
|
||||
let app_version = AppVersion::global(cx).to_string();
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
let os_name = telemetry::os_name();
|
||||
let os_name = cx.app_metadata().os_name;
|
||||
let system = System::new_with_specifics(
|
||||
RefreshKind::new().with_memory(MemoryRefreshKind::everything()),
|
||||
);
|
||||
let memory = system.total_memory();
|
||||
let architecture = env::consts::ARCH;
|
||||
let os_version = cx
|
||||
.app_metadata()
|
||||
.os_version
|
||||
.map(|os_version| os_version.to_string());
|
||||
let commit_sha = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => {
|
||||
AppCommitSha::try_global(cx).map(|sha| sha.0.clone())
|
||||
@@ -34,24 +37,24 @@ impl SystemSpecs {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let os_version = telemetry::os_version();
|
||||
SystemSpecs {
|
||||
app_version,
|
||||
release_channel: release_channel.display_name(),
|
||||
os_name,
|
||||
os_version,
|
||||
memory,
|
||||
architecture,
|
||||
commit_sha,
|
||||
}
|
||||
})
|
||||
SystemSpecs {
|
||||
app_version,
|
||||
release_channel: release_channel.display_name(),
|
||||
os_name,
|
||||
os_version,
|
||||
memory,
|
||||
architecture,
|
||||
commit_sha,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SystemSpecs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let os_information = format!("OS: {} {}", self.os_name, self.os_version);
|
||||
let os_information = match &self.os_version {
|
||||
Some(os_version) => format!("OS: {} {}", self.os_name, os_version),
|
||||
None => format!("OS: {}", self.os_name),
|
||||
};
|
||||
let app_version_information = format!(
|
||||
"Zed: v{} ({})",
|
||||
self.app_version,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct CharBag(u64);
|
||||
|
||||
impl CharBag {
|
||||
|
||||
@@ -316,7 +316,7 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum GitFileStatus {
|
||||
Added,
|
||||
Modified,
|
||||
|
||||
@@ -87,7 +87,7 @@ cbindgen = "0.26.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
block = "0.1"
|
||||
cocoa.workspace = true
|
||||
cocoa = "0.25"
|
||||
core-foundation.workspace = true
|
||||
core-graphics = "0.23"
|
||||
core-text = "20.1"
|
||||
|
||||
@@ -76,7 +76,6 @@ fn main() {
|
||||
cx.open_window(options, |cx| {
|
||||
cx.activate(false);
|
||||
cx.new_view(|_cx| AnimationExample {})
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ fn main() {
|
||||
text: "World".into(),
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ fn main() {
|
||||
}),
|
||||
|
||||
window_bounds: Some(WindowBounds::Windowed(Bounds {
|
||||
size: size(px(1100.), px(600.)),
|
||||
origin: Point::new(px(200.), px(200.)),
|
||||
size: size(px(1100.), px(600.)).into(),
|
||||
origin: Point::new(DevicePixels::from(200), DevicePixels::from(200)),
|
||||
})),
|
||||
|
||||
..Default::default()
|
||||
@@ -93,7 +93,6 @@ fn main() {
|
||||
local_resource: Arc::new(PathBuf::from_str("examples/image/app-icon.png").unwrap()),
|
||||
remote_resource: "https://picsum.photos/512/512".into(),
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,8 +29,7 @@ fn main() {
|
||||
}]);
|
||||
cx.open_window(WindowOptions::default(), |cx| {
|
||||
cx.new_view(|_cx| SetMenus {})
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -24,18 +24,21 @@ fn main() {
|
||||
|
||||
for screen in cx.displays() {
|
||||
let options = {
|
||||
let margin_right = px(16.);
|
||||
let margin_height = px(-48.);
|
||||
let popup_margin_width = DevicePixels::from(16);
|
||||
let popup_margin_height = DevicePixels::from(-0) - DevicePixels::from(48);
|
||||
|
||||
let size = Size {
|
||||
let window_size = Size {
|
||||
width: px(400.),
|
||||
height: px(72.),
|
||||
};
|
||||
|
||||
let bounds = gpui::Bounds::<Pixels> {
|
||||
origin: screen.bounds().upper_right()
|
||||
- point(size.width + margin_right, margin_height),
|
||||
size,
|
||||
let screen_bounds = screen.bounds();
|
||||
let size: Size<DevicePixels> = window_size.into();
|
||||
|
||||
let bounds = gpui::Bounds::<DevicePixels> {
|
||||
origin: screen_bounds.upper_right()
|
||||
- point(size.width + popup_margin_width, popup_margin_height),
|
||||
size: window_size.into(),
|
||||
};
|
||||
|
||||
WindowOptions {
|
||||
@@ -58,8 +61,7 @@ fn main() {
|
||||
cx.new_view(|_| WindowContent {
|
||||
text: format!("{:?}", screen.id()).into(),
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,12 +27,13 @@ use util::ResultExt;
|
||||
|
||||
use crate::{
|
||||
current_platform, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
|
||||
AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
||||
Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId,
|
||||
Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
|
||||
PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
|
||||
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext,
|
||||
Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
|
||||
AppMetadata, AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
|
||||
Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
||||
PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
|
||||
RenderablePromptHandle, Reservation, SharedString, SubscriberSet, Subscription, SvgRenderer,
|
||||
Task, TextSystem, View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle,
|
||||
WindowId,
|
||||
};
|
||||
|
||||
mod async_context;
|
||||
@@ -168,6 +169,11 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns metadata associated with the application
|
||||
pub fn metadata(&self) -> AppMetadata {
|
||||
self.0.borrow().app_metadata.clone()
|
||||
}
|
||||
|
||||
/// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background.
|
||||
pub fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.0.borrow().background_executor.clone()
|
||||
@@ -202,6 +208,7 @@ type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
|
||||
pub struct AppContext {
|
||||
pub(crate) this: Weak<AppCell>,
|
||||
pub(crate) platform: Rc<dyn Platform>,
|
||||
app_metadata: AppMetadata,
|
||||
text_system: Arc<TextSystem>,
|
||||
flushing_effects: bool,
|
||||
pending_updates: usize,
|
||||
@@ -254,10 +261,17 @@ impl AppContext {
|
||||
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
||||
let entities = EntityMap::new();
|
||||
|
||||
let app_metadata = AppMetadata {
|
||||
os_name: platform.os_name(),
|
||||
os_version: platform.os_version().ok(),
|
||||
app_version: platform.app_version().ok(),
|
||||
};
|
||||
|
||||
let app = Rc::new_cyclic(|this| AppCell {
|
||||
app: RefCell::new(AppContext {
|
||||
this: this.clone(),
|
||||
platform: platform.clone(),
|
||||
app_metadata,
|
||||
text_system,
|
||||
actions: Rc::new(ActionRegistry::default()),
|
||||
flushing_effects: false,
|
||||
@@ -332,6 +346,11 @@ impl AppContext {
|
||||
self.platform.quit();
|
||||
}
|
||||
|
||||
/// Get metadata about the app and platform.
|
||||
pub fn app_metadata(&self) -> AppMetadata {
|
||||
self.app_metadata.clone()
|
||||
}
|
||||
|
||||
/// Schedules all windows in the application to be redrawn. This can be called
|
||||
/// multiple times in an update cycle and still result in a single redraw.
|
||||
pub fn refresh(&mut self) {
|
||||
@@ -471,26 +490,26 @@ impl AppContext {
|
||||
&mut self,
|
||||
options: crate::WindowOptions,
|
||||
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
|
||||
) -> anyhow::Result<WindowHandle<V>> {
|
||||
) -> WindowHandle<V> {
|
||||
self.update(|cx| {
|
||||
let id = cx.windows.insert(None);
|
||||
let handle = WindowHandle::new(id);
|
||||
match Window::new(handle.into(), options, cx) {
|
||||
Ok(mut window) => {
|
||||
let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
|
||||
window.root_view.replace(root_view.into());
|
||||
cx.window_handles.insert(id, window.handle);
|
||||
cx.windows.get_mut(id).unwrap().replace(window);
|
||||
Ok(handle)
|
||||
}
|
||||
Err(e) => {
|
||||
cx.windows.remove(id);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
let mut window = Window::new(handle.into(), options, cx);
|
||||
let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
|
||||
window.root_view.replace(root_view.into());
|
||||
cx.window_handles.insert(id, window.handle);
|
||||
cx.windows.get_mut(id).unwrap().replace(window);
|
||||
handle
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns Ok() if the platform supports opening windows.
|
||||
/// This returns false (for example) on linux when we could
|
||||
/// not establish a connection to X or Wayland.
|
||||
pub fn can_open_windows(&self) -> anyhow::Result<()> {
|
||||
self.platform.can_open_windows()
|
||||
}
|
||||
|
||||
/// Instructs the platform to activate the application by bringing it to the foreground.
|
||||
pub fn activate(&self, ignoring_other_apps: bool) {
|
||||
self.platform.activate(ignoring_other_apps);
|
||||
@@ -597,12 +616,6 @@ impl AppContext {
|
||||
self.platform.app_path()
|
||||
}
|
||||
|
||||
/// On Linux, returns the name of the compositor in use.
|
||||
/// Is blank on other platforms.
|
||||
pub fn compositor_name(&self) -> &'static str {
|
||||
self.platform.compositor_name()
|
||||
}
|
||||
|
||||
/// Returns the file URL of the executable with the specified name in the application bundle
|
||||
pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
|
||||
self.platform.path_for_auxiliary_executable(name)
|
||||
@@ -722,7 +735,11 @@ impl AppContext {
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
self.update_window(window, |_, cx| cx.draw()).unwrap();
|
||||
self.update_window(window, |_, cx| {
|
||||
println!("flush_effects. cx.draw()");
|
||||
cx.draw()
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if self.pending_effects.is_empty() {
|
||||
|
||||
@@ -151,7 +151,7 @@ impl AsyncAppContext {
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.open_window(options, build_root_view)
|
||||
Ok(lock.open_window(options, build_root_view))
|
||||
}
|
||||
|
||||
/// Schedule a future to be polled in the background.
|
||||
|
||||
@@ -193,22 +193,19 @@ impl TestAppContext {
|
||||
},
|
||||
|cx| cx.new_view(build_window),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Adds a new window with no content.
|
||||
pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
|
||||
let mut cx = self.app.borrow_mut();
|
||||
let bounds = Bounds::maximized(None, &mut cx);
|
||||
let window = cx
|
||||
.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| cx.new_view(|_| Empty),
|
||||
)
|
||||
.unwrap();
|
||||
let window = cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| cx.new_view(|_| Empty),
|
||||
);
|
||||
drop(cx);
|
||||
let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
|
||||
cx.run_until_parked();
|
||||
@@ -225,15 +222,13 @@ impl TestAppContext {
|
||||
{
|
||||
let mut cx = self.app.borrow_mut();
|
||||
let bounds = Bounds::maximized(None, &mut cx);
|
||||
let window = cx
|
||||
.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| cx.new_view(build_root_view),
|
||||
)
|
||||
.unwrap();
|
||||
let window = cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| cx.new_view(build_root_view),
|
||||
);
|
||||
drop(cx);
|
||||
let view = window.root_view(self).unwrap();
|
||||
let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use anyhow::{bail, Context};
|
||||
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
||||
use std::{
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
/// Convert an RGB hex color code number to a color type
|
||||
pub fn rgb(hex: u32) -> Rgba {
|
||||
@@ -270,15 +267,6 @@ impl Ord for Hsla {
|
||||
|
||||
impl Eq for Hsla {}
|
||||
|
||||
impl Hash for Hsla {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_u32(u32::from_be_bytes(self.h.to_be_bytes()));
|
||||
state.write_u32(u32::from_be_bytes(self.s.to_be_bytes()));
|
||||
state.write_u32(u32::from_be_bytes(self.l.to_be_bytes()));
|
||||
state.write_u32(u32::from_be_bytes(self.a.to_be_bytes()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct an [`Hsla`] object from plain values
|
||||
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
||||
Hsla {
|
||||
|
||||
@@ -363,6 +363,15 @@ pub struct Size<T: Clone + Default + Debug> {
|
||||
pub height: T,
|
||||
}
|
||||
|
||||
impl From<Size<DevicePixels>> for Size<Pixels> {
|
||||
fn from(size: Size<DevicePixels>) -> Self {
|
||||
Size {
|
||||
width: Pixels(size.width.0 as f32),
|
||||
height: Pixels(size.height.0 as f32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new `Size<T>` with the provided width and height.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -624,6 +633,15 @@ impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<Pixels>> for Size<DevicePixels> {
|
||||
fn from(size: Size<Pixels>) -> Self {
|
||||
Size {
|
||||
width: DevicePixels(size.width.0 as i32),
|
||||
height: DevicePixels(size.height.0 as i32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<Pixels>> for Size<DefiniteLength> {
|
||||
fn from(size: Size<Pixels>) -> Self {
|
||||
Size {
|
||||
@@ -704,27 +722,28 @@ pub struct Bounds<T: Clone + Default + Debug> {
|
||||
pub size: Size<T>,
|
||||
}
|
||||
|
||||
impl Bounds<Pixels> {
|
||||
impl Bounds<DevicePixels> {
|
||||
/// Generate a centered bounds for the given display or primary display if none is provided
|
||||
pub fn centered(
|
||||
display_id: Option<DisplayId>,
|
||||
size: Size<Pixels>,
|
||||
size: impl Into<Size<DevicePixels>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Self {
|
||||
let display = display_id
|
||||
.and_then(|id| cx.find_display(id))
|
||||
.or_else(|| cx.primary_display());
|
||||
|
||||
let size = size.into();
|
||||
display
|
||||
.map(|display| {
|
||||
let center = display.bounds().center();
|
||||
Bounds {
|
||||
origin: point(center.x - size.width / 2., center.y - size.height / 2.),
|
||||
origin: point(center.x - size.width / 2, center.y - size.height / 2),
|
||||
size,
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| Bounds {
|
||||
origin: point(px(0.), px(0.)),
|
||||
origin: point(DevicePixels(0), DevicePixels(0)),
|
||||
size,
|
||||
})
|
||||
}
|
||||
@@ -738,8 +757,8 @@ impl Bounds<Pixels> {
|
||||
display
|
||||
.map(|display| display.bounds())
|
||||
.unwrap_or_else(|| Bounds {
|
||||
origin: point(px(0.), px(0.)),
|
||||
size: size(px(1024.), px(768.)),
|
||||
origin: point(DevicePixels(0), DevicePixels(0)),
|
||||
size: size(DevicePixels(1024), DevicePixels(768)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1290,16 +1309,6 @@ impl<T: PartialOrd + Default + Debug + Clone> Bounds<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<DevicePixels> {
|
||||
/// Converts the size from physical to logical pixels.
|
||||
pub(crate) fn to_pixels(self, scale_factor: f32) -> Size<Pixels> {
|
||||
size(
|
||||
px(self.width.0 as f32 / scale_factor),
|
||||
px(self.height.0 as f32 / scale_factor),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounds<Pixels> {
|
||||
/// Scales the bounds by a given factor, typically used to adjust for display scaling.
|
||||
///
|
||||
@@ -1337,33 +1346,6 @@ impl Bounds<Pixels> {
|
||||
size: self.size.scale(factor),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the bounds from logical pixels to physical pixels
|
||||
pub fn to_device_pixels(&self, factor: f32) -> Bounds<DevicePixels> {
|
||||
Bounds {
|
||||
origin: point(
|
||||
DevicePixels((self.origin.x.0 * factor) as i32),
|
||||
DevicePixels((self.origin.y.0 * factor) as i32),
|
||||
),
|
||||
size: size(
|
||||
DevicePixels((self.size.width.0 * factor) as i32),
|
||||
DevicePixels((self.size.height.0 * factor) as i32),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bounds<DevicePixels> {
|
||||
/// Convert the bounds from physical pixels to logical pixels
|
||||
pub fn to_pixels(self, scale_factor: f32) -> Bounds<Pixels> {
|
||||
Bounds {
|
||||
origin: point(
|
||||
px(self.origin.x.0 as f32 / scale_factor),
|
||||
px(self.origin.y.0 as f32 / scale_factor),
|
||||
),
|
||||
size: self.size.to_pixels(scale_factor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
|
||||
|
||||
@@ -486,7 +486,6 @@ mod test {
|
||||
focus_handle: cx.focus_handle(),
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
|
||||
@@ -295,7 +295,7 @@ impl KeyBindingContextPredicate {
|
||||
}
|
||||
_ if is_identifier_char(next) => {
|
||||
let len = source
|
||||
.find(|c: char| !is_identifier_char(c) && !is_vim_operator_char(c))
|
||||
.find(|c: char| !is_identifier_char(c))
|
||||
.unwrap_or(source.len());
|
||||
let (identifier, rest) = source.split_at(len);
|
||||
source = skip_whitespace(rest);
|
||||
@@ -356,7 +356,7 @@ fn is_identifier_char(c: char) -> bool {
|
||||
}
|
||||
|
||||
fn is_vim_operator_char(c: char) -> bool {
|
||||
c == '>' || c == '<' || c == '~' || c == '"'
|
||||
c == '>' || c == '<'
|
||||
}
|
||||
|
||||
fn skip_whitespace(source: &str) -> &str {
|
||||
|
||||
@@ -70,19 +70,6 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
match guess_compositor() {
|
||||
"Wayland" => Rc::new(WaylandClient::new()),
|
||||
"X11" => Rc::new(X11Client::new()),
|
||||
"Headless" => Rc::new(HeadlessClient::new()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return which compositor we're guessing we'll use.
|
||||
/// Does not attempt to connect to the given compositor
|
||||
#[cfg(target_os = "linux")]
|
||||
#[inline]
|
||||
pub fn guess_compositor() -> &'static str {
|
||||
let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
|
||||
let x11_display = std::env::var_os("DISPLAY");
|
||||
|
||||
@@ -90,14 +77,13 @@ pub fn guess_compositor() -> &'static str {
|
||||
let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
|
||||
|
||||
if use_wayland {
|
||||
"Wayland"
|
||||
Rc::new(WaylandClient::new())
|
||||
} else if use_x11 {
|
||||
"X11"
|
||||
Rc::new(X11Client::new())
|
||||
} else {
|
||||
"Headless"
|
||||
Rc::new(HeadlessClient::new())
|
||||
}
|
||||
}
|
||||
|
||||
// todo("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
@@ -120,12 +106,14 @@ pub(crate) trait Platform: 'static {
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn active_window(&self) -> Option<AnyWindowHandle>;
|
||||
|
||||
fn can_open_windows(&self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>>;
|
||||
) -> Box<dyn PlatformWindow>;
|
||||
|
||||
/// Returns the appearance of the application's windows.
|
||||
fn window_appearance(&self) -> WindowAppearance;
|
||||
@@ -155,9 +143,9 @@ pub(crate) trait Platform: 'static {
|
||||
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
""
|
||||
}
|
||||
fn os_name(&self) -> &'static str;
|
||||
fn os_version(&self) -> Result<SemanticVersion>;
|
||||
fn app_version(&self) -> Result<SemanticVersion>;
|
||||
fn app_path(&self) -> Result<PathBuf>;
|
||||
fn local_timezone(&self) -> UtcOffset;
|
||||
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
|
||||
@@ -187,12 +175,12 @@ pub trait PlatformDisplay: Send + Sync + Debug {
|
||||
fn uuid(&self) -> Result<Uuid>;
|
||||
|
||||
/// Get the bounds for this display
|
||||
fn bounds(&self) -> Bounds<Pixels>;
|
||||
fn bounds(&self) -> Bounds<DevicePixels>;
|
||||
|
||||
/// Get the default bounds for this display to place a window
|
||||
fn default_bounds(&self) -> Bounds<Pixels> {
|
||||
fn default_bounds(&self) -> Bounds<DevicePixels> {
|
||||
let center = self.bounds().center();
|
||||
let offset = DEFAULT_WINDOW_SIZE / 2.0;
|
||||
let offset = DEFAULT_WINDOW_SIZE / 2;
|
||||
let origin = point(center.x - offset.width, center.y - offset.height);
|
||||
Bounds::new(origin, DEFAULT_WINDOW_SIZE)
|
||||
}
|
||||
@@ -211,7 +199,7 @@ impl Debug for DisplayId {
|
||||
unsafe impl Send for DisplayId {}
|
||||
|
||||
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn bounds(&self) -> Bounds<Pixels>;
|
||||
fn bounds(&self) -> Bounds<DevicePixels>;
|
||||
fn is_maximized(&self) -> bool;
|
||||
fn window_bounds(&self) -> WindowBounds;
|
||||
fn content_size(&self) -> Size<Pixels>;
|
||||
@@ -300,6 +288,19 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
|
||||
}
|
||||
|
||||
/// Basic metadata about the current application and operating system.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AppMetadata {
|
||||
/// The name of the current operating system
|
||||
pub os_name: &'static str,
|
||||
|
||||
/// The operating system's version
|
||||
pub os_version: Option<SemanticVersion>,
|
||||
|
||||
/// The current version of the application
|
||||
pub app_version: Option<SemanticVersion>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub(crate) enum AtlasKey {
|
||||
Glyph(RenderGlyphParams),
|
||||
@@ -569,7 +570,7 @@ pub struct WindowOptions {
|
||||
/// The variables that can be configured when creating a new window
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WindowParams {
|
||||
pub bounds: Bounds<Pixels>,
|
||||
pub bounds: Bounds<DevicePixels>,
|
||||
|
||||
/// The titlebar configuration of the window
|
||||
pub titlebar: Option<TitlebarOptions>,
|
||||
@@ -597,13 +598,13 @@ pub(crate) struct WindowParams {
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum WindowBounds {
|
||||
/// Indicates that the window should open in a windowed state with the given bounds.
|
||||
Windowed(Bounds<Pixels>),
|
||||
Windowed(Bounds<DevicePixels>),
|
||||
/// Indicates that the window should open in a maximized state.
|
||||
/// The bounds provided here represent the restore size of the window.
|
||||
Maximized(Bounds<Pixels>),
|
||||
Maximized(Bounds<DevicePixels>),
|
||||
/// Indicates that the window should open in fullscreen mode.
|
||||
/// The bounds provided here represent the restore size of the window.
|
||||
Fullscreen(Bounds<Pixels>),
|
||||
Fullscreen(Bounds<DevicePixels>),
|
||||
}
|
||||
|
||||
impl Default for WindowBounds {
|
||||
@@ -614,7 +615,7 @@ impl Default for WindowBounds {
|
||||
|
||||
impl WindowBounds {
|
||||
/// Retrieve the inner bounds
|
||||
pub fn get_bounds(&self) -> Bounds<Pixels> {
|
||||
pub fn get_bounds(&self) -> Bounds<DevicePixels> {
|
||||
match self {
|
||||
WindowBounds::Windowed(bounds) => *bounds,
|
||||
WindowBounds::Maximized(bounds) => *bounds,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use super::{BladeAtlas, PATH_TEXTURE_FORMAT};
|
||||
use crate::{
|
||||
AtlasTextureKind, AtlasTile, Bounds, ContentMask, Hsla, MonochromeSprite, Path, PathId,
|
||||
PathVertex, DevicePixels, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
|
||||
PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
|
||||
Underline,
|
||||
};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
@@ -417,10 +417,10 @@ impl BladeRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
|
||||
pub fn update_drawable_size(&mut self, size: Size<f64>) {
|
||||
let gpu_size = gpu::Extent {
|
||||
width: size.width.0 as u32,
|
||||
height: size.height.0 as u32,
|
||||
width: size.width as u32,
|
||||
height: size.height as u32,
|
||||
depth: 1,
|
||||
};
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ impl LinuxClient for HeadlessClient {
|
||||
None
|
||||
}
|
||||
|
||||
fn can_open_windows(&self) -> anyhow::Result<()> {
|
||||
return Err(anyhow::anyhow!("neither DISPLAY, nor WAYLAND_DISPLAY found. You can still run zed for remote development with --dev-server-token."));
|
||||
}
|
||||
|
||||
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||
None
|
||||
}
|
||||
@@ -67,14 +71,8 @@ impl LinuxClient for HeadlessClient {
|
||||
&self,
|
||||
_handle: AnyWindowHandle,
|
||||
_params: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>> {
|
||||
Err(anyhow::anyhow!(
|
||||
"neither DISPLAY nor WAYLAND_DISPLAY is set. You can run in headless mode"
|
||||
))
|
||||
}
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
"headless"
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_cursor_style(&self, _style: CursorStyle) {}
|
||||
|
||||
@@ -39,8 +39,8 @@ use crate::{
|
||||
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CosmicTextSystem, CursorStyle,
|
||||
DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers,
|
||||
OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler,
|
||||
PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, SharedString,
|
||||
Size, Task, WindowAppearance, WindowOptions, WindowParams,
|
||||
PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, Size, Task,
|
||||
WindowAppearance, WindowOptions, WindowParams,
|
||||
};
|
||||
|
||||
use super::x11::X11Client;
|
||||
@@ -54,17 +54,18 @@ pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
|
||||
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
|
||||
|
||||
pub trait LinuxClient {
|
||||
fn compositor_name(&self) -> &'static str;
|
||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
|
||||
fn can_open_windows(&self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>>;
|
||||
) -> Box<dyn PlatformWindow>;
|
||||
fn set_cursor_style(&self, style: CursorStyle);
|
||||
fn open_uri(&self, uri: &str);
|
||||
fn write_to_primary(&self, item: ClipboardItem);
|
||||
@@ -151,12 +152,12 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
});
|
||||
}
|
||||
|
||||
fn quit(&self) {
|
||||
self.with_common(|common| common.signal.stop());
|
||||
fn can_open_windows(&self) -> anyhow::Result<()> {
|
||||
self.can_open_windows()
|
||||
}
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
self.compositor_name()
|
||||
fn quit(&self) {
|
||||
self.with_common(|common| common.signal.stop());
|
||||
}
|
||||
|
||||
fn restart(&self, binary_path: Option<PathBuf>) {
|
||||
@@ -244,7 +245,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>> {
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
self.open_window(handle, options)
|
||||
}
|
||||
|
||||
@@ -368,6 +369,23 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
});
|
||||
}
|
||||
|
||||
fn os_name(&self) -> &'static str {
|
||||
"Linux"
|
||||
}
|
||||
|
||||
fn os_version(&self) -> Result<SemanticVersion> {
|
||||
Ok(SemanticVersion::new(1, 0, 0))
|
||||
}
|
||||
|
||||
fn app_version(&self) -> Result<SemanticVersion> {
|
||||
const VERSION: Option<&str> = option_env!("RELEASE_VERSION");
|
||||
if let Some(version) = VERSION {
|
||||
version.parse()
|
||||
} else {
|
||||
Ok(SemanticVersion::new(1, 0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
fn app_path(&self) -> Result<PathBuf> {
|
||||
// get the path of the executable of the current process
|
||||
let exe_path = std::env::current_exe()?;
|
||||
@@ -492,8 +510,6 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
self.read_from_clipboard()
|
||||
}
|
||||
|
||||
fn add_recent_document(&self, _path: &Path) {}
|
||||
}
|
||||
|
||||
pub(super) fn open_uri_internal(uri: &str, activation_token: Option<&str>) {
|
||||
|
||||
@@ -63,9 +63,7 @@ use crate::platform::linux::is_within_click_distance;
|
||||
use crate::platform::linux::wayland::cursor::Cursor;
|
||||
use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
|
||||
use crate::platform::linux::wayland::window::WaylandWindow;
|
||||
use crate::platform::linux::xdg_desktop_portal::{
|
||||
cursor_settings, Event as XDPEvent, XDPEventSource,
|
||||
};
|
||||
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
|
||||
use crate::platform::linux::LinuxClient;
|
||||
use crate::platform::PlatformWindow;
|
||||
use crate::{
|
||||
@@ -405,14 +403,9 @@ impl WaylandClient {
|
||||
|
||||
let handle = event_loop.handle();
|
||||
handle
|
||||
.insert_source(main_receiver, {
|
||||
let handle = handle.clone();
|
||||
move |event, _, _: &mut WaylandClientStatePtr| {
|
||||
if let calloop::channel::Event::Msg(runnable) = event {
|
||||
handle.insert_idle(|_| {
|
||||
runnable.run();
|
||||
});
|
||||
}
|
||||
.insert_source(main_receiver, |event, _, _: &mut WaylandClientStatePtr| {
|
||||
if let calloop::channel::Event::Msg(runnable) = event {
|
||||
runnable.run();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@@ -432,7 +425,7 @@ impl WaylandClient {
|
||||
|
||||
let (primary, clipboard) = unsafe { create_clipboards_from_external(display) };
|
||||
|
||||
let mut cursor = Cursor::new(&conn, &globals, 24);
|
||||
let cursor = Cursor::new(&conn, &globals, 24);
|
||||
|
||||
handle
|
||||
.insert_source(XDPEventSource::new(&common.background_executor), {
|
||||
@@ -448,24 +441,10 @@ impl WaylandClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
XDPEvent::CursorTheme(theme) => {
|
||||
if let Some(client) = client.0.upgrade() {
|
||||
let mut client = client.borrow_mut();
|
||||
client.cursor.set_theme(theme.as_str(), None);
|
||||
}
|
||||
}
|
||||
XDPEvent::CursorSize(size) => {
|
||||
if let Some(client) = client.0.upgrade() {
|
||||
let mut client = client.borrow_mut();
|
||||
client.cursor.set_size(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let foreground = common.foreground_executor.clone();
|
||||
|
||||
let mut state = Rc::new(RefCell::new(WaylandClientState {
|
||||
serial_tracker: SerialTracker::new(),
|
||||
globals,
|
||||
@@ -527,18 +506,6 @@ impl WaylandClient {
|
||||
pending_open_uri: None,
|
||||
}));
|
||||
|
||||
foreground
|
||||
.spawn({
|
||||
let state = state.clone();
|
||||
async move {
|
||||
if let Ok((theme, size)) = cursor_settings().await {
|
||||
let mut state = state.borrow_mut();
|
||||
state.cursor.set_theme(theme.as_str(), size);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
WaylandSource::new(conn, event_queue)
|
||||
.insert(handle)
|
||||
.unwrap();
|
||||
@@ -557,7 +524,7 @@ impl LinuxClient for WaylandClient {
|
||||
Rc::new(WaylandDisplay {
|
||||
id: id.clone(),
|
||||
name: output.name.clone(),
|
||||
bounds: output.bounds.to_pixels(output.scale as f32),
|
||||
bounds: output.bounds,
|
||||
}) as Rc<dyn PlatformDisplay>
|
||||
})
|
||||
.collect()
|
||||
@@ -573,7 +540,7 @@ impl LinuxClient for WaylandClient {
|
||||
Rc::new(WaylandDisplay {
|
||||
id: object_id.clone(),
|
||||
name: output.name.clone(),
|
||||
bounds: output.bounds.to_pixels(output.scale as f32),
|
||||
bounds: output.bounds,
|
||||
}) as Rc<dyn PlatformDisplay>
|
||||
})
|
||||
})
|
||||
@@ -587,7 +554,7 @@ impl LinuxClient for WaylandClient {
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
params: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>> {
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
let (window, surface_id) = WaylandWindow::new(
|
||||
@@ -596,10 +563,10 @@ impl LinuxClient for WaylandClient {
|
||||
WaylandClientStatePtr(Rc::downgrade(&self.0)),
|
||||
params,
|
||||
state.common.appearance,
|
||||
)?;
|
||||
);
|
||||
state.windows.insert(surface_id, window.0.clone());
|
||||
|
||||
Ok(Box::new(window))
|
||||
Box::new(window)
|
||||
}
|
||||
|
||||
fn set_cursor_style(&self, style: CursorStyle) {
|
||||
@@ -721,10 +688,6 @@ impl LinuxClient for WaylandClient {
|
||||
.as_ref()
|
||||
.map(|window| window.handle())
|
||||
}
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
"Wayland"
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {
|
||||
@@ -1109,15 +1072,14 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
..
|
||||
} => {
|
||||
let focused_window = state.keyboard_focused_window.clone();
|
||||
let Some(focused_window) = focused_window else {
|
||||
return;
|
||||
};
|
||||
|
||||
let keymap_state = state.keymap_state.as_mut().unwrap();
|
||||
keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
state.modifiers = Modifiers::from_xkb(keymap_state);
|
||||
|
||||
let Some(focused_window) = focused_window else {
|
||||
return;
|
||||
};
|
||||
|
||||
let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
|
||||
modifiers: state.modifiers,
|
||||
});
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
use crate::Globals;
|
||||
use util::ResultExt;
|
||||
|
||||
use wayland_client::protocol::wl_pointer::WlPointer;
|
||||
use wayland_client::protocol::wl_surface::WlSurface;
|
||||
use wayland_client::protocol::{wl_pointer::WlPointer, wl_shm::WlShm};
|
||||
use wayland_client::Connection;
|
||||
use wayland_cursor::{CursorImageBuffer, CursorTheme};
|
||||
|
||||
pub(crate) struct Cursor {
|
||||
theme: Option<CursorTheme>,
|
||||
theme_name: Option<String>,
|
||||
surface: WlSurface,
|
||||
size: u32,
|
||||
shm: WlShm,
|
||||
connection: Connection,
|
||||
}
|
||||
|
||||
impl Drop for Cursor {
|
||||
@@ -26,49 +22,10 @@ impl Cursor {
|
||||
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
|
||||
Self {
|
||||
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
|
||||
theme_name: None,
|
||||
surface: globals.compositor.create_surface(&globals.qh, ()),
|
||||
shm: globals.shm.clone(),
|
||||
connection: connection.clone(),
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_theme(&mut self, theme_name: &str, size: Option<u32>) {
|
||||
if let Some(size) = size {
|
||||
self.size = size;
|
||||
}
|
||||
if let Some(theme) =
|
||||
CursorTheme::load_from_name(&self.connection, self.shm.clone(), theme_name, self.size)
|
||||
.log_err()
|
||||
{
|
||||
self.theme = Some(theme);
|
||||
self.theme_name = Some(theme_name.to_string());
|
||||
} else if let Some(theme) =
|
||||
CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err()
|
||||
{
|
||||
self.theme = Some(theme);
|
||||
self.theme_name = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_size(&mut self, size: u32) {
|
||||
self.size = size;
|
||||
self.theme = self
|
||||
.theme_name
|
||||
.as_ref()
|
||||
.and_then(|name| {
|
||||
CursorTheme::load_from_name(
|
||||
&self.connection,
|
||||
self.shm.clone(),
|
||||
name.as_str(),
|
||||
self.size,
|
||||
)
|
||||
.log_err()
|
||||
})
|
||||
.or_else(|| CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err());
|
||||
}
|
||||
|
||||
pub fn set_icon(&mut self, wl_pointer: &WlPointer, serial_id: u32, mut cursor_icon_name: &str) {
|
||||
if let Some(theme) = &mut self.theme {
|
||||
let mut buffer: Option<&CursorImageBuffer>;
|
||||
|
||||
@@ -6,14 +6,14 @@ use std::{
|
||||
use uuid::Uuid;
|
||||
use wayland_backend::client::ObjectId;
|
||||
|
||||
use crate::{Bounds, DisplayId, Pixels, PlatformDisplay};
|
||||
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct WaylandDisplay {
|
||||
/// The ID of the wl_output object
|
||||
pub id: ObjectId,
|
||||
pub name: Option<String>,
|
||||
pub bounds: Bounds<Pixels>,
|
||||
pub bounds: Bounds<DevicePixels>,
|
||||
}
|
||||
|
||||
impl Hash for WaylandDisplay {
|
||||
@@ -35,7 +35,7 @@ impl PlatformDisplay for WaylandDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::ffi::c_void;
|
||||
use std::num::NonZeroU32;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -25,9 +26,9 @@ use crate::platform::linux::wayland::serial::SerialKind;
|
||||
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
|
||||
use crate::scene::Scene;
|
||||
use crate::{
|
||||
px, size, AnyWindowHandle, Bounds, Globals, Modifiers, Output, Pixels, PlatformDisplay,
|
||||
PlatformInput, Point, PromptLevel, Size, WaylandClientStatePtr, WindowAppearance,
|
||||
WindowBackgroundAppearance, WindowBounds, WindowParams,
|
||||
px, size, AnyWindowHandle, Bounds, DevicePixels, Globals, Modifiers, Output, Pixels,
|
||||
PlatformDisplay, PlatformInput, Point, PromptLevel, Size, WaylandClientStatePtr,
|
||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowParams,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -75,13 +76,13 @@ pub struct WaylandWindowState {
|
||||
display: Option<(ObjectId, Output)>,
|
||||
globals: Globals,
|
||||
renderer: BladeRenderer,
|
||||
bounds: Bounds<Pixels>,
|
||||
bounds: Bounds<u32>,
|
||||
scale: f32,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
decoration_state: WaylandDecorationState,
|
||||
fullscreen: bool,
|
||||
maximized: bool,
|
||||
windowed_bounds: Bounds<Pixels>,
|
||||
windowed_bounds: Bounds<DevicePixels>,
|
||||
client: WaylandClientStatePtr,
|
||||
handle: AnyWindowHandle,
|
||||
active: bool,
|
||||
@@ -106,7 +107,9 @@ impl WaylandWindowState {
|
||||
client: WaylandClientStatePtr,
|
||||
globals: Globals,
|
||||
options: WindowParams,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> Self {
|
||||
let bounds = options.bounds.map(|p| p.0 as u32);
|
||||
|
||||
let raw = RawWindow {
|
||||
window: surface.id().as_ptr().cast::<c_void>(),
|
||||
display: surface
|
||||
@@ -127,18 +130,18 @@ impl WaylandWindowState {
|
||||
},
|
||||
)
|
||||
}
|
||||
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
|
||||
.unwrap(),
|
||||
);
|
||||
let config = BladeSurfaceConfig {
|
||||
size: gpu::Extent {
|
||||
width: options.bounds.size.width.0 as u32,
|
||||
height: options.bounds.size.height.0 as u32,
|
||||
width: bounds.size.width,
|
||||
height: bounds.size.height,
|
||||
depth: 1,
|
||||
},
|
||||
transparent: options.window_background != WindowBackgroundAppearance::Opaque,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
xdg_surface,
|
||||
acknowledged_first_configure: false,
|
||||
surface,
|
||||
@@ -150,7 +153,7 @@ impl WaylandWindowState {
|
||||
outputs: HashMap::default(),
|
||||
display: None,
|
||||
renderer: BladeRenderer::new(gpu, config),
|
||||
bounds: options.bounds,
|
||||
bounds,
|
||||
scale: 1.0,
|
||||
input_handler: None,
|
||||
decoration_state: WaylandDecorationState::Client,
|
||||
@@ -161,7 +164,7 @@ impl WaylandWindowState {
|
||||
appearance,
|
||||
handle,
|
||||
active: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +224,7 @@ impl WaylandWindow {
|
||||
client: WaylandClientStatePtr,
|
||||
params: WindowParams,
|
||||
appearance: WindowAppearance,
|
||||
) -> anyhow::Result<(Self, ObjectId)> {
|
||||
) -> (Self, ObjectId) {
|
||||
let surface = globals.compositor.create_surface(&globals.qh, ());
|
||||
let xdg_surface = globals
|
||||
.wm_base
|
||||
@@ -264,14 +267,14 @@ impl WaylandWindow {
|
||||
client,
|
||||
globals,
|
||||
params,
|
||||
)?)),
|
||||
))),
|
||||
callbacks: Rc::new(RefCell::new(Callbacks::default())),
|
||||
});
|
||||
|
||||
// Kick things off
|
||||
surface.commit();
|
||||
|
||||
Ok((this, surface.id()))
|
||||
(this, surface.id())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,16 +349,10 @@ impl WaylandWindowStatePtr {
|
||||
pub fn handle_toplevel_event(&self, event: xdg_toplevel::Event) -> bool {
|
||||
match event {
|
||||
xdg_toplevel::Event::Configure {
|
||||
width,
|
||||
height,
|
||||
mut width,
|
||||
mut height,
|
||||
states,
|
||||
} => {
|
||||
let mut size = if width == 0 || height == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(size(px(width as f32), px(height as f32)))
|
||||
};
|
||||
|
||||
let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8));
|
||||
let maximized = states.contains(&(xdg_toplevel::State::Maximized as u8));
|
||||
|
||||
@@ -365,20 +362,19 @@ impl WaylandWindowStatePtr {
|
||||
state.maximized = maximized;
|
||||
|
||||
if got_unmaximized {
|
||||
size = Some(state.windowed_bounds.size);
|
||||
} else if !fullscreen && !maximized {
|
||||
if let Some(size) = size {
|
||||
state.windowed_bounds = Bounds {
|
||||
origin: Point::default(),
|
||||
size,
|
||||
};
|
||||
}
|
||||
width = state.windowed_bounds.size.width.0;
|
||||
height = state.windowed_bounds.size.height.0;
|
||||
} else if width != 0 && height != 0 && !fullscreen && !maximized {
|
||||
state.windowed_bounds = Bounds {
|
||||
origin: Point::default(),
|
||||
size: size(width.into(), height.into()),
|
||||
};
|
||||
}
|
||||
|
||||
let width = NonZeroU32::new(width as u32);
|
||||
let height = NonZeroU32::new(height as u32);
|
||||
drop(state);
|
||||
if let Some(size) = size {
|
||||
self.resize(size);
|
||||
}
|
||||
self.resize(width, height);
|
||||
|
||||
false
|
||||
}
|
||||
@@ -487,43 +483,63 @@ impl WaylandWindowStatePtr {
|
||||
bounds
|
||||
}
|
||||
|
||||
pub fn set_size_and_scale(&self, size: Option<Size<Pixels>>, scale: Option<f32>) {
|
||||
let (size, scale) = {
|
||||
pub fn set_size_and_scale(
|
||||
&self,
|
||||
width: Option<NonZeroU32>,
|
||||
height: Option<NonZeroU32>,
|
||||
scale: Option<f32>,
|
||||
) {
|
||||
let (width, height, scale) = {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if size.map_or(true, |size| size == state.bounds.size)
|
||||
if width.map_or(true, |width| width.get() == state.bounds.size.width)
|
||||
&& height.map_or(true, |height| height.get() == state.bounds.size.height)
|
||||
&& scale.map_or(true, |scale| scale == state.scale)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(size) = size {
|
||||
state.bounds.size = size;
|
||||
if let Some(width) = width {
|
||||
state.bounds.size.width = width.get();
|
||||
}
|
||||
if let Some(height) = height {
|
||||
state.bounds.size.height = height.get();
|
||||
}
|
||||
if let Some(scale) = scale {
|
||||
state.scale = scale;
|
||||
}
|
||||
let device_bounds = state.bounds.to_device_pixels(state.scale);
|
||||
state.renderer.update_drawable_size(device_bounds.size);
|
||||
(state.bounds.size, state.scale)
|
||||
let width = state.bounds.size.width;
|
||||
let height = state.bounds.size.height;
|
||||
let scale = state.scale;
|
||||
state.renderer.update_drawable_size(size(
|
||||
width as f64 * scale as f64,
|
||||
height as f64 * scale as f64,
|
||||
));
|
||||
(width, height, scale)
|
||||
};
|
||||
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().resize {
|
||||
fun(size, scale);
|
||||
fun(
|
||||
Size {
|
||||
width: px(width as f32),
|
||||
height: px(height as f32),
|
||||
},
|
||||
scale,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let state = self.state.borrow();
|
||||
if let Some(viewport) = &state.viewport {
|
||||
viewport.set_destination(size.width.0 as i32, size.height.0 as i32);
|
||||
viewport.set_destination(width as i32, height as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&self, size: Size<Pixels>) {
|
||||
self.set_size_and_scale(Some(size), None);
|
||||
pub fn resize(&self, width: Option<NonZeroU32>, height: Option<NonZeroU32>) {
|
||||
self.set_size_and_scale(width, height, None);
|
||||
}
|
||||
|
||||
pub fn rescale(&self, scale: f32) {
|
||||
self.set_size_and_scale(None, Some(scale));
|
||||
self.set_size_and_scale(None, None, Some(scale));
|
||||
}
|
||||
|
||||
/// Notifies the window of the state of the decorations.
|
||||
@@ -609,8 +625,8 @@ impl rwh::HasDisplayHandle for WaylandWindow {
|
||||
}
|
||||
|
||||
impl PlatformWindow for WaylandWindow {
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
self.borrow().bounds
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.borrow().bounds.map(|p| DevicePixels(p as i32))
|
||||
}
|
||||
|
||||
fn is_maximized(&self) -> bool {
|
||||
@@ -624,13 +640,16 @@ impl PlatformWindow for WaylandWindow {
|
||||
} else if state.maximized {
|
||||
WindowBounds::Maximized(state.windowed_bounds)
|
||||
} else {
|
||||
drop(state);
|
||||
WindowBounds::Windowed(self.bounds())
|
||||
WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p as i32)))
|
||||
}
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
self.borrow().bounds.size
|
||||
let state = self.borrow();
|
||||
Size {
|
||||
width: Pixels(state.bounds.size.width as f32),
|
||||
height: Pixels(state.bounds.size.height as f32),
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
@@ -642,12 +661,11 @@ impl PlatformWindow for WaylandWindow {
|
||||
}
|
||||
|
||||
fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
let state = self.borrow();
|
||||
state.display.as_ref().map(|(id, display)| {
|
||||
self.borrow().display.as_ref().map(|(id, display)| {
|
||||
Rc::new(WaylandDisplay {
|
||||
id: id.clone(),
|
||||
name: display.name.clone(),
|
||||
bounds: display.bounds.to_pixels(state.scale),
|
||||
bounds: display.bounds,
|
||||
}) as Rc<dyn PlatformDisplay>
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::ffi::OsString;
|
||||
use std::ops::Deref;
|
||||
use std::rc::{Rc, Weak};
|
||||
@@ -16,9 +16,9 @@ use x11rb::connection::{Connection, RequestConnection};
|
||||
use x11rb::cursor;
|
||||
use x11rb::errors::ConnectionError;
|
||||
use x11rb::protocol::randr::ConnectionExt as _;
|
||||
use x11rb::protocol::xinput::ConnectionExt;
|
||||
use x11rb::protocol::xinput::{ConnectionExt, KeyCode};
|
||||
use x11rb::protocol::xkb::ConnectionExt as _;
|
||||
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
|
||||
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _, KeyButMask};
|
||||
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
|
||||
use x11rb::resource_manager::Database;
|
||||
use x11rb::xcb_ffi::XCBConnection;
|
||||
@@ -32,7 +32,7 @@ use crate::platform::{LinuxCommon, PlatformWindow};
|
||||
use crate::{
|
||||
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
|
||||
Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
|
||||
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
ScrollDelta, Size, TouchPhase, WindowParams, X11Window, XimXCBConnection,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -96,6 +96,13 @@ impl From<xim::ClientError> for EventHandlerError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct KeyEvent {
|
||||
time: Instant,
|
||||
state: KeyButMask,
|
||||
code: KeyCode,
|
||||
}
|
||||
|
||||
pub struct X11ClientState {
|
||||
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
|
||||
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
|
||||
@@ -113,7 +120,7 @@ pub struct X11ClientState {
|
||||
pub(crate) windows: HashMap<xproto::Window, WindowRef>,
|
||||
pub(crate) focused_window: Option<xproto::Window>,
|
||||
pub(crate) xkb: xkbc::State,
|
||||
pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
|
||||
pub(crate) ximc: Option<X11rbClient<XimXCBConnection>>,
|
||||
pub(crate) xim_handler: Option<XimHandler>,
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
@@ -131,6 +138,8 @@ pub struct X11ClientState {
|
||||
pub(crate) common: LinuxCommon,
|
||||
pub(crate) clipboard: X11ClipboardContext<Clipboard>,
|
||||
pub(crate) primary: X11ClipboardContext<Primary>,
|
||||
|
||||
last_key_event: Option<KeyEvent>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -165,17 +174,9 @@ impl X11Client {
|
||||
let handle = event_loop.handle();
|
||||
|
||||
handle
|
||||
.insert_source(main_receiver, {
|
||||
let handle = handle.clone();
|
||||
move |event, _, _: &mut X11Client| {
|
||||
if let calloop::channel::Event::Msg(runnable) = event {
|
||||
// Insert the runnables as idle callbacks, so we make sure that user-input and X11
|
||||
// events have higher priority and runnables are only worked off after the event
|
||||
// callbacks.
|
||||
handle.insert_idle(|_| {
|
||||
runnable.run();
|
||||
});
|
||||
}
|
||||
.insert_source(main_receiver, |event, _, _: &mut X11Client| {
|
||||
if let calloop::channel::Event::Msg(runnable) = event {
|
||||
runnable.run();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@@ -282,7 +283,9 @@ impl X11Client {
|
||||
|
||||
let xcb_connection = Rc::new(xcb_connection);
|
||||
|
||||
let ximc = X11rbClient::init(Rc::clone(&xcb_connection), x_root_index, None).ok();
|
||||
let xim_xcb_connection =
|
||||
XimXCBConnection::new(Rc::clone(&xcb_connection), Rc::new(Cell::new(true)));
|
||||
let ximc = X11rbClient::init(xim_xcb_connection.clone(), x_root_index, None).ok();
|
||||
let xim_handler = if ximc.is_some() {
|
||||
Some(XimHandler::new())
|
||||
} else {
|
||||
@@ -302,8 +305,42 @@ impl X11Client {
|
||||
{
|
||||
let xcb_connection = xcb_connection.clone();
|
||||
move |_readiness, _, client| {
|
||||
// println!("---------------------------------------------------");
|
||||
// let mut events_count = 0;
|
||||
// let start = Instant::now();
|
||||
|
||||
xim_xcb_connection.set_can_flush(false);
|
||||
while let Some(event) = xcb_connection.poll_for_event()? {
|
||||
// events_count += 1;
|
||||
|
||||
// let event_start = Instant::now();
|
||||
// println!(
|
||||
// "--> event {}: {} (sequnce: {:?})",
|
||||
// events_count,
|
||||
// ev_name,
|
||||
// event.wire_sequence_number()
|
||||
// );
|
||||
|
||||
// let _drop = util::defer(move || {
|
||||
// println!(
|
||||
// "<-- event {}: {}, took: {:?}",
|
||||
// events_count,
|
||||
// ev_name,
|
||||
// event_start.elapsed()
|
||||
// );
|
||||
// });
|
||||
|
||||
let mut state = client.0.borrow_mut();
|
||||
|
||||
let last_key_event = match event {
|
||||
Event::KeyPress(ev) | Event::KeyRelease(ev) => {
|
||||
state.last_key_event.replace(KeyEvent { time: Instant::now(), state: ev.state, code: ev.detail });
|
||||
None
|
||||
}
|
||||
_ => state.last_key_event.clone()
|
||||
};
|
||||
// let last_key_event_time = state.last_key_event.clone();
|
||||
|
||||
if state.ximc.is_none() || state.xim_handler.is_none() {
|
||||
drop(state);
|
||||
client.handle_event(event);
|
||||
@@ -312,9 +349,9 @@ impl X11Client {
|
||||
|
||||
let mut ximc = state.ximc.take().unwrap();
|
||||
let mut xim_handler = state.xim_handler.take().unwrap();
|
||||
|
||||
let xim_connected = xim_handler.connected;
|
||||
drop(state);
|
||||
|
||||
let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
|
||||
Ok(handled) => handled,
|
||||
Err(err) => {
|
||||
@@ -322,21 +359,63 @@ impl X11Client {
|
||||
false
|
||||
}
|
||||
};
|
||||
let xim_callback_event = xim_handler.last_callback_event.take();
|
||||
if let Some(xim_event) = xim_handler.last_callback_event.take() {
|
||||
match xim_event {
|
||||
XimCallbackEvent::XimXEvent(
|
||||
event @ Event::KeyPress(key_event),
|
||||
)
|
||||
| XimCallbackEvent::XimXEvent(
|
||||
event @ Event::KeyRelease(key_event),
|
||||
) => {
|
||||
// let ev_name = event_name(&event);
|
||||
// println!("---> XimXEvent: {}", ev_name);
|
||||
|
||||
if let Some(last_key_event) = last_key_event {
|
||||
// println!(
|
||||
// "last_key_event_time: {}",
|
||||
// last_key_event_time
|
||||
// );
|
||||
// println!(
|
||||
// "event.time: {} (sequence: {})",
|
||||
// key_event.time, key_event.sequence
|
||||
// );
|
||||
// let lag_ms = last_key_event_time.saturating_sub(key_event.time);
|
||||
// if lag_ms > 100
|
||||
let lag_ms = last_key_event.time.elapsed();
|
||||
if (lag_ms > Duration::from_millis(100) && last_key_event.state == key_event.state && last_key_event.code == key_event.detail) ||
|
||||
(lag_ms > Duration::from_millis(50) && last_key_event.state != key_event.state && last_key_event.code != key_event.detail)
|
||||
{
|
||||
let name = event_name(&event);
|
||||
println!(">>>>>>>>>>>>>> dropping old event (name: {}, sequence: {}, lag_ms: {:?}) <<<<<<<<<<<<<<<<<<<<<", name, key_event.sequence, lag_ms);
|
||||
// drop(state);
|
||||
// continue;
|
||||
} else {
|
||||
client.handle_event(event);
|
||||
}
|
||||
} else {
|
||||
client.handle_event(event);
|
||||
}
|
||||
}
|
||||
XimCallbackEvent::XimXEvent(event) => {
|
||||
client.handle_event(event);
|
||||
}
|
||||
XimCallbackEvent::XimCommitEvent(window, text) => {
|
||||
client.xim_handle_commit(window, text);
|
||||
}
|
||||
XimCallbackEvent::XimPreeditEvent(window, text) => {
|
||||
client.xim_handle_preedit(window, text);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut state = client.0.borrow_mut();
|
||||
state.ximc = Some(ximc);
|
||||
state.xim_handler = Some(xim_handler);
|
||||
drop(state);
|
||||
|
||||
if let Some(event) = xim_callback_event {
|
||||
client.handle_xim_callback_event(event);
|
||||
}
|
||||
|
||||
if xim_filtered {
|
||||
// println!(" -> xim filtered");
|
||||
continue;
|
||||
}
|
||||
|
||||
if xim_connected {
|
||||
client.xim_handle_event(event);
|
||||
} else {
|
||||
@@ -344,12 +423,45 @@ impl X11Client {
|
||||
}
|
||||
}
|
||||
|
||||
// println!(
|
||||
// "-------------- events_count: {:?}, took: {:?} ----------\n\n",
|
||||
// events_count,
|
||||
// start.elapsed()
|
||||
// );
|
||||
|
||||
xim_xcb_connection.set_can_flush(true);
|
||||
xim_xcb_connection.flush()?;
|
||||
|
||||
Ok(calloop::PostAction::Continue)
|
||||
}
|
||||
},
|
||||
)
|
||||
.expect("Failed to initialize x11 event source");
|
||||
|
||||
// handle
|
||||
// .insert_source(xim_rx, {
|
||||
// move |chan_event, _, client| match chan_event {
|
||||
// channel::Event::Msg(xim_event) => {
|
||||
// // println!(".......... XimCallBackEvent ...............");
|
||||
// match xim_event {
|
||||
// XimCallbackEvent::XimXEvent(event) => {
|
||||
// println!("--> XimXEvent");
|
||||
// client.handle_event(event);
|
||||
// }
|
||||
// XimCallbackEvent::XimCommitEvent(window, text) => {
|
||||
// client.xim_handle_commit(window, text);
|
||||
// }
|
||||
// XimCallbackEvent::XimPreeditEvent(window, text) => {
|
||||
// client.xim_handle_preedit(window, text);
|
||||
// }
|
||||
// };
|
||||
// // println!("......... DONE: XimCallBackEvent ...............");
|
||||
// }
|
||||
// channel::Event::Closed => {
|
||||
// log::error!("XIM Event Sender dropped")
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// .expect("Failed to initialize XIM event source");
|
||||
handle
|
||||
.insert_source(XDPEventSource::new(&common.background_executor), {
|
||||
move |event, _, client| match event {
|
||||
@@ -359,9 +471,6 @@ impl X11Client {
|
||||
window.window.set_appearance(appearance);
|
||||
}
|
||||
}
|
||||
XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {
|
||||
// noop, X11 manages this for us.
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@@ -400,6 +509,8 @@ impl X11Client {
|
||||
|
||||
clipboard,
|
||||
primary,
|
||||
|
||||
last_key_event: None,
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -461,11 +572,12 @@ impl X11Client {
|
||||
state
|
||||
.windows
|
||||
.get(&win)
|
||||
.filter(|window_reference| !window_reference.window.state.borrow().destroyed)
|
||||
.map(|window_reference| window_reference.window.clone())
|
||||
}
|
||||
|
||||
fn handle_event(&self, event: Event) -> Option<()> {
|
||||
// println!("handle_event: {:?}", event_name(&event));
|
||||
|
||||
match event {
|
||||
Event::ClientMessage(event) => {
|
||||
let window = self.get_window(event.window)?;
|
||||
@@ -498,6 +610,7 @@ impl X11Client {
|
||||
Event::Expose(event) => {
|
||||
let window = self.get_window(event.window)?;
|
||||
window.refresh();
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
Event::FocusIn(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
@@ -528,20 +641,15 @@ impl X11Client {
|
||||
0,
|
||||
event.locked_group.into(),
|
||||
);
|
||||
|
||||
let modifiers = Modifiers::from_xkb(&state.xkb);
|
||||
if state.modifiers == modifiers {
|
||||
drop(state);
|
||||
} else {
|
||||
let focused_window_id = state.focused_window?;
|
||||
state.modifiers = modifiers;
|
||||
drop(state);
|
||||
let focused_window_id = state.focused_window?;
|
||||
state.modifiers = modifiers;
|
||||
drop(state);
|
||||
|
||||
let focused_window = self.get_window(focused_window_id)?;
|
||||
focused_window.handle_input(PlatformInput::ModifiersChanged(
|
||||
ModifiersChangedEvent { modifiers },
|
||||
));
|
||||
}
|
||||
let focused_window = self.get_window(focused_window_id)?;
|
||||
focused_window.handle_input(PlatformInput::ModifiersChanged(
|
||||
ModifiersChangedEvent { modifiers },
|
||||
));
|
||||
}
|
||||
Event::KeyPress(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
@@ -593,6 +701,7 @@ impl X11Client {
|
||||
keystroke
|
||||
};
|
||||
drop(state);
|
||||
// println!("keydown. keystroke.key: {:?}", keystroke.key);
|
||||
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: false,
|
||||
@@ -798,28 +907,21 @@ impl X11Client {
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn handle_xim_callback_event(&self, event: XimCallbackEvent) {
|
||||
match event {
|
||||
XimCallbackEvent::XimXEvent(event) => {
|
||||
self.handle_event(event);
|
||||
}
|
||||
XimCallbackEvent::XimCommitEvent(window, text) => {
|
||||
self.xim_handle_commit(window, text);
|
||||
}
|
||||
XimCallbackEvent::XimPreeditEvent(window, text) => {
|
||||
self.xim_handle_preedit(window, text);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn xim_handle_event(&self, event: Event) -> Option<()> {
|
||||
let name = event_name(&event);
|
||||
match event {
|
||||
Event::KeyPress(event) | Event::KeyRelease(event) => {
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
let mut ximc = state.ximc.take().unwrap();
|
||||
let mut xim_handler = state.xim_handler.take().unwrap();
|
||||
drop(state);
|
||||
xim_handler.window = event.event;
|
||||
|
||||
println!(
|
||||
"--> ximc.forward_event({}), sequence: {}",
|
||||
name, event.sequence
|
||||
);
|
||||
ximc.forward_event(
|
||||
xim_handler.im_id,
|
||||
xim_handler.ic_id,
|
||||
@@ -892,10 +994,6 @@ impl X11Client {
|
||||
}
|
||||
|
||||
impl LinuxClient for X11Client {
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
"X11"
|
||||
}
|
||||
|
||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
|
||||
f(&mut self.0.borrow_mut().common)
|
||||
}
|
||||
@@ -908,11 +1006,8 @@ impl LinuxClient for X11Client {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(root_id, _)| {
|
||||
Some(Rc::new(X11Display::new(
|
||||
&state.xcb_connection,
|
||||
state.scale_factor,
|
||||
root_id,
|
||||
)?) as Rc<dyn PlatformDisplay>)
|
||||
Some(Rc::new(X11Display::new(&state.xcb_connection, root_id)?)
|
||||
as Rc<dyn PlatformDisplay>)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -921,12 +1016,8 @@ impl LinuxClient for X11Client {
|
||||
let state = self.0.borrow();
|
||||
|
||||
Some(Rc::new(
|
||||
X11Display::new(
|
||||
&state.xcb_connection,
|
||||
state.scale_factor,
|
||||
state.x_root_index,
|
||||
)
|
||||
.expect("There should always be a root index"),
|
||||
X11Display::new(&state.xcb_connection, state.x_root_index)
|
||||
.expect("There should always be a root index"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -935,7 +1026,6 @@ impl LinuxClient for X11Client {
|
||||
|
||||
Some(Rc::new(X11Display::new(
|
||||
&state.xcb_connection,
|
||||
state.scale_factor,
|
||||
id.0 as usize,
|
||||
)?))
|
||||
}
|
||||
@@ -944,7 +1034,7 @@ impl LinuxClient for X11Client {
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
params: WindowParams,
|
||||
) -> anyhow::Result<Box<dyn PlatformWindow>> {
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
let mut state = self.0.borrow_mut();
|
||||
let x_window = state.xcb_connection.generate_id().unwrap();
|
||||
|
||||
@@ -959,7 +1049,7 @@ impl LinuxClient for X11Client {
|
||||
&state.atoms,
|
||||
state.scale_factor,
|
||||
state.common.appearance,
|
||||
)?;
|
||||
);
|
||||
|
||||
let screen_resources = state
|
||||
.xcb_connection
|
||||
@@ -1027,7 +1117,7 @@ impl LinuxClient for X11Client {
|
||||
};
|
||||
|
||||
state.windows.insert(x_window, window_ref);
|
||||
Ok(Box::new(window))
|
||||
Box::new(window)
|
||||
}
|
||||
|
||||
fn set_cursor_style(&self, style: CursorStyle) {
|
||||
@@ -1138,3 +1228,111 @@ pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
|
||||
fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
|
||||
value.integral as f32 + value.frac as f32 / u32::MAX as f32
|
||||
}
|
||||
|
||||
fn event_name(event: &Event) -> &'static str {
|
||||
match event {
|
||||
Event::Unknown(_) => "Event::Unknown",
|
||||
Event::Error(_) => "Event::Error",
|
||||
Event::ButtonPress(_) => "Event::ButtonPress",
|
||||
Event::ButtonRelease(_) => "Event::ButtonRelease",
|
||||
Event::CirculateNotify(_) => "Event::CirculateNotify",
|
||||
Event::CirculateRequest(_) => "Event::CirculateRequest",
|
||||
Event::ClientMessage(_) => "Event::ClientMessage",
|
||||
Event::ColormapNotify(_) => "Event::ColormapNotify",
|
||||
Event::ConfigureNotify(_) => "Event::ConfigureNotify",
|
||||
Event::ConfigureRequest(_) => "Event::ConfigureRequest",
|
||||
Event::CreateNotify(_) => "Event::CreateNotify",
|
||||
Event::DestroyNotify(_) => "Event::DestroyNotify",
|
||||
Event::EnterNotify(_) => "Event::EnterNotify",
|
||||
Event::Expose(_) => "Event::Expose",
|
||||
Event::FocusIn(_) => "Event::FocusIn",
|
||||
Event::FocusOut(_) => "Event::FocusOut",
|
||||
Event::GeGeneric(_) => "Event::GeGeneric",
|
||||
Event::GraphicsExposure(_) => "Event::GraphicsExposure",
|
||||
Event::GravityNotify(_) => "Event::GravityNotify",
|
||||
Event::KeyPress(_) => "Event::KeyPress",
|
||||
Event::KeyRelease(_) => "Event::KeyRelease",
|
||||
Event::KeymapNotify(_) => "Event::KeymapNotify",
|
||||
Event::LeaveNotify(_) => "Event::LeaveNotify",
|
||||
Event::MapNotify(_) => "Event::MapNotify",
|
||||
Event::MapRequest(_) => "Event::MapRequest",
|
||||
Event::MappingNotify(_) => "Event::MappingNotify",
|
||||
Event::MotionNotify(_) => "Event::MotionNotify",
|
||||
Event::NoExposure(_) => "Event::NoExposure",
|
||||
Event::PropertyNotify(_) => "Event::PropertyNotify",
|
||||
Event::ReparentNotify(_) => "Event::ReparentNotify",
|
||||
Event::ResizeRequest(_) => "Event::ResizeRequest",
|
||||
Event::SelectionClear(_) => "Event::SelectionClear",
|
||||
Event::SelectionNotify(_) => "Event::SelectionNotify",
|
||||
Event::SelectionRequest(_) => "Event::SelectionRequest",
|
||||
Event::UnmapNotify(_) => "Event::UnmapNotify",
|
||||
Event::VisibilityNotify(_) => "Event::VisibilityNotify",
|
||||
Event::RandrNotify(_) => "Event::RandrNotify",
|
||||
Event::RandrScreenChangeNotify(_) => "Event::RandrScreenChangeNotify",
|
||||
Event::ShapeNotify(_) => "Event::ShapeNotify",
|
||||
Event::XfixesCursorNotify(_) => "Event::XfixesCursorNotify",
|
||||
Event::XfixesSelectionNotify(_) => "Event::XfixesSelectionNotify",
|
||||
Event::XinputBarrierHit(_) => "Event::XinputBarrierHit",
|
||||
Event::XinputBarrierLeave(_) => "Event::XinputBarrierLeave",
|
||||
Event::XinputButtonPress(_) => "Event::XinputButtonPress",
|
||||
Event::XinputButtonRelease(_) => "Event::XinputButtonRelease",
|
||||
Event::XinputChangeDeviceNotify(_) => "Event::XinputChangeDeviceNotify",
|
||||
Event::XinputDeviceButtonPress(_) => "Event::XinputDeviceButtonPress",
|
||||
Event::XinputDeviceButtonRelease(_) => "Event::XinputDeviceButtonRelease",
|
||||
Event::XinputDeviceButtonStateNotify(_) => "Event::XinputDeviceButtonStateNotify",
|
||||
Event::XinputDeviceChanged(_) => "Event::XinputDeviceChanged",
|
||||
Event::XinputDeviceFocusIn(_) => "Event::XinputDeviceFocusIn",
|
||||
Event::XinputDeviceFocusOut(_) => "Event::XinputDeviceFocusOut",
|
||||
Event::XinputDeviceKeyPress(_) => "Event::XinputDeviceKeyPress",
|
||||
Event::XinputDeviceKeyRelease(_) => "Event::XinputDeviceKeyRelease",
|
||||
Event::XinputDeviceKeyStateNotify(_) => "Event::XinputDeviceKeyStateNotify",
|
||||
Event::XinputDeviceMappingNotify(_) => "Event::XinputDeviceMappingNotify",
|
||||
Event::XinputDeviceMotionNotify(_) => "Event::XinputDeviceMotionNotify",
|
||||
Event::XinputDevicePresenceNotify(_) => "Event::XinputDevicePresenceNotify",
|
||||
Event::XinputDevicePropertyNotify(_) => "Event::XinputDevicePropertyNotify",
|
||||
Event::XinputDeviceStateNotify(_) => "Event::XinputDeviceStateNotify",
|
||||
Event::XinputDeviceValuator(_) => "Event::XinputDeviceValuator",
|
||||
Event::XinputEnter(_) => "Event::XinputEnter",
|
||||
Event::XinputFocusIn(_) => "Event::XinputFocusIn",
|
||||
Event::XinputFocusOut(_) => "Event::XinputFocusOut",
|
||||
Event::XinputGesturePinchBegin(_) => "Event::XinputGesturePinchBegin",
|
||||
Event::XinputGesturePinchEnd(_) => "Event::XinputGesturePinchEnd",
|
||||
Event::XinputGesturePinchUpdate(_) => "Event::XinputGesturePinchUpdate",
|
||||
Event::XinputGestureSwipeBegin(_) => "Event::XinputGestureSwipeBegin",
|
||||
Event::XinputGestureSwipeEnd(_) => "Event::XinputGestureSwipeEnd",
|
||||
Event::XinputGestureSwipeUpdate(_) => "Event::XinputGestureSwipeUpdate",
|
||||
Event::XinputHierarchy(_) => "Event::XinputHierarchy",
|
||||
Event::XinputKeyPress(_) => "Event::XinputKeyPress",
|
||||
Event::XinputKeyRelease(_) => "Event::XinputKeyRelease",
|
||||
Event::XinputLeave(_) => "Event::XinputLeave",
|
||||
Event::XinputMotion(_) => "Event::XinputMotion",
|
||||
Event::XinputProperty(_) => "Event::XinputProperty",
|
||||
Event::XinputProximityIn(_) => "Event::XinputProximityIn",
|
||||
Event::XinputProximityOut(_) => "Event::XinputProximityOut",
|
||||
Event::XinputRawButtonPress(_) => "Event::XinputRawButtonPress",
|
||||
Event::XinputRawButtonRelease(_) => "Event::XinputRawButtonRelease",
|
||||
Event::XinputRawKeyPress(_) => "Event::XinputRawKeyPress",
|
||||
Event::XinputRawKeyRelease(_) => "Event::XinputRawKeyRelease",
|
||||
Event::XinputRawMotion(_) => "Event::XinputRawMotion",
|
||||
Event::XinputRawTouchBegin(_) => "Event::XinputRawTouchBegin",
|
||||
Event::XinputRawTouchEnd(_) => "Event::XinputRawTouchEnd",
|
||||
Event::XinputRawTouchUpdate(_) => "Event::XinputRawTouchUpdate",
|
||||
Event::XinputTouchBegin(_) => "Event::XinputTouchBegin",
|
||||
Event::XinputTouchEnd(_) => "Event::XinputTouchEnd",
|
||||
Event::XinputTouchOwnership(_) => "Event::XinputTouchOwnership",
|
||||
Event::XinputTouchUpdate(_) => "Event::XinputTouchUpdate",
|
||||
Event::XkbAccessXNotify(_) => "Event::XkbAccessXNotify",
|
||||
Event::XkbActionMessage(_) => "Event::XkbActionMessage",
|
||||
Event::XkbBellNotify(_) => "Event::XkbBellNotify",
|
||||
Event::XkbCompatMapNotify(_) => "Event::XkbCompatMapNotify",
|
||||
Event::XkbControlsNotify(_) => "Event::XkbControlsNotify",
|
||||
Event::XkbExtensionDeviceNotify(_) => "Event::XkbExtensionDeviceNotify",
|
||||
Event::XkbIndicatorMapNotify(_) => "Event::XkbIndicatorMapNotify",
|
||||
Event::XkbIndicatorStateNotify(_) => "Event::XkbIndicatorStateNotify",
|
||||
Event::XkbMapNotify(_) => "Event::XkbMapNotify",
|
||||
Event::XkbNamesNotify(_) => "Event::XkbNamesNotify",
|
||||
Event::XkbNewKeyboardNotify(_) => "Event::XkbNewKeyboardNotify",
|
||||
Event::XkbStateNotify(_) => "Event::XkbStateNotify",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,25 @@ use anyhow::Result;
|
||||
use uuid::Uuid;
|
||||
use x11rb::{connection::Connection as _, xcb_ffi::XCBConnection};
|
||||
|
||||
use crate::{px, Bounds, DisplayId, Pixels, PlatformDisplay, Size};
|
||||
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Size};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct X11Display {
|
||||
x_screen_index: usize,
|
||||
bounds: Bounds<Pixels>,
|
||||
bounds: Bounds<DevicePixels>,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl X11Display {
|
||||
pub(crate) fn new(
|
||||
xc: &XCBConnection,
|
||||
scale_factor: f32,
|
||||
x_screen_index: usize,
|
||||
) -> Option<Self> {
|
||||
pub(crate) fn new(xc: &XCBConnection, x_screen_index: usize) -> Option<Self> {
|
||||
let screen = xc.setup().roots.get(x_screen_index).unwrap();
|
||||
Some(Self {
|
||||
x_screen_index,
|
||||
x_screen_index: x_screen_index,
|
||||
bounds: Bounds {
|
||||
origin: Default::default(),
|
||||
size: Size {
|
||||
width: px(screen.width_in_pixels as f32 / scale_factor),
|
||||
height: px(screen.height_in_pixels as f32 / scale_factor),
|
||||
width: DevicePixels(screen.width_in_pixels as i32),
|
||||
height: DevicePixels(screen.height_in_pixels as i32),
|
||||
},
|
||||
},
|
||||
uuid: Uuid::from_bytes([0; 16]),
|
||||
@@ -41,7 +37,7 @@ impl PlatformDisplay for X11Display {
|
||||
Ok(self.uuid)
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
platform::blade::{BladeRenderer, BladeSurfaceConfig},
|
||||
px, size, AnyWindowHandle, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels,
|
||||
size, AnyWindowHandle, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels,
|
||||
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
|
||||
PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
|
||||
WindowKind, WindowParams, X11ClientStatePtr,
|
||||
WindowParams, X11ClientStatePtr,
|
||||
};
|
||||
|
||||
use blade_graphics as gpu;
|
||||
@@ -29,6 +29,7 @@ use std::{
|
||||
ptr::NonNull,
|
||||
rc::Rc,
|
||||
sync::{self, Arc},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use super::{X11Display, XINPUT_MASTER_DEVICE};
|
||||
@@ -47,8 +48,6 @@ x11rb::atom_manager! {
|
||||
_NET_WM_STATE_HIDDEN,
|
||||
_NET_WM_STATE_FOCUSED,
|
||||
_NET_WM_MOVERESIZE,
|
||||
_NET_WM_WINDOW_TYPE,
|
||||
_NET_WM_WINDOW_TYPE_NOTIFICATION,
|
||||
_GTK_SHOW_WINDOW_MENU,
|
||||
}
|
||||
}
|
||||
@@ -156,13 +155,12 @@ pub struct Callbacks {
|
||||
}
|
||||
|
||||
pub struct X11WindowState {
|
||||
pub destroyed: bool,
|
||||
client: X11ClientStatePtr,
|
||||
executor: ForegroundExecutor,
|
||||
atoms: XcbAtoms,
|
||||
x_root_window: xproto::Window,
|
||||
_raw: RawWindow,
|
||||
bounds: Bounds<Pixels>,
|
||||
bounds: Bounds<i32>,
|
||||
scale_factor: f32,
|
||||
renderer: BladeRenderer,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
@@ -219,7 +217,7 @@ impl X11WindowState {
|
||||
atoms: &XcbAtoms,
|
||||
scale_factor: f32,
|
||||
appearance: WindowAppearance,
|
||||
) -> anyhow::Result<Self> {
|
||||
) -> Self {
|
||||
let x_screen_index = params
|
||||
.display_id
|
||||
.map_or(x_main_screen_index, |did| did.0 as usize);
|
||||
@@ -251,7 +249,8 @@ impl X11WindowState {
|
||||
xcb_connection
|
||||
.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
|
||||
.unwrap()
|
||||
.check()?;
|
||||
.check()
|
||||
.unwrap();
|
||||
id
|
||||
};
|
||||
|
||||
@@ -273,17 +272,18 @@ impl X11WindowState {
|
||||
visual.depth,
|
||||
x_window,
|
||||
visual_set.root,
|
||||
(params.bounds.origin.x.0 * scale_factor) as i16,
|
||||
(params.bounds.origin.y.0 * scale_factor) as i16,
|
||||
(params.bounds.size.width.0 * scale_factor) as u16,
|
||||
(params.bounds.size.height.0 * scale_factor) as u16,
|
||||
params.bounds.origin.x.0 as i16,
|
||||
params.bounds.origin.y.0 as i16,
|
||||
params.bounds.size.width.0 as u16,
|
||||
params.bounds.size.height.0 as u16,
|
||||
0,
|
||||
xproto::WindowClass::INPUT_OUTPUT,
|
||||
visual.id,
|
||||
&win_aux,
|
||||
)
|
||||
.unwrap()
|
||||
.check()?;
|
||||
.check()
|
||||
.unwrap();
|
||||
|
||||
if let Some(titlebar) = params.titlebar {
|
||||
if let Some(title) = titlebar.title {
|
||||
@@ -298,17 +298,6 @@ impl X11WindowState {
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
if params.kind == WindowKind::PopUp {
|
||||
xcb_connection
|
||||
.change_property32(
|
||||
xproto::PropMode::REPLACE,
|
||||
x_window,
|
||||
atoms._NET_WM_WINDOW_TYPE,
|
||||
xproto::AtomEnum::ATOM,
|
||||
&[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
xcb_connection
|
||||
.change_property32(
|
||||
@@ -357,7 +346,7 @@ impl X11WindowState {
|
||||
},
|
||||
)
|
||||
}
|
||||
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let config = BladeSurfaceConfig {
|
||||
@@ -367,23 +356,20 @@ impl X11WindowState {
|
||||
transparent: params.window_background != WindowBackgroundAppearance::Opaque,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
client,
|
||||
executor,
|
||||
display: Rc::new(
|
||||
X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(),
|
||||
),
|
||||
display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
|
||||
_raw: raw,
|
||||
x_root_window: visual_set.root,
|
||||
bounds: params.bounds,
|
||||
bounds: params.bounds.map(|v| v.0),
|
||||
scale_factor,
|
||||
renderer: BladeRenderer::new(gpu, config),
|
||||
atoms: *atoms,
|
||||
input_handler: None,
|
||||
appearance,
|
||||
handle,
|
||||
destroyed: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
@@ -409,10 +395,6 @@ impl Drop for X11Window {
|
||||
.unwrap();
|
||||
self.0.xcb_connection.flush().unwrap();
|
||||
|
||||
// Mark window as destroyed so that we can filter out when X11 events
|
||||
// for it still come in.
|
||||
state.destroyed = true;
|
||||
|
||||
let this_ptr = self.0.clone();
|
||||
let client_ptr = state.client.clone();
|
||||
state
|
||||
@@ -445,8 +427,8 @@ impl X11Window {
|
||||
atoms: &XcbAtoms,
|
||||
scale_factor: f32,
|
||||
appearance: WindowAppearance,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(Self(X11WindowStatePtr {
|
||||
) -> Self {
|
||||
Self(X11WindowStatePtr {
|
||||
state: Rc::new(RefCell::new(X11WindowState::new(
|
||||
handle,
|
||||
client,
|
||||
@@ -458,11 +440,11 @@ impl X11Window {
|
||||
atoms,
|
||||
scale_factor,
|
||||
appearance,
|
||||
)?)),
|
||||
))),
|
||||
callbacks: Rc::new(RefCell::new(Callbacks::default())),
|
||||
xcb_connection: xcb_connection.clone(),
|
||||
x_window,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
|
||||
@@ -550,8 +532,15 @@ impl X11WindowStatePtr {
|
||||
}
|
||||
|
||||
pub fn handle_input(&self, input: PlatformInput) {
|
||||
// println!("handle_input called");
|
||||
// let start = Instant::now();
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
||||
// println!(
|
||||
// "handle_input. got input callback. elapsed: {:?}",
|
||||
// start.elapsed()
|
||||
// );
|
||||
if !fun(input.clone()).propagate {
|
||||
// println!("handle_input. return here. elapsed: {:?}", start.elapsed());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -629,7 +618,6 @@ impl X11WindowStatePtr {
|
||||
let is_resize;
|
||||
{
|
||||
let mut state = self.state.borrow_mut();
|
||||
let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
|
||||
|
||||
is_resize = bounds.size.width != state.bounds.size.width
|
||||
|| bounds.size.height != state.bounds.size.height;
|
||||
@@ -644,10 +632,9 @@ impl X11WindowStatePtr {
|
||||
|
||||
let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
|
||||
if state.renderer.viewport_size() != gpu_size {
|
||||
state.renderer.update_drawable_size(size(
|
||||
DevicePixels(gpu_size.width as i32),
|
||||
DevicePixels(gpu_size.height as i32),
|
||||
));
|
||||
state
|
||||
.renderer
|
||||
.update_drawable_size(size(gpu_size.width as f64, gpu_size.height as f64));
|
||||
resize_args = Some((state.content_size(), state.scale_factor));
|
||||
}
|
||||
}
|
||||
@@ -682,8 +669,8 @@ impl X11WindowStatePtr {
|
||||
}
|
||||
|
||||
impl PlatformWindow for X11Window {
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
self.0.state.borrow().bounds
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.0.state.borrow().bounds.map(|v| v.into())
|
||||
}
|
||||
|
||||
fn is_maximized(&self) -> bool {
|
||||
@@ -697,11 +684,7 @@ impl PlatformWindow for X11Window {
|
||||
|
||||
fn window_bounds(&self) -> WindowBounds {
|
||||
let state = self.0.state.borrow();
|
||||
if self.is_maximized() {
|
||||
WindowBounds::Maximized(state.bounds)
|
||||
} else {
|
||||
WindowBounds::Windowed(state.bounds)
|
||||
}
|
||||
WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p)))
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
use std::default::Default;
|
||||
use std::{cell::Cell, default::Default, rc::Rc};
|
||||
|
||||
use x11rb::protocol::{xproto, Event};
|
||||
use xim::{AHashMap, AttributeName, Client, ClientError, ClientHandler, InputStyle};
|
||||
use calloop::channel;
|
||||
|
||||
use x11rb::{
|
||||
connection::{Connection, RequestConnection},
|
||||
cookie::{CookieWithFds, VoidCookie},
|
||||
protocol::{xproto, Event},
|
||||
xcb_ffi::XCBConnection,
|
||||
};
|
||||
use xim::{
|
||||
x11rb::HasConnection, AHashMap, AttributeName, Client, ClientError, ClientHandler, InputStyle,
|
||||
};
|
||||
|
||||
pub enum XimCallbackEvent {
|
||||
XimXEvent(x11rb::protocol::Event),
|
||||
@@ -12,6 +21,7 @@ pub enum XimCallbackEvent {
|
||||
pub struct XimHandler {
|
||||
pub im_id: u16,
|
||||
pub ic_id: u16,
|
||||
// pub xim_tx: channel::Sender<XimCallbackEvent>,
|
||||
pub connected: bool,
|
||||
pub window: xproto::Window,
|
||||
pub last_callback_event: Option<XimCallbackEvent>,
|
||||
@@ -22,6 +32,7 @@ impl XimHandler {
|
||||
Self {
|
||||
im_id: Default::default(),
|
||||
ic_id: Default::default(),
|
||||
// xim_tx,
|
||||
connected: false,
|
||||
window: Default::default(),
|
||||
last_callback_event: None,
|
||||
@@ -78,10 +89,12 @@ impl<C: Client<XEvent = xproto::KeyPressEvent>> ClientHandler<C> for XimHandler
|
||||
_input_context_id: u16,
|
||||
text: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.last_callback_event = Some(XimCallbackEvent::XimCommitEvent(
|
||||
self.window,
|
||||
String::from(text),
|
||||
));
|
||||
self.last_callback_event
|
||||
.replace(XimCallbackEvent::XimCommitEvent(
|
||||
self.window,
|
||||
String::from(text),
|
||||
));
|
||||
// .ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -95,11 +108,20 @@ impl<C: Client<XEvent = xproto::KeyPressEvent>> ClientHandler<C> for XimHandler
|
||||
) -> Result<(), ClientError> {
|
||||
match xev.response_type {
|
||||
x11rb::protocol::xproto::KEY_PRESS_EVENT => {
|
||||
self.last_callback_event = Some(XimCallbackEvent::XimXEvent(Event::KeyPress(xev)));
|
||||
// println!(
|
||||
// "XimHandler. handle_forward_event(KeyPress). sequence: {}",
|
||||
// xev.sequence
|
||||
// );
|
||||
self.last_callback_event
|
||||
.replace(XimCallbackEvent::XimXEvent(Event::KeyPress(xev)));
|
||||
}
|
||||
x11rb::protocol::xproto::KEY_RELEASE_EVENT => {
|
||||
self.last_callback_event =
|
||||
Some(XimCallbackEvent::XimXEvent(Event::KeyRelease(xev)));
|
||||
// println!(
|
||||
// "XimHandler. handle_forward_event(KeyRelease), sequence: {}",
|
||||
// xev.sequence
|
||||
// );
|
||||
self.last_callback_event
|
||||
.replace(XimCallbackEvent::XimXEvent(Event::KeyRelease(xev)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -138,10 +160,185 @@ impl<C: Client<XEvent = xproto::KeyPressEvent>> ClientHandler<C> for XimHandler
|
||||
// XIMPrimary, XIMHighlight, XIMSecondary, XIMTertiary are not specified,
|
||||
// but interchangeable as above
|
||||
// Currently there's no way to support these.
|
||||
self.last_callback_event = Some(XimCallbackEvent::XimPreeditEvent(
|
||||
self.window,
|
||||
String::from(preedit_string),
|
||||
));
|
||||
self.last_callback_event
|
||||
.replace(XimCallbackEvent::XimPreeditEvent(
|
||||
self.window,
|
||||
String::from(preedit_string),
|
||||
));
|
||||
// self.xim_tx
|
||||
// .send()
|
||||
// .ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct XimXCBConnection(Rc<XCBConnection>, Rc<Cell<bool>>);
|
||||
|
||||
impl XimXCBConnection {
|
||||
pub fn new(connection: Rc<XCBConnection>, can_flush: Rc<Cell<bool>>) -> Self {
|
||||
Self(connection, can_flush)
|
||||
}
|
||||
|
||||
pub fn set_can_flush(&self, can_flush: bool) {
|
||||
self.1.set(can_flush);
|
||||
}
|
||||
}
|
||||
|
||||
impl HasConnection for XimXCBConnection {
|
||||
type Connection = Self;
|
||||
|
||||
fn conn(&self) -> &Self::Connection {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection for XimXCBConnection {
|
||||
fn wait_for_raw_event_with_sequence(
|
||||
&self,
|
||||
) -> Result<x11rb::connection::RawEventAndSeqNumber<Self::Buf>, x11rb::errors::ConnectionError>
|
||||
{
|
||||
self.0.wait_for_raw_event_with_sequence()
|
||||
}
|
||||
|
||||
fn poll_for_raw_event_with_sequence(
|
||||
&self,
|
||||
) -> Result<
|
||||
Option<x11rb::connection::RawEventAndSeqNumber<Self::Buf>>,
|
||||
x11rb::errors::ConnectionError,
|
||||
> {
|
||||
self.0.poll_for_raw_event_with_sequence()
|
||||
}
|
||||
|
||||
fn flush(&self) -> Result<(), x11rb::errors::ConnectionError> {
|
||||
if self.1.get() {
|
||||
// println!("*real* flush");
|
||||
self.0.flush()
|
||||
} else {
|
||||
// println!("fake flush");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(&self) -> &xproto::Setup {
|
||||
self.0.setup()
|
||||
}
|
||||
|
||||
fn generate_id(&self) -> Result<u32, x11rb::errors::ReplyOrIdError> {
|
||||
self.0.generate_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestConnection for XimXCBConnection {
|
||||
type Buf = <XCBConnection as RequestConnection>::Buf;
|
||||
|
||||
fn send_request_with_reply<R>(
|
||||
&self,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
fds: Vec<x11rb::utils::RawFdContainer>,
|
||||
) -> Result<x11rb::cookie::Cookie<'_, Self, R>, x11rb::errors::ConnectionError>
|
||||
where
|
||||
R: x11rb::x11_utils::TryParse,
|
||||
{
|
||||
self.0
|
||||
.send_request_with_reply::<R>(bufs, fds)
|
||||
.map(|cookie| x11rb::cookie::Cookie::new(self, cookie.sequence_number()))
|
||||
}
|
||||
|
||||
fn send_request_with_reply_with_fds<R>(
|
||||
&self,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
fds: Vec<x11rb::utils::RawFdContainer>,
|
||||
) -> Result<CookieWithFds<'_, Self, R>, x11rb::errors::ConnectionError>
|
||||
where
|
||||
R: x11rb::x11_utils::TryParseFd,
|
||||
{
|
||||
self.0
|
||||
.send_request_with_reply_with_fds::<R>(bufs, fds)
|
||||
.map(|cookie| CookieWithFds::new(self, cookie.sequence_number()))
|
||||
}
|
||||
|
||||
fn send_request_without_reply(
|
||||
&self,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
fds: Vec<x11rb::utils::RawFdContainer>,
|
||||
) -> Result<VoidCookie<'_, Self>, x11rb::errors::ConnectionError> {
|
||||
self.0
|
||||
.send_request_without_reply(bufs, fds)
|
||||
.map(|cookie| VoidCookie::new(self, cookie.sequence_number()))
|
||||
}
|
||||
|
||||
fn discard_reply(
|
||||
&self,
|
||||
sequence: x11rb::connection::SequenceNumber,
|
||||
kind: x11rb::connection::RequestKind,
|
||||
mode: x11rb::connection::DiscardMode,
|
||||
) {
|
||||
self.0.discard_reply(sequence, kind, mode)
|
||||
}
|
||||
|
||||
fn prefetch_extension_information(
|
||||
&self,
|
||||
extension_name: &'static str,
|
||||
) -> Result<(), x11rb::errors::ConnectionError> {
|
||||
self.0.prefetch_extension_information(extension_name)
|
||||
}
|
||||
|
||||
fn extension_information(
|
||||
&self,
|
||||
extension_name: &'static str,
|
||||
) -> Result<Option<x11rb::x11_utils::ExtensionInformation>, x11rb::errors::ConnectionError>
|
||||
{
|
||||
self.0.extension_information(extension_name)
|
||||
}
|
||||
|
||||
fn wait_for_reply_or_raw_error(
|
||||
&self,
|
||||
sequence: x11rb::connection::SequenceNumber,
|
||||
) -> Result<x11rb::connection::ReplyOrError<Self::Buf>, x11rb::errors::ConnectionError> {
|
||||
self.0.wait_for_reply_or_raw_error(sequence)
|
||||
}
|
||||
|
||||
fn wait_for_reply(
|
||||
&self,
|
||||
sequence: x11rb::connection::SequenceNumber,
|
||||
) -> Result<Option<Self::Buf>, x11rb::errors::ConnectionError> {
|
||||
self.0.wait_for_reply(sequence)
|
||||
}
|
||||
|
||||
fn wait_for_reply_with_fds_raw(
|
||||
&self,
|
||||
sequence: x11rb::connection::SequenceNumber,
|
||||
) -> Result<
|
||||
x11rb::connection::ReplyOrError<x11rb::connection::BufWithFds<Self::Buf>, Self::Buf>,
|
||||
x11rb::errors::ConnectionError,
|
||||
> {
|
||||
self.0.wait_for_reply_with_fds_raw(sequence)
|
||||
}
|
||||
|
||||
fn check_for_raw_error(
|
||||
&self,
|
||||
sequence: x11rb::connection::SequenceNumber,
|
||||
) -> Result<Option<Self::Buf>, x11rb::errors::ConnectionError> {
|
||||
self.0.check_for_raw_error(sequence)
|
||||
}
|
||||
|
||||
fn prefetch_maximum_request_bytes(&self) {
|
||||
self.0.prefetch_maximum_request_bytes()
|
||||
}
|
||||
|
||||
fn maximum_request_bytes(&self) -> usize {
|
||||
self.0.maximum_request_bytes()
|
||||
}
|
||||
|
||||
fn parse_error(
|
||||
&self,
|
||||
error: &[u8],
|
||||
) -> Result<x11rb::x11_utils::X11Error, x11rb::errors::ParseError> {
|
||||
self.0.parse_error(error)
|
||||
}
|
||||
|
||||
fn parse_event(&self, event: &[u8]) -> Result<Event, x11rb::errors::ParseError> {
|
||||
self.0.parse_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
//! Provides a [calloop] event source from [XDG Desktop Portal] events
|
||||
//!
|
||||
//! This module uses the [ashpd] crate
|
||||
//! This module uses the [ashpd] crate and handles many async loop
|
||||
use std::future::Future;
|
||||
|
||||
use ashpd::desktop::settings::{ColorScheme, Settings};
|
||||
use calloop::channel::Channel;
|
||||
use calloop::channel::{Channel, Sender};
|
||||
use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
|
||||
use smol::stream::StreamExt;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{BackgroundExecutor, WindowAppearance};
|
||||
|
||||
pub enum Event {
|
||||
WindowAppearance(WindowAppearance),
|
||||
CursorTheme(String),
|
||||
CursorSize(u32),
|
||||
}
|
||||
|
||||
pub struct XDPEventSource {
|
||||
@@ -23,63 +23,35 @@ impl XDPEventSource {
|
||||
pub fn new(executor: &BackgroundExecutor) -> Self {
|
||||
let (sender, channel) = calloop::channel::channel();
|
||||
|
||||
let background = executor.clone();
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
let settings = Settings::new().await?;
|
||||
|
||||
if let Ok(mut cursor_theme_changed) = settings
|
||||
.receive_setting_changed_with_args(
|
||||
"org.gnome.desktop.interface",
|
||||
"cursor-theme",
|
||||
)
|
||||
.await
|
||||
{
|
||||
let sender = sender.clone();
|
||||
background
|
||||
.spawn(async move {
|
||||
while let Some(theme) = cursor_theme_changed.next().await {
|
||||
let theme = theme?;
|
||||
sender.send(Event::CursorTheme(theme))?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
if let Ok(mut cursor_size_changed) = settings
|
||||
.receive_setting_changed_with_args::<u32>(
|
||||
"org.gnome.desktop.interface",
|
||||
"cursor-size",
|
||||
)
|
||||
.await
|
||||
{
|
||||
let sender = sender.clone();
|
||||
background
|
||||
.spawn(async move {
|
||||
while let Some(size) = cursor_size_changed.next().await {
|
||||
let size = size?;
|
||||
sender.send(Event::CursorSize(size))?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
let mut appearance_changed = settings.receive_color_scheme_changed().await?;
|
||||
while let Some(scheme) = appearance_changed.next().await {
|
||||
sender.send(Event::WindowAppearance(WindowAppearance::from_native(
|
||||
scheme,
|
||||
)))?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
Self::spawn_observer(executor, Self::appearance_observer(sender.clone()));
|
||||
|
||||
Self { channel }
|
||||
}
|
||||
|
||||
fn spawn_observer(
|
||||
executor: &BackgroundExecutor,
|
||||
to_spawn: impl Future<Output = Result<(), anyhow::Error>> + Send + 'static,
|
||||
) {
|
||||
executor
|
||||
.spawn(async move {
|
||||
to_spawn.await.log_err();
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
|
||||
async fn appearance_observer(sender: Sender<Event>) -> Result<(), anyhow::Error> {
|
||||
let settings = Settings::new().await?;
|
||||
|
||||
// We observe the color change during the execution of the application
|
||||
let mut stream = settings.receive_color_scheme_changed().await?;
|
||||
while let Some(scheme) = stream.next().await {
|
||||
sender.send(Event::WindowAppearance(WindowAppearance::from_native(
|
||||
scheme,
|
||||
)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl EventSource for XDPEventSource {
|
||||
@@ -170,16 +142,3 @@ pub fn should_auto_hide_scrollbars(executor: &BackgroundExecutor) -> Result<bool
|
||||
Ok(auto_hide)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn cursor_settings() -> Result<(String, Option<u32>), anyhow::Error> {
|
||||
let settings = Settings::new().await?;
|
||||
let cursor_theme = settings
|
||||
.read::<String>("org.gnome.desktop.interface", "cursor-theme")
|
||||
.await?;
|
||||
let cursor_size = settings
|
||||
.read::<u32>("org.gnome.desktop.interface", "cursor-size")
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok((cursor_theme, cursor_size))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{px, size, Bounds, DisplayId, Pixels, PlatformDisplay};
|
||||
use crate::{point, size, Bounds, DevicePixels, DisplayId, PlatformDisplay};
|
||||
use anyhow::Result;
|
||||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
@@ -102,15 +102,18 @@ impl PlatformDisplay for MacDisplay {
|
||||
]))
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
unsafe {
|
||||
// CGDisplayBounds is in "global display" coordinates, where 0 is
|
||||
// the top left of the primary display.
|
||||
let bounds = CGDisplayBounds(self.0);
|
||||
|
||||
Bounds {
|
||||
origin: Default::default(),
|
||||
size: size(px(bounds.size.width as f32), px(bounds.size.height as f32)),
|
||||
origin: point(DevicePixels(0), DevicePixels(0)),
|
||||
size: size(
|
||||
DevicePixels(bounds.size.width as i32),
|
||||
DevicePixels(bounds.size.height as i32),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ mod sys {
|
||||
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
#[link(name = "CoreVideo", kind = "framework")]
|
||||
#[allow(improper_ctypes, unknown_lints, clippy::duplicated_attributes)]
|
||||
#[allow(improper_ctypes)]
|
||||
extern "C" {
|
||||
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task,
|
||||
WindowAppearance, WindowParams,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, bail};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
appkit::{
|
||||
@@ -367,18 +367,6 @@ impl MacPlatform {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn os_version(&self) -> Result<SemanticVersion> {
|
||||
unsafe {
|
||||
let process_info = NSProcessInfo::processInfo(nil);
|
||||
let version = process_info.operatingSystemVersion();
|
||||
Ok(SemanticVersion::new(
|
||||
version.majorVersion as usize,
|
||||
version.minorVersion as usize,
|
||||
version.patchVersion as usize,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Platform for MacPlatform {
|
||||
@@ -516,16 +504,16 @@ impl Platform for MacPlatform {
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> Result<Box<dyn PlatformWindow>> {
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
// Clippy thinks that this evaluates to `()`, for some reason.
|
||||
#[allow(clippy::unit_arg, clippy::clone_on_copy)]
|
||||
let renderer_context = self.0.lock().renderer_context.clone();
|
||||
Ok(Box::new(MacWindow::open(
|
||||
Box::new(MacWindow::open(
|
||||
handle,
|
||||
options,
|
||||
self.foreground_executor(),
|
||||
renderer_context,
|
||||
)))
|
||||
))
|
||||
}
|
||||
|
||||
fn window_appearance(&self) -> WindowAppearance {
|
||||
@@ -717,6 +705,40 @@ impl Platform for MacPlatform {
|
||||
self.0.lock().validate_menu_command = Some(callback);
|
||||
}
|
||||
|
||||
fn os_name(&self) -> &'static str {
|
||||
"macOS"
|
||||
}
|
||||
|
||||
fn os_version(&self) -> Result<SemanticVersion> {
|
||||
unsafe {
|
||||
let process_info = NSProcessInfo::processInfo(nil);
|
||||
let version = process_info.operatingSystemVersion();
|
||||
Ok(SemanticVersion::new(
|
||||
version.majorVersion as usize,
|
||||
version.minorVersion as usize,
|
||||
version.patchVersion as usize,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn app_version(&self) -> Result<SemanticVersion> {
|
||||
unsafe {
|
||||
let bundle: id = NSBundle::mainBundle();
|
||||
if bundle.is_null() {
|
||||
Err(anyhow!("app is not running inside a bundle"))
|
||||
} else {
|
||||
let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
|
||||
if version.is_null() {
|
||||
bail!("bundle does not have version");
|
||||
}
|
||||
let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
|
||||
let bytes = version.UTF8String() as *const u8;
|
||||
let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
|
||||
version.parse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn app_path(&self) -> Result<PathBuf> {
|
||||
unsafe {
|
||||
let bundle: id = NSBundle::mainBundle();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::{ns_string, renderer, MacDisplay, NSRange, NSStringExt};
|
||||
use crate::{
|
||||
platform::PlatformInputHandler, point, px, size, AnyWindowHandle, Bounds, DisplayLink,
|
||||
ExternalPaths, FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Size, Timer,
|
||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowParams,
|
||||
platform::PlatformInputHandler, point, px, size, AnyWindowHandle, Bounds, DevicePixels,
|
||||
DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke,
|
||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel,
|
||||
Size, Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind,
|
||||
WindowParams,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
@@ -344,7 +345,7 @@ struct MacWindowState {
|
||||
external_files_dragged: bool,
|
||||
// Whether the next left-mouse click is also the focusing click.
|
||||
first_mouse: bool,
|
||||
fullscreen_restore_bounds: Bounds<Pixels>,
|
||||
fullscreen_restore_bounds: Bounds<DevicePixels>,
|
||||
}
|
||||
|
||||
impl MacWindowState {
|
||||
@@ -438,7 +439,7 @@ impl MacWindowState {
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
let mut window_frame = unsafe { NSWindow::frame(self.native_window) };
|
||||
let screen_frame = unsafe {
|
||||
let screen = NSWindow::screen(self.native_window);
|
||||
@@ -451,12 +452,12 @@ impl MacWindowState {
|
||||
|
||||
let bounds = Bounds::new(
|
||||
point(
|
||||
px((window_frame.origin.x - screen_frame.origin.x) as f32),
|
||||
px((window_frame.origin.y - screen_frame.origin.y) as f32),
|
||||
((window_frame.origin.x - screen_frame.origin.x) as i32).into(),
|
||||
((window_frame.origin.y - screen_frame.origin.y) as i32).into(),
|
||||
),
|
||||
size(
|
||||
px(window_frame.size.width as f32),
|
||||
px(window_frame.size.height as f32),
|
||||
(window_frame.size.width as i32).into(),
|
||||
(window_frame.size.height as i32).into(),
|
||||
),
|
||||
);
|
||||
bounds
|
||||
@@ -598,6 +599,14 @@ impl MacWindow {
|
||||
let native_view = NSView::init(native_view);
|
||||
assert!(!native_view.is_null());
|
||||
|
||||
let window_size = {
|
||||
let scale = get_scale_factor(native_window);
|
||||
size(
|
||||
bounds.size.width.0 as f32 * scale,
|
||||
bounds.size.height.0 as f32 * scale,
|
||||
)
|
||||
};
|
||||
|
||||
let mut window = Self(Arc::new(Mutex::new(MacWindowState {
|
||||
handle,
|
||||
executor,
|
||||
@@ -608,7 +617,7 @@ impl MacWindow {
|
||||
renderer_context,
|
||||
native_window as *mut _,
|
||||
native_view as *mut _,
|
||||
bounds.size.map(|pixels| pixels.0),
|
||||
window_size,
|
||||
window_background != WindowBackgroundAppearance::Opaque,
|
||||
),
|
||||
request_frame_callback: None,
|
||||
@@ -763,7 +772,7 @@ impl Drop for MacWindow {
|
||||
}
|
||||
|
||||
impl PlatformWindow for MacWindow {
|
||||
fn bounds(&self) -> Bounds<Pixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.0.as_ref().lock().bounds()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::{px, Bounds, DisplayId, Pixels, PlatformDisplay, Point};
|
||||
use anyhow::{Ok, Result};
|
||||
|
||||
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Point};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TestDisplay {
|
||||
id: DisplayId,
|
||||
uuid: uuid::Uuid,
|
||||
bounds: Bounds<Pixels>,
|
||||
bounds: Bounds<DevicePixels>,
|
||||
}
|
||||
|
||||
impl TestDisplay {
|
||||
@@ -13,7 +14,10 @@ impl TestDisplay {
|
||||
TestDisplay {
|
||||
id: DisplayId(1),
|
||||
uuid: uuid::Uuid::new_v4(),
|
||||
bounds: Bounds::from_corners(Point::default(), Point::new(px(1920.), px(1080.))),
|
||||
bounds: Bounds::from_corners(
|
||||
Point::default(),
|
||||
Point::new(DevicePixels(1920), DevicePixels(1080)),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +31,7 @@ impl PlatformDisplay for TestDisplay {
|
||||
Ok(self.uuid)
|
||||
}
|
||||
|
||||
fn bounds(&self) -> crate::Bounds<crate::Pixels> {
|
||||
fn bounds(&self) -> crate::Bounds<crate::DevicePixels> {
|
||||
self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user