Compare commits

..

20 Commits

Author SHA1 Message Date
Mikayla
9eb92d4599 Make project panel settings live update 2024-04-12 11:17:51 -07:00
Mikayla
88a1070b13 Merge branch 'main' into auto-folded-dirs 2024-04-12 10:54:55 -07:00
Mikayla
60e1ffbce5 Default auto-folding to off, restore previous tests 2024-04-12 10:41:51 -07:00
Abykhodau Yury
b82dda64e1 Merge github.com:zed-industries/zed into auto-folded-dirs 2024-04-03 21:49:18 +03:00
Abykhodau Yury
dfbf6b9ee9 Improve performance of project panel 2024-04-03 21:49:13 +03:00
Abykhodau Yury
dbd2a247db fix Snapshot import issue 2024-03-28 23:19:51 +02:00
Abykhodau Yury
da4af5a651 Merge github.com:zed-industries/zed into auto-folded-dirs 2024-03-28 20:44:56 +02:00
Abykhodau Yury
e9c33bd819 Delete collecting the vector in is_foldable check 2024-03-28 20:41:41 +02:00
Abykhodau Yury
3dd6a7c6e5 Merge branch 'main' of github.com:zed-industries/zed into auto-folded-dirs 2024-02-28 22:10:32 +02:00
Abykhodau Yury
b946797390 Solve performance issues with project panel auto folding dirs 2024-02-28 22:10:21 +02:00
Abykhodau Yury
fd32cc4679 Merge branch 'main' of github.com:zed-industries/zed into auto-folded-dirs 2024-02-26 10:58:26 +02:00
Abykhodau Yury
4d5509be6b Refactor project panel tests according to changes in auto folding of directories 2024-02-26 10:56:56 +02:00
Abykhodau Yury
17cea27bbc Add support of fold/unfold directory functionality 2024-02-25 22:39:16 +02:00
Abykhodau Yury
fe4b744ae9 Refactor project_panel to get rid of redundant state 2024-02-25 14:46:45 +02:00
Abykhodau Yury
a5b8b5fdb3 Adjust tests after adding functioality of auto collapsed dir paths 2024-02-12 11:00:06 +02:00
Abykhodau Yury
4275283201 Merge branch 'main' of github.com:zed-industries/zed into auto-folded-dirs 2024-02-12 08:33:07 +02:00
Abykhodau Yury
6de4aa5990 Adding test for auto collapsing dir paths 2024-02-11 18:32:03 +02:00
Abykhodau Yury
b375001228 Add 'auto_collapse_dirs' parameter to project_panel settings 2024-02-10 21:36:25 +02:00
Abykhodau Yury
00dd254ba9 Add support of fold/unfold action for directories 2024-02-10 19:26:01 +02:00
Abykhodau Yury
00d8a92326 Add support for collapsed paths in nested directories within the project panel 2024-02-10 15:00:02 +02:00
180 changed files with 2447 additions and 7309 deletions

View File

@@ -1,49 +0,0 @@
name: bump_patch_version
on:
workflow_dispatch:
inputs:
branch:
description: "Branch name to run on"
required: true
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.event.input.branch }}
cancel-in-progress: true
jobs:
bump_patch_version:
runs-on:
- self-hosted
- test
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.branch }}
ssh-key: ${{ secrets.ZED_BOT_DEPLOY_KEY }}
- name: Bump Patch Version
run: |
set -eux
channel=$(cat crates/zed/RELEASE_CHANNEL)
tag_suffix=""
case $channel in
stable)
;;
preview)
tag_suffix="-pre"
;;
*)
echo "this must be run on either of stable|preview release branches" >&2
exit 1
;;
esac
which cargo-set-version > /dev/null || cargo install cargo-edit --features vendored-openssl
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
git tag v${output}${tag_suffix}
git push origin HEAD v${output}${tag_suffix}

View File

@@ -205,7 +205,6 @@ jobs:
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
script/draft-release-notes "$version" "$channel" > target/release-notes.md
- name: Generate license file
run: script/generate-licenses
@@ -249,7 +248,7 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
body_file: target/release-notes.md
body: ""
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -11,7 +11,7 @@ If you're looking for ideas about what to work on, check out:
- Our [public roadmap](https://zed.dev/roadmap) contains a rough outline of our near-term priorities for Zed.
- Our [top-ranking issues](https://github.com/zed-industries/zed/issues/5393) based on votes by the community.
For adding themes or support for a new language to Zed, check out our [extension docs](https://github.com/zed-industries/extensions/blob/main/AUTHORING_EXTENSIONS.md).
Outside of a handful of extremely popular languages and themes, we are generally not looking to extend Zed's language or theme support by directly building them into Zed. We really want to build a plugin system to handle making the editor extensible going forward. If you are passionate about shipping new languages or themes we suggest contributing to the extension system to help us get there faster.
## Proposing changes

173
Cargo.lock generated
View File

@@ -520,7 +520,7 @@ dependencies = [
"polling 3.3.2",
"rustix 0.38.32",
"slab",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"windows-sys 0.52.0",
]
@@ -861,7 +861,7 @@ dependencies = [
"ring 0.17.7",
"time",
"tokio",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize",
]
@@ -897,7 +897,7 @@ dependencies = [
"http-body",
"percent-encoding",
"pin-project-lite",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid",
]
@@ -926,7 +926,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"regex-lite",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"url",
]
@@ -949,7 +949,7 @@ dependencies = [
"http 0.2.9",
"once_cell",
"regex-lite",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -971,7 +971,7 @@ dependencies = [
"http 0.2.9",
"once_cell",
"regex-lite",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -994,7 +994,7 @@ dependencies = [
"http 0.2.9",
"once_cell",
"regex-lite",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1022,7 +1022,7 @@ dependencies = [
"sha2 0.10.7",
"subtle",
"time",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize",
]
@@ -1055,7 +1055,7 @@ dependencies = [
"pin-project-lite",
"sha1",
"sha2 0.10.7",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1087,7 +1087,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"pin-utils",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1131,7 +1131,7 @@ dependencies = [
"pin-utils",
"rustls",
"tokio",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1146,7 +1146,7 @@ dependencies = [
"http 0.2.9",
"pin-project-lite",
"tokio",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize",
]
@@ -1194,7 +1194,7 @@ dependencies = [
"aws-smithy-types",
"http 0.2.9",
"rustc_version",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1517,7 +1517,7 @@ dependencies = [
"futures-io",
"futures-lite 2.2.0",
"piper",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2275,7 +2275,7 @@ dependencies = [
"toml 0.8.10",
"tower",
"tower-http 0.4.4",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing-subscriber",
"unindent",
"util",
@@ -4327,16 +4327,15 @@ dependencies = [
"time",
"unindent",
"url",
"windows 0.53.0",
]
[[package]]
name = "git2"
version = "0.18.3"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70"
checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1"
dependencies = [
"bitflags 2.4.2",
"bitflags 1.3.2",
"libc",
"libgit2-sys",
"log",
@@ -4574,7 +4573,7 @@ dependencies = [
"slab",
"tokio",
"tokio-util",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -4885,7 +4884,7 @@ dependencies = [
"socket2 0.4.9",
"tokio",
"tower-service",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"want",
]
@@ -5200,7 +5199,7 @@ dependencies = [
"polling 2.8.0",
"slab",
"sluice",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing-futures",
"url",
"waker-fn",
@@ -5488,6 +5487,7 @@ dependencies = [
"log",
"lsp",
"node_runtime",
"parking_lot",
"project",
"regex",
"rope",
@@ -5517,12 +5517,14 @@ dependencies = [
"tree-sitter-jsdoc",
"tree-sitter-json 0.20.0",
"tree-sitter-markdown",
"tree-sitter-nu",
"tree-sitter-proto",
"tree-sitter-python",
"tree-sitter-regex",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-typescript",
"tree-sitter-vue",
"tree-sitter-yaml",
"unindent",
"util",
@@ -5564,9 +5566,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libgit2-sys"
version = "0.16.2+1.7.2"
version = "0.14.2+1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4"
dependencies = [
"cc",
"libc",
@@ -7166,7 +7168,7 @@ dependencies = [
"concurrent-queue",
"pin-project-lite",
"rustix 0.38.32",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"windows-sys 0.52.0",
]
@@ -8070,7 +8072,7 @@ dependencies = [
"serde",
"serde_json",
"strum",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"util",
"zstd",
]
@@ -8437,7 +8439,7 @@ dependencies = [
"strum",
"thiserror",
"time",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"url",
"uuid",
]
@@ -9211,7 +9213,7 @@ dependencies = [
"time",
"tokio",
"tokio-stream",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"url",
"uuid",
"webpki-roots",
@@ -9298,7 +9300,7 @@ dependencies = [
"stringprep",
"thiserror",
"time",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid",
"whoami",
]
@@ -9343,7 +9345,7 @@ dependencies = [
"stringprep",
"thiserror",
"time",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid",
"whoami",
]
@@ -9368,7 +9370,7 @@ dependencies = [
"serde",
"sqlx-core",
"time",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"url",
"uuid",
]
@@ -9737,6 +9739,7 @@ dependencies = [
"file_icons",
"fuzzy",
"gpui",
"itertools 0.11.0",
"language",
"picker",
"project",
@@ -9841,7 +9844,6 @@ dependencies = [
"serde_json",
"settings",
"shellexpand",
"shlex",
"smol",
"task",
"terminal",
@@ -10200,7 +10202,7 @@ dependencies = [
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -10295,7 +10297,7 @@ dependencies = [
"tokio",
"tower-layer",
"tower-service",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -10332,7 +10334,7 @@ dependencies = [
"pin-project-lite",
"tower-layer",
"tower-service",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -10356,7 +10358,16 @@ dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
"tracing-core 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tracing"
version = "0.1.40"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
dependencies = [
"pin-project-lite",
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
]
[[package]]
@@ -10375,6 +10386,14 @@ name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
dependencies = [
"once_cell",
"valuable",
@@ -10387,35 +10406,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"pin-project",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
dependencies = [
"log",
"once_cell",
"tracing-core",
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
]
[[package]]
name = "tracing-serde"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
dependencies = [
"serde",
"tracing-core",
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
dependencies = [
"matchers",
"nu-ansi-term",
@@ -10426,8 +10442,8 @@ dependencies = [
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing 0.1.40 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
"tracing-log",
"tracing-serde",
]
@@ -10581,6 +10597,15 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-nu"
version = "0.0.1"
source = "git+https://github.com/nushell/tree-sitter-nu?rev=7dd29f9616822e5fc259f5b4ae6c4ded9a71a132#7dd29f9616822e5fc259f5b4ae6c4ded9a71a132"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-proto"
version = "0.0.2"
@@ -10639,6 +10664,15 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-vue"
version = "0.0.1"
source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=6608d9d60c386f19d80af7d8132322fa11199c42#6608d9d60c386f19d80af7d8132322fa11199c42"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-yaml"
version = "0.0.1"
@@ -11281,7 +11315,7 @@ dependencies = [
"anyhow",
"log",
"once_cell",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmtime",
"wasmtime-c-api-macros",
]
@@ -11493,7 +11527,7 @@ dependencies = [
"system-interface",
"thiserror",
"tokio",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"url",
"wasmtime",
"wiggle",
@@ -11730,7 +11764,7 @@ dependencies = [
"async-trait",
"bitflags 2.4.2",
"thiserror",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmtime",
"wiggle-macro",
]
@@ -12486,7 +12520,7 @@ dependencies = [
"serde_repr",
"sha1",
"static_assertions",
"tracing",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"uds_windows",
"windows-sys 0.52.0",
"xdg-home",
@@ -12522,7 +12556,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.133.0"
version = "0.132.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -12623,14 +12657,14 @@ dependencies = [
[[package]]
name = "zed_clojure"
version = "0.0.2"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_csharp"
version = "0.0.2"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4",
]
@@ -12672,6 +12706,15 @@ dependencies = [
"wit-bindgen",
]
[[package]]
name = "zed_extension_api"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5f4ae4e302a80591635ef9a236b35fde6fcc26cfd060e66fde4ba9f9fd394a1"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "zed_extension_api"
version = "0.0.6"
@@ -12694,13 +12737,6 @@ dependencies = [
[[package]]
name = "zed_gleam"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zed_glsl"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -12722,7 +12758,7 @@ dependencies = [
[[package]]
name = "zed_lua"
version = "0.0.2"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -12743,7 +12779,7 @@ dependencies = [
[[package]]
name = "zed_prisma"
version = "0.0.2"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4",
]
@@ -12764,7 +12800,7 @@ dependencies = [
[[package]]
name = "zed_terraform"
version = "0.0.2"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -12783,16 +12819,9 @@ dependencies = [
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_vue"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zed_zig"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@@ -110,7 +110,6 @@ members = [
"extensions/emmet",
"extensions/erlang",
"extensions/gleam",
"extensions/glsl",
"extensions/haskell",
"extensions/html",
"extensions/lua",
@@ -122,7 +121,6 @@ members = [
"extensions/terraform",
"extensions/toml",
"extensions/uiua",
"extensions/vue",
"extensions/zig",
"tooling/xtask",
@@ -258,11 +256,9 @@ env_logger = "0.9"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.18", default-features = false }
git2 = { version = "0.15", default-features = false }
globset = "0.4"
heed = { git = "https://github.com/meilisearch/heed", rev = "036ac23f73a021894974b9adc815bc95b3e0482a", features = [
"read-txn-no-tls",
] }
heed = { git = "https://github.com/meilisearch/heed", rev = "036ac23f73a021894974b9adc815bc95b3e0482a", features = ["read-txn-no-tls"] }
hex = "0.4.3"
ignore = "0.4.22"
indoc = "1"
@@ -301,7 +297,6 @@ serde_json_lenient = { version = "0.1", features = [
] }
serde_repr = "0.1"
sha2 = "0.10"
shlex = "1.3"
shellexpand = "2.1.0"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
@@ -337,12 +332,15 @@ tree-sitter-html = "0.19.0"
tree-sitter-jsdoc = { git = "https://github.com/tree-sitter/tree-sitter-jsdoc", ref = "6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55" }
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "7dd29f9616822e5fc259f5b4ae6c4ded9a71a132" }
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
tree-sitter-python = "0.20.2"
tree-sitter-regex = "0.20.0"
tree-sitter-ruby = "0.20.0"
tree-sitter-rust = "0.20.3"
tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9" }
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42" }
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
unindent = "0.1.7"
unicase = "2.6"
@@ -367,16 +365,10 @@ sys-locale = "0.3.1"
version = "0.53.0"
features = [
"implement",
"Foundation_Numerics",
"Wdk_System_SystemServices",
"Win32_Globalization",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_DirectWrite",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Gdi",
"Win32_Graphics_Imaging",
"Win32_Graphics_Imaging_D2D",
"Win32_Media",
"Win32_Security",
"Win32_Security_Credentials",

View File

@@ -1,3 +1,3 @@
collab: RUST_LOG=${RUST_LOG:-info} cargo run --package=collab serve
collab: RUST_LOG=${RUST_LOG:-warn,tower_http=info,collab=info} cargo run --package=collab serve
livekit: livekit-server --dev
blob_store: ./script/run-local-minio

View File

@@ -38,8 +38,6 @@ brew install zed-preview
See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
Also... we're hiring! Check out our [jobs](https://zed.dev/jobs) page for open roles.
## Licensing
License information for third party dependencies must be correctly provided for CI to pass.

View File

@@ -1,3 +0,0 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36667 3.79167C5.53364 3.79167 4.85833 4.46697 4.85833 5.3C4.85833 6.13303 5.53364 6.80833 6.36667 6.80833C7.1997 6.80833 7.875 6.13303 7.875 5.3C7.875 4.46697 7.1997 3.79167 6.36667 3.79167ZM2.1 5.925H3.67944C3.9626 7.14732 5.05824 8.05833 6.36667 8.05833C7.67509 8.05833 8.77073 7.14732 9.05389 5.925H14.9C15.2452 5.925 15.525 5.64518 15.525 5.3C15.525 4.95482 15.2452 4.675 14.9 4.675H9.05389C8.77073 3.45268 7.67509 2.54167 6.36667 2.54167C5.05824 2.54167 3.9626 3.45268 3.67944 4.675H2.1C1.75482 4.675 1.475 4.95482 1.475 5.3C1.475 5.64518 1.75482 5.925 2.1 5.925ZM13.3206 12.325C13.0374 13.5473 11.9418 14.4583 10.6333 14.4583C9.32491 14.4583 8.22927 13.5473 7.94611 12.325H2.1C1.75482 12.325 1.475 12.0452 1.475 11.7C1.475 11.3548 1.75482 11.075 2.1 11.075H7.94611C8.22927 9.85268 9.32491 8.94167 10.6333 8.94167C11.9418 8.94167 13.0374 9.85268 13.3206 11.075H14.9C15.2452 11.075 15.525 11.3548 15.525 11.7C15.525 12.0452 15.2452 12.325 14.9 12.325H13.3206ZM9.125 11.7C9.125 10.867 9.8003 10.1917 10.6333 10.1917C11.4664 10.1917 12.1417 10.867 12.1417 11.7C12.1417 12.533 11.4664 13.2083 10.6333 13.2083C9.8003 13.2083 9.125 12.533 9.125 11.7Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -613,18 +613,17 @@
"%": "project_panel::NewFile",
"/": "project_panel::NewSearchInDirectory",
"d": "project_panel::NewDirectory",
"enter": "project_panel::OpenPermanent",
"enter": "project_panel::Open",
"escape": "project_panel::ToggleFocus",
"h": "project_panel::CollapseSelectedEntry",
"j": "menu::SelectNext",
"k": "menu::SelectPrev",
"l": "project_panel::ExpandSelectedEntry",
"o": "project_panel::OpenPermanent",
"o": "project_panel::Open",
"shift-d": "project_panel::Delete",
"shift-r": "project_panel::Rename",
"t": "project_panel::OpenPermanent",
"v": "project_panel::OpenPermanent",
"p": "project_panel::Open",
"t": "project_panel::Open",
"v": "project_panel::Open",
"x": "project_panel::RevealInFinder"
}
}

View File

@@ -47,20 +47,11 @@
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
// Centered layout related settings.
"centered_layout": {
// The relative width of the left padding of the central pane from the
// workspace when the centered layout is used.
"left_padding": 0.2,
// The relative width of the right padding of the central pane from the
// workspace when the centered layout is used.
"right_padding": 0.2
},
// The key to use for adding multiple cursors
// Currently "alt" or "cmd_or_ctrl" (also aliased as
// "cmd" and "ctrl") are supported.
"multi_cursor_modifier": "alt",
// Whether to enable vim modes and key bindings.
// Whether to enable vim modes and key bindings
"vim_mode": false,
// Whether to show the informational hover box when moving the mouse
// over symbols in the editor.
@@ -69,8 +60,6 @@
"confirm_quit": false,
// Whether to restore last closed project when fresh Zed instance is opened.
"restore_on_startup": "last_workspace",
// Size of the drop target in the editor.
"drop_target_size": 0.2,
// Whether the cursor blinks in the editor.
"cursor_blink": true,
// Whether to pop the completions menu while typing in an editor without
@@ -103,9 +92,8 @@
// 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,
// Whether to automatically add matching closing characters when typing
// opening parenthesis, bracket, brace, single or double quote characters.
// For example, when you type (, Zed will add a closing ) at the correct position.
// Whether to automatically type closing characters for you. For example,
// when you type (, Zed will automatically add a closing ) at the correct position.
"use_autoclose": true,
// Controls how the editor handles the autoclosed characters.
// When set to `false`(default), skipping over and auto-removing of the closing characters
@@ -157,10 +145,10 @@
"show": "auto",
// Whether to show git diff indicators in the scrollbar.
"git_diff": true,
// Whether to show buffer search results in the scrollbar.
"search_results": true,
// Whether to show selected symbol occurrences in the scrollbar.
"selected_symbol": true,
// Whether to show selections in the scrollbar.
"selections": true,
// Whether to show symbols selections in the scrollbar.
"symbols_selections": true,
// Whether to show diagnostic indicators in the scrollbar.
"diagnostics": true
},
@@ -183,9 +171,6 @@
},
// The number of lines to keep above/below the cursor when scrolling.
"vertical_scroll_margin": 3,
// Scroll sensitivity multiplier. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
"relative_line_numbers": false,
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
@@ -214,8 +199,6 @@
"scroll_debounce_ms": 50
},
"project_panel": {
// Whether to show the project panel button in the status bar
"button": true,
// Default width of the project panel.
"default_width": 240,
// Where to dock the project panel. Can be 'left' or 'right'.
@@ -407,15 +390,7 @@
// "git_gutter": "tracked_files"
// 2. Hide the gutter
// "git_gutter": "hide"
"git_gutter": "tracked_files",
// Control whether the git blame information is shown inline,
// in the currently focused line.
"inline_blame": {
"enabled": true
// Sets a delay after which the inline blame information is shown.
// Delay is restarted with every cursor movement.
// "delay_ms": 600
}
"git_gutter": "tracked_files"
},
"copilot": {
// The set of glob patterns for which copilot should be disabled
@@ -504,8 +479,6 @@
// Whether or not selecting text in the terminal will automatically
// copy to the system clipboard.
"copy_on_select": false,
// Whether to show the terminal button in the status bar
"button": true,
// Any key-value pairs added to this list will be added to the terminal's
// environment. Use `:` to separate multiple values.
"env": {

View File

@@ -5,9 +5,6 @@ edition = "2021"
publish = false
license = "AGPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/anthropic.rs"
@@ -20,3 +17,6 @@ util.workspace = true
[dev-dependencies]
tokio.workspace = true
[lints]
workspace = true

View File

@@ -1094,39 +1094,33 @@ impl AssistantPanel {
let view = cx.view().clone();
let scroll_handle = self.saved_conversations_scroll_handle.clone();
let conversation_count = self.saved_conversations.len();
todo!("replace canvas")
// canvas(
// move |_, cx| {
// let saved_conversations = uniform_list(
// view.clone(),
// "saved_conversations",
// conversation_count,
// |this, range, cx| {
// range
// .map(|ix| this.render_saved_conversation(ix, cx))
// .collect()
// },
// )
// .track_scroll(scroll_handle.clone())
// .into_any_element();
// saved_conversations.layout(absolute_offset, available_space, cx)
// // compute layout for saved conversations
// saved_conversations
// },
// move |bounds, saved_conversations, cx| {
// saved_conversations.layout(
// bounds.origin,
// bounds.size.map(AvailableSpace::Definite),
// cx,
// );
// saved_conversations
// },
// |_bounds, mut saved_conversations, cx| saved_conversations.paint(cx),
// )
// .size_full()
// .into_any_element()
} else if let Some(editor) = self.active_conversation_editor() {
let editor = editor.clone();
canvas(
move |bounds, cx| {
let mut saved_conversations = uniform_list(
view,
"saved_conversations",
conversation_count,
|this, range, cx| {
range
.map(|ix| this.render_saved_conversation(ix, cx))
.collect()
},
)
.track_scroll(scroll_handle)
.into_any_element();
saved_conversations.layout(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
);
saved_conversations
},
|_bounds, mut saved_conversations, cx| saved_conversations.paint(cx),
)
.size_full()
.into_any_element()
} else {
let editor = self.active_conversation_editor().unwrap();
let conversation = editor.read(cx).conversation.clone();
div()
.size_full()
@@ -1141,8 +1135,6 @@ impl AssistantPanel {
.children(self.render_remaining_tokens(&conversation, cx)),
)
.into_any_element()
} else {
div().into_any_element()
},
))
}
@@ -2073,7 +2065,7 @@ impl ConversationEditor {
workspace: workspace.downgrade(),
_subscriptions,
};
cx.defer(|this, cx| this.update_active_buffer(workspace, cx));
this.update_active_buffer(workspace, cx);
this.update_message_headers(cx);
this
}

View File

@@ -52,19 +52,12 @@ impl Render for Breadcrumbs {
Some(BreadcrumbText {
text: "".into(),
highlights: None,
font: None,
}),
);
}
let highlighted_segments = segments.into_iter().map(|segment| {
let mut text_style = cx.text_style();
if let Some(font) = segment.font {
text_style.font_family = font.family;
text_style.font_features = font.features;
text_style.font_style = font.style;
text_style.font_weight = font.weight;
}
text_style.color = Color::Muted.color(cx);
StyledText::new(segment.text.replace('\n', ""))

View File

@@ -132,7 +132,7 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
move |_: &SignOut, cx| {
if let Some(client) = client.upgrade() {
cx.spawn(|cx| async move {
client.sign_out(&cx).await;
client.disconnect(&cx);
})
.detach();
}
@@ -1250,15 +1250,6 @@ impl Client {
})
}
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncAppContext) {
self.state.write().credentials = None;
self.disconnect(&cx);
if self.has_keychain_credentials(cx).await {
delete_credentials_from_keychain(cx).await.log_err();
}
}
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) {
self.peer.teardown();
self.set_status(Status::SignedOut, cx);

View File

@@ -64,7 +64,7 @@ toml.workspace = true
tower = "0.4"
tower-http = { workspace = true, features = ["trace"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
tracing-subscriber = { git = "https://github.com/tokio-rs/tracing", rev = "tracing-subscriber-0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
util.workspace = true
uuid.workspace = true

View File

@@ -106,12 +106,8 @@ async fn get_extension_versions(
}
#[derive(Debug, Deserialize)]
struct DownloadLatestExtensionPathParams {
struct DownloadLatestExtensionParams {
extension_id: String,
}
#[derive(Debug, Deserialize)]
struct DownloadLatestExtensionQueryParams {
min_schema_version: Option<i32>,
max_schema_version: Option<i32>,
min_wasm_api_version: Option<SemanticVersion>,
@@ -120,14 +116,13 @@ struct DownloadLatestExtensionQueryParams {
async fn download_latest_extension(
Extension(app): Extension<Arc<AppState>>,
Path(params): Path<DownloadLatestExtensionPathParams>,
Query(query): Query<DownloadLatestExtensionQueryParams>,
Path(params): Path<DownloadLatestExtensionParams>,
) -> Result<Redirect> {
let constraints = maybe!({
let min_schema_version = query.min_schema_version?;
let max_schema_version = query.max_schema_version?;
let min_wasm_api_version = query.min_wasm_api_version?;
let max_wasm_api_version = query.max_wasm_api_version?;
let min_schema_version = params.min_schema_version?;
let max_schema_version = params.max_schema_version?;
let min_wasm_api_version = params.min_wasm_api_version?;
let max_wasm_api_version = params.max_wasm_api_version?;
Some(ExtensionVersionConstraints {
schema_versions: min_schema_version..=max_schema_version,

View File

@@ -3,7 +3,6 @@ use crate::{
tests::{rust_lang, TestServer},
};
use call::ActiveCall;
use collections::HashMap;
use editor::{
actions::{
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Redo, Rename, RevertSelectedHunks,
@@ -19,10 +18,7 @@ use language::{
language_settings::{AllLanguageSettings, InlayHintSettings},
FakeLspAdapter,
};
use project::{
project_settings::{InlineBlameSettings, ProjectSettings},
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
};
use project::SERVER_PROGRESS_DEBOUNCE_TIMEOUT;
use rpc::RECEIVE_TIMEOUT;
use serde_json::json;
use settings::SettingsStore;
@@ -736,60 +732,12 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
6..9
);
rename.editor.update(cx, |rename_editor, cx| {
let rename_selection = rename_editor.selections.newest::<usize>(cx);
assert_eq!(
rename_selection.range(),
0..3,
"Rename that was triggered from zero selection caret, should propose the whole word."
);
rename_editor.buffer().update(cx, |rename_buffer, cx| {
rename_buffer.edit([(0..3, "THREE")], None, cx);
});
});
});
// Cancel the rename, and repeat the same, but use selections instead of cursor movement
editor_b.update(cx_b, |editor, cx| {
editor.cancel(&editor::actions::Cancel, cx);
});
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([7..8]));
editor.rename(&Rename, cx).unwrap()
});
fake_language_server
.handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
assert_eq!(params.position, lsp::Position::new(0, 8));
Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
lsp::Position::new(0, 6),
lsp::Position::new(0, 9),
))))
})
.next()
.await
.unwrap();
prepare_rename.await.unwrap();
editor_b.update(cx_b, |editor, cx| {
use editor::ToOffset;
let rename = editor.pending_rename().unwrap();
let buffer = editor.buffer().read(cx).snapshot(cx);
let lsp_rename_start = rename.range.start.to_offset(&buffer);
let lsp_rename_end = rename.range.end.to_offset(&buffer);
assert_eq!(lsp_rename_start..lsp_rename_end, 6..9);
rename.editor.update(cx, |rename_editor, cx| {
let rename_selection = rename_editor.selections.newest::<usize>(cx);
assert_eq!(
rename_selection.range(),
1..2,
"Rename that was triggered from a selection, should have the same selection range in the rename proposal"
);
rename_editor.buffer().update(cx, |rename_buffer, cx| {
rename_buffer.edit([(0..lsp_rename_end - lsp_rename_start, "THREE")], None, cx);
});
});
});
let confirm_rename = editor_b.update(cx_b, |editor, cx| {
Editor::confirm_rename(editor, &ConfirmRename, cx).unwrap()
});
@@ -2051,26 +1999,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
cx_a.update(editor::init);
cx_b.update(editor::init);
// Turn inline-blame-off by default so no state is transferred without us explicitly doing so
let inline_blame_off_settings = Some(InlineBlameSettings {
enabled: false,
delay_ms: None,
min_column: None,
});
cx_a.update(|cx| {
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<ProjectSettings>(cx, |settings| {
settings.git.inline_blame = inline_blame_off_settings;
});
});
});
cx_b.update(|cx| {
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<ProjectSettings>(cx, |settings| {
settings.git.inline_blame = inline_blame_off_settings;
});
});
});
client_a
.fs()
@@ -2090,7 +2018,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
blame_entry("3a3a3a", 2..3),
blame_entry("4c4c4c", 3..4),
],
permalinks: HashMap::default(), // This field is deprecrated
permalinks: [
("1b1b1b", "http://example.com/codehost/idx-0"),
("0d0d0d", "http://example.com/codehost/idx-1"),
("3a3a3a", "http://example.com/codehost/idx-2"),
("4c4c4c", "http://example.com/codehost/idx-3"),
]
.into_iter()
.map(|(sha, url)| (sha.parse().unwrap(), url.parse().unwrap()))
.collect(),
messages: [
("1b1b1b", "message for idx-0"),
("0d0d0d", "message for idx-1"),
@@ -2100,7 +2036,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
.into_iter()
.map(|(sha, message)| (sha.parse().unwrap(), message.into()))
.collect(),
remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
};
client_a.fs().set_blame_for_repo(
Path::new("/my-repo/.git"),
@@ -2165,11 +2100,13 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
blame.update(cx, |blame, _| {
for (idx, entry) in entries.iter().flatten().enumerate() {
let details = blame.details_for_entry(entry).unwrap();
assert_eq!(details.message, format!("message for idx-{}", idx));
assert_eq!(
details.permalink.unwrap().to_string(),
format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
blame.permalink_for_entry(entry).unwrap().to_string(),
format!("http://example.com/codehost/idx-{}", idx)
);
assert_eq!(
blame.message_for_entry(entry).unwrap(),
format!("message for idx-{}", idx)
);
}
});

View File

@@ -305,10 +305,6 @@ impl ChannelView {
});
}
ChannelBufferEvent::BufferEdited => {
// Emit the edited event on the editor context so that other views can update it's state (e.g. markdown preview)
self.editor.update(cx, |_, cx| {
cx.emit(EditorEvent::Edited);
});
if self.editor.read(cx).is_focused(cx) {
self.acknowledge_buffer_version(cx);
} else {

View File

@@ -557,7 +557,6 @@ mod tests {
use clock::FakeSystemClock;
use gpui::TestAppContext;
use language::{Language, LanguageConfig};
use project::Project;
use rpc::proto;
use settings::SettingsStore;
use util::{http::FakeHttpClient, test::marked_text_ranges};
@@ -631,7 +630,6 @@ mod tests {
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
theme::init(theme::LoadThemes::JustBase, cx);
Project::init_settings(cx);
language::init(cx);
editor::init(cx);
client::init(&client, cx);

View File

@@ -2834,31 +2834,34 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) ->
let thickness = px(1.);
let color = cx.theme().colors().text;
canvas(move |bounds, cx| {
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
let right = bounds.right();
let top = bounds.top();
canvas(
|_, _| {},
move |bounds, _, cx| {
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
let right = bounds.right();
let top = bounds.top();
cx.paint_quad(fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
cx.paint_quad(fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
),
),
),
color,
));
cx.paint_quad(fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
));
})
color,
));
cx.paint_quad(fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
));
},
)
.w(width)
.h(line_height)
}

View File

@@ -318,23 +318,26 @@ impl Render for CollabTitlebarItem {
}
fn render_color_ribbon(color: Hsla) -> impl Element {
canvas(move |bounds, cx| {
let height = bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(height.0 / 2.0);
let mut path = Path::new(bounds.lower_left());
path.curve_to(
bounds.origin + point(horizontal_offset, vertical_offset),
bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
bounds.lower_right(),
bounds.upper_right() + point(px(0.0), vertical_offset),
);
path.line_to(bounds.lower_left());
cx.paint_path(path, color);
})
canvas(
move |_, _| {},
move |bounds, _, cx| {
let height = bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(height.0 / 2.0);
let mut path = Path::new(bounds.lower_left());
path.curve_to(
bounds.origin + point(horizontal_offset, vertical_offset),
bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
bounds.lower_right(),
bounds.upper_right() + point(px(0.0), vertical_offset),
);
path.line_to(bounds.lower_left());
cx.paint_path(path, color);
},
)
.h_1()
.w_full()
}

View File

@@ -44,6 +44,8 @@ use workspace::{
actions!(diagnostics, [Deploy, ToggleWarnings]);
const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut AppContext) {
ProjectDiagnosticsSettings::register(cx);
cx.observe_new_views(ProjectDiagnosticsEditor::register)
@@ -61,7 +63,6 @@ struct ProjectDiagnosticsEditor {
paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
include_warnings: bool,
context: u32,
_subscriptions: Vec<Subscription>,
}
@@ -115,8 +116,7 @@ impl ProjectDiagnosticsEditor {
workspace.register_action(Self::deploy);
}
fn new_with_context(
context: u32,
fn new(
project_handle: Model<Project>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
@@ -136,15 +136,8 @@ impl ProjectDiagnosticsEditor {
.entry(*language_server_id)
.or_default()
.insert(path.clone());
if this.is_dirty(cx) {
return;
}
let selections = this.editor.read(cx).selections.all::<usize>(cx);
if selections.len() < 2
&& selections
.first()
.map_or(true, |selection| selection.end == selection.start)
if this.editor.read(cx).selections.all::<usize>(cx).is_empty()
&& !this.is_dirty(cx)
{
this.update_excerpts(Some(*language_server_id), cx);
}
@@ -181,7 +174,6 @@ impl ProjectDiagnosticsEditor {
let summary = project.diagnostic_summary(false, cx);
let mut this = Self {
project: project_handle,
context,
summary,
workspace,
excerpts,
@@ -201,19 +193,6 @@ impl ProjectDiagnosticsEditor {
this
}
fn new(
project_handle: Model<Project>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
) -> Self {
Self::new_with_context(
editor::DEFAULT_MULTIBUFFER_CONTEXT,
project_handle,
workspace,
cx,
)
}
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
workspace.activate_item(&existing, cx);
@@ -444,16 +423,18 @@ impl ProjectDiagnosticsEditor {
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
if let Some((range, start_ix)) = &mut pending_range {
if let Some(entry) = resolved_entry.as_ref() {
if entry.range.start.row <= range.end.row + 1 + self.context * 2 {
if entry.range.start.row
<= range.end.row + 1 + CONTEXT_LINE_COUNT * 2
{
range.end = range.end.max(entry.range.end);
continue;
}
}
let excerpt_start =
Point::new(range.start.row.saturating_sub(self.context), 0);
Point::new(range.start.row.saturating_sub(CONTEXT_LINE_COUNT), 0);
let excerpt_end = snapshot.clip_point(
Point::new(range.end.row + self.context, u32::MAX),
Point::new(range.end.row + CONTEXT_LINE_COUNT, u32::MAX),
Bias::Left,
);
let excerpt_id = excerpts
@@ -1042,12 +1023,7 @@ mod tests {
// Open the project diagnostics view while there are already diagnostics.
let view = window.build_view(cx, |cx| {
ProjectDiagnosticsEditor::new_with_context(
1,
project.clone(),
workspace.downgrade(),
cx,
)
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});
view.next_notification(cx).await;
@@ -1357,12 +1333,7 @@ mod tests {
let workspace = window.root(cx).unwrap();
let view = window.build_view(cx, |cx| {
ProjectDiagnosticsEditor::new_with_context(
1,
project.clone(),
workspace.downgrade(),
cx,
)
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});
// Two language servers start updating diagnostics

View File

@@ -245,7 +245,6 @@ gpui::actions!(
Tab,
TabPrev,
ToggleGitBlame,
ToggleGitBlameInline,
ToggleInlayHints,
ToggleLineNumbers,
ToggleSoftWrap,

View File

@@ -131,14 +131,13 @@ use ui::{
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::item::ItemHandle;
use workspace::notifications::NotificationId;
use workspace::Toast;
use workspace::{
searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
};
use workspace::{OpenInTerminal, OpenTerminal, Toast};
use crate::hover_links::find_url;
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const MAX_LINE_LEN: usize = 1024;
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
@@ -156,7 +155,7 @@ pub fn render_parsed_markdown(
parsed: &language::ParsedMarkdown,
editor_style: &EditorStyle,
workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
cx: &mut ViewContext<Editor>,
) -> InteractiveText {
let code_span_background_color = cx
.theme()
@@ -464,10 +463,7 @@ pub struct Editor {
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
use_autoclose: bool,
auto_replace_emoji_shortcode: bool,
show_git_blame_gutter: bool,
show_git_blame_inline: bool,
show_git_blame_inline_delay_task: Option<Task<()>>,
git_blame_inline_enabled: bool,
show_git_blame: bool,
blame: Option<Model<GitBlame>>,
blame_subscription: Option<Subscription>,
custom_context_menu: Option<
@@ -476,15 +472,15 @@ pub struct Editor {
+ Fn(&mut Self, DisplayPoint, &mut ViewContext<Self>) -> Option<View<ui::ContextMenu>>,
>,
>,
last_layout_bounds: Option<Bounds<Pixels>>,
expect_layout_bounds_change: Option<Bounds<Pixels>>,
last_bounds: Option<Bounds<Pixels>>,
expect_bounds_change: Option<Bounds<Pixels>>,
}
#[derive(Clone)]
pub struct EditorSnapshot {
pub mode: EditorMode,
show_gutter: bool,
render_git_blame_gutter: bool,
show_git_blame: bool,
pub display_snapshot: DisplaySnapshot,
pub placeholder_text: Option<Arc<str>>,
is_focused: bool,
@@ -1492,8 +1488,8 @@ impl Editor {
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
gutter_hovered: false,
pixel_position_of_newest_cursor: None,
last_layout_bounds: None,
expect_layout_bounds_change: None,
last_bounds: None,
expect_bounds_change: None,
gutter_width: Default::default(),
style: None,
show_cursor_names: false,
@@ -1502,10 +1498,7 @@ impl Editor {
vim_replace_map: Default::default(),
show_inline_completions: mode == EditorMode::Full,
custom_context_menu: None,
show_git_blame_gutter: false,
show_git_blame_inline: false,
show_git_blame_inline_delay_task: None,
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
show_git_blame: false,
blame: None,
blame_subscription: None,
_subscriptions: vec![
@@ -1537,11 +1530,6 @@ impl Editor {
if mode == EditorMode::Full {
let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
if this.git_blame_inline_enabled {
this.git_blame_inline_enabled = true;
this.start_git_blame_inline(false, cx);
}
}
this.report_editor_event("open", None, cx);
@@ -1658,7 +1646,10 @@ impl Editor {
EditorSnapshot {
mode: self.mode,
show_gutter: self.show_gutter,
render_git_blame_gutter: self.render_git_blame_gutter(cx),
show_git_blame: self
.blame
.as_ref()
.map_or(false, |blame| blame.read(cx).has_generated_entries()),
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
scroll_anchor: self.scroll_manager.anchor(),
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
@@ -1828,29 +1819,6 @@ impl Editor {
old_cursor_position: &Anchor,
cx: &mut ViewContext<Self>,
) {
// Copy selections to primary selection buffer
#[cfg(target_os = "linux")]
if local {
let selections = self.selections.all::<usize>(cx);
let buffer_handle = self.buffer.read(cx).read(cx);
let mut text = String::new();
for (index, selection) in selections.iter().enumerate() {
let text_for_selection = buffer_handle
.text_for_range(selection.start..selection.end)
.collect::<String>();
text.push_str(&text_for_selection);
if index != selections.len() - 1 {
text.push('\n');
}
}
if !text.is_empty() {
cx.write_to_primary(ClipboardItem::new(text));
}
}
if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() {
self.buffer.update(cx, |buffer, cx| {
buffer.set_active_selections(
@@ -1947,9 +1915,6 @@ impl Editor {
self.refresh_document_highlights(cx);
refresh_matching_bracket_highlights(self, cx);
self.discard_inline_completion(cx);
if self.git_blame_inline_enabled {
self.start_inline_blame_timer(cx);
}
}
self.blink_manager.update(cx, BlinkManager::pause_blinking);
@@ -3767,7 +3732,7 @@ impl Editor {
buffer
.edited_ranges_for_transaction::<usize>(transaction)
.collect(),
DEFAULT_MULTIBUFFER_CONTEXT,
1,
cx,
),
);
@@ -3829,22 +3794,6 @@ impl Editor {
None
}
fn start_inline_blame_timer(&mut self, cx: &mut ViewContext<Self>) {
if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
self.show_git_blame_inline = false;
self.show_git_blame_inline_delay_task = Some(cx.spawn(|this, mut cx| async move {
cx.background_executor().timer(delay).await;
this.update(&mut cx, |this, cx| {
this.show_git_blame_inline = true;
cx.notify();
})
.log_err();
}));
}
}
fn refresh_document_highlights(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
if self.pending_rename.is_some() {
return None;
@@ -4943,25 +4892,6 @@ impl Editor {
}
}
pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
let project_path = buffer.read(cx).project_path(cx)?;
let project = self.project.as_ref()?.read(cx);
let entry = project.entry_for_path(&project_path, cx)?;
let abs_path = project.absolute_path(&project_path, cx)?;
let parent = if entry.is_symlink {
abs_path.canonicalize().ok()?
} else {
abs_path
}
.parent()?
.to_path_buf();
Some(parent)
}) {
cx.dispatch_action(OpenTerminal { working_directory }.boxed_clone());
}
}
fn gather_revert_changes(
&mut self,
selections: &[Selection<Anchor>],
@@ -8055,7 +7985,7 @@ impl Editor {
ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
location.buffer.clone(),
ranges_for_buffer,
DEFAULT_MULTIBUFFER_CONTEXT,
1,
cx,
))
}
@@ -8094,7 +8024,7 @@ impl Editor {
.buffer
.read(cx)
.text_anchor_for_position(selection.head(), cx)?;
let (tail_buffer, cursor_buffer_position_end) = self
let (tail_buffer, _) = self
.buffer
.read(cx)
.text_anchor_for_position(selection.tail(), cx)?;
@@ -8104,7 +8034,6 @@ impl Editor {
let snapshot = cursor_buffer.read(cx).snapshot();
let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
let prepare_rename = project.update(cx, |project, cx| {
project.prepare_rename(cursor_buffer.clone(), cursor_buffer_offset, cx)
});
@@ -8133,8 +8062,6 @@ impl Editor {
let rename_buffer_range = rename_range.to_offset(&snapshot);
let cursor_offset_in_rename_range =
cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
let cursor_offset_in_rename_range_end =
cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
this.take_rename(false, cx);
let buffer = this.buffer.read(cx).read(cx);
@@ -8163,23 +8090,7 @@ impl Editor {
editor.buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, old_name.clone())], None, cx)
});
let rename_selection_range = match cursor_offset_in_rename_range
.cmp(&cursor_offset_in_rename_range_end)
{
Ordering::Equal => {
editor.select_all(&SelectAll, cx);
return editor;
}
Ordering::Less => {
cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
}
Ordering::Greater => {
cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
}
};
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([rename_selection_range]);
});
editor.select_all(&SelectAll, cx);
editor
});
@@ -8932,89 +8843,40 @@ impl Editor {
}
pub fn toggle_git_blame(&mut self, _: &ToggleGitBlame, cx: &mut ViewContext<Self>) {
self.show_git_blame_gutter = !self.show_git_blame_gutter;
if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
self.start_git_blame(true, cx);
if self.show_git_blame {
self.blame_subscription.take();
self.blame.take();
self.show_git_blame = false
} else {
if let Err(error) = self.show_git_blame_internal(cx) {
log::error!("failed to toggle on 'git blame': {}", error);
return;
}
self.show_git_blame = true
}
cx.notify();
}
pub fn toggle_git_blame_inline(
&mut self,
_: &ToggleGitBlameInline,
cx: &mut ViewContext<Self>,
) {
self.toggle_git_blame_inline_internal(true, cx);
cx.notify();
}
pub fn git_blame_inline_enabled(&self) -> bool {
self.git_blame_inline_enabled
}
fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
fn show_git_blame_internal(&mut self, cx: &mut ViewContext<Self>) -> Result<()> {
if let Some(project) = self.project.as_ref() {
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
return;
anyhow::bail!("git blame not available in multi buffers")
};
let project = project.clone();
let blame = cx.new_model(|cx| GitBlame::new(buffer, project, user_triggered, cx));
let blame = cx.new_model(|cx| GitBlame::new(buffer, project, cx));
self.blame_subscription = Some(cx.observe(&blame, |_, _, cx| cx.notify()));
self.blame = Some(blame);
}
}
fn toggle_git_blame_inline_internal(
&mut self,
user_triggered: bool,
cx: &mut ViewContext<Self>,
) {
if self.git_blame_inline_enabled {
self.git_blame_inline_enabled = false;
self.show_git_blame_inline = false;
self.show_git_blame_inline_delay_task.take();
} else {
self.git_blame_inline_enabled = true;
self.start_git_blame_inline(user_triggered, cx);
}
cx.notify();
}
fn start_git_blame_inline(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
self.start_git_blame(user_triggered, cx);
if ProjectSettings::get_global(cx)
.git
.inline_blame_delay()
.is_some()
{
self.start_inline_blame_timer(cx);
} else {
self.show_git_blame_inline = true
}
Ok(())
}
pub fn blame(&self) -> Option<&Model<GitBlame>> {
self.blame.as_ref()
}
pub fn render_git_blame_gutter(&mut self, cx: &mut WindowContext) -> bool {
self.show_git_blame_gutter && self.has_blame_entries(cx)
}
pub fn render_git_blame_inline(&mut self, cx: &mut WindowContext) -> bool {
self.focus_handle.is_focused(cx) && self.show_git_blame_inline && self.has_blame_entries(cx)
}
fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
self.blame()
.map_or(false, |blame| blame.read(cx).has_generated_entries())
}
fn get_permalink_to_line(&mut self, cx: &mut ViewContext<Self>) -> Result<url::Url> {
let (path, repo) = maybe!({
let project_handle = self.project.as_ref()?.clone();
@@ -9584,14 +9446,6 @@ impl Editor {
let editor_settings = EditorSettings::get_global(cx);
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
if self.mode == EditorMode::Full {
let inline_blame_enabled = ProjectSettings::get_global(cx).git.inline_blame_enabled();
if self.git_blame_inline_enabled != inline_blame_enabled {
self.toggle_git_blame_inline_internal(false, cx);
}
}
cx.notify();
}
@@ -10204,7 +10058,7 @@ impl EditorSnapshot {
};
let git_blame_entries_width = self
.render_git_blame_gutter
.show_git_blame
.then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
@@ -10739,11 +10593,6 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
let mut text_style = cx.text_style().clone();
text_style.color = diagnostic_style(diagnostic.severity, true, cx.theme().status());
let theme_settings = ThemeSettings::get_global(cx);
text_style.font_family = theme_settings.buffer_font.family.clone();
text_style.font_style = theme_settings.buffer_font.style;
text_style.font_features = theme_settings.buffer_font.features;
text_style.font_weight = theme_settings.buffer_font.weight;
let multi_line_diagnostic = diagnostic.message.contains('\n');
@@ -10778,8 +10627,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
let icon_size = buttons(&diagnostic, cx.block_id)
.into_any_element()
.layout(AvailableSpace::min_size(), cx)
.size;
.measure(AvailableSpace::min_size(), cx);
h_flex()
.id(cx.block_id)

View File

@@ -15,7 +15,6 @@ pub struct EditorSettings {
pub scrollbar: Scrollbar,
pub gutter: Gutter,
pub vertical_scroll_margin: f32,
pub scroll_sensitivity: f32,
pub relative_line_numbers: bool,
pub seed_search_query_from_cursor: SeedQuerySetting,
pub multi_cursor_modifier: MultiCursorModifier,
@@ -58,8 +57,8 @@ pub struct Toolbar {
pub struct Scrollbar {
pub show: ShowScrollbar,
pub git_diff: bool,
pub selected_symbol: bool,
pub search_results: bool,
pub selections: bool,
pub symbols_selections: bool,
pub diagnostics: bool,
}
@@ -139,11 +138,6 @@ pub struct EditorSettingsContent {
///
/// Default: 3.
pub vertical_scroll_margin: Option<f32>,
/// Scroll sensitivity multiplier. This multiplier is applied
/// to both the horizontal and vertical delta values while scrolling.
///
/// Default: 1.0
pub scroll_sensitivity: Option<f32>,
/// Whether the line numbers on editors gutter are relative or not.
///
/// Default: false
@@ -184,7 +178,7 @@ pub struct ToolbarContent {
}
/// Scrollbar related settings
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ScrollbarContent {
/// When to show the scrollbar in the editor.
///
@@ -194,14 +188,14 @@ pub struct ScrollbarContent {
///
/// Default: true
pub git_diff: Option<bool>,
/// Whether to show buffer search result indicators in the scrollbar.
/// Whether to show buffer search result markers in the scrollbar.
///
/// Default: true
pub search_results: Option<bool>,
/// Whether to show selected symbol occurrences in the scrollbar.
pub selections: Option<bool>,
/// Whether to show symbols highlighted markers in the scrollbar.
///
/// Default: true
pub selected_symbol: Option<bool>,
pub symbols_selections: Option<bool>,
/// Whether to show diagnostic indicators in the scrollbar.
///
/// Default: true

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,11 @@
use std::{sync::Arc, time::Duration};
use anyhow::Result;
use collections::HashMap;
use git::{
blame::{Blame, BlameEntry},
permalink::{build_commit_permalink, parse_git_remote_url},
Oid,
};
use gpui::{Model, ModelContext, Subscription, Task};
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
use language::{Bias, Buffer, BufferSnapshot, Edit};
use project::{Item, Project};
use smallvec::SmallVec;
use sum_tree::SumTree;
@@ -47,32 +44,23 @@ impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
}
}
#[derive(Clone, Debug)]
pub struct CommitDetails {
pub message: String,
pub parsed_message: ParsedMarkdown,
pub permalink: Option<Url>,
}
pub struct GitBlame {
project: Model<Project>,
buffer: Model<Buffer>,
entries: SumTree<GitBlameEntry>,
commit_details: HashMap<Oid, CommitDetails>,
permalinks: HashMap<Oid, Url>,
messages: HashMap<Oid, String>,
buffer_snapshot: BufferSnapshot,
buffer_edits: text::Subscription,
task: Task<Result<()>>,
generated: bool,
user_triggered: bool,
regenerate_on_edit_task: Task<Result<()>>,
_regenerate_subscriptions: Vec<Subscription>,
_refresh_subscription: Subscription,
}
impl GitBlame {
pub fn new(
buffer: Model<Buffer>,
project: Model<Project>,
user_triggered: bool,
cx: &mut ModelContext<Self>,
) -> Self {
let entries = SumTree::from_item(
@@ -83,19 +71,7 @@ impl GitBlame {
&(),
);
let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
language::Event::DirtyChanged => {
if !buffer.read(cx).is_dirty() {
this.generate(cx);
}
}
language::Event::Edited => {
this.regenerate_on_edit(cx);
}
_ => {}
});
let project_subscription = cx.subscribe(&project, {
let refresh_subscription = cx.subscribe(&project, {
let buffer = buffer.clone();
move |this, _, event, cx| match event {
@@ -126,12 +102,11 @@ impl GitBlame {
buffer_snapshot,
entries,
buffer_edits,
user_triggered,
commit_details: HashMap::default(),
permalinks: HashMap::default(),
messages: HashMap::default(),
task: Task::ready(Ok(())),
generated: false,
regenerate_on_edit_task: Task::ready(Ok(())),
_regenerate_subscriptions: vec![buffer_subscriptions, project_subscription],
_refresh_subscription: refresh_subscription,
};
this.generate(cx);
this
@@ -141,8 +116,12 @@ impl GitBlame {
self.generated
}
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<CommitDetails> {
self.commit_details.get(&entry.sha).cloned()
pub fn permalink_for_entry(&self, entry: &BlameEntry) -> Option<Url> {
self.permalinks.get(&entry.sha).cloned()
}
pub fn message_for_entry(&self, entry: &BlameEntry) -> Option<String> {
self.messages.get(&entry.sha).cloned()
}
pub fn blame_for_rows<'a>(
@@ -275,7 +254,6 @@ impl GitBlame {
let buffer_edits = self.buffer.update(cx, |buffer, _| buffer.subscribe());
let snapshot = self.buffer.read(cx).snapshot();
let blame = self.project.read(cx).blame_buffer(&self.buffer, None, cx);
let languages = self.project.read(cx).languages().clone();
self.task = cx.spawn(|this, mut cx| async move {
let result = cx
@@ -287,155 +265,65 @@ impl GitBlame {
entries,
permalinks,
messages,
remote_url,
} = blame.await?;
let entries = build_blame_entry_sum_tree(entries, snapshot.max_point().row);
let commit_details =
parse_commit_messages(messages, remote_url, &permalinks, &languages)
.await;
let mut current_row = 0;
let mut entries = SumTree::from_iter(
entries.into_iter().flat_map(|entry| {
let mut entries = SmallVec::<[GitBlameEntry; 2]>::new();
anyhow::Ok((entries, commit_details))
if entry.range.start > current_row {
let skipped_rows = entry.range.start - current_row;
entries.push(GitBlameEntry {
rows: skipped_rows,
blame: None,
});
}
entries.push(GitBlameEntry {
rows: entry.range.len() as u32,
blame: Some(entry.clone()),
});
current_row = entry.range.end;
entries
}),
&(),
);
let max_row = snapshot.max_point().row;
if max_row >= current_row {
entries.push(
GitBlameEntry {
rows: (max_row + 1) - current_row,
blame: None,
},
&(),
);
}
anyhow::Ok((entries, permalinks, messages))
}
})
.await;
this.update(&mut cx, |this, cx| match result {
Ok((entries, commit_details)) => {
Ok((entries, permalinks, messages)) => {
this.buffer_edits = buffer_edits;
this.buffer_snapshot = snapshot;
this.entries = entries;
this.commit_details = commit_details;
this.permalinks = permalinks;
this.messages = messages;
this.generated = true;
cx.notify();
}
Err(error) => this.project.update(cx, |_, cx| {
if this.user_triggered {
log::error!("failed to get git blame data: {error:?}");
let notification = format!("{:#}", error).trim().to_string();
cx.emit(project::Event::Notification(notification));
} else {
// If we weren't triggered by a user, we just log errors in the background, instead of sending
// notifications.
// Except for `NoRepositoryError`, which can happen often if a user has inline-blame turned on
// and opens a non-git file.
if error.downcast_ref::<project::NoRepositoryError>().is_none() {
log::error!("failed to get git blame data: {error:?}");
}
}
log::error!("failed to get git blame data: {error:?}");
let notification = format!("{:#}", error).trim().to_string();
cx.emit(project::Event::Notification(notification));
}),
})
});
}
fn regenerate_on_edit(&mut self, cx: &mut ModelContext<Self>) {
self.regenerate_on_edit_task = cx.spawn(|this, mut cx| async move {
cx.background_executor()
.timer(REGENERATE_ON_EDIT_DEBOUNCE_INTERVAL)
.await;
this.update(&mut cx, |this, cx| {
this.generate(cx);
})
});
}
}
const REGENERATE_ON_EDIT_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(2);
fn build_blame_entry_sum_tree(entries: Vec<BlameEntry>, max_row: u32) -> SumTree<GitBlameEntry> {
let mut current_row = 0;
let mut entries = SumTree::from_iter(
entries.into_iter().flat_map(|entry| {
let mut entries = SmallVec::<[GitBlameEntry; 2]>::new();
if entry.range.start > current_row {
let skipped_rows = entry.range.start - current_row;
entries.push(GitBlameEntry {
rows: skipped_rows,
blame: None,
});
}
entries.push(GitBlameEntry {
rows: entry.range.len() as u32,
blame: Some(entry.clone()),
});
current_row = entry.range.end;
entries
}),
&(),
);
if max_row >= current_row {
entries.push(
GitBlameEntry {
rows: (max_row + 1) - current_row,
blame: None,
},
&(),
);
}
entries
}
async fn parse_commit_messages(
messages: impl IntoIterator<Item = (Oid, String)>,
remote_url: Option<String>,
deprecated_permalinks: &HashMap<Oid, Url>,
languages: &Arc<LanguageRegistry>,
) -> HashMap<Oid, CommitDetails> {
let mut commit_details = HashMap::default();
let parsed_remote_url = remote_url.as_deref().and_then(parse_git_remote_url);
for (oid, message) in messages {
let parsed_message = parse_markdown(&message, &languages).await;
let permalink = if let Some(git_remote) = parsed_remote_url.as_ref() {
Some(build_commit_permalink(
git::permalink::BuildCommitPermalinkParams {
remote: git_remote,
sha: oid.to_string().as_str(),
},
))
} else {
// DEPRECATED (18 Apr 24): Sending permalinks over the wire is deprecated. Clients
// now do the parsing. This is here for backwards compatibility, so that
// when an old peer sends a client no `parsed_remote_url` but `deprecated_permalinks`,
// we fall back to that.
deprecated_permalinks.get(&oid).cloned()
};
commit_details.insert(
oid,
CommitDetails {
message,
parsed_message,
permalink,
},
);
}
commit_details
}
async fn parse_markdown(text: &str, language_registry: &Arc<LanguageRegistry>) -> ParsedMarkdown {
let mut parsed_message = ParsedMarkdown::default();
markdown::parse_markdown_block(
text,
language_registry,
None,
&mut parsed_message.text,
&mut parsed_message.highlights,
&mut parsed_message.region_ranges,
&mut parsed_message.regions,
)
.await;
parsed_message
}
#[cfg(test)]
@@ -506,7 +394,7 @@ mod tests {
.await
.unwrap();
let blame = cx.new_model(|cx| GitBlame::new(buffer.clone(), project.clone(), true, cx));
let blame = cx.new_model(|cx| GitBlame::new(buffer.clone(), project.clone(), cx));
let event = project.next_event(cx).await;
assert_eq!(
@@ -575,7 +463,7 @@ mod tests {
.await
.unwrap();
let git_blame = cx.new_model(|cx| GitBlame::new(buffer.clone(), project, false, cx));
let git_blame = cx.new_model(|cx| GitBlame::new(buffer.clone(), project, cx));
cx.executor().run_until_parked();
@@ -655,7 +543,7 @@ mod tests {
.await
.unwrap();
let git_blame = cx.new_model(|cx| GitBlame::new(buffer.clone(), project, false, cx));
let git_blame = cx.new_model(|cx| GitBlame::new(buffer.clone(), project, cx));
cx.executor().run_until_parked();
@@ -804,7 +692,7 @@ mod tests {
.await
.unwrap();
let git_blame = cx.new_model(|cx| GitBlame::new(buffer.clone(), project, false, cx));
let git_blame = cx.new_model(|cx| GitBlame::new(buffer.clone(), project, cx));
cx.executor().run_until_parked();
git_blame.update(cx, |blame, cx| blame.check_invariants(cx));

View File

@@ -30,7 +30,7 @@ use std::{
sync::Arc,
};
use text::{BufferId, Selection};
use theme::{Theme, ThemeSettings};
use theme::Theme;
use ui::{h_flex, prelude::*, Label};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableItemHandle};
@@ -827,18 +827,13 @@ impl Item for Editor {
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string());
let settings = ThemeSettings::get_global(cx);
let mut breadcrumbs = vec![BreadcrumbText {
text: filename,
highlights: None,
font: Some(settings.buffer_font.clone()),
}];
breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
text: symbol.text,
highlights: Some(symbol.highlight_ranges),
font: Some(settings.buffer_font.clone()),
}));
Some(breadcrumbs)
}
@@ -1173,7 +1168,7 @@ impl SearchableItem for Editor {
}
fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext<Self>) {
self.expect_layout_bounds_change = self.last_layout_bounds;
self.expect_bounds_change = self.last_bounds;
}
}

View File

@@ -3,7 +3,6 @@ use crate::{
GoToTypeDefinition, Rename, RevealInFinder, SelectMode, ToggleCodeActions,
};
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
use workspace::OpenInTerminal;
pub struct MouseContextMenu {
pub(crate) position: Point<Pixels>,
@@ -84,7 +83,6 @@ pub fn deploy_context_menu(
)
.separator()
.action("Reveal in Finder", Box::new(RevealInFinder))
.action("Open in Terminal", Box::new(OpenInTerminal))
})
};
let mouse_context_menu = MouseContextMenu::new(position, context_menu, cx);

View File

@@ -72,7 +72,7 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
let original_y = scroll_position.y;
if let Some(last_bounds) = self.expect_layout_bounds_change.take() {
if let Some(last_bounds) = self.expect_bounds_change.take() {
if scroll_position.y != 0. {
scroll_position.y += (bounds.top() - last_bounds.top()) / line_height;
if scroll_position.y < 0. {

View File

@@ -130,20 +130,21 @@ impl LspAdapter for ExtensionLspAdapter {
}
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
let code_action_kinds = self
.extension
.manifest
.language_servers
.get(&self.language_server_id)
.and_then(|server| server.code_action_kinds.clone());
if self.extension.manifest.id.as_ref() == "terraform" {
// This is taken from the original Terraform implementation, including
// the TODOs:
// TODO: file issue for server supported code actions
// TODO: reenable default actions / delete override
return Some(vec![]);
}
code_action_kinds.or(Some(vec![
Some(vec![
CodeActionKind::EMPTY,
CodeActionKind::QUICKFIX,
CodeActionKind::REFACTOR,
CodeActionKind::REFACTOR_EXTRACT,
CodeActionKind::SOURCE,
]))
])
}
fn language_ids(&self) -> HashMap<String, String> {

View File

@@ -106,8 +106,6 @@ pub struct LanguageServerManifestEntry {
languages: Vec<Arc<str>>,
#[serde(default)]
pub language_ids: HashMap<String, String>,
#[serde(default)]
pub code_action_kinds: Option<Vec<lsp::CodeActionKind>>,
}
impl LanguageServerManifestEntry {

View File

@@ -4,22 +4,15 @@ use ui::prelude::*;
#[derive(IntoElement)]
pub struct ExtensionCard {
overridden_by_dev_extension: bool,
children: SmallVec<[AnyElement; 2]>,
}
impl ExtensionCard {
pub fn new() -> Self {
Self {
overridden_by_dev_extension: false,
children: SmallVec::new(),
}
}
pub fn overridden_by_dev_extension(mut self, overridden: bool) -> Self {
self.overridden_by_dev_extension = overridden;
self
}
}
impl ParentElement for ExtensionCard {
@@ -41,24 +34,7 @@ impl RenderOnce for ExtensionCard {
.border_1()
.border_color(cx.theme().colors().border)
.rounded_md()
.children(self.children)
.when(self.overridden_by_dev_extension, |card| {
card.child(
h_flex()
.absolute()
.top_0()
.left_0()
.occlude()
.size_full()
.items_center()
.justify_center()
.bg(theme::color_alpha(
cx.theme().colors().elevated_surface_background,
0.8,
))
.child(Label::new("Overridden by dev extension.")),
)
}),
.children(self.children),
)
}
}

View File

@@ -48,7 +48,6 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
("lua", &["lua"]),
("make", &["Makefile"]),
("nix", &["nix"]),
("nu", &["nu"]),
("ocaml", &["ml", "mli"]),
("php", &["php"]),
("prisma", &["prisma"]),
@@ -62,7 +61,6 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
("templ", &["templ"]),
("terraform", &["tf", "tfvars", "hcl"]),
("toml", &["Cargo.lock", "toml"]),
("vue", &["vue"]),
("wgsl", &["wgsl"]),
("zig", &["zig"]),
];

View File

@@ -190,15 +190,6 @@ impl ExtensionsPage {
}
}
/// Returns whether a dev extension currently exists for the extension with the given ID.
fn dev_extension_exists(extension_id: &str, cx: &mut ViewContext<Self>) -> bool {
let extension_store = ExtensionStore::global(cx).read(cx);
extension_store
.dev_extensions()
.any(|dev_extension| dev_extension.id.as_ref() == extension_id)
}
fn extension_status(extension_id: &str, cx: &mut ViewContext<Self>) -> ExtensionStatus {
let extension_store = ExtensionStore::global(cx).read(cx);
@@ -426,21 +417,13 @@ impl ExtensionsPage {
) -> ExtensionCard {
let this = cx.view().clone();
let status = Self::extension_status(&extension.id, cx);
let has_dev_extension = Self::dev_extension_exists(&extension.id, cx);
let extension_id = extension.id.clone();
let (install_or_uninstall_button, upgrade_button) =
self.buttons_for_entry(extension, &status, has_dev_extension, cx);
let version = extension.manifest.version.clone();
self.buttons_for_entry(extension, &status, cx);
let repository_url = extension.manifest.repository.clone();
let installed_version = match status {
ExtensionStatus::Installed(installed_version) => Some(installed_version),
_ => None,
};
ExtensionCard::new()
.overridden_by_dev_extension(has_dev_extension)
.child(
h_flex()
.justify_between()
@@ -452,14 +435,9 @@ impl ExtensionsPage {
Headline::new(extension.manifest.name.clone())
.size(HeadlineSize::Medium),
)
.child(Headline::new(format!("v{version}")).size(HeadlineSize::XSmall))
.children(
installed_version
.filter(|installed_version| *installed_version != version)
.map(|installed_version| {
Headline::new(format!("(v{installed_version} installed)",))
.size(HeadlineSize::XSmall)
}),
.child(
Headline::new(format!("v{}", extension.manifest.version))
.size(HeadlineSize::XSmall),
),
)
.child(
@@ -599,24 +577,16 @@ impl ExtensionsPage {
&self,
extension: &ExtensionMetadata,
status: &ExtensionStatus,
has_dev_extension: bool,
cx: &mut ViewContext<Self>,
) -> (Button, Option<Button>) {
let is_compatible = extension::is_version_compatible(&extension);
if has_dev_extension {
// If we have a dev extension for the given extension, just treat it as uninstalled.
// The button here is a placeholder, as it won't be interactable anyways.
return (
Button::new(SharedString::from(extension.id.clone()), "Install"),
None,
);
}
let disabled = !is_compatible;
match status.clone() {
ExtensionStatus::NotInstalled => (
Button::new(SharedString::from(extension.id.clone()), "Install").on_click(
cx.listener({
Button::new(SharedString::from(extension.id.clone()), "Install")
.disabled(disabled)
.on_click(cx.listener({
let extension_id = extension.id.clone();
move |this, _, cx| {
this.telemetry
@@ -625,8 +595,7 @@ impl ExtensionsPage {
store.install_latest_extension(extension_id.clone(), cx)
});
}
}),
),
})),
None,
),
ExtensionStatus::Installing => (
@@ -657,20 +626,7 @@ impl ExtensionsPage {
} else {
Some(
Button::new(SharedString::from(extension.id.clone()), "Upgrade")
.when(!is_compatible, |upgrade_button| {
upgrade_button.disabled(true).tooltip({
let version = extension.manifest.version.clone();
move |cx| {
Tooltip::text(
format!(
"v{version} is not compatible with this version of Zed.",
),
cx,
)
}
})
})
.disabled(!is_compatible)
.disabled(disabled)
.on_click(cx.listener({
let extension_id = extension.id.clone();
let version = extension.manifest.version.clone();
@@ -936,25 +892,24 @@ impl Render for ExtensionsPage {
let view = cx.view().clone();
let scroll_handle = self.list.clone();
this.child(
// canvas(
// move |bounds, cx| {
// let mut list = uniform_list::<_, ExtensionCard, _>(
// view,
// "entries",
// count,
// Self::render_extensions,
// )
// .size_full()
// .pb_4()
// .track_scroll(scroll_handle)
// .into_any_element();
// list.layout(bounds.origin, bounds.size.into(), cx);
// list
// },
// |_bounds, mut list, cx| list.paint(cx),
// )
// .size_full(),
todo!("replace canvas"),
canvas(
move |bounds, cx| {
let mut list = uniform_list::<_, ExtensionCard, _>(
view,
"entries",
count,
Self::render_extensions,
)
.size_full()
.pb_4()
.track_scroll(scroll_handle)
.into_any_element();
list.layout(bounds.origin, bounds.size.into(), cx);
list
},
|_bounds, mut list, cx| list.paint(cx),
)
.size_full(),
)
}))
}

View File

@@ -24,7 +24,6 @@ text.workspace = true
time.workspace = true
url.workspace = true
serde.workspace = true
windows.workspace = true
[dev-dependencies]
unindent.workspace = true

View File

@@ -14,9 +14,6 @@ use time::OffsetDateTime;
use time::UtcOffset;
use url::Url;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
pub use git2 as libgit;
#[derive(Debug, Clone, Default)]
@@ -24,7 +21,6 @@ pub struct Blame {
pub entries: Vec<BlameEntry>,
pub messages: HashMap<Oid, String>,
pub permalinks: HashMap<Oid, Url>,
pub remote_url: Option<String>,
}
impl Blame {
@@ -45,8 +41,6 @@ impl Blame {
for entry in entries.iter_mut() {
unique_shas.insert(entry.sha);
// DEPRECATED (18 Apr 24): Sending permalinks over the wire is deprecated. Clients
// now do the parsing.
if let Some(remote) = parsed_remote_url.as_ref() {
permalinks.entry(entry.sha).or_insert_with(|| {
build_commit_permalink(BuildCommitPermalinkParams {
@@ -65,23 +59,17 @@ impl Blame {
entries,
permalinks,
messages,
remote_url,
})
}
}
const GIT_BLAME_NO_COMMIT_ERROR: &'static str = "fatal: no such ref: HEAD";
const GIT_BLAME_NO_PATH: &'static str = "fatal: no such path";
fn run_git_blame(
git_binary: &Path,
working_directory: &Path,
path: &Path,
contents: &Rope,
) -> Result<String> {
let mut child = Command::new(git_binary);
child
let child = Command::new(git_binary)
.current_dir(working_directory)
.arg("blame")
.arg("--incremental")
@@ -90,12 +78,7 @@ fn run_git_blame(
.arg(path.as_os_str())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
let child = child
.stderr(Stdio::piped())
.spawn()
.map_err(|e| anyhow!("Failed to start git blame process: {}", e))?;
@@ -115,10 +98,6 @@ fn run_git_blame(
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let trimmed = stderr.trim();
if trimmed == GIT_BLAME_NO_COMMIT_ERROR || trimmed.contains(GIT_BLAME_NO_PATH) {
return Ok(String::new());
}
return Err(anyhow!("git blame process failed: {}", stderr));
}

View File

@@ -4,25 +4,15 @@ use collections::HashMap;
use std::path::Path;
use std::process::Command;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
pub fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result<HashMap<Oid, String>> {
const MARKER: &'static str = "<MARKER>";
let mut command = Command::new("git");
command
let output = Command::new("git")
.current_dir(working_directory)
.arg("show")
.arg("-s")
.arg(format!("--format=%B{}", MARKER))
.args(shas.iter().map(ToString::to_string));
#[cfg(windows)]
command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
let output = command
.args(shas.iter().map(ToString::to_string))
.output()
.map_err(|e| anyhow!("Failed to start git blame process: {}", e))?;

View File

@@ -3,7 +3,7 @@ use std::ops::Range;
use anyhow::{anyhow, Result};
use url::Url;
pub enum GitHostingProvider {
pub(crate) enum GitHostingProvider {
Github,
Gitlab,
Gitee,
@@ -90,18 +90,18 @@ pub fn build_permalink(params: BuildPermalinkParams) -> Result<Url> {
Ok(permalink)
}
pub struct ParsedGitRemote<'a> {
pub(crate) struct ParsedGitRemote<'a> {
pub provider: GitHostingProvider,
pub owner: &'a str,
pub repo: &'a str,
}
pub struct BuildCommitPermalinkParams<'a> {
pub(crate) struct BuildCommitPermalinkParams<'a> {
pub remote: &'a ParsedGitRemote<'a>,
pub sha: &'a str,
}
pub fn build_commit_permalink(params: BuildCommitPermalinkParams) -> Url {
pub(crate) fn build_commit_permalink(params: BuildCommitPermalinkParams) -> Url {
let BuildCommitPermalinkParams { sha, remote } = params;
let ParsedGitRemote {
@@ -122,7 +122,7 @@ pub fn build_commit_permalink(params: BuildCommitPermalinkParams) -> Url {
provider.base_url().join(&path).unwrap()
}
pub fn parse_git_remote_url(url: &str) -> Option<ParsedGitRemote> {
pub(crate) fn parse_git_remote_url(url: &str) -> Option<ParsedGitRemote> {
if url.starts_with("git@github.com:") || url.starts_with("https://github.com/") {
let repo_with_owner = url
.trim_start_matches("git@github.com:")

View File

@@ -115,7 +115,7 @@ wayland-protocols = { version = "0.31.2", features = [
] }
oo7 = "0.3.0"
open = "5.1.2"
x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr", "xinput"] }
x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr"] }
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
[target.'cfg(windows)'.dependencies]

View File

@@ -547,23 +547,11 @@ impl AppContext {
self.platform.window_appearance()
}
/// Writes data to the primary selection buffer.
/// Only available on Linux.
pub fn write_to_primary(&self, item: ClipboardItem) {
self.platform.write_to_primary(item)
}
/// Writes data to the platform clipboard.
pub fn write_to_clipboard(&self, item: ClipboardItem) {
self.platform.write_to_clipboard(item)
}
/// Reads data from the primary selection buffer.
/// Only available on Linux.
pub fn read_from_primary(&self) -> Option<ClipboardItem> {
self.platform.read_from_primary()
}
/// Reads data from the platform clipboard.
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.platform.read_from_clipboard()

View File

@@ -734,8 +734,7 @@ impl VisualTestContext {
self.update(|cx| {
cx.with_element_context(|cx| {
let mut element = f(cx);
element.layout(space, cx);
cx.with_element_offset(origin, |cx| element.before_paint(cx));
element.layout(origin, space, cx);
element.paint(cx);
});

View File

@@ -33,7 +33,7 @@
use crate::{
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext,
ElementId, LayoutId, Pixels, Size, ViewContext, WindowContext, ELEMENT_ARENA,
ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
@@ -45,46 +45,33 @@ use std::{any::Any, fmt::Debug, mem, ops::DerefMut};
/// for more details.
pub trait Element: 'static + IntoElement {
/// The type of state returned from [`Element::before_layout`]. A mutable reference to this state is subsequently
/// provided to [`Element::after_layout`], [`Element::before_paint`] and [`Element::paint`].
/// provided to [`Element::after_layout`] and [`Element::paint`].
type BeforeLayout: 'static;
/// The type of state returned from [`Element::after_layout`]. A mutable reference to this state is subsequently
/// provided to [`Element::before_paint`] and [`Element::paint`].
type AfterLayout: 'static;
/// The type of state returned from [`Element::before_paint`]. A mutable reference to this state is subsequently
/// provided to [`Element::paint`].
type BeforePaint: 'static;
type AfterLayout: 'static;
/// Before an element can be painted, we need to know where it's going to be and how big it is.
/// Use this method to request a layout from Taffy and initialize the element's state.
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout);
/// todo!()
/// After laying out an element, we need to commit its bounds to the current frame for hitbox
/// purposes. The state argument is the same state that was returned from [`Element::before_layout()`].
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout);
/// Before painting an element, we need to commit its bounds to the current frame for hitbox
/// purposes.
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> Self::BeforePaint;
) -> Self::AfterLayout;
/// Once layout has been completed, this method will be called to paint the element to the screen.
/// The state argument is the same state that was returned from [`Element::before_layout()`].
fn paint(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
after_layout: &mut Self::AfterLayout,
before_paint: &mut Self::BeforePaint,
cx: &mut ElementContext,
);
@@ -176,7 +163,6 @@ impl<C: RenderOnce> Component<C> {
impl<C: RenderOnce> Element for Component<C> {
type BeforeLayout = AnyElement;
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut element = self
@@ -190,23 +176,12 @@ impl<C: RenderOnce> Element for Component<C> {
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
let bounds = element.after_layout(cx);
(bounds, ())
}
fn before_paint(
&mut self,
_: Bounds<Pixels>,
element: &mut AnyElement,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
element.before_paint(cx);
element.after_layout(cx);
}
fn paint(
@@ -214,7 +189,6 @@ impl<C: RenderOnce> Element for Component<C> {
_: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
_: &mut Self::BeforePaint,
cx: &mut ElementContext,
) {
element.paint(cx)
@@ -238,59 +212,46 @@ trait ElementObject {
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
fn after_layout(&mut self, cx: &mut ElementContext) -> Option<Bounds<Pixels>>;
fn before_paint(&mut self, cx: &mut ElementContext);
fn after_layout(&mut self, cx: &mut ElementContext);
fn paint(&mut self, cx: &mut ElementContext);
fn layout(
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> ElementMeasurement;
) -> Size<Pixels>;
}
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
pub struct Drawable<E: Element> {
/// The drawn element.
pub element: E,
phase: ElementDrawPhase<E::BeforeLayout, E::AfterLayout, E::BeforePaint>,
phase: ElementDrawPhase<E::BeforeLayout, E::AfterLayout>,
}
#[derive(Default)]
enum ElementDrawPhase<BeforeLayout, AfterLayout, BeforePaint> {
enum ElementDrawPhase<BeforeLayout, AfterLayout> {
#[default]
Start,
BeforeLayout {
layout_id: LayoutId,
before_layout: BeforeLayout,
},
AfterLayout {
LayoutComputed {
layout_id: LayoutId,
available_space: Option<Size<AvailableSpace>>,
available_space: Size<AvailableSpace>,
before_layout: BeforeLayout,
after_layout: AfterLayout,
},
BeforePaint {
AfterLayout {
node_id: DispatchNodeId,
bounds: Bounds<Pixels>,
before_layout: BeforeLayout,
after_layout: AfterLayout,
before_paint: BeforePaint,
},
Painted,
}
/// todo!()
#[derive(Default)]
pub struct ElementMeasurement {
/// The size of the element.
pub size: Size<Pixels>,
/// The bounds for the focus target inside of the measured element.
pub focus_target_bounds: Option<Bounds<Pixels>>,
}
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
impl<E: Element> Drawable<E> {
fn new(element: E) -> Self {
@@ -314,133 +275,92 @@ impl<E: Element> Drawable<E> {
}
}
fn after_layout(&mut self, cx: &mut ElementContext) -> Option<Bounds<Pixels>> {
fn after_layout(&mut self, cx: &mut ElementContext) {
match mem::take(&mut self.phase) {
ElementDrawPhase::BeforeLayout {
layout_id,
mut before_layout,
}
| ElementDrawPhase::LayoutComputed {
layout_id,
mut before_layout,
..
} => {
let bounds = cx.layout_bounds(layout_id);
let (focus_target_bounds, after_layout) =
self.element.after_layout(bounds, &mut before_layout, cx);
let node_id = cx.window.next_frame.dispatch_tree.push_node();
let after_layout = self.element.after_layout(bounds, &mut before_layout, cx);
self.phase = ElementDrawPhase::AfterLayout {
layout_id,
available_space: None,
node_id,
bounds,
before_layout,
after_layout,
};
focus_target_bounds
cx.window.next_frame.dispatch_tree.pop_node();
}
_ => panic!("must call before_layout before after_layout"),
}
}
fn before_paint(&mut self, cx: &mut ElementContext) {
match mem::take(&mut self.phase) {
ElementDrawPhase::AfterLayout {
layout_id,
mut before_layout,
mut after_layout,
..
} => {
let bounds = cx.layout_bounds(layout_id);
let node_id = cx.window.next_frame.dispatch_tree.push_node();
let before_paint =
self.element
.before_paint(bounds, &mut before_layout, &mut after_layout, cx);
self.phase = ElementDrawPhase::BeforePaint {
node_id,
bounds,
before_layout,
after_layout,
before_paint,
};
cx.window.next_frame.dispatch_tree.pop_node();
}
_ => panic!("must call after_layout before before_paint"),
}
}
fn paint(&mut self, cx: &mut ElementContext) -> E::BeforeLayout {
match mem::take(&mut self.phase) {
ElementDrawPhase::BeforePaint {
ElementDrawPhase::AfterLayout {
node_id,
bounds,
mut before_layout,
mut after_layout,
mut before_paint,
..
} => {
cx.window.next_frame.dispatch_tree.set_active_node(node_id);
self.element.paint(
bounds,
&mut before_layout,
&mut after_layout,
&mut before_paint,
cx,
);
self.element
.paint(bounds, &mut before_layout, &mut after_layout, cx);
self.phase = ElementDrawPhase::Painted;
before_layout
}
_ => panic!("must call before_paint before paint"),
_ => panic!("must call after_layout before paint"),
}
}
fn layout(
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> ElementMeasurement {
) -> Size<Pixels> {
if matches!(&self.phase, ElementDrawPhase::Start) {
self.before_layout(cx);
}
match mem::take(&mut self.phase) {
let layout_id = match mem::take(&mut self.phase) {
ElementDrawPhase::BeforeLayout {
layout_id,
mut before_layout,
before_layout,
} => {
cx.compute_layout(layout_id, available_space);
let bounds = cx.layout_bounds(layout_id);
let (focus_target_bounds, after_layout) =
self.element.after_layout(bounds, &mut before_layout, cx);
self.phase = ElementDrawPhase::AfterLayout {
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
available_space: Some(available_space),
available_space,
before_layout,
after_layout,
};
ElementMeasurement {
size: bounds.size,
focus_target_bounds,
}
layout_id
}
ElementDrawPhase::AfterLayout {
ElementDrawPhase::LayoutComputed {
layout_id,
available_space: prev_available_space,
mut before_layout,
..
before_layout,
} => {
if Some(available_space) != prev_available_space {
if available_space != prev_available_space {
cx.compute_layout(layout_id, available_space);
}
let bounds = cx.layout_bounds(layout_id);
let (focus_target_bounds, after_layout) =
self.element.after_layout(bounds, &mut before_layout, cx);
self.phase = ElementDrawPhase::AfterLayout {
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
available_space: Some(available_space),
available_space,
before_layout,
after_layout,
};
ElementMeasurement {
size: bounds.size,
focus_target_bounds,
}
layout_id
}
_ => panic!("cannot layout after painting"),
}
_ => panic!("cannot measure after painting"),
};
cx.layout_bounds(layout_id).size
}
}
@@ -457,24 +377,20 @@ where
Drawable::before_layout(self, cx)
}
fn after_layout(&mut self, cx: &mut ElementContext) -> Option<Bounds<Pixels>> {
Drawable::after_layout(self, cx)
}
fn before_paint(&mut self, cx: &mut ElementContext) {
Drawable::before_paint(self, cx);
fn after_layout(&mut self, cx: &mut ElementContext) {
Drawable::after_layout(self, cx);
}
fn paint(&mut self, cx: &mut ElementContext) {
Drawable::paint(self, cx);
}
fn layout(
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> ElementMeasurement {
Drawable::layout(self, available_space, cx)
) -> Size<Pixels> {
Drawable::measure(self, available_space, cx)
}
}
@@ -504,14 +420,9 @@ impl AnyElement {
self.0.before_layout(cx)
}
/// todo!()
pub fn after_layout(&mut self, cx: &mut ElementContext) -> Option<Bounds<Pixels>> {
self.0.after_layout(cx)
}
/// Commits the element bounds of this [AnyElement] for hitbox purposes.
pub fn before_paint(&mut self, cx: &mut ElementContext) {
self.0.before_paint(cx)
pub fn after_layout(&mut self, cx: &mut ElementContext) {
self.0.after_layout(cx)
}
/// Paints the element stored in this `AnyElement`.
@@ -520,19 +431,30 @@ impl AnyElement {
}
/// Initializes this element and performs layout within the given available space to determine its size.
pub fn layout(
pub fn measure(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> ElementMeasurement {
self.0.layout(available_space, cx)
) -> Size<Pixels> {
self.0.measure(available_space, cx)
}
/// Initializes this element, performs layout if needed and commits its bounds for hitbox purposes.
pub fn layout(
&mut self,
absolute_offset: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> Size<Pixels> {
let size = self.measure(available_space, cx);
cx.with_absolute_element_offset(absolute_offset, |cx| self.after_layout(cx));
size
}
}
impl Element for AnyElement {
type BeforeLayout = ();
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let layout_id = self.before_layout(cx);
@@ -544,19 +466,8 @@ impl Element for AnyElement {
_: Bounds<Pixels>,
_: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
let bounds = self.after_layout(cx);
(bounds, ())
}
fn before_paint(
&mut self,
_: Bounds<Pixels>,
_: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
self.before_paint(cx)
self.after_layout(cx)
}
fn paint(
@@ -564,7 +475,6 @@ impl Element for AnyElement {
_: Bounds<Pixels>,
_: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
_: &mut Self::BeforePaint,
cx: &mut ElementContext,
) {
self.paint(cx)
@@ -597,7 +507,6 @@ impl IntoElement for Empty {
impl Element for Empty {
type BeforeLayout = ();
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
(cx.request_layout(&crate::Style::default(), None), ())
@@ -606,17 +515,7 @@ impl Element for Empty {
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
(None, ())
}
fn before_paint(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_state: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) {
}
@@ -626,7 +525,6 @@ impl Element for Empty {
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_before_paint: &mut Self::BeforePaint,
_cx: &mut ElementContext,
) {
}

View File

@@ -71,7 +71,6 @@ impl ParentElement for Anchored {
impl Element for Anchored {
type BeforeLayout = AnchoredState;
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
let child_layout_ids = self
@@ -92,27 +91,9 @@ impl Element for Anchored {
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
let mut focus_target_bounds = None;
for child in &mut self.children {
if let Some(child_focus_target_bounds) = child.after_layout(cx) {
if focus_target_bounds.is_none() {
focus_target_bounds = Some(child_focus_target_bounds);
}
}
}
(focus_target_bounds, ())
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
if before_layout.child_layout_ids.is_empty() {
@@ -186,7 +167,7 @@ impl Element for Anchored {
cx.with_element_offset(offset, |cx| {
for child in &mut self.children {
child.before_paint(cx);
child.after_layout(cx);
}
})
}
@@ -196,7 +177,6 @@ impl Element for Anchored {
_bounds: crate::Bounds<crate::Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_before_paint: &mut Self::BeforePaint,
cx: &mut ElementContext,
) {
for child in &mut self.children {

View File

@@ -1,6 +1,6 @@
use std::time::{Duration, Instant};
use crate::{AnyElement, Bounds, Element, ElementId, IntoElement, Pixels};
use crate::{AnyElement, Element, ElementId, IntoElement};
pub use easing::*;
@@ -86,8 +86,8 @@ struct AnimationState {
impl<E: IntoElement + 'static> Element for AnimationElement<E> {
type BeforeLayout = AnyElement;
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(
&mut self,
@@ -136,29 +136,18 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_bounds: crate::Bounds<crate::Pixels>,
element: &mut Self::BeforeLayout,
cx: &mut crate::ElementContext,
) -> (Option<Bounds<Pixels>>, ()) {
(element.after_layout(cx), ())
}
fn before_paint(
&mut self,
_bounds: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
cx: &mut crate::ElementContext,
) -> Self::BeforePaint {
element.before_paint(cx);
) -> Self::AfterLayout {
element.after_layout(cx);
}
fn paint(
&mut self,
_bounds: Bounds<Pixels>,
_bounds: crate::Bounds<crate::Pixels>,
element: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_before_paint: &mut Self::BeforePaint,
_: &mut Self::AfterLayout,
cx: &mut crate::ElementContext,
) {
element.paint(cx);

View File

@@ -4,8 +4,12 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe
/// Construct a canvas element with the given paint callback.
/// Useful for adding short term custom drawing to a view.
pub fn canvas(paint: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext)) -> Canvas {
pub fn canvas<T>(
after_layout: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut ElementContext),
) -> Canvas<T> {
Canvas {
after_layout: Some(Box::new(after_layout)),
paint: Some(Box::new(paint)),
style: StyleRefinement::default(),
}
@@ -13,12 +17,13 @@ pub fn canvas(paint: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext))
/// A canvas element, meant for accessing the low level paint API without defining a whole
/// custom element
pub struct Canvas {
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext)>>,
pub struct Canvas<T> {
after_layout: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
style: StyleRefinement,
}
impl IntoElement for Canvas {
impl<T: 'static> IntoElement for Canvas<T> {
type Element = Self;
fn into_element(self) -> Self::Element {
@@ -26,10 +31,9 @@ impl IntoElement for Canvas {
}
}
impl Element for Canvas {
impl<T: 'static> Element for Canvas<T> {
type BeforeLayout = Style;
type AfterLayout = ();
type BeforePaint = ();
type AfterLayout = Option<T>;
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
let mut style = Style::default();
@@ -40,35 +44,28 @@ impl Element for Canvas {
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
(None, ())
}
fn before_paint(
&mut self,
_bounds: Bounds<Pixels>,
bounds: Bounds<Pixels>,
_before_layout: &mut Style,
_after_layout: &mut Self::AfterLayout,
_cx: &mut ElementContext,
) {
cx: &mut ElementContext,
) -> Option<T> {
Some(self.after_layout.take().unwrap()(bounds, cx))
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
style: &mut Style,
_after_layout: &mut Self::AfterLayout,
_before_paint: &mut Self::BeforePaint,
after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
style.paint(bounds, cx, |cx| (self.paint.take().unwrap())(bounds, cx));
let after_layout = after_layout.take().unwrap();
style.paint(bounds, cx, |cx| {
(self.paint.take().unwrap())(bounds, after_layout, cx)
});
}
}
impl Styled for Canvas {
impl<T> Styled for Canvas<T> {
fn style(&mut self) -> &mut crate::StyleRefinement {
&mut self.style
}

View File

@@ -28,7 +28,6 @@ impl Deferred {
impl Element for Deferred {
type BeforeLayout = ();
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, ()) {
let layout_id = self.child.as_mut().unwrap().before_layout(cx);
@@ -40,17 +39,6 @@ impl Element for Deferred {
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
let bounds = self.child.as_mut().unwrap().after_layout(cx);
(bounds, ())
}
fn before_paint(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
let child = self.child.take().unwrap();
let element_offset = cx.element_offset();
@@ -62,7 +50,6 @@ impl Element for Deferred {
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_before_paint: &mut Self::BeforePaint,
_cx: &mut ElementContext,
) {
}

View File

@@ -1121,8 +1121,7 @@ impl ParentElement for Div {
impl Element for Div {
type BeforeLayout = DivFrameState;
type AfterLayout = ();
type BeforePaint = Option<Hitbox>;
type AfterLayout = Option<Hitbox>;
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut child_layout_ids = SmallVec::new();
@@ -1144,7 +1143,7 @@ impl Element for Div {
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
) -> Option<Hitbox> {
let mut child_min = point(Pixels::MAX, Pixels::MAX);
let mut child_max = Point::default();
let content_size = if before_layout.child_layout_ids.is_empty() {
@@ -1178,49 +1177,25 @@ impl Element for Div {
(child_max - child_min).into()
};
let focus_target_bounds = self.interactivity.after_layout(
self.interactivity.after_layout(
bounds,
content_size,
cx,
|_, scroll_offset, mut focus_target_bounds, cx| {
|_style, scroll_offset, hitbox, cx| {
cx.with_element_offset(scroll_offset, |cx| {
for child in &mut self.children {
if let Some(child_focus_bounds) = child.after_layout(cx) {
focus_target_bounds = Some(child_focus_bounds);
}
}
focus_target_bounds
})
},
);
(focus_target_bounds, ())
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> Option<Hitbox> {
self.interactivity
.before_paint(bounds, cx, |_style, scroll_offset, hitbox, cx| {
cx.with_element_offset(scroll_offset, |cx| {
for child in &mut self.children {
child.before_paint(cx);
child.after_layout(cx);
}
});
hitbox
})
},
)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
) {
@@ -1361,13 +1336,13 @@ impl Interactivity {
)
}
/// todo!()
/// Commit the bounds of this element according to this interactivity state's configured styles.
pub fn after_layout<R>(
&mut self,
bounds: Bounds<Pixels>,
content_size: Size<Pixels>,
cx: &mut ElementContext,
f: impl FnOnce(&Style, Point<Pixels>, Option<Bounds<Pixels>>, &mut ElementContext) -> R,
f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut ElementContext) -> R,
) -> R {
self.content_size = content_size;
cx.with_element_state::<InteractiveElementState, _>(
@@ -1392,40 +1367,6 @@ impl Interactivity {
}
}
cx.with_text_style(style.text_style().cloned(), |cx| {
cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
let scroll_offset = self.clamp_scroll_position(bounds, &style, cx);
let focus_target_bounds =
self.tracked_focus_handle.as_ref().and_then(|focus_handle| {
if focus_handle.is_focused(cx) {
Some(bounds)
} else {
None
}
});
let result = f(&style, scroll_offset, focus_target_bounds, cx);
(result, element_state)
})
})
},
)
}
/// Commit the bounds of this element according to this interactivity state's configured styles.
pub fn before_paint<R>(
&mut self,
bounds: Bounds<Pixels>,
cx: &mut ElementContext,
f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut ElementContext) -> R,
) -> R {
cx.with_element_state::<InteractiveElementState, _>(
self.element_id.clone(),
|element_state, cx| {
let mut element_state =
element_state.map(|element_state| element_state.unwrap_or_default());
let style = self.compute_style_internal(None, element_state.as_mut(), cx);
cx.with_text_style(style.text_style().cloned(), |cx| {
cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
let hitbox = if self.should_insert_hitbox(&style) {
@@ -1434,11 +1375,7 @@ impl Interactivity {
None
};
let scroll_offset = self
.scroll_offset
.as_ref()
.map(|scroll_offset| *scroll_offset.borrow())
.unwrap_or_default();
let scroll_offset = self.clamp_scroll_position(bounds, &style, cx);
let result = f(&style, scroll_offset, hitbox, cx);
(result, element_state)
})
@@ -2326,7 +2263,6 @@ where
{
type BeforeLayout = E::BeforeLayout;
type AfterLayout = E::AfterLayout;
type BeforePaint = E::BeforePaint;
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
self.element.before_layout(cx)
@@ -2335,21 +2271,10 @@ where
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
state: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
self.element.after_layout(bounds, before_layout, cx)
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> E::BeforePaint {
self.element
.before_paint(bounds, before_layout, after_layout, cx)
) -> E::AfterLayout {
self.element.after_layout(bounds, state, cx)
}
fn paint(
@@ -2357,11 +2282,9 @@ where
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
after_layout: &mut Self::AfterLayout,
before_paint: &mut Self::BeforePaint,
cx: &mut ElementContext,
) {
self.element
.paint(bounds, before_layout, after_layout, before_paint, cx)
self.element.paint(bounds, before_layout, after_layout, cx)
}
}
@@ -2423,7 +2346,6 @@ where
{
type BeforeLayout = E::BeforeLayout;
type AfterLayout = E::AfterLayout;
type BeforePaint = E::BeforePaint;
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
self.element.before_layout(cx)
@@ -2432,21 +2354,10 @@ where
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
state: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
self.element.after_layout(bounds, before_layout, cx)
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> E::BeforePaint {
self.element
.before_paint(bounds, before_layout, after_layout, cx)
) -> E::AfterLayout {
self.element.after_layout(bounds, state, cx)
}
fn paint(
@@ -2454,11 +2365,9 @@ where
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
after_layout: &mut Self::AfterLayout,
before_paint: &mut Self::BeforePaint,
cx: &mut ElementContext,
) {
self.element
.paint(bounds, before_layout, after_layout, before_paint, cx);
self.element.paint(bounds, before_layout, after_layout, cx);
}
}

View File

@@ -230,8 +230,7 @@ impl Img {
impl Element for Img {
type BeforeLayout = ();
type AfterLayout = ();
type BeforePaint = Option<Hitbox>;
type AfterLayout = Option<Hitbox>;
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let layout_id = self.interactivity.before_layout(cx, |mut style, cx| {
@@ -262,29 +261,16 @@ impl Element for Img {
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
self.interactivity
.after_layout(bounds, bounds.size, cx, |_, _, _, _| {});
(None, ())
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> Option<Hitbox> {
self.interactivity
.before_paint(bounds, cx, |_, _, hitbox, _| hitbox)
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
hitbox: &mut Self::BeforePaint,
_: &mut Self::BeforeLayout,
hitbox: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
let source = self.source.clone();

View File

@@ -89,28 +89,17 @@ pub enum ListSizingBehavior {
Auto,
}
/// Layout information computed by the [List] element during `after_layout`.
pub struct ListLayout {
struct LayoutItemsResponse {
max_item_width: Pixels,
scroll_top: ListOffset,
items: VecDeque<MeasuredItem>,
focus_target: Option<FocusTarget>,
available_item_space: Size<AvailableSpace>,
item_elements: VecDeque<AnyElement>,
}
struct MeasuredItem {
element: AnyElement,
size: Size<Pixels>,
}
struct FocusTarget {
item_index: usize,
bounds_in_item: Bounds<Pixels>,
}
/// Frame state used by the [List] element during paint.
pub struct ListBeforePaintState {
/// Frame state used by the [List] element after layout.
pub struct ListAfterLayoutState {
hitbox: Hitbox,
layout: ListLayout,
layout: LayoutItemsResponse,
}
#[derive(Clone)]
@@ -387,14 +376,13 @@ impl StateInner {
available_height: Pixels,
padding: &Edges<Pixels>,
cx: &mut ElementContext,
) -> ListLayout {
) -> LayoutItemsResponse {
let old_items = self.items.clone();
let mut measured_items = VecDeque::new();
let mut items = VecDeque::new();
let mut item_elements = VecDeque::new();
let mut rendered_height = padding.top;
let mut max_item_width = px(0.);
let mut scroll_top = self.logical_scroll_top();
let mut focus_target = None;
let available_item_space = size(
available_width.map_or(AvailableSpace::MinContent, |width| {
@@ -423,20 +411,10 @@ impl StateInner {
// If we're within the visible area or the height wasn't cached, render and measure the item's element
if visible_height < available_height || size.is_none() {
let mut element = (self.render_item)(scroll_top.item_ix + ix, cx);
let element_measurement = element.layout(available_item_space, cx);
size = Some(element_measurement.size);
let element_size = element.measure(available_item_space, cx);
size = Some(element_size);
if visible_height < available_height {
if let Some(focus_target_bounds) = element_measurement.focus_target_bounds {
focus_target = Some(FocusTarget {
item_index: items.len(),
bounds_in_item: focus_target_bounds,
});
}
items.push_back(MeasuredItem {
element,
size: element_measurement.size,
});
item_elements.push_back(element);
}
}
@@ -457,26 +435,11 @@ impl StateInner {
cursor.prev(&());
if cursor.item().is_some() {
let mut element = (self.render_item)(cursor.start().0, cx);
let element_measurement = element.layout(available_item_space, cx);
let element_size = element.measure(available_item_space, cx);
rendered_height += element_measurement.size.height;
measured_items.push_front(ListItem::Rendered {
size: element_measurement.size,
});
items.push_front(MeasuredItem {
element,
size: element_measurement.size,
});
if let Some(focus_target_bounds) = element_measurement.focus_target_bounds {
focus_target = Some(FocusTarget {
item_index: 0,
bounds_in_item: focus_target_bounds,
});
} else if let Some(focus_target) = focus_target.as_mut() {
focus_target.item_index += 1;
}
rendered_height += element_size.height;
measured_items.push_front(ListItem::Rendered { size: element_size });
item_elements.push_front(element)
} else {
break;
}
@@ -511,7 +474,7 @@ impl StateInner {
*size
} else {
let mut element = (self.render_item)(cursor.start().0, cx);
element.layout(available_item_space, cx).size
element.measure(available_item_space, cx)
};
leading_overdraw += size.height;
@@ -530,11 +493,11 @@ impl StateInner {
self.items = new_items;
ListLayout {
LayoutItemsResponse {
max_item_width,
scroll_top,
items,
focus_target,
available_item_space,
item_elements,
}
}
}
@@ -560,8 +523,7 @@ pub struct ListOffset {
impl Element for List {
type BeforeLayout = ();
type AfterLayout = Option<ListLayout>;
type BeforePaint = ListBeforePaintState;
type AfterLayout = ListAfterLayoutState;
fn before_layout(
&mut self,
@@ -630,15 +592,17 @@ impl Element for List {
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
) -> ListAfterLayoutState {
let state = &mut *self.state.0.borrow_mut();
state.reset = false;
let mut style = Style::default();
style.refine(&self.style);
let hitbox = cx.insert_hitbox(bounds, false);
// If the width of the list has changed, invalidate all cached item heights
if state.last_layout_bounds.map_or(true, |last_bounds| {
last_bounds.size.width != bounds.size.width
@@ -650,77 +614,48 @@ impl Element for List {
}
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let layout = state.layout_items(Some(bounds.size.width), bounds.size.height, &padding, cx);
let focus_target_bounds = layout.focus_target.as_ref().map(|focus_target| {
let mut item_origin =
bounds.origin + Point::new(px(0.), padding.top - layout.scroll_top.offset_in_item);
item_origin.y -= layout.scroll_top.offset_in_item;
for item in layout.items.iter().take(focus_target.item_index) {
item_origin.y += item.size.height;
}
Bounds::new(
item_origin + focus_target.bounds_in_item.origin,
focus_target.bounds_in_item.size,
)
});
(focus_target_bounds, Some(layout))
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
_: &mut Self::BeforeLayout,
layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> ListBeforePaintState {
let mut layout = layout.take().unwrap();
let state = &mut *self.state.0.borrow_mut();
state.reset = false;
let mut style = Style::default();
style.refine(&self.style);
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let hitbox = cx.insert_hitbox(bounds, false);
let mut layout_response =
state.layout_items(Some(bounds.size.width), bounds.size.height, &padding, cx);
// Only paint the visible items, if there is actually any space for them (taking padding into account)
if bounds.size.height > padding.top + padding.bottom {
// Paint the visible items
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
item_origin.y -= layout.scroll_top.offset_in_item;
for item in &mut layout.items {
cx.with_absolute_element_offset(item_origin, |cx| {
item.element.before_paint(cx)
});
item_origin.y += item.size.height;
item_origin.y -= layout_response.scroll_top.offset_in_item;
for mut item_element in &mut layout_response.item_elements {
let item_size = item_element.measure(layout_response.available_item_space, cx);
item_element.layout(item_origin, layout_response.available_item_space, cx);
item_origin.y += item_size.height;
}
});
}
state.last_layout_bounds = Some(bounds);
state.last_padding = Some(padding);
ListBeforePaintState { hitbox, layout }
ListAfterLayoutState {
hitbox,
layout: layout_response,
}
}
fn paint(
&mut self,
bounds: Bounds<crate::Pixels>,
_: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
before_paint: &mut Self::BeforePaint,
after_layout: &mut Self::AfterLayout,
cx: &mut crate::ElementContext,
) {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
for item in &mut before_paint.layout.items {
item.element.paint(cx);
for item in &mut after_layout.layout.item_elements {
item.paint(cx);
}
});
let list_state = self.state.clone();
let height = bounds.size.height;
let scroll_top = before_paint.layout.scroll_top;
let hitbox_id = before_paint.hitbox.id;
let scroll_top = after_layout.layout.scroll_top;
let hitbox_id = after_layout.hitbox.id;
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(cx) {
list_state.0.borrow_mut().scroll(

View File

@@ -38,8 +38,7 @@ impl Svg {
impl Element for Svg {
type BeforeLayout = ();
type AfterLayout = ();
type BeforePaint = Option<Hitbox>;
type AfterLayout = Option<Hitbox>;
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let layout_id = self
@@ -53,28 +52,15 @@ impl Element for Svg {
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
self.interactivity
.after_layout(bounds, bounds.size, cx, |_, _, _, _| {});
(None, ())
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> Option<Hitbox> {
self.interactivity
.before_paint(bounds, cx, |_, _, hitbox, _| hitbox)
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
) where

View File

@@ -19,7 +19,6 @@ use util::ResultExt;
impl Element for &'static str {
type BeforeLayout = TextState;
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
@@ -28,19 +27,9 @@ impl Element for &'static str {
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
(None, ())
}
fn before_paint(
&mut self,
_bounds: Bounds<Pixels>,
_text_state: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
_cx: &mut ElementContext,
) {
}
@@ -49,7 +38,6 @@ impl Element for &'static str {
&mut self,
bounds: Bounds<Pixels>,
text_state: &mut TextState,
_: &mut Self::AfterLayout,
_: &mut (),
cx: &mut ElementContext,
) {
@@ -76,7 +64,6 @@ impl IntoElement for String {
impl Element for SharedString {
type BeforeLayout = TextState;
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
@@ -85,19 +72,9 @@ impl Element for SharedString {
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
(None, ())
}
fn before_paint(
&mut self,
_bounds: Bounds<Pixels>,
_text_state: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_cx: &mut ElementContext,
) {
}
@@ -106,8 +83,7 @@ impl Element for SharedString {
&mut self,
bounds: Bounds<Pixels>,
text_state: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_: &mut Self::BeforePaint,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
let text_str: &str = self.as_ref();
@@ -174,7 +150,6 @@ impl StyledText {
impl Element for StyledText {
type BeforeLayout = TextState;
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
@@ -183,19 +158,9 @@ impl Element for StyledText {
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
(None, ())
}
fn before_paint(
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
_cx: &mut ElementContext,
) {
}
@@ -205,7 +170,6 @@ impl Element for StyledText {
bounds: Bounds<Pixels>,
text_state: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
_: &mut (),
cx: &mut ElementContext,
) {
text_state.paint(bounds, &self.text, cx)
@@ -439,27 +403,16 @@ impl InteractiveText {
impl Element for InteractiveText {
type BeforeLayout = TextState;
type AfterLayout = ();
type BeforePaint = Hitbox;
type AfterLayout = Hitbox;
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
self.text.before_layout(cx)
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
(None, ())
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> Hitbox {
cx.with_element_state::<InteractiveTextState, _>(
@@ -477,7 +430,7 @@ impl Element for InteractiveText {
}
}
self.text.before_paint(bounds, state, &mut (), cx);
self.text.after_layout(bounds, state, cx);
let hitbox = cx.insert_hitbox(bounds, false);
(hitbox, interactive_state)
},
@@ -488,7 +441,6 @@ impl Element for InteractiveText {
&mut self,
bounds: Bounds<Pixels>,
text_state: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
hitbox: &mut Hitbox,
cx: &mut ElementContext,
) {
@@ -625,7 +577,7 @@ impl Element for InteractiveText {
});
}
self.text.paint(bounds, text_state, &mut (), &mut (), cx);
self.text.paint(bounds, text_state, &mut (), cx);
((), Some(interactive_state))
},

View File

@@ -6,9 +6,8 @@
use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
ElementId, ElementMeasurement, Hitbox, InteractiveElement, Interactivity, IntoElement,
LayoutId, Pixels, Render, ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext,
WindowContext,
ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render,
ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -71,9 +70,8 @@ pub struct UniformList {
/// Frame state used by the [UniformList].
pub struct UniformListFrameState {
item_height: Pixels,
item_size: Size<Pixels>,
items: SmallVec<[AnyElement; 32]>,
visible_range: Range<usize>,
}
/// A handle for controlling the scroll position of a uniform list.
@@ -107,22 +105,19 @@ impl Styled for UniformList {
impl Element for UniformList {
type BeforeLayout = UniformListFrameState;
type AfterLayout = ();
type BeforePaint = Option<Hitbox>;
type AfterLayout = Option<Hitbox>;
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let max_items = self.item_count;
let item_measurement = self.measure_item(None, cx);
let item_size = self.measure_item(None, cx);
let layout_id = self.interactivity.before_layout(cx, |style, cx| {
cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| {
let desired_height = item_measurement.size.height * max_items;
let desired_height = item_size.height * max_items;
let width = known_dimensions
.width
.unwrap_or(match available_space.width {
AvailableSpace::Definite(x) => x,
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
item_measurement.size.width
}
AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
});
let height = match available_space.height {
@@ -136,9 +131,8 @@ impl Element for UniformList {
(
layout_id,
UniformListFrameState {
item_height: Pixels::default(),
item_size,
items: SmallVec::new(),
visible_range: 0..0,
},
)
}
@@ -146,26 +140,28 @@ impl Element for UniformList {
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::BeforeLayout,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
) -> Option<Hitbox> {
let style = self.interactivity.compute_style(None, cx);
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let padded_size = size(
bounds.size.width - border.left - padding.left - border.right - padding.right,
bounds.size.height - border.top - padding.top - border.bottom - padding.bottom,
let padded_bounds = Bounds::from_corners(
bounds.origin + point(border.left + padding.left, border.top + padding.top),
bounds.lower_right()
- point(border.right + padding.right, border.bottom + padding.bottom),
);
let item_height = self.measure_item(Some(padded_size.width), cx).size.height;
let content_size = Size {
width: padded_size.width,
height: item_height * self.item_count + padding.top + padding.bottom,
width: padded_bounds.size.width,
height: before_layout.item_size.height * self.item_count + padding.top + padding.bottom,
};
let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap();
// todo!("add support for scrolling to the focused element?");
let deferred_scroll_to_item = self
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
let shared_scroll_to_item = self
.scroll_handle
.as_mut()
.and_then(|handle| handle.deferred_scroll_to_item.take());
@@ -174,103 +170,73 @@ impl Element for UniformList {
bounds,
content_size,
cx,
|style, mut scroll_offset, mut focus_target_bounds, cx| {
if self.item_count == 0 {
return (focus_target_bounds, ());
}
|style, mut scroll_offset, hitbox, cx| {
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let padded_size = size(
bounds.size.width - border.left - padding.left - border.right - padding.right,
bounds.size.height - border.top - padding.top - border.bottom - padding.bottom,
);
let content_height = item_height * self.item_count + padding.top + padding.bottom;
let min_scroll_offset = padded_size.height - content_height;
let is_scrolled = scroll_offset.y != px(0.);
if is_scrolled && scroll_offset.y < min_scroll_offset {
shared_scroll_offset.borrow_mut().y = min_scroll_offset;
scroll_offset.y = min_scroll_offset;
}
if let Some(ix) = deferred_scroll_to_item {
let list_height = padded_size.height;
let mut updated_scroll_offset = shared_scroll_offset.borrow_mut();
let item_top = item_height * ix + padding.top;
let item_bottom = item_top + item_height;
let scroll_top = -updated_scroll_offset.y;
if item_top < scroll_top + padding.top {
updated_scroll_offset.y = -(item_top) + padding.top;
} else if item_bottom > scroll_top + list_height - padding.bottom {
updated_scroll_offset.y = -(item_bottom - list_height) - padding.bottom;
}
scroll_offset = *updated_scroll_offset;
}
let first_visible_element_ix =
(-(scroll_offset.y + padding.top) / item_height).floor() as usize;
let last_visible_element_ix =
((-scroll_offset.y + padded_size.height) / item_height).ceil() as usize;
let visible_range =
first_visible_element_ix..cmp::min(last_visible_element_ix, self.item_count);
let mut items = (self.render_items)(visible_range.clone(), cx);
let available_space = size(
AvailableSpace::Definite(padded_size.width),
AvailableSpace::Definite(item_height),
);
for mut item in items {
let measurement = item.layout(available_space, cx);
if measurement.focus_target_bounds.is_some() {
focus_target_bounds = measurement.focus_target_bounds;
}
state.items.push(item);
}
state.item_height = item_height;
state.visible_range = visible_range;
(focus_target_bounds, ())
},
)
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) -> Option<Hitbox> {
self.interactivity
.before_paint(bounds, cx, |style, scroll_offset, hitbox, cx| {
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let padded_bounds = Bounds::from_corners(
bounds.origin + point(border.left + padding.left, border.top),
bounds.lower_right() - point(border.right + padding.right, border.bottom),
);
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (item, ix) in state.items.iter_mut().zip(state.visible_range.clone()) {
let item_y = state.item_height * ix + scroll_offset.y + padding.top;
let item_origin = padded_bounds.origin + point(px(0.), item_y);
cx.with_absolute_element_offset(item_origin, |cx| item.before_paint(cx));
if self.item_count > 0 {
let content_height =
item_height * self.item_count + padding.top + padding.bottom;
let min_scroll_offset = padded_bounds.size.height - content_height;
let is_scrolled = scroll_offset.y != px(0.);
if is_scrolled && scroll_offset.y < min_scroll_offset {
shared_scroll_offset.borrow_mut().y = min_scroll_offset;
scroll_offset.y = min_scroll_offset;
}
});
if let Some(ix) = shared_scroll_to_item {
let list_height = padded_bounds.size.height;
let mut updated_scroll_offset = shared_scroll_offset.borrow_mut();
let item_top = item_height * ix + padding.top;
let item_bottom = item_top + item_height;
let scroll_top = -updated_scroll_offset.y;
if item_top < scroll_top + padding.top {
updated_scroll_offset.y = -(item_top) + padding.top;
} else if item_bottom > scroll_top + list_height - padding.bottom {
updated_scroll_offset.y = -(item_bottom - list_height) - padding.bottom;
}
scroll_offset = *updated_scroll_offset;
}
let first_visible_element_ix =
(-(scroll_offset.y + padding.top) / item_height).floor() as usize;
let last_visible_element_ix = ((-scroll_offset.y + padded_bounds.size.height)
/ item_height)
.ceil() as usize;
let visible_range = first_visible_element_ix
..cmp::min(last_visible_element_ix, self.item_count);
let mut items = (self.render_items)(visible_range.clone(), cx);
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (mut item, ix) in items.into_iter().zip(visible_range) {
let item_origin = padded_bounds.origin
+ point(px(0.), item_height * ix + scroll_offset.y + padding.top);
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.layout(item_origin, available_space, cx);
before_layout.items.push(item);
}
});
}
hitbox
})
},
)
}
fn paint(
&mut self,
bounds: Bounds<crate::Pixels>,
before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
) {
@@ -298,13 +264,9 @@ impl UniformList {
self
}
fn measure_item(
&self,
list_width: Option<Pixels>,
cx: &mut ElementContext,
) -> ElementMeasurement {
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut ElementContext) -> Size<Pixels> {
if self.item_count == 0 {
return ElementMeasurement::default();
return Size::default();
}
let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1);
@@ -316,7 +278,7 @@ impl UniformList {
}),
AvailableSpace::MinContent,
);
item_to_measure.layout(available_space, cx)
item_to_measure.measure(available_space, cx)
}
/// Track and render scroll state of this list with reference to the given scroll handle.

View File

@@ -1257,7 +1257,6 @@ where
/// origin: Point { x: 15.0, y: 15.0 },
/// size: Size { width: 15.0, height: 30.0 },
/// });
/// ```
pub fn map<U>(&self, f: impl Fn(T) -> U) -> Bounds<U>
where
U: Clone + Default + Debug,
@@ -1284,7 +1283,6 @@ where
/// origin: Point { x: 15.0, y: 15.0 },
/// size: Size { width: 10.0, height: 20.0 },
/// });
/// ```
pub fn map_origin(self, f: impl Fn(Point<T>) -> Point<T>) -> Bounds<T> {
Bounds {
origin: f(self.origin),

View File

@@ -151,9 +151,7 @@ pub(crate) trait Platform: 'static {
fn set_cursor_style(&self, style: CursorStyle);
fn should_auto_hide_scrollbars(&self) -> bool;
fn write_to_primary(&self, item: ClipboardItem);
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_primary(&self) -> Option<ClipboardItem>;
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;

View File

@@ -162,7 +162,7 @@ impl BladeAtlasState {
usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
}
AtlasTextureKind::Polychrome => {
format = gpu::TextureFormat::Bgra8Unorm;
format = gpu::TextureFormat::Rgba8Unorm;
usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
}
AtlasTextureKind::Path => {

View File

@@ -79,14 +79,8 @@ impl LinuxClient for HeadlessClient {
//todo(linux)
fn set_cursor_style(&self, _style: CursorStyle) {}
fn write_to_primary(&self, item: crate::ClipboardItem) {}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
None
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
None
}

View File

@@ -56,9 +56,7 @@ pub trait LinuxClient {
options: WindowParams,
) -> Box<dyn PlatformWindow>;
fn set_cursor_style(&self, style: CursorStyle);
fn write_to_primary(&self, item: ClipboardItem);
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_primary(&self) -> Option<ClipboardItem>;
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn run(&self);
}
@@ -408,6 +406,7 @@ impl<P: LinuxClient + 'static> Platform for P {
})
}
//todo(linux): add trait methods for accessing the primary selection
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
let url = url.to_string();
self.background_executor().spawn(async move {
@@ -462,18 +461,10 @@ impl<P: LinuxClient + 'static> Platform for P {
Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
}
fn write_to_primary(&self, item: ClipboardItem) {
self.write_to_primary(item)
}
fn write_to_clipboard(&self, item: ClipboardItem) {
self.write_to_clipboard(item)
}
fn read_from_primary(&self) -> Option<ClipboardItem> {
self.read_from_primary()
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.read_from_clipboard()
}

View File

@@ -95,7 +95,6 @@ impl Globals {
pub(crate) struct WaylandClientState {
globals: Globals,
wl_pointer: Option<wl_pointer::WlPointer>,
// Surface to Window mapping
windows: HashMap<ObjectId, WaylandWindowStatePtr>,
// Output to scale mapping
@@ -104,13 +103,9 @@ pub(crate) struct WaylandClientState {
click: ClickState,
repeat: KeyRepeat,
modifiers: Modifiers,
scroll_direction: f64,
axis_source: AxisSource,
mouse_location: Option<Point<Pixels>>,
continuous_scroll_delta: Option<Point<Pixels>>,
discrete_scroll_delta: Option<Point<f32>>,
vertical_modifier: f32,
horizontal_modifier: f32,
scroll_event_received: bool,
enter_token: Option<()>,
button_pressed: Option<MouseButton>,
mouse_focused_window: Option<WaylandWindowStatePtr>,
@@ -118,8 +113,8 @@ pub(crate) struct WaylandClientState {
loop_handle: LoopHandle<'static, WaylandClientStatePtr>,
cursor_icon_name: String,
cursor: Cursor,
clipboard: Option<Clipboard>,
primary: Option<Primary>,
clipboard: Clipboard,
primary: Primary,
event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>,
common: LinuxCommon,
}
@@ -163,33 +158,26 @@ impl WaylandClientStatePtr {
state.mouse_focused_window = Some(window);
}
}
if state.windows.is_empty() {
// Drop the clipboard to prevent a seg fault after we've closed all Wayland connections.
state.clipboard = None;
state.primary = None;
state.common.signal.stop();
}
}
}
#[derive(Clone)]
pub struct WaylandClient(Rc<RefCell<WaylandClientState>>);
const WL_SEAT_MIN_VERSION: u32 = 4;
const WL_OUTPUT_VERSION: u32 = 2;
fn wl_seat_version(version: u32) -> u32 {
// We rely on the wl_pointer.frame event
const WL_SEAT_MIN_VERSION: u32 = 5;
const WL_SEAT_MAX_VERSION: u32 = 9;
if version < WL_SEAT_MIN_VERSION {
if version >= wl_pointer::EVT_AXIS_VALUE120_SINCE {
wl_pointer::EVT_AXIS_VALUE120_SINCE
} else if version >= WL_SEAT_MIN_VERSION {
WL_SEAT_MIN_VERSION
} else {
panic!(
"wl_seat below required version: {} < {}",
version, WL_SEAT_MIN_VERSION
);
}
version.clamp(WL_SEAT_MIN_VERSION, WL_SEAT_MAX_VERSION)
}
impl WaylandClient {
@@ -247,7 +235,6 @@ impl WaylandClient {
let mut state = Rc::new(RefCell::new(WaylandClientState {
globals,
wl_pointer: None,
output_scales: outputs,
windows: HashMap::default(),
common,
@@ -270,13 +257,9 @@ impl WaylandClient {
function: false,
platform: false,
},
scroll_event_received: false,
scroll_direction: -1.0,
axis_source: AxisSource::Wheel,
mouse_location: None,
continuous_scroll_delta: None,
discrete_scroll_delta: None,
vertical_modifier: -1.0,
horizontal_modifier: -1.0,
button_pressed: None,
mouse_focused_window: None,
keyboard_focused_window: None,
@@ -284,8 +267,8 @@ impl WaylandClient {
cursor_icon_name: "arrow".to_string(),
enter_token: None,
cursor,
clipboard: Some(clipboard),
primary: Some(primary),
clipboard,
primary,
event_loop: Some(event_loop),
}));
@@ -351,15 +334,7 @@ impl LinuxClient for WaylandClient {
}
.to_string();
let mut state = self.0.borrow_mut();
state.cursor_icon_name = cursor_icon_name.clone();
if state.mouse_focused_window.is_some() {
let wl_pointer = state
.wl_pointer
.clone()
.expect("window is focused by pointer");
state.cursor.set_icon(&wl_pointer, &cursor_icon_name);
}
self.0.borrow_mut().cursor_icon_name = cursor_icon_name;
}
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
@@ -383,44 +358,14 @@ impl LinuxClient for WaylandClient {
.log_err();
}
fn write_to_primary(&self, item: crate::ClipboardItem) {
self.0
.borrow_mut()
.primary
.as_mut()
.unwrap()
.set_contents(item.text);
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
self.0
.borrow_mut()
.clipboard
.as_mut()
.unwrap()
.set_contents(item.text);
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.primary
.as_mut()
.unwrap()
.get_contents()
.ok()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
self.0.borrow_mut().clipboard.set_contents(item.text);
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.clipboard
.as_mut()
.unwrap()
.get_contents()
.ok()
.map(|s| crate::ClipboardItem {
@@ -449,7 +394,6 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
version,
} => match &interface[..] {
"wl_seat" => {
state.wl_pointer = None;
registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
}
"wl_output" => {
@@ -629,9 +573,7 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
seat.get_keyboard(qh, ());
}
if capabilities.contains(wl_seat::Capability::Pointer) {
let client = state.get_client();
let mut state = client.borrow_mut();
state.wl_pointer = Some(seat.get_pointer(qh, ()));
seat.get_pointer(qh, ());
}
}
}
@@ -838,11 +780,10 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
if let Some(window) = get_window(&mut state, &surface.id()) {
state.enter_token = Some(());
state.mouse_focused_window = Some(window.clone());
state.cursor.mark_dirty();
state.cursor.set_serial_id(serial);
state
.cursor
.set_icon(&wl_pointer, cursor_icon_name.as_str());
.set_icon(&wl_pointer, Some(cursor_icon_name.as_str()));
drop(state);
window.set_focused(true);
}
@@ -873,6 +814,9 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
return;
}
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
state
.cursor
.set_icon(&wl_pointer, Some(cursor_icon_name.as_str()));
if let Some(window) = state.mouse_focused_window.clone() {
let input = PlatformInput::MouseMove(MouseMoveEvent {
@@ -943,137 +887,77 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
_ => {}
}
}
// Axis Events
wl_pointer::Event::AxisRelativeDirection {
direction: WEnum::Value(direction),
..
} => {
state.scroll_direction = match direction {
AxisRelativeDirection::Identical => -1.0,
AxisRelativeDirection::Inverted => 1.0,
_ => -1.0,
}
}
wl_pointer::Event::AxisSource {
axis_source: WEnum::Value(axis_source),
} => {
state.axis_source = axis_source;
}
wl_pointer::Event::AxisValue120 {
axis: WEnum::Value(axis),
value120,
} => {
if let Some(focused_window) = state.mouse_focused_window.clone() {
let value = value120 as f64 * state.scroll_direction;
let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
position: state.mouse_location.unwrap(),
delta: match axis {
wl_pointer::Axis::VerticalScroll => {
ScrollDelta::Pixels(point(px(0.0), px(value as f32)))
}
wl_pointer::Axis::HorizontalScroll => {
ScrollDelta::Pixels(point(px(value as f32), px(0.0)))
}
_ => unimplemented!(),
},
modifiers: state.modifiers,
touch_phase: TouchPhase::Moved,
});
drop(state);
focused_window.handle_input(input)
}
}
wl_pointer::Event::Axis {
time,
axis: WEnum::Value(axis),
value,
..
} => {
let axis_source = state.axis_source;
let axis_modifier = match axis {
wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
_ => 1.0,
};
let supports_relative_direction =
wl_pointer.version() >= wl_pointer::EVT_AXIS_RELATIVE_DIRECTION_SINCE;
state.scroll_event_received = true;
let scroll_delta = state
.continuous_scroll_delta
.get_or_insert(point(px(0.0), px(0.0)));
// TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
let modifier = 3.0;
match axis {
wl_pointer::Axis::VerticalScroll => {
scroll_delta.y += px(value as f32 * modifier * axis_modifier);
}
wl_pointer::Axis::HorizontalScroll => {
scroll_delta.x += px(value as f32 * modifier * axis_modifier);
}
_ => unreachable!(),
// We handle discrete scroll events with `AxisValue120`.
if wl_pointer.version() >= wl_pointer::EVT_AXIS_VALUE120_SINCE
&& state.axis_source == AxisSource::Wheel
{
return;
}
}
wl_pointer::Event::AxisDiscrete {
axis: WEnum::Value(axis),
discrete,
} => {
state.scroll_event_received = true;
let axis_modifier = match axis {
wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
_ => 1.0,
};
if let Some(focused_window) = state.mouse_focused_window.clone() {
let value = value * state.scroll_direction;
// TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
let modifier = 3.0;
let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
match axis {
wl_pointer::Axis::VerticalScroll => {
scroll_delta.y += discrete as f32 * axis_modifier * modifier;
}
wl_pointer::Axis::HorizontalScroll => {
scroll_delta.x += discrete as f32 * axis_modifier * modifier;
}
_ => unreachable!(),
}
}
wl_pointer::Event::AxisRelativeDirection {
axis: WEnum::Value(axis),
direction: WEnum::Value(direction),
} => match (axis, direction) {
(wl_pointer::Axis::VerticalScroll, AxisRelativeDirection::Identical) => {
state.vertical_modifier = -1.0
}
(wl_pointer::Axis::VerticalScroll, AxisRelativeDirection::Inverted) => {
state.vertical_modifier = 1.0
}
(wl_pointer::Axis::HorizontalScroll, AxisRelativeDirection::Identical) => {
state.horizontal_modifier = -1.0
}
(wl_pointer::Axis::HorizontalScroll, AxisRelativeDirection::Inverted) => {
state.horizontal_modifier = 1.0
}
_ => unreachable!(),
},
wl_pointer::Event::AxisValue120 {
axis: WEnum::Value(axis),
value120,
} => {
state.scroll_event_received = true;
let axis_modifier = match axis {
wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
_ => unreachable!(),
};
let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
let wheel_percent = value120 as f32 / 120.0;
match axis {
wl_pointer::Axis::VerticalScroll => {
scroll_delta.y += wheel_percent * axis_modifier;
}
wl_pointer::Axis::HorizontalScroll => {
scroll_delta.x += wheel_percent * axis_modifier;
}
_ => unreachable!(),
}
}
wl_pointer::Event::Frame => {
if state.scroll_event_received {
state.scroll_event_received = false;
let continuous = state.continuous_scroll_delta.take();
let discrete = state.discrete_scroll_delta.take();
if let Some(continuous) = continuous {
if let Some(window) = state.mouse_focused_window.clone() {
let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
position: state.mouse_location.unwrap(),
delta: ScrollDelta::Pixels(continuous),
modifiers: state.modifiers,
touch_phase: TouchPhase::Moved,
});
drop(state);
window.handle_input(input);
}
} else if let Some(discrete) = discrete {
if let Some(window) = state.mouse_focused_window.clone() {
let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
position: state.mouse_location.unwrap(),
delta: ScrollDelta::Lines(discrete),
modifiers: state.modifiers,
touch_phase: TouchPhase::Moved,
});
drop(state);
window.handle_input(input);
}
}
let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
position: state.mouse_location.unwrap(),
delta: match axis {
wl_pointer::Axis::VerticalScroll => {
ScrollDelta::Pixels(point(px(0.0), px(value as f32)))
}
wl_pointer::Axis::HorizontalScroll => {
ScrollDelta::Pixels(point(px(value as f32), px(0.0)))
}
_ => unimplemented!(),
},
modifiers: state.modifiers,
touch_phase: TouchPhase::Moved,
});
drop(state);
focused_window.handle_input(input)
}
}
_ => {}

View File

@@ -8,7 +8,7 @@ use wayland_cursor::{CursorImageBuffer, CursorTheme};
pub(crate) struct Cursor {
theme: Option<CursorTheme>,
current_icon_name: Option<String>,
current_icon_name: String,
surface: WlSurface,
serial_id: u32,
}
@@ -24,29 +24,19 @@ impl Cursor {
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
Self {
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
current_icon_name: None,
current_icon_name: "default".to_string(),
surface: globals.compositor.create_surface(&globals.qh, ()),
serial_id: 0,
}
}
pub fn mark_dirty(&mut self) {
self.current_icon_name = None;
}
pub fn set_serial_id(&mut self, serial_id: u32) {
self.serial_id = serial_id;
}
pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: &str) {
let need_update = self
.current_icon_name
.as_ref()
.map_or(true, |current_icon_name| {
current_icon_name != cursor_icon_name
});
if need_update {
pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: Option<&str>) {
let mut cursor_icon_name = cursor_icon_name.unwrap_or("default");
if self.current_icon_name != cursor_icon_name {
if let Some(theme) = &mut self.theme {
let mut buffer: Option<&CursorImageBuffer>;
@@ -78,7 +68,7 @@ impl Cursor {
self.surface.damage(0, 0, width as i32, height as i32);
self.surface.commit();
self.current_icon_name = Some(cursor_icon_name.to_string());
self.current_icon_name = cursor_icon_name.to_string();
}
} else {
log::warn!("Linux: Wayland: Unable to load cursor themes");

View File

@@ -68,7 +68,6 @@ unsafe impl HasRawDisplayHandle for RawWindow {
pub struct WaylandWindowState {
xdg_surface: xdg_surface::XdgSurface,
pub surface: wl_surface::WlSurface,
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
toplevel: xdg_toplevel::XdgToplevel,
viewport: Option<wp_viewport::WpViewport>,
outputs: HashSet<ObjectId>,
@@ -91,13 +90,11 @@ pub struct WaylandWindowStatePtr {
}
impl WaylandWindowState {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
surface: wl_surface::WlSurface,
xdg_surface: xdg_surface::XdgSurface,
toplevel: xdg_toplevel::XdgToplevel,
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
viewport: Option<wp_viewport::WpViewport>,
toplevel: xdg_toplevel::XdgToplevel,
client: WaylandClientStatePtr,
globals: Globals,
options: WindowParams,
@@ -135,7 +132,6 @@ impl WaylandWindowState {
Self {
xdg_surface,
surface,
decoration,
toplevel,
viewport,
globals,
@@ -162,27 +158,16 @@ impl Drop for WaylandWindow {
let mut state = self.0.state.borrow_mut();
let surface_id = state.surface.id();
let client = state.client.clone();
state.renderer.destroy();
if let Some(decoration) = &state.decoration {
decoration.destroy();
}
state.toplevel.destroy();
if let Some(viewport) = &state.viewport {
viewport.destroy();
}
state.xdg_surface.destroy();
state.surface.destroy();
let state_ptr = self.0.clone();
state
.globals
.executor
.spawn(async move {
state_ptr.close();
client.drop_window(&surface_id)
})
.detach();
state.globals.executor.spawn(async move {
state_ptr.close();
client.drop_window(&surface_id)
});
drop(state);
}
}
@@ -212,18 +197,13 @@ impl WaylandWindow {
}
// Attempt to set up window decorations based on the requested configuration
let decoration = globals
.decoration_manager
.as_ref()
.map(|decoration_manager| {
let decoration = decoration_manager.get_toplevel_decoration(
&toplevel,
&globals.qh,
surface.id(),
);
decoration.set_mode(zxdg_toplevel_decoration_v1::Mode::ClientSide);
decoration
});
if let Some(decoration_manager) = globals.decoration_manager.as_ref() {
let decoration =
decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id());
// Request client side decorations if possible
decoration.set_mode(zxdg_toplevel_decoration_v1::Mode::ClientSide);
}
let viewport = globals
.viewporter
@@ -236,9 +216,8 @@ impl WaylandWindow {
state: Rc::new(RefCell::new(WaylandWindowState::new(
surface.clone(),
xdg_surface,
toplevel,
decoration,
viewport,
toplevel,
client,
globals,
params,
@@ -340,7 +319,7 @@ impl WaylandWindowStatePtr {
}
result
} else {
true
false
}
}
_ => false,
@@ -502,12 +481,13 @@ impl WaylandWindowStatePtr {
}
}
if let PlatformInput::KeyDown(event) = input {
if let Some(ime_key) = &event.keystroke.ime_key {
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
if let Some(ime_key) = &event.keystroke.ime_key {
drop(state);
input_handler.replace_text_in_range(None, ime_key);
self.state.borrow_mut().input_handler = Some(input_handler);
let mut state = self.state.borrow_mut();
state.input_handler = Some(input_handler);
}
}
}

View File

@@ -12,10 +12,9 @@ use util::ResultExt;
use x11rb::connection::{Connection, RequestConnection};
use x11rb::errors::ConnectionError;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::xinput::ConnectionExt;
use x11rb::protocol::xkb::ConnectionExt as _;
use x11rb::protocol::xproto::ConnectionExt as _;
use x11rb::protocol::{randr, xinput, xkb, xproto, Event};
use x11rb::protocol::{randr, xkb, xproto, Event};
use x11rb::xcb_ffi::XCBConnection;
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb as xkbc;
@@ -64,10 +63,6 @@ pub struct X11ClientState {
pub(crate) focused_window: Option<xproto::Window>,
pub(crate) xkb: xkbc::State,
pub(crate) scroll_devices: Vec<xinput::DeviceInfo>,
pub(crate) scroll_x: Option<f32>,
pub(crate) scroll_y: Option<f32>,
pub(crate) common: LinuxCommon,
pub(crate) clipboard: X11ClipboardContext<Clipboard>,
pub(crate) primary: X11ClipboardContext<Primary>,
@@ -97,19 +92,6 @@ impl X11Client {
xcb_connection
.prefetch_extension_information(randr::X11_EXTENSION_NAME)
.unwrap();
xcb_connection
.prefetch_extension_information(xinput::X11_EXTENSION_NAME)
.unwrap();
let xinput_version = xcb_connection
.xinput_xi_query_version(2, 0)
.unwrap()
.reply()
.unwrap();
assert!(
xinput_version.major_version >= 2,
"XInput Extension v2 not supported."
);
let atoms = XcbAtoms::new(&xcb_connection).unwrap();
let xkb = xcb_connection
@@ -143,46 +125,6 @@ impl X11Client {
xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
};
let device_list = xcb_connection
.xinput_list_input_devices()
.unwrap()
.reply()
.unwrap();
let scroll_devices = device_list
.devices
.iter()
.scan(0, |class_info_idx, device_info| {
Some(*class_info_idx + device_info.num_class_info as usize)
})
.zip(device_list.devices.iter())
.map(|(class_info_idx, device_info)| {
(
device_info,
device_list.infos
[(class_info_idx - device_info.num_class_info as usize)..(class_info_idx)]
.to_vec(),
)
})
.filter(|(device_info, class_info)| {
device_info.device_use == xinput::DeviceUse::IS_X_EXTENSION_POINTER
&& class_info.iter().any(|class_info| match class_info.info {
xinput::InputInfoInfo::Valuator(xinput::InputInfoInfoValuator {
mode,
..
}) => mode == xinput::ValuatorMode::RELATIVE,
_ => false,
})
})
.map(|(device_info, _)| *device_info)
.collect::<Vec<_>>();
for device in &scroll_devices {
xcb_connection
.xinput_open_device(device.device_id)
.unwrap()
.reply()
.unwrap();
}
let clipboard = X11ClipboardContext::<Clipboard>::new().unwrap();
let primary = X11ClipboardContext::<Primary>::new().unwrap();
@@ -224,11 +166,6 @@ impl X11Client {
windows: HashMap::default(),
focused_window: None,
xkb: xkb_state,
scroll_devices,
scroll_x: None,
scroll_y: None,
clipboard,
primary,
})))
@@ -377,58 +314,22 @@ impl X11Client {
click_count: current_count,
first_mouse: false,
}));
} else if event.detail >= 4 && event.detail <= 5 {
// https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
let scroll_direction = if event.detail == 4 { 1.0 } else { -1.0 };
let scroll_y = SCROLL_LINES * scroll_direction;
drop(state);
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Lines(Point::new(0.0, scroll_y as f32)),
modifiers,
touch_phase: TouchPhase::Moved,
}));
} else {
log::warn!("Unknown button press: {event:?}");
}
}
Event::XinputMotion(event) => {
let window = self.get_window(event.event)?;
let position = Point::new(
(event.event_x as f32 / u16::MAX as f32).into(),
(event.event_y as f32 / u16::MAX as f32).into(),
);
let axisvalues = event
.axisvalues
.iter()
.map(|axisvalue| {
axisvalue.integral as f32 + axisvalue.frac as f32 / u32::MAX as f32
})
.collect::<Vec<_>>();
if event.valuator_mask[0] & 4 == 4 {
let new_scroll = axisvalues[0];
let old_scroll = self.0.borrow().scroll_x;
self.0.borrow_mut().scroll_x = Some(new_scroll);
if let Some(old_scroll) = old_scroll {
let delta_scroll = (old_scroll - new_scroll).into();
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Pixels(Point::new(delta_scroll, 0.0.into())),
modifiers: crate::Modifiers::none(),
touch_phase: TouchPhase::default(),
}));
}
}
if event.valuator_mask[0] & 8 == 8 {
let new_scroll = axisvalues[0] / 2.0;
let old_scroll = self.0.borrow().scroll_y;
self.0.borrow_mut().scroll_y = Some(new_scroll);
if let Some(old_scroll) = old_scroll {
let delta_scroll = (old_scroll - new_scroll).into();
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Pixels(Point::new(0.0.into(), delta_scroll)),
modifiers: crate::Modifiers::none(),
touch_phase: TouchPhase::default(),
}));
}
}
}
Event::ButtonRelease(event) => {
let window = self.get_window(event.event)?;
let state = self.0.borrow();
@@ -528,7 +429,6 @@ impl LinuxClient for X11Client {
state.x_root_index,
x_window,
&state.atoms,
&state.scroll_devices,
);
let screen_resources = state
@@ -603,26 +503,10 @@ impl LinuxClient for X11Client {
//todo(linux)
fn set_cursor_style(&self, _style: CursorStyle) {}
fn write_to_primary(&self, item: crate::ClipboardItem) {
self.0.borrow_mut().primary.set_contents(item.text);
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
self.0.borrow_mut().clipboard.set_contents(item.text);
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.primary
.get_contents()
.ok()
.map(|text| crate::ClipboardItem {
text,
metadata: None,
})
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()

View File

@@ -13,10 +13,7 @@ use raw_window_handle as rwh;
use util::ResultExt;
use x11rb::{
connection::Connection,
protocol::{
xinput,
xproto::{self, ConnectionExt as _, CreateWindowAux},
},
protocol::xproto::{self, ConnectionExt as _, CreateWindowAux},
wrapper::ConnectionExt,
xcb_ffi::XCBConnection,
};
@@ -143,7 +140,6 @@ impl X11WindowState {
x_main_screen_index: usize,
x_window: xproto::Window,
atoms: &XcbAtoms,
scroll_devices: &Vec<xinput::DeviceInfo>,
) -> Self {
let x_screen_index = params
.display_id
@@ -164,6 +160,8 @@ impl X11WindowState {
| xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::BUTTON_MOTION,
);
@@ -183,20 +181,6 @@ impl X11WindowState {
)
.unwrap();
for device in scroll_devices {
xinput::ConnectionExt::xinput_xi_select_events(
&xcb_connection,
x_window,
&[xinput::EventMask {
deviceid: device.device_id as u16,
mask: vec![xinput::XIEventMask::MOTION],
}],
)
.unwrap()
.check()
.unwrap();
}
if let Some(titlebar) = params.titlebar {
if let Some(title) = titlebar.title {
xcb_connection
@@ -278,7 +262,6 @@ impl X11Window {
x_main_screen_index: usize,
x_window: xproto::Window,
atoms: &XcbAtoms,
scroll_devices: &Vec<xinput::DeviceInfo>,
) -> Self {
X11Window {
state: Rc::new(RefCell::new(X11WindowState::new(
@@ -287,7 +270,6 @@ impl X11Window {
x_main_screen_index,
x_window,
atoms,
scroll_devices,
))),
callbacks: Rc::new(RefCell::new(Callbacks::default())),
xcb_connection: xcb_connection.clone(),

View File

@@ -570,7 +570,6 @@ impl Platform for MacPlatform {
let _ = done_tx.send(result);
}
});
let block = block.copy();
let _: () = msg_send![workspace, setDefaultApplicationAtURL: app toOpenURLsWithScheme: scheme completionHandler: block];
}
@@ -849,8 +848,6 @@ impl Platform for MacPlatform {
}
}
fn write_to_primary(&self, _item: ClipboardItem) {}
fn write_to_clipboard(&self, item: ClipboardItem) {
let state = self.0.lock();
unsafe {
@@ -888,10 +885,6 @@ impl Platform for MacPlatform {
}
}
fn read_from_primary(&self) -> Option<ClipboardItem> {
None
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
let state = self.0.lock();
unsafe {

View File

@@ -334,7 +334,7 @@ impl MacTextSystemState {
self.postscript_names_by_font_id
.get(&font_id)
.map_or(false, |postscript_name| {
postscript_name == "AppleColorEmoji" || postscript_name == ".AppleColorEmojiUI"
postscript_name == "AppleColorEmoji"
})
}

View File

@@ -337,6 +337,7 @@ struct MacWindowState {
handle: AnyWindowHandle,
executor: ForegroundExecutor,
native_window: id,
native_window_was_closed: bool,
native_view: NonNull<Object>,
display_link: Option<DisplayLink>,
renderer: renderer::Renderer,
@@ -604,10 +605,6 @@ impl MacWindow {
registerForDraggedTypes:
NSArray::arrayWithObject(nil, NSFilenamesPboardType)
];
let () = msg_send![
native_window,
setReleasedWhenClosed: NO
];
let native_view: id = msg_send![VIEW_CLASS, alloc];
let native_view = NSView::init(native_view);
@@ -625,6 +622,7 @@ impl MacWindow {
handle,
executor,
native_window,
native_window_was_closed: false,
native_view: NonNull::new_unchecked(native_view),
display_link: None,
renderer: renderer::new_renderer(
@@ -772,17 +770,19 @@ impl Drop for MacWindow {
this.renderer.destroy();
let window = this.native_window;
this.display_link.take();
unsafe {
this.native_window.setDelegate_(nil);
if !this.native_window_was_closed {
unsafe {
this.native_window.setDelegate_(nil);
}
this.executor
.spawn(async move {
unsafe {
window.close();
}
})
.detach();
}
this.executor
.spawn(async move {
unsafe {
window.close();
window.autorelease();
}
})
.detach();
}
}
@@ -1592,6 +1592,7 @@ extern "C" fn close_window(this: &Object, _: Sel) {
let close_callback = {
let window_state = get_window_state(this);
let mut lock = window_state.as_ref().lock();
lock.native_window_was_closed = true;
lock.close_callback.take()
};

View File

@@ -23,7 +23,6 @@ pub(crate) struct TestPlatform {
active_display: Rc<dyn PlatformDisplay>,
active_cursor: Mutex<CursorStyle>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
current_primary_item: Mutex<Option<ClipboardItem>>,
pub(crate) prompts: RefCell<TestPrompts>,
pub opened_url: RefCell<Option<String>>,
weak: Weak<Self>,
@@ -45,7 +44,6 @@ impl TestPlatform {
active_display: Rc::new(TestDisplay::new()),
active_window: Default::default(),
current_clipboard_item: Mutex::new(None),
current_primary_item: Mutex::new(None),
weak: weak.clone(),
opened_url: Default::default(),
})
@@ -127,11 +125,8 @@ impl Platform for TestPlatform {
#[cfg(target_os = "macos")]
return Arc::new(crate::platform::mac::MacTextSystem::new());
#[cfg(target_os = "linux")]
#[cfg(not(target_os = "macos"))]
return Arc::new(crate::platform::cosmic_text::CosmicTextSystem::new());
#[cfg(target_os = "windows")]
return Arc::new(crate::platform::windows::DirectWriteTextSystem::new().unwrap());
}
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
@@ -284,18 +279,10 @@ impl Platform for TestPlatform {
false
}
fn write_to_primary(&self, item: ClipboardItem) {
*self.current_primary_item.lock() = Some(item);
}
fn write_to_clipboard(&self, item: ClipboardItem) {
*self.current_clipboard_item.lock() = Some(item);
}
fn read_from_primary(&self) -> Option<ClipboardItem> {
self.current_primary_item.lock().clone()
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.current_clipboard_item.lock().clone()
}

View File

@@ -1,11 +1,9 @@
mod direct_write;
mod dispatcher;
mod display;
mod platform;
mod util;
mod window;
pub(crate) use direct_write::*;
pub(crate) use dispatcher::*;
pub(crate) use display::*;
pub(crate) use platform::*;

File diff suppressed because it is too large Load Diff

View File

@@ -121,18 +121,21 @@ impl WindowsDisplay {
}
pub(crate) fn frequency(&self) -> Option<u32> {
get_monitor_info(self.handle).ok().and_then(|info| {
let mut devmode = DEVMODEW::default();
unsafe {
EnumDisplaySettingsW(
PCWSTR(info.szDevice.as_ptr()),
ENUM_CURRENT_SETTINGS,
&mut devmode,
)
}
.as_bool()
.then(|| devmode.dmDisplayFrequency)
})
available_monitors()
.get(self.display_id.0 as usize)
.and_then(|hmonitor| get_monitor_info(*hmonitor).ok())
.and_then(|info| {
let mut devmode = DEVMODEW::default();
unsafe {
EnumDisplaySettingsW(
PCWSTR(info.szDevice.as_ptr()),
ENUM_CURRENT_SETTINGS,
&mut devmode,
)
}
.as_bool()
.then(|| devmode.dmDisplayFrequency)
})
}
}

View File

@@ -57,7 +57,7 @@ pub(crate) struct WindowsPlatformInner {
background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
main_receiver: flume::Receiver<Runnable>,
text_system: Arc<dyn PlatformTextSystem>,
text_system: Arc<CosmicTextSystem>,
callbacks: Mutex<Callbacks>,
pub raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
pub(crate) dispatch_event: OwnedHandle,
@@ -155,13 +155,7 @@ impl WindowsPlatform {
let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, dispatch_event.to_raw()));
let background_executor = BackgroundExecutor::new(dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(dispatcher);
let text_system = if let Some(direct_write) = DirectWriteTextSystem::new().log_err() {
log::info!("Using direct write text system.");
Arc::new(direct_write) as Arc<dyn PlatformTextSystem>
} else {
log::info!("Using cosmic text system.");
Arc::new(CosmicTextSystem::new()) as Arc<dyn PlatformTextSystem>
};
let text_system = Arc::new(CosmicTextSystem::new());
let callbacks = Mutex::new(Callbacks::default());
let raw_window_handles = RwLock::new(SmallVec::new());
let settings = RefCell::new(WindowsPlatformSystemSettings::new());
@@ -692,17 +686,9 @@ impl Platform for WindowsPlatform {
false
}
fn write_to_primary(&self, _item: ClipboardItem) {}
fn write_to_clipboard(&self, item: ClipboardItem) {
if item.text.len() > 0 {
let mut ctx = ClipboardContext::new().unwrap();
ctx.set_contents(item.text().to_owned()).unwrap();
}
}
fn read_from_primary(&self) -> Option<ClipboardItem> {
None
let mut ctx = ClipboardContext::new().unwrap();
ctx.set_contents(item.text().to_owned()).unwrap();
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {

View File

@@ -52,6 +52,7 @@ pub(crate) struct WindowsWindowInner {
pub(crate) handle: AnyWindowHandle,
hide_title_bar: bool,
display: RefCell<Rc<WindowsDisplay>>,
last_ime_input: RefCell<Option<String>>,
click_state: RefCell<ClickState>,
fullscreen: Cell<Option<StyleAndBounds>>,
}
@@ -113,6 +114,7 @@ impl WindowsWindowInner {
let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
let callbacks = RefCell::new(Callbacks::default());
let display = RefCell::new(display);
let last_ime_input = RefCell::new(None);
let click_state = RefCell::new(ClickState::new());
let fullscreen = Cell::new(None);
Self {
@@ -127,6 +129,7 @@ impl WindowsWindowInner {
handle,
hide_title_bar,
display,
last_ime_input,
click_state,
fullscreen,
}
@@ -284,6 +287,7 @@ impl WindowsWindowInner {
WM_CHAR => self.handle_char_msg(msg, wparam, lparam),
WM_IME_STARTCOMPOSITION => self.handle_ime_position(),
WM_IME_COMPOSITION => self.handle_ime_composition(lparam),
WM_IME_CHAR => self.handle_ime_char(wparam),
WM_SETCURSOR => self.handle_set_cursor(lparam),
_ => None,
};
@@ -781,6 +785,7 @@ impl WindowsWindowInner {
let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
let result = if string_len >= 0 {
let mut buffer = vec![0u8; string_len as usize + 2];
// let mut buffer = [0u8; MAX_PATH as _];
ImmGetCompositionStringW(
ctx,
GCS_COMPSTR,
@@ -810,34 +815,7 @@ impl WindowsWindowInner {
}
}
fn parse_ime_compostion_result(&self) -> Option<String> {
unsafe {
let ctx = ImmGetContext(self.hwnd);
let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
let result = if string_len >= 0 {
let mut buffer = vec![0u8; string_len as usize + 2];
ImmGetCompositionStringW(
ctx,
GCS_RESULTSTR,
Some(buffer.as_mut_ptr() as _),
string_len as _,
);
let wstring = std::slice::from_raw_parts::<u16>(
buffer.as_mut_ptr().cast::<u16>(),
string_len as usize / 2,
);
let string = String::from_utf16_lossy(wstring);
Some(string)
} else {
None
};
ImmReleaseContext(self.hwnd, ctx);
result
}
}
fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
let mut ime_input = None;
if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
let Some((string, string_len)) = self.parse_ime_compostion_string() else {
return None;
@@ -851,10 +829,10 @@ impl WindowsWindowInner {
Some(0..string_len),
);
self.input_handler.set(Some(input_handler));
ime_input = Some(string);
*self.last_ime_input.borrow_mut() = Some(string);
}
if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
let Some(ref comp_string) = ime_input else {
let Some(ref comp_string) = *self.last_ime_input.borrow() else {
return None;
};
let caret_pos = self.retrieve_composition_cursor_position();
@@ -864,22 +842,32 @@ impl WindowsWindowInner {
input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
self.input_handler.set(Some(input_handler));
}
if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
let Some(comp_result) = self.parse_ime_compostion_result() else {
return None;
};
let Some(mut input_handler) = self.input_handler.take() else {
return Some(1);
};
input_handler.replace_text_in_range(None, &comp_result);
self.input_handler.set(Some(input_handler));
self.invalidate_client_area();
return Some(0);
}
// currently, we don't care other stuff
None
}
fn parse_ime_char(&self, wparam: WPARAM) -> Option<String> {
let src = [wparam.0 as u16];
let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
return None;
};
Some(first_char.to_string())
}
fn handle_ime_char(&self, wparam: WPARAM) -> Option<isize> {
let Some(ime_char) = self.parse_ime_char(wparam) else {
return Some(1);
};
let Some(mut input_handler) = self.input_handler.take() else {
return Some(1);
};
input_handler.replace_text_in_range(None, &ime_char);
self.input_handler.set(Some(input_handler));
*self.last_ime_input.borrow_mut() = None;
self.invalidate_client_area();
Some(0)
}
fn handle_drag_drop(&self, input: PlatformInput) {
let mut callbacks = self.callbacks.borrow_mut();
let Some(ref mut func) = callbacks.input else {

View File

@@ -26,29 +26,6 @@ macro_rules! create_definitions {
}
}
)*
/// Get the tag name list of the font OpenType features
/// only enabled or disabled features are returned
#[cfg(target_os = "windows")]
pub fn tag_value_list(&self) -> Vec<(String, bool)> {
let mut result = Vec::new();
$(
{
let value = if (self.enabled & (1 << $idx)) != 0 {
Some(true)
} else if (self.disabled & (1 << $idx)) != 0 {
Some(false)
} else {
None
};
if let Some(enable) = value {
let tag_name = stringify!($name).to_owned();
result.push((tag_name, enable));
}
}
)*
result
}
}
impl std::fmt::Debug for FontFeatures {
@@ -117,11 +94,9 @@ macro_rules! create_definitions {
let mut map = serializer.serialize_map(None)?;
$(
{
let feature = stringify!($name);
if let Some(value) = self.$name() {
map.serialize_entry(feature, &value)?;
}
let feature = stringify!($name);
if let Some(value) = self.$name() {
map.serialize_entry(feature, &value)?;
}
)*
@@ -186,5 +161,5 @@ create_definitions!(
(swsh, 30),
(titl, 31),
(tnum, 32),
(zero, 33),
(zero, 33)
);

View File

@@ -1,8 +1,8 @@
use crate::{
seal::Sealed, AfterLayoutIndex, AnyElement, AnyModel, AnyWeakModel, AppContext,
BeforePaintIndex, Bounds, ContentMask, Element, ElementContext, ElementId, Entity, EntityId,
Flatten, FocusHandle, FocusableView, IntoElement, LayoutId, Model, PaintIndex, Pixels, Render,
Style, StyleRefinement, TextStyle, ViewContext, VisualContext, WeakModel,
seal::Sealed, AfterLayoutIndex, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds,
ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
FocusableView, IntoElement, LayoutId, Model, PaintIndex, Pixels, Render, Style,
StyleRefinement, TextStyle, ViewContext, VisualContext, WeakModel,
};
use anyhow::{Context, Result};
use refineable::Refineable;
@@ -24,16 +24,13 @@ impl<V> Sealed for View<V> {}
struct AnyViewState {
after_layout_range: Range<AfterLayoutIndex>,
before_paint_range: Range<BeforePaintIndex>,
paint_range: Range<PaintIndex>,
focus_target_bounds: Option<Bounds<Pixels>>,
cache_key: ViewCacheKey,
}
#[derive(Default)]
struct ViewCacheKey {
after_layout_bounds: Bounds<Pixels>,
before_paint_bounds: Bounds<Pixels>,
bounds: Bounds<Pixels>,
content_mask: ContentMask<Pixels>,
text_style: TextStyle,
}
@@ -95,7 +92,6 @@ impl<V: 'static> View<V> {
impl<V: Render> Element for View<V> {
type BeforeLayout = AnyElement;
type AfterLayout = ();
type BeforePaint = ();
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
@@ -106,24 +102,14 @@ impl<V: Render> Element for View<V> {
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
(element.after_layout(cx), ())
}
fn before_paint(
&mut self,
_: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
cx.set_view_id(self.entity_id());
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
element.before_paint(cx)
element.after_layout(cx)
})
}
@@ -132,7 +118,6 @@ impl<V: Render> Element for View<V> {
_: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
_: &mut Self::BeforePaint,
cx: &mut ElementContext,
) {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
@@ -292,8 +277,7 @@ impl<V: Render> From<View<V>> for AnyView {
impl Element for AnyView {
type BeforeLayout = Option<AnyElement>;
type AfterLayout = ();
type BeforePaint = ();
type AfterLayout = Option<AnyElement>;
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
if let Some(style) = self.cached_style.as_ref() {
@@ -315,16 +299,20 @@ impl Element for AnyView {
bounds: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (Option<Bounds<Pixels>>, Self::AfterLayout) {
) -> Option<AnyElement> {
cx.set_view_id(self.entity_id());
if self.cached_style.is_some() {
let focus_target_bounds = cx.with_element_state::<AnyViewState, _>(
cx.with_element_state::<AnyViewState, _>(
Some(ElementId::View(self.entity_id())),
|element_state, cx| {
let mut element_state = element_state.unwrap();
let content_mask = cx.content_mask();
let text_style = cx.text_style();
if let Some(mut element_state) = element_state {
if element_state.cache_key.after_layout_bounds == bounds
if element_state.cache_key.bounds == bounds
&& element_state.cache_key.content_mask == content_mask
&& element_state.cache_key.text_style == text_style
&& !cx.window.dirty_views.contains(&self.entity_id())
&& !cx.window.refreshing
@@ -333,106 +321,34 @@ impl Element for AnyView {
cx.reuse_after_layout(element_state.after_layout_range.clone());
let after_layout_end = cx.after_layout_index();
element_state.after_layout_range = after_layout_start..after_layout_end;
return (element_state.focus_target_bounds, Some(element_state));
return (None, Some(element_state));
}
}
let after_layout_start = cx.after_layout_index();
let mut rendered_element = (self.render)(self, cx);
let element_measurement = rendered_element.layout(bounds.size.into(), cx);
*element = Some(rendered_element);
let focus_target_bounds =
element_measurement
.focus_target_bounds
.map(|focus_target_bounds| {
Bounds::new(
bounds.origin + focus_target_bounds.origin,
focus_target_bounds.size,
)
});
let mut element = (self.render)(self, cx);
element.layout(bounds.origin, bounds.size.into(), cx);
let after_layout_end = cx.after_layout_index();
let view_state = AnyViewState {
after_layout_range: after_layout_start..after_layout_end,
before_paint_range: BeforePaintIndex::default()
..BeforePaintIndex::default(),
paint_range: PaintIndex::default()..PaintIndex::default(),
focus_target_bounds,
cache_key: ViewCacheKey {
after_layout_bounds: bounds,
text_style,
..Default::default()
},
};
(focus_target_bounds, Some(view_state))
},
);
(focus_target_bounds, ())
} else {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
let bounds = element.as_mut().unwrap().after_layout(cx);
(bounds, ())
})
}
}
fn before_paint(
&mut self,
bounds: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
cx.set_view_id(self.entity_id());
if self.cached_style.is_some() {
cx.with_element_state::<AnyViewState, _>(
Some(ElementId::View(self.entity_id())),
|element_state, cx| {
let mut element_state = element_state.unwrap().unwrap();
let content_mask = cx.content_mask();
if let Some(element) = element {
let before_paint_start = cx.before_paint_index();
cx.with_absolute_element_offset(bounds.origin, |cx| {
element.before_paint(cx)
});
let before_paint_end = cx.before_paint_index();
element_state.before_paint_range = before_paint_start..before_paint_end;
element_state.cache_key.before_paint_bounds = bounds;
element_state.cache_key.content_mask = content_mask;
} else if element_state.cache_key.before_paint_bounds == bounds
&& element_state.cache_key.content_mask == content_mask
{
let before_paint_start = cx.before_paint_index();
cx.reuse_before_paint(element_state.before_paint_range.clone());
let before_paint_end = cx.before_paint_index();
element_state.before_paint_range = before_paint_start..before_paint_end;
} else {
let mut rendered_element = (self.render)(self, cx);
let after_layout_start = cx.after_layout_index();
rendered_element.layout(bounds.size.into(), cx);
let after_layout_end = cx.after_layout_index();
let before_paint_start = cx.before_paint_index();
cx.with_absolute_element_offset(bounds.origin, |cx| {
rendered_element.before_paint(cx)
});
let before_paint_end = cx.before_paint_index();
element_state.after_layout_range = after_layout_start..after_layout_end;
element_state.before_paint_range = before_paint_start..before_paint_end;
element_state.cache_key.before_paint_bounds = bounds;
element_state.cache_key.content_mask = content_mask;
*element = Some(rendered_element);
}
((), Some(element_state))
(
Some(element),
Some(AnyViewState {
after_layout_range: after_layout_start..after_layout_end,
paint_range: PaintIndex::default()..PaintIndex::default(),
cache_key: ViewCacheKey {
bounds,
content_mask,
text_style,
},
}),
)
},
)
} else {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
element.as_mut().unwrap().before_paint(cx)
let mut element = element.take().unwrap();
element.after_layout(cx);
Some(element)
})
}
}
@@ -440,9 +356,8 @@ impl Element for AnyView {
fn paint(
&mut self,
_bounds: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
_: &mut Self::BeforePaint,
_: &mut Self::BeforeLayout,
element: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
if self.cached_style.is_some() {

View File

@@ -66,13 +66,11 @@ impl HitboxId {
/// See [ElementContext::insert_hitbox] for more details.
#[derive(Clone, Debug, Deref)]
pub struct Hitbox {
/// A unique identifier for the hitbox.
/// A unique identifier for the hitbox
pub id: HitboxId,
/// The bounds of the hitbox.
/// The bounds of the hitbox
#[deref]
pub bounds: Bounds<Pixels>,
/// The content mask when the hitbox was inserted.
pub content_mask: ContentMask<Pixels>,
/// Whether the hitbox occludes other hitboxes inserted prior.
pub opaque: bool,
}
@@ -121,7 +119,7 @@ pub(crate) struct DeferredDraw {
text_style_stack: Vec<TextStyleRefinement>,
element: Option<AnyElement>,
absolute_offset: Point<Pixels>,
before_paint_range: Range<BeforePaintIndex>,
layout_range: Range<AfterLayoutIndex>,
paint_range: Range<PaintIndex>,
}
@@ -146,12 +144,6 @@ pub(crate) struct Frame {
#[derive(Clone, Default)]
pub(crate) struct AfterLayoutIndex {
accessed_element_states_index: usize,
line_layout_index: LineLayoutIndex,
}
#[derive(Clone, Default)]
pub(crate) struct BeforePaintIndex {
hitboxes_index: usize,
tooltips_index: usize,
deferred_draws_index: usize,
@@ -209,8 +201,7 @@ impl Frame {
pub(crate) fn hit_test(&self, position: Point<Pixels>) -> HitTest {
let mut hit_test = HitTest::default();
for hitbox in self.hitboxes.iter().rev() {
let bounds = hitbox.bounds.intersect(&hitbox.content_mask.bounds);
if bounds.contains(&position) {
if hitbox.bounds.contains(&position) {
hit_test.0.push(hitbox.id);
if hitbox.opaque {
break;
@@ -405,8 +396,7 @@ impl<'a> ElementContext<'a> {
// Layout all root elements.
let mut root_element = self.window.root_view.as_ref().unwrap().clone().into_any();
root_element.layout(self.window.viewport_size.into(), self);
root_element.before_paint(self);
root_element.layout(Point::default(), self.window.viewport_size.into(), self);
let mut sorted_deferred_draws =
(0..self.window.next_frame.deferred_draws.len()).collect::<SmallVec<[_; 8]>>();
@@ -418,15 +408,13 @@ impl<'a> ElementContext<'a> {
let mut tooltip_element = None;
if let Some(prompt) = self.window.prompt.take() {
let mut element = prompt.view.any_view().into_any();
element.layout(self.window.viewport_size.into(), self);
element.before_paint(self);
element.layout(Point::default(), self.window.viewport_size.into(), self);
prompt_element = Some(element);
self.window.prompt = Some(prompt);
} else if let Some(active_drag) = self.app.active_drag.take() {
let mut element = active_drag.view.clone().into_any();
element.layout(AvailableSpace::min_size(), self);
let offset = self.mouse_position() - active_drag.cursor_offset;
self.with_element_offset(offset, |cx| element.before_paint(cx));
element.layout(offset, AvailableSpace::min_size(), self);
active_drag_element = Some(element);
self.app.active_drag = Some(active_drag);
} else {
@@ -455,7 +443,7 @@ impl<'a> ElementContext<'a> {
let tooltip_request = tooltip_request.unwrap();
let mut element = tooltip_request.tooltip.view.clone().into_any();
let mouse_position = tooltip_request.tooltip.mouse_position;
let tooltip_size = element.layout(AvailableSpace::min_size(), self).size;
let tooltip_size = element.measure(AvailableSpace::min_size(), self);
let mut tooltip_bounds = Bounds::new(mouse_position + point(px(1.), px(1.)), tooltip_size);
let window_bounds = Bounds {
@@ -487,7 +475,7 @@ impl<'a> ElementContext<'a> {
}
}
self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.before_paint(cx));
self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.after_layout(cx));
self.window.tooltip_bounds = Some(TooltipBounds {
id: tooltip_request.id,
@@ -509,16 +497,16 @@ impl<'a> ElementContext<'a> {
.dispatch_tree
.set_active_node(deferred_draw.parent_node);
let layout_start = self.before_paint_index();
let layout_start = self.after_layout_index();
if let Some(element) = deferred_draw.element.as_mut() {
self.with_absolute_element_offset(deferred_draw.absolute_offset, |cx| {
element.before_paint(cx)
element.after_layout(cx)
});
} else {
self.reuse_before_paint(deferred_draw.before_paint_range.clone());
self.reuse_after_layout(deferred_draw.layout_range.clone());
}
let layout_end = self.before_paint_index();
deferred_draw.before_paint_range = layout_start..layout_end;
let layout_end = self.after_layout_index();
deferred_draw.layout_range = layout_start..layout_end;
}
assert_eq!(
self.window.next_frame.deferred_draws.len(),
@@ -557,26 +545,6 @@ impl<'a> ElementContext<'a> {
pub(crate) fn after_layout_index(&self) -> AfterLayoutIndex {
AfterLayoutIndex {
accessed_element_states_index: self.window.next_frame.accessed_element_states.len(),
line_layout_index: self.window.text_system.layout_index(),
}
}
pub(crate) fn reuse_after_layout(&mut self, range: Range<AfterLayoutIndex>) {
let window = &mut self.window;
window.next_frame.accessed_element_states.extend(
window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index
..range.end.accessed_element_states_index]
.iter()
.cloned(),
);
window
.text_system
.reuse_layouts(range.start.line_layout_index..range.end.line_layout_index);
}
pub(crate) fn before_paint_index(&self) -> BeforePaintIndex {
BeforePaintIndex {
hitboxes_index: self.window.next_frame.hitboxes.len(),
tooltips_index: self.window.next_frame.tooltip_requests.len(),
deferred_draws_index: self.window.next_frame.deferred_draws.len(),
@@ -586,7 +554,7 @@ impl<'a> ElementContext<'a> {
}
}
pub(crate) fn reuse_before_paint(&mut self, range: Range<BeforePaintIndex>) {
pub(crate) fn reuse_after_layout(&mut self, range: Range<AfterLayoutIndex>) {
let window = &mut self.window;
window.next_frame.hitboxes.extend(
window.rendered_frame.hitboxes[range.start.hitboxes_index..range.end.hitboxes_index]
@@ -624,7 +592,7 @@ impl<'a> ElementContext<'a> {
priority: deferred_draw.priority,
element: None,
absolute_offset: deferred_draw.absolute_offset,
before_paint_range: deferred_draw.before_paint_range.clone(),
layout_range: deferred_draw.layout_range.clone(),
paint_range: deferred_draw.paint_range.clone(),
}),
);
@@ -1003,7 +971,7 @@ impl<'a> ElementContext<'a> {
assert_eq!(
window.draw_phase,
DrawPhase::Layout,
"defer_draw can only be called during before_layout or before_paint"
"defer_draw can only be called during before_layout or after_layout"
);
let parent_node = window.next_frame.dispatch_tree.active_node_id().unwrap();
window.next_frame.deferred_draws.push(DeferredDraw {
@@ -1013,7 +981,7 @@ impl<'a> ElementContext<'a> {
priority,
element: Some(element),
absolute_offset,
before_paint_range: BeforePaintIndex::default()..BeforePaintIndex::default(),
layout_range: AfterLayoutIndex::default()..AfterLayoutIndex::default(),
paint_range: PaintIndex::default()..PaintIndex::default(),
});
}
@@ -1426,7 +1394,7 @@ impl<'a> ElementContext<'a> {
bounds
}
/// This method should be called during `before_paint`. You can use
/// This method should be called during `after_layout`. You can use
/// the returned [Hitbox] during `paint` or in an event handler
/// to determine whether the inserted hitbox was the topmost.
pub fn insert_hitbox(&mut self, bounds: Bounds<Pixels>, opaque: bool) -> Hitbox {
@@ -1436,8 +1404,7 @@ impl<'a> ElementContext<'a> {
window.next_hitbox_id.0 += 1;
let hitbox = Hitbox {
id,
bounds,
content_mask,
bounds: bounds.intersect(&content_mask.bounds),
opaque,
};
window.next_frame.hitboxes.push(hitbox.clone());

View File

@@ -155,7 +155,7 @@ impl FocusableView for ImageView {
impl Render for ImageView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let checkered_background = |bounds: Bounds<Pixels>, cx: &mut ElementContext| {
let checkered_background = |bounds: Bounds<Pixels>, _, cx: &mut ElementContext| {
let square_size = 32.0;
let start_y = bounds.origin.y.0;
@@ -190,7 +190,7 @@ impl Render for ImageView {
}
};
let checkered_background = canvas(checkered_background)
let checkered_background = canvas(|_, _| (), checkered_background)
.border_2()
.border_color(cx.theme().styles.colors.border)
.size_full()

View File

@@ -56,7 +56,7 @@ use std::{
},
};
use syntax_map::SyntaxSnapshot;
pub use task_context::{BasicContextProvider, ContextProvider, ContextProviderWithTasks};
pub use task_context::{ContextProvider, ContextProviderWithTasks, SymbolContextProvider};
use theme::SyntaxTheme;
use tree_sitter::{self, wasmtime, Query, WasmStore};
use util::http::HttpClient;

View File

@@ -5,10 +5,10 @@ use std::{ops::Range, path::PathBuf};
use crate::{HighlightId, Language, LanguageRegistry};
use gpui::{px, FontStyle, FontWeight, HighlightStyle, StrikethroughStyle, UnderlineStyle};
use pulldown_cmark::{CodeBlockKind, Event, Parser, Tag, TagEnd};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
/// Parsed Markdown content.
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct ParsedMarkdown {
/// The Markdown text.
pub text: String,
@@ -165,10 +165,7 @@ pub async fn parse_markdown_block(
let mut current_language = None;
let mut list_stack = Vec::new();
let mut options = pulldown_cmark::Options::all();
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
for event in Parser::new_ext(markdown, options) {
for event in Parser::new_ext(markdown, Options::all()) {
let prev_len = text.len();
match event {
Event::Text(t) => {
@@ -252,7 +249,7 @@ pub async fn parse_markdown_block(
new_paragraph(text, &mut list_stack);
current_language = if let CodeBlockKind::Fenced(language) = kind {
language_registry
.language_for_name_or_extension(language.as_ref())
.language_for_name(language.as_ref())
.await
.ok()
} else {
@@ -360,35 +357,3 @@ pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option<u64>, bool)
text.push_str(" ");
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_dividers() {
let input = r#"
### instance-method `format`
---
→ `void`
Parameters:
- `const int &`
- `const std::tm &`
- `int & dest`
---
```cpp
// In my_formatter_flag
public: void format(const int &, const std::tm &, int &dest)
```
"#;
let mut options = pulldown_cmark::Options::all();
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
let parser = pulldown_cmark::Parser::new_ext(input, options);
for event in parser.into_iter() {
println!("{:?}", event);
}
}
}

View File

@@ -1,56 +1,34 @@
use std::path::Path;
use crate::Location;
use anyhow::Result;
use gpui::AppContext;
use task::{TaskTemplates, TaskVariables, VariableName};
use text::{Point, ToPoint};
/// Language Contexts are used by Zed tasks to extract information about the source file where the tasks are supposed to be scheduled from.
/// Multiple context providers may be used together: by default, Zed provides a base [`BasicContextProvider`] context that fills all non-custom [`VariableName`] variants.
///
/// The context will be used to fill data for the tasks, and filter out the ones that do not have the variables required.
/// Language Contexts are used by Zed tasks to extract information about source file.
pub trait ContextProvider: Send + Sync {
/// Builds a specific context to be placed on top of the basic one (replacing all conflicting entries) and to be used for task resolving later.
fn build_context(
&self,
_worktree_abs_path: Option<&Path>,
_location: &Location,
_cx: &mut AppContext,
) -> Result<TaskVariables> {
fn build_context(&self, _: Location, _: &mut AppContext) -> Result<TaskVariables> {
Ok(TaskVariables::default())
}
/// Provides all tasks, associated with the current language.
fn associated_tasks(&self) -> Option<TaskTemplates> {
None
}
// Determines whether the [`BasicContextProvider`] variables should be filled too (if `false`), or omitted (if `true`).
fn is_basic(&self) -> bool {
false
}
}
/// A context provided that tries to provide values for all non-custom [`VariableName`] variants for a currently opened file.
/// Applied as a base for every custom [`ContextProvider`] unless explicitly oped out.
pub struct BasicContextProvider;
impl ContextProvider for BasicContextProvider {
fn is_basic(&self) -> bool {
true
}
/// A context provider that finds out what symbol is currently focused in the buffer.
pub struct SymbolContextProvider;
impl ContextProvider for SymbolContextProvider {
fn build_context(
&self,
worktree_abs_path: Option<&Path>,
location: &Location,
location: Location,
cx: &mut AppContext,
) -> Result<TaskVariables> {
let buffer = location.buffer.read(cx);
let buffer_snapshot = buffer.snapshot();
let symbols = buffer_snapshot.symbols_containing(location.range.start, None);
) -> gpui::Result<TaskVariables> {
let symbols = location
.buffer
.read(cx)
.snapshot()
.symbols_containing(location.range.start, None);
let symbol = symbols.unwrap_or_default().last().map(|symbol| {
let range = symbol
.name_ranges
@@ -59,40 +37,9 @@ impl ContextProvider for BasicContextProvider {
.unwrap_or(0..symbol.text.len());
symbol.text[range].to_string()
});
let current_file = buffer
.file()
.and_then(|file| file.as_local())
.map(|file| file.abs_path(cx).to_string_lossy().to_string());
let Point { row, column } = location.range.start.to_point(&buffer_snapshot);
let row = row + 1;
let column = column + 1;
let selected_text = buffer
.chars_for_range(location.range.clone())
.collect::<String>();
let mut task_variables = TaskVariables::from_iter([
(VariableName::Row, row.to_string()),
(VariableName::Column, column.to_string()),
]);
if let Some(symbol) = symbol {
task_variables.insert(VariableName::Symbol, symbol);
}
if !selected_text.trim().is_empty() {
task_variables.insert(VariableName::SelectedText, selected_text);
}
if let Some(path) = current_file {
task_variables.insert(VariableName::File, path);
}
if let Some(worktree_path) = worktree_abs_path {
task_variables.insert(
VariableName::WorktreeRoot,
worktree_path.to_string_lossy().to_string(),
);
}
Ok(task_variables)
Ok(TaskVariables::from_iter(
Some(VariableName::Symbol).zip(symbol),
))
}
}
@@ -114,12 +61,7 @@ impl ContextProvider for ContextProviderWithTasks {
Some(self.templates.clone())
}
fn build_context(
&self,
worktree_abs_path: Option<&Path>,
location: &Location,
cx: &mut AppContext,
) -> Result<TaskVariables> {
BasicContextProvider.build_context(worktree_abs_path, location, cx)
fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<TaskVariables> {
SymbolContextProvider.build_context(location, cx)
}
}

View File

@@ -363,15 +363,14 @@ impl Render for SyntaxTreeView {
.text_bg(cx.theme().colors().background).into_any_element();
rendered = rendered.child(
// canvas(
// move |bounds, cx| {
// list.layout(bounds.origin, bounds.size.into(), cx);
// list
// },
// |_, mut list, cx| list.paint(cx),
// )
// .size_full(),
todo!("replace canvas"),
canvas(
move |bounds, cx| {
list.layout(bounds.origin, bounds.size.into(), cx);
list
},
|_, mut list, cx| list.paint(cx),
)
.size_full(),
);
}

View File

@@ -22,6 +22,7 @@ lazy_static.workspace = true
log.workspace = true
lsp.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
project.workspace = true
regex.workspace = true
rope.workspace = true
@@ -48,12 +49,14 @@ tree-sitter-heex.workspace = true
tree-sitter-jsdoc.workspace = true
tree-sitter-json.workspace = true
tree-sitter-markdown.workspace = true
tree-sitter-nu.workspace = true
tree-sitter-proto.workspace = true
tree-sitter-python.workspace = true
tree-sitter-regex.workspace = true
tree-sitter-ruby.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-typescript.workspace = true
tree-sitter-vue.workspace = true
tree-sitter-yaml.workspace = true
tree-sitter.workspace = true
util.workspace = true

View File

@@ -1,18 +0,0 @@
use language::ContextProviderWithTasks;
use task::{TaskTemplate, TaskTemplates, VariableName};
pub(super) fn bash_task_context() -> ContextProviderWithTasks {
ContextProviderWithTasks::new(TaskTemplates(vec![
TaskTemplate {
label: "execute selection".to_owned(),
command: VariableName::SelectedText.template_value(),
ignore_previously_resolved: true,
..TaskTemplate::default()
},
TaskTemplate {
label: format!("run '{}'", VariableName::File.template_value()),
command: VariableName::File.template_value(),
..TaskTemplate::default()
},
]))
}

View File

@@ -4,8 +4,6 @@ use futures::StreamExt;
use gpui::AsyncAppContext;
pub use language::*;
use lsp::LanguageServerBinary;
use project::project_settings::{BinarySettings, ProjectSettings};
use settings::Settings;
use smol::fs::{self, File};
use std::{any::Any, env::consts, path::PathBuf, sync::Arc};
use util::{
@@ -16,51 +14,10 @@ use util::{
pub struct CLspAdapter;
impl CLspAdapter {
const SERVER_NAME: &'static str = "clangd";
}
#[async_trait(?Send)]
impl super::LspAdapter for CLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName(Self::SERVER_NAME.into())
}
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
cx: &AsyncAppContext,
) -> Option<LanguageServerBinary> {
let configured_binary = cx.update(|cx| {
ProjectSettings::get_global(cx)
.lsp
.get(Self::SERVER_NAME)
.and_then(|s| s.binary.clone())
});
if let Ok(Some(BinarySettings {
path: Some(path),
arguments,
})) = configured_binary
{
Some(LanguageServerBinary {
path: path.into(),
arguments: arguments
.unwrap_or_default()
.iter()
.map(|arg| arg.into())
.collect(),
env: None,
})
} else {
let env = delegate.shell_env().await;
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
Some(LanguageServerBinary {
path,
arguments: vec![],
env: Some(env),
})
}
LanguageServerName("clangd".into())
}
async fn fetch_latest_server_version(
@@ -88,6 +45,20 @@ impl super::LspAdapter for CLspAdapter {
Ok(Box::new(version) as Box<_>)
}
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: &AsyncAppContext,
) -> Option<LanguageServerBinary> {
let env = delegate.shell_env().await;
let path = delegate.which("clangd".as_ref()).await?;
Some(LanguageServerBinary {
path,
arguments: vec![],
env: Some(env),
})
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,

View File

@@ -88,7 +88,7 @@ impl super::LspAdapter for GoLspAdapter {
})
} else {
let env = delegate.shell_env().await;
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
let path = delegate.which("gopls".as_ref()).await?;
Some(LanguageServerBinary {
path,
arguments: server_binary_arguments(),

View File

@@ -8,25 +8,23 @@ use smol::stream::StreamExt;
use std::{str, sync::Arc};
use util::{asset_str, ResultExt};
use crate::{
bash::bash_task_context, elixir::elixir_task_context, python::python_task_context,
rust::RustContextProvider,
};
use crate::{elixir::elixir_task_context, rust::RustContextProvider};
use self::{deno::DenoSettings, elixir::ElixirSettings};
mod bash;
mod c;
mod css;
mod deno;
mod elixir;
mod go;
mod json;
mod nu;
mod python;
mod ruby;
mod rust;
mod tailwind;
mod typescript;
mod vue;
mod yaml;
// 1. Add tree-sitter-{language} parser to zed crate
@@ -68,6 +66,7 @@ pub fn init(
("jsdoc", tree_sitter_jsdoc::language()),
("json", tree_sitter_json::language()),
("markdown", tree_sitter_markdown::language()),
("nu", tree_sitter_nu::language()),
("proto", tree_sitter_proto::language()),
("python", tree_sitter_python::language()),
("regex", tree_sitter_regex::language()),
@@ -75,6 +74,7 @@ pub fn init(
("rust", tree_sitter_rust::language()),
("tsx", tree_sitter_typescript::language_tsx()),
("typescript", tree_sitter_typescript::language_typescript()),
("vue", tree_sitter_vue::language()),
("yaml", tree_sitter_yaml::language()),
]);
@@ -85,7 +85,13 @@ pub fn init(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || Ok((config.clone(), load_queries($name), None)),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new(language::SymbolContextProvider)),
))
},
);
};
($name:literal, $adapters:expr) => {
@@ -99,7 +105,13 @@ pub fn init(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || Ok((config.clone(), load_queries($name), None)),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new(language::SymbolContextProvider)),
))
},
);
};
($name:literal, $adapters:expr, $context_provider:expr) => {
@@ -123,7 +135,7 @@ pub fn init(
);
};
}
language!("bash", Vec::new(), bash_task_context());
language!("bash");
language!("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
language!("cpp", vec![Arc::new(c::CLspAdapter)]);
language!(
@@ -185,8 +197,7 @@ pub fn init(
"python",
vec![Arc::new(python::PythonLspAdapter::new(
node_runtime.clone(),
))],
python_task_context()
))]
);
language!(
"rust",
@@ -258,6 +269,14 @@ pub fn init(
"yaml",
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))]
);
language!("nu", vec![Arc::new(nu::NuLanguageServer {})]);
language!(
"vue",
vec![
Arc::new(vue::VueLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
]
);
language!("proto");
languages.register_secondary_lsp_adapter(
@@ -276,10 +295,6 @@ pub fn init(
"Svelte".into(),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
);
languages.register_secondary_lsp_adapter(
"Vue.js".into(),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
);
let mut subscription = languages.subscribe();
let mut prev_language_settings = languages.language_settings();

View File

@@ -0,0 +1,52 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use std::{any::Any, path::PathBuf};
pub struct NuLanguageServer;
#[async_trait(?Send)]
impl LspAdapter for NuLanguageServer {
fn name(&self) -> LanguageServerName {
LanguageServerName("nu".into())
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Any + Send>> {
Ok(Box::new(()))
}
async fn fetch_server_binary(
&self,
_version: Box<dyn 'static + Send + Any>,
_container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
Err(anyhow!(
"nu v0.87.0 or greater must be installed and available in your $PATH"
))
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
Some(LanguageServerBinary {
path: "nu".into(),
env: None,
arguments: vec!["--lsp".into()],
})
}
fn can_be_reinstalled(&self) -> bool {
false
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
None
}
}

View File

@@ -0,0 +1,4 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
(parameter_pipes "|" @open "|" @close)

View File

@@ -0,0 +1,10 @@
name = "Nu"
grammar = "nu"
path_suffixes = ["nu"]
line_comments = ["# "]
autoclose_before = ";:.,=}])>` \n\t\""
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
]

View File

@@ -0,0 +1,284 @@
;;; ---
;;; keywords
[
"def"
"alias"
"export-env"
"export"
"extern"
"module"
"let"
"let-env"
"mut"
"const"
"hide-env"
"source"
"source-env"
"overlay"
"register"
"loop"
"while"
"error"
"do"
"if"
"else"
"try"
"catch"
"match"
"break"
"continue"
"return"
] @keyword
(hide_mod "hide" @keyword)
(decl_use "use" @keyword)
(ctrl_for
"for" @keyword
"in" @keyword
)
(overlay_list "list" @keyword.storage.modifier)
(overlay_hide "hide" @keyword.storage.modifier)
(overlay_new "new" @keyword.storage.modifier)
(overlay_use
"use" @keyword.storage.modifier
"as" @keyword
)
(ctrl_error "make" @keyword.storage.modifier)
;;; ---
;;; literals
(val_number) @constant.numeric
(val_duration
unit: [
"ns" "µs" "us" "ms" "sec" "min" "hr" "day" "wk"
] @variable.parameter
)
(val_filesize
unit: [
"b" "B"
"kb" "kB" "Kb" "KB"
"mb" "mB" "Mb" "MB"
"gb" "gB" "Gb" "GB"
"tb" "tB" "Tb" "TB"
"pb" "pB" "Pb" "PB"
"eb" "eB" "Eb" "EB"
"kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB"
"mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB"
"gib" "giB" "gIB" "gIb" "Gib" "GIb" "GIB"
"tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB"
"pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB"
"eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB"
] @variable.parameter
)
(val_binary
[
"0b"
"0o"
"0x"
] @constant.numeric
"[" @punctuation.bracket
digit: [
"," @punctuation.delimiter
(hex_digit) @constant.number
]
"]" @punctuation.bracket
) @constant.numeric
(val_bool) @constant.builtin
(val_nothing) @constant.builtin
(val_string) @string
(val_date) @constant.number
(inter_escape_sequence) @constant.character.escape
(escape_sequence) @constant.character.escape
(val_interpolated [
"$\""
"$\'"
"\""
"\'"
] @string)
(unescaped_interpolated_content) @string
(escaped_interpolated_content) @string
(expr_interpolated ["(" ")"] @variable.parameter)
;;; ---
;;; operators
(expr_binary [
"+"
"-"
"*"
"/"
"mod"
"//"
"++"
"**"
"=="
"!="
"<"
"<="
">"
">="
"=~"
"!~"
"and"
"or"
"xor"
"bit-or"
"bit-xor"
"bit-and"
"bit-shl"
"bit-shr"
"in"
"not-in"
"starts-with"
"ends-with"
] @operator )
(where_command [
"+"
"-"
"*"
"/"
"mod"
"//"
"++"
"**"
"=="
"!="
"<"
"<="
">"
">="
"=~"
"!~"
"and"
"or"
"xor"
"bit-or"
"bit-xor"
"bit-and"
"bit-shl"
"bit-shr"
"in"
"not-in"
"starts-with"
"ends-with"
] @operator)
(assignment [
"="
"+="
"-="
"*="
"/="
"++="
] @operator)
(expr_unary ["not" "-"] @operator)
(val_range [
".."
"..="
"..<"
] @operator)
["=>" "=" "|"] @operator
[
"o>" "out>"
"e>" "err>"
"e+o>" "err+out>"
"o+e>" "out+err>"
] @special
;;; ---
;;; punctuation
[
","
";"
] @punctuation.delimiter
(param_short_flag "-" @punctuation.delimiter)
(param_long_flag ["--"] @punctuation.delimiter)
(long_flag ["--"] @punctuation.delimiter)
(param_rest "..." @punctuation.delimiter)
(param_type [":"] @punctuation.special)
(param_value ["="] @punctuation.special)
(param_cmd ["@"] @punctuation.special)
(param_opt ["?"] @punctuation.special)
[
"(" ")"
"{" "}"
"[" "]"
] @punctuation.bracket
(val_record
(record_entry ":" @punctuation.delimiter))
;;; ---
;;; identifiers
(param_rest
name: (_) @variable.parameter)
(param_opt
name: (_) @variable.parameter)
(parameter
param_name: (_) @variable.parameter)
(param_cmd
(cmd_identifier) @string)
(param_long_flag) @variable.parameter
(param_short_flag) @variable.parameter
(short_flag) @variable.parameter
(long_flag) @variable.parameter
(scope_pattern [(wild_card) @function])
(cmd_identifier) @function
(command
"^" @punctuation.delimiter
head: (_) @function
)
"where" @function
(path
["." "?"] @punctuation.delimiter
) @variable.parameter
(val_variable
"$" @variable.parameter
[
(identifier) @namespace
"in"
"nu"
"env"
"nothing"
] @special
)
;;; ---
;;; types
(flat_type) @type.builtin
(list_type
"list" @type.enum
["<" ">"] @punctuation.bracket
)
(collection_type
["record" "table"] @type.enum
"<" @punctuation.bracket
key: (_) @variable.parameter
["," ":"] @punctuation.delimiter
">" @punctuation.bracket
)
(shebang) @comment
(comment) @comment

View File

@@ -0,0 +1,3 @@
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use async_trait::async_trait;
use language::{ContextProviderWithTasks, LanguageServerName, LspAdapter, LspAdapterDelegate};
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime;
use std::{
@@ -9,7 +9,6 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
use task::{TaskTemplate, TaskTemplates, VariableName};
use util::ResultExt;
const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";
@@ -181,30 +180,6 @@ async fn get_cached_server_binary(
}
}
pub(super) fn python_task_context() -> ContextProviderWithTasks {
ContextProviderWithTasks::new(TaskTemplates(vec![
TaskTemplate {
label: "execute selection".to_owned(),
command: "python3".to_owned(),
args: vec![
"-c".to_owned(),
format!(
"exec(r'''{}''')",
VariableName::SelectedText.template_value()
),
],
ignore_previously_resolved: true,
..TaskTemplate::default()
},
TaskTemplate {
label: format!("run '{}'", VariableName::File.template_value()),
command: "python3".to_owned(),
args: vec![VariableName::File.template_value()],
..TaskTemplate::default()
},
]))
}
#[cfg(test)]
mod tests {
use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};

View File

@@ -331,26 +331,25 @@ const RUST_PACKAGE_TASK_VARIABLE: VariableName =
impl ContextProvider for RustContextProvider {
fn build_context(
&self,
_: Option<&Path>,
location: &Location,
location: Location,
cx: &mut gpui::AppContext,
) -> Result<TaskVariables> {
let mut context = SymbolContextProvider.build_context(location.clone(), cx)?;
let local_abs_path = location
.buffer
.read(cx)
.file()
.and_then(|file| Some(file.as_local()?.abs_path(cx)));
Ok(
if let Some(package_name) = local_abs_path
.as_deref()
.and_then(|local_abs_path| local_abs_path.parent())
.and_then(human_readable_package_name)
{
TaskVariables::from_iter(Some((RUST_PACKAGE_TASK_VARIABLE.clone(), package_name)))
} else {
TaskVariables::default()
},
)
if let Some(package_name) = local_abs_path
.as_deref()
.and_then(|local_abs_path| local_abs_path.parent())
.and_then(human_readable_package_name)
{
context.insert(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name);
}
Ok(context)
}
fn associated_tasks(&self) -> Option<TaskTemplates> {

242
crates/languages/src/vue.rs Normal file
View File

@@ -0,0 +1,242 @@
use anyhow::{anyhow, ensure, Result};
use async_trait::async_trait;
use futures::StreamExt;
pub use language::*;
use lsp::{CodeActionKind, LanguageServerBinary};
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use smol::fs::{self};
use std::{
any::Any,
ffi::OsString,
path::{Path, PathBuf},
sync::Arc,
};
use util::{maybe, ResultExt};
pub struct VueLspVersion {
vue_version: String,
ts_version: String,
}
pub struct VueLspAdapter {
node: Arc<dyn NodeRuntime>,
typescript_install_path: Mutex<Option<PathBuf>>,
}
impl VueLspAdapter {
const SERVER_PATH: &'static str =
"node_modules/@vue/language-server/bin/vue-language-server.js";
// TODO: this can't be hardcoded, yet we have to figure out how to pass it in initialization_options.
const TYPESCRIPT_PATH: &'static str = "node_modules/typescript/lib";
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
let typescript_install_path = Mutex::new(None);
Self {
node,
typescript_install_path,
}
}
}
#[async_trait(?Send)]
impl super::LspAdapter for VueLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("vue-language-server".into())
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(VueLspVersion {
// We hardcode the version to 1.8 since we do not support @vue/language-server 2.0 yet.
vue_version: "1.8".to_string(),
ts_version: self.node.npm_package_latest_version("typescript").await?,
}) as Box<_>)
}
async fn initialization_options(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
let typescript_sdk_path = self.typescript_install_path.lock();
let typescript_sdk_path = typescript_sdk_path
.as_ref()
.expect("initialization_options called without a container_dir for typescript");
Ok(Some(serde_json::json!({
"typescript": {
"tsdk": typescript_sdk_path
}
})))
}
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
// REFACTOR is explicitly disabled, as vue-lsp does not adhere to LSP protocol for code actions with these - it
// sends back a CodeAction with neither `command` nor `edits` fields set, which is against the spec.
Some(vec![
CodeActionKind::EMPTY,
CodeActionKind::QUICKFIX,
CodeActionKind::REFACTOR_REWRITE,
])
}
async fn fetch_server_binary(
&self,
latest_version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let latest_version = latest_version.downcast::<VueLspVersion>().unwrap();
let server_path = container_dir.join(Self::SERVER_PATH);
let ts_path = container_dir.join(Self::TYPESCRIPT_PATH);
let vue_package_name = "@vue/language-server";
let should_install_vue_language_server = self
.node
.should_install_npm_package(
vue_package_name,
&server_path,
&container_dir,
&latest_version.vue_version,
)
.await;
if should_install_vue_language_server {
self.node
.npm_install_packages(
&container_dir,
&[(vue_package_name, latest_version.vue_version.as_str())],
)
.await?;
}
ensure!(
fs::metadata(&server_path).await.is_ok(),
"@vue/language-server package installation failed"
);
let ts_package_name = "typescript";
let should_install_ts_language_server = self
.node
.should_install_npm_package(
ts_package_name,
&server_path,
&container_dir,
&latest_version.ts_version,
)
.await;
if should_install_ts_language_server {
self.node
.npm_install_packages(
&container_dir,
&[(ts_package_name, latest_version.ts_version.as_str())],
)
.await?;
}
ensure!(
fs::metadata(&ts_path).await.is_ok(),
"typescript for Vue package installation failed"
);
*self.typescript_install_path.lock() = Some(ts_path);
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: vue_server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone()).await?;
*self.typescript_install_path.lock() = Some(ts_path);
Some(server)
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone())
.await
.map(|(mut binary, ts_path)| {
binary.arguments = vec!["--help".into()];
(binary, ts_path)
})?;
*self.typescript_install_path.lock() = Some(ts_path);
Some(server)
}
async fn label_for_completion(
&self,
item: &lsp::CompletionItem,
language: &Arc<language::Language>,
) -> Option<language::CodeLabel> {
use lsp::CompletionItemKind as Kind;
let len = item.label.len();
let grammar = language.grammar()?;
let highlight_id = match item.kind? {
Kind::CLASS | Kind::INTERFACE => grammar.highlight_id_for_name("type"),
Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"),
Kind::CONSTANT => grammar.highlight_id_for_name("constant"),
Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"),
Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("tag"),
Kind::VARIABLE => grammar.highlight_id_for_name("type"),
Kind::KEYWORD => grammar.highlight_id_for_name("keyword"),
Kind::VALUE => grammar.highlight_id_for_name("tag"),
_ => None,
}?;
let text = match &item.detail {
Some(detail) => format!("{} {}", item.label, detail),
None => item.label.clone(),
};
Some(language::CodeLabel {
text,
runs: vec![(0..len, highlight_id)],
filter_range: 0..len,
})
}
}
fn vue_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
}
type TypescriptPath = PathBuf;
async fn get_cached_server_binary(
container_dir: PathBuf,
node: Arc<dyn NodeRuntime>,
) -> Option<(LanguageServerBinary, TypescriptPath)> {
maybe!(async {
let mut last_version_dir = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let server_path = last_version_dir.join(VueLspAdapter::SERVER_PATH);
let typescript_path = last_version_dir.join(VueLspAdapter::TYPESCRIPT_PATH);
if server_path.exists() && typescript_path.exists() {
Ok((
LanguageServerBinary {
path: node.binary_path().await?,
env: None,
arguments: vue_server_binary_arguments(&server_path),
},
typescript_path,
))
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
})
.await
.log_err()
}

View File

@@ -138,16 +138,8 @@ struct AnyResponse<'a> {
struct Response<T> {
jsonrpc: &'static str,
id: RequestId,
#[serde(flatten)]
value: LspResult<T>,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum LspResult<T> {
#[serde(rename = "result")]
Ok(Option<T>),
Error(Option<Error>),
result: Option<T>,
error: Option<Error>,
}
/// Language server protocol RPC notification message.
@@ -875,14 +867,16 @@ impl LanguageServer {
Ok(result) => Response {
jsonrpc: JSON_RPC_VERSION,
id,
value: LspResult::Ok(Some(result)),
result: Some(result),
error: None,
},
Err(error) => Response {
jsonrpc: JSON_RPC_VERSION,
id,
value: LspResult::Error(Some(Error {
result: None,
error: Some(Error {
message: error.to_string(),
})),
}),
},
};
if let Some(response) =
@@ -1509,27 +1503,4 @@ mod tests {
let expected_id = RequestId::Int(2);
assert_eq!(notification.id, Some(expected_id));
}
#[test]
fn test_serialize_has_no_nulls() {
// Ensure we're not setting both result and error variants. (ticket #10595)
let no_tag = Response::<u32> {
jsonrpc: "",
id: RequestId::Int(0),
value: LspResult::Ok(None),
};
assert_eq!(
serde_json::to_string(&no_tag).unwrap(),
"{\"jsonrpc\":\"\",\"id\":0,\"result\":null}"
);
let no_tag = Response::<u32> {
jsonrpc: "",
id: RequestId::Int(0),
value: LspResult::Error(None),
};
assert_eq!(
serde_json::to_string(&no_tag).unwrap(),
"{\"jsonrpc\":\"\",\"id\":0,\"error\":null}"
);
}
}

View File

@@ -7756,20 +7756,13 @@ impl Project {
.as_local()
.context("worktree was not local")?
.snapshot();
let (work_directory, repo) = match worktree
let (work_directory, repo) = worktree
.repository_and_work_directory_for_path(&buffer_project_path.path)
{
Some(work_dir_repo) => work_dir_repo,
None => anyhow::bail!(NoRepositoryError {}),
};
.context("failed to get repo for blamed buffer")?;
let repo_entry = match worktree.get_local_repo(&repo) {
Some(repo_entry) => repo_entry,
None => anyhow::bail!(NoRepositoryError {}),
};
let repo = repo_entry.repo().clone();
let repo_entry = worktree
.get_local_repo(&repo)
.context("failed to get repo for blamed buffer")?;
let relative_path = buffer_project_path
.path
@@ -7780,6 +7773,7 @@ impl Project {
Some(version) => buffer.rope_for_version(&version).clone(),
None => buffer.as_rope().clone(),
};
let repo = repo_entry.repo().clone();
anyhow::Ok((repo, relative_path, content))
});
@@ -10727,7 +10721,6 @@ fn serialize_blame_buffer_response(blame: git::blame::Blame) -> proto::BlameBuff
entries,
messages,
permalinks,
remote_url: blame.remote_url,
}
}
@@ -10776,7 +10769,6 @@ fn deserialize_blame_buffer_response(response: proto::BlameBufferResponse) -> gi
entries,
permalinks,
messages,
remote_url: response.remote_url,
}
}
@@ -10790,14 +10782,3 @@ fn remove_empty_hover_blocks(mut hover: Hover) -> Option<Hover> {
Some(hover)
}
}
#[derive(Debug)]
pub struct NoRepositoryError {}
impl std::fmt::Display for NoRepositoryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "no git repository for worktree found")
}
}
impl std::error::Error for NoRepositoryError {}

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