Unified · Split
| @@ -0,0 +1,12 @@ | |||
| 1 | # This config is different from config.toml in this directory, as the latter is recognized by Cargo. | ||
| 2 | # This file is placed in $HOME/.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with $HOME/.cargo/config.toml | ||
| 3 | # with preference for settings from Zeds config.toml. | ||
| 4 | # TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence. | ||
| 5 | # Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure | ||
| 6 | # The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file | ||
| 7 | # we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml` | ||
| 8 | # would be incovenient. | ||
| 9 | # We *could* override things like RUSTFLAGS manually by setting them as environment variables, but that is less DRY; worse yet, if you forget to set proper environment variables | ||
| 10 | # in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it. | ||
| 11 | [build] | ||
| 12 | rustflags = ["-D", "warnings"] | ||
| @@ -19,16 +19,12 @@ runs: · 18 unmodified lines | |||
| 19 | 19 | ||
| 20 | - name: Limit target directory size | 20 | - name: Limit target directory size |
| 21 | shell: bash -euxo pipefail {0} | 21 | shell: bash -euxo pipefail {0} |
| 22 | run: script/clear-target-dir-if-larger-than 70 | 22 | run: script/clear-target-dir-if-larger-than 100 |
| 23 | 23 | ||
| 24 | - name: Run check | 24 | - name: Run check |
| 25 | env: | ||
| 26 | RUSTFLAGS: -D warnings | ||
| 27 | shell: bash -euxo pipefail {0} | 25 | shell: bash -euxo pipefail {0} |
| 28 | run: cargo check --tests --workspace | 26 | run: cargo check --tests --workspace |
| 29 | 27 | ||
| 30 | - name: Run tests | 28 | - name: Run tests |
| 31 | env: | ||
| 32 | RUSTFLAGS: -D warnings | ||
| 33 | shell: bash -euxo pipefail {0} | 29 | shell: bash -euxo pipefail {0} |
| 34 | run: cargo nextest run --workspace --no-fail-fast | 30 | run: cargo nextest run --workspace --no-fail-fast |
| @@ -29,6 +29,9 @@ jobs: · 28 unmodified lines | |||
| 29 | clean: false | 29 | clean: false |
| 30 | submodules: "recursive" | 30 | submodules: "recursive" |
| 31 | 31 | ||
| 32 | - name: Set up default .cargo/config.toml | ||
| 33 | run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml | ||
| 34 | |||
| 32 | - name: Run rustfmt | 35 | - name: Run rustfmt |
| 33 | uses: ./.github/actions/check_formatting | 36 | uses: ./.github/actions/check_formatting |
| 34 | 37 | ||
| @@ -87,7 +90,7 @@ jobs: · 52 unmodified lines | |||
| 87 | submodules: "recursive" | 90 | submodules: "recursive" |
| 88 | 91 | ||
| 89 | - name: Limit target directory size | 92 | - name: Limit target directory size |
| 90 | run: script/clear-target-dir-if-larger-than 70 | 93 | run: script/clear-target-dir-if-larger-than 100 |
| 91 | 94 | ||
| 92 | - name: Determine version and release channel | 95 | - name: Determine version and release channel |
| 93 | if: ${{ startsWith(github.ref, 'refs/tags/v') }} | 96 | if: ${{ startsWith(github.ref, 'refs/tags/v') }} |
| @@ -131,8 +134,6 @@ jobs: · 37 unmodified lines | |||
| 131 | 134 | ||
| 132 | - uses: softprops/action-gh-release@v1 | 135 | - uses: softprops/action-gh-release@v1 |
| 133 | name: Upload app bundle to release | 136 | name: Upload app bundle to release |
| 134 | # TODO kb seems that zed.dev relies on GitHub releases for release version tracking. | ||
| 135 | # Find alternatives for `nightly` or just go on with more releases? | ||
| 136 | if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }} | 137 | if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }} |
| 137 | with: | 138 | with: |
| 138 | draft: true | 139 | draft: true |
| @@ -79,7 +79,7 @@ jobs: · 78 unmodified lines | |||
| 79 | submodules: "recursive" | 79 | submodules: "recursive" |
| 80 | 80 | ||
| 81 | - name: Limit target directory size | 81 | - name: Limit target directory size |
| 82 | run: script/clear-target-dir-if-larger-than 70 | 82 | run: script/clear-target-dir-if-larger-than 100 |
| 83 | 83 | ||
| 84 | - name: Set release channel to nightly | 84 | - name: Set release channel to nightly |
| 85 | run: | | 85 | run: | |
| @@ -1186,12 +1186,14 @@ version = "0.1.0" · 1185 unmodified lines | |||
| 1186 | dependencies = [ | 1186 | dependencies = [ |
| 1187 | "anyhow", | 1187 | "anyhow", |
| 1188 | "async-broadcast", | 1188 | "async-broadcast", |
| 1189 | "async-trait", | ||
| 1189 | "audio2", | 1190 | "audio2", |
| 1190 | "client2", | 1191 | "client2", |
| 1191 | "collections", | 1192 | "collections", |
| 1192 | "fs2", | 1193 | "fs2", |
| 1193 | "futures 0.3.28", | 1194 | "futures 0.3.28", |
| 1194 | "gpui2", | 1195 | "gpui2", |
| 1196 | "image", | ||
| 1195 | "language2", | 1197 | "language2", |
| 1196 | "live_kit_client2", | 1198 | "live_kit_client2", |
| 1197 | "log", | 1199 | "log", |
| @@ -1203,7 +1205,10 @@ dependencies = [ · 5 unmodified lines | |||
| 1203 | "serde_derive", | 1205 | "serde_derive", |
| 1204 | "serde_json", | 1206 | "serde_json", |
| 1205 | "settings2", | 1207 | "settings2", |
| 1208 | "smallvec", | ||
| 1209 | "ui2", | ||
| 1206 | "util", | 1210 | "util", |
| 1211 | "workspace2", | ||
| 1207 | ] | 1212 | ] |
| 1208 | 1213 | ||
| 1209 | [[package]] | 1214 | [[package]] |
| @@ -1398,7 +1403,7 @@ dependencies = [ · 188 unmodified lines | |||
| 1398 | "smol", | 1403 | "smol", |
| 1399 | "sum_tree", | 1404 | "sum_tree", |
| 1400 | "tempfile", | 1405 | "tempfile", |
| 1401 | "text", | 1406 | "text2", |
| 1402 | "thiserror", | 1407 | "thiserror", |
| 1403 | "time", | 1408 | "time", |
| 1404 | "tiny_http", | 1409 | "tiny_http", |
| @@ -1664,7 +1669,7 @@ dependencies = [ · 259 unmodified lines | |||
| 1664 | 1669 | ||
| 1665 | [[package]] | 1670 | [[package]] |
| 1666 | name = "collab" | 1671 | name = "collab" |
| 1667 | version = "0.28.0" | 1672 | version = "0.29.0" |
| 1668 | dependencies = [ | 1673 | dependencies = [ |
| 1669 | "anyhow", | 1674 | "anyhow", |
| 1670 | "async-trait", | 1675 | "async-trait", |
| @@ -1869,7 +1874,7 @@ dependencies = [ · 198 unmodified lines | |||
| 1869 | "editor2", | 1874 | "editor2", |
| 1870 | "feature_flags2", | 1875 | "feature_flags2", |
| 1871 | "futures 0.3.28", | 1876 | "futures 0.3.28", |
| 1872 | "fuzzy", | 1877 | "fuzzy2", |
| 1873 | "gpui2", | 1878 | "gpui2", |
| 1874 | "language2", | 1879 | "language2", |
| 1875 | "lazy_static", | 1880 | "lazy_static", |
| @@ -6763,7 +6768,6 @@ dependencies = [ · 4887 unmodified lines | |||
| 6763 | "anyhow", | 6768 | "anyhow", |
| 6764 | "client2", | 6769 | "client2", |
| 6765 | "collections", | 6770 | "collections", |
| 6766 | "context_menu", | ||
| 6767 | "db2", | 6771 | "db2", |
| 6768 | "editor2", | 6772 | "editor2", |
| 6769 | "futures 0.3.28", | 6773 | "futures 0.3.28", |
| @@ -8032,6 +8036,35 @@ dependencies = [ · 1262 unmodified lines | |||
| 8032 | "workspace", | 8036 | "workspace", |
| 8033 | ] | 8037 | ] |
| 8034 | 8038 | ||
| 8039 | [[package]] | ||
| 8040 | name = "search2" | ||
| 8041 | version = "0.1.0" | ||
| 8042 | dependencies = [ | ||
| 8043 | "anyhow", | ||
| 8044 | "bitflags 1.3.2", | ||
| 8045 | "client2", | ||
| 8046 | "collections", | ||
| 8047 | "editor2", | ||
| 8048 | "futures 0.3.28", | ||
| 8049 | "gpui2", | ||
| 8050 | "language2", | ||
| 8051 | "log", | ||
| 8052 | "menu2", | ||
| 8053 | "postage", | ||
| 8054 | "project2", | ||
| 8055 | "serde", | ||
| 8056 | "serde_derive", | ||
| 8057 | "serde_json", | ||
| 8058 | "settings2", | ||
| 8059 | "smallvec", | ||
| 8060 | "smol", | ||
| 8061 | "theme2", | ||
| 8062 | "ui2", | ||
| 8063 | "unindent", | ||
| 8064 | "util", | ||
| 8065 | "workspace2", | ||
| 8066 | ] | ||
| 8067 | |||
| 8035 | [[package]] | 8068 | [[package]] |
| 8036 | name = "security-framework" | 8069 | name = "security-framework" |
| 8037 | version = "2.9.2" | 8070 | version = "2.9.2" |
| @@ -8855,6 +8888,13 @@ version = "1.1.0" · 817 unmodified lines | |||
| 8855 | source = "registry+https://github.com/rust-lang/crates.io-index" | 8888 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 8856 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" | 8889 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" |
| 8857 | 8890 | ||
| 8891 | [[package]] | ||
| 8892 | name = "story" | ||
| 8893 | version = "0.1.0" | ||
| 8894 | dependencies = [ | ||
| 8895 | "gpui2", | ||
| 8896 | ] | ||
| 8897 | |||
| 8858 | [[package]] | 8898 | [[package]] |
| 8859 | name = "storybook2" | 8899 | name = "storybook2" |
| 8860 | version = "0.1.0" | 8900 | version = "0.1.0" |
| @@ -8876,6 +8916,7 @@ dependencies = [ · 15 unmodified lines | |||
| 8876 | "settings2", | 8916 | "settings2", |
| 8877 | "simplelog", | 8917 | "simplelog", |
| 8878 | "smallvec", | 8918 | "smallvec", |
| 8919 | "story", | ||
| 8879 | "strum", | 8920 | "strum", |
| 8880 | "theme", | 8921 | "theme", |
| 8881 | "theme2", | 8922 | "theme2", |
| @@ -8883,17 +8924,6 @@ dependencies = [ · 1 unmodified line | |||
| 8883 | "util", | 8924 | "util", |
| 8884 | ] | 8925 | ] |
| 8885 | 8926 | ||
| 8886 | [[package]] | ||
| 8887 | name = "storybook3" | ||
| 8888 | version = "0.1.0" | ||
| 8889 | dependencies = [ | ||
| 8890 | "anyhow", | ||
| 8891 | "gpui2", | ||
| 8892 | "settings2", | ||
| 8893 | "theme2", | ||
| 8894 | "ui2", | ||
| 8895 | ] | ||
| 8896 | |||
| 8897 | [[package]] | 8927 | [[package]] |
| 8898 | name = "stringprep" | 8928 | name = "stringprep" |
| 8899 | version = "0.1.4" | 8929 | version = "0.1.4" |
| @@ -9397,6 +9427,7 @@ dependencies = [ · 497 unmodified lines | |||
| 9397 | "serde_derive", | 9427 | "serde_derive", |
| 9398 | "serde_json", | 9428 | "serde_json", |
| 9399 | "settings2", | 9429 | "settings2", |
| 9430 | "story", | ||
| 9400 | "toml 0.5.11", | 9431 | "toml 0.5.11", |
| 9401 | "util", | 9432 | "util", |
| 9402 | "uuid 1.4.1", | 9433 | "uuid 1.4.1", |
| @@ -9899,7 +9930,7 @@ dependencies = [ · 496 unmodified lines | |||
| 9899 | [[package]] | 9930 | [[package]] |
| 9900 | name = "tree-sitter" | 9931 | name = "tree-sitter" |
| 9901 | version = "0.20.10" | 9932 | version = "0.20.10" |
| 9902 | source = "git+https://github.com/tree-sitter/tree-sitter?rev=35a6052fbcafc5e5fc0f9415b8652be7dcaf7222#35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" | 9933 | source = "git+https://github.com/tree-sitter/tree-sitter?rev=3b0159d25559b603af566ade3c83d930bf466db1#3b0159d25559b603af566ade3c83d930bf466db1" |
| 9903 | dependencies = [ | 9934 | dependencies = [ |
| 9904 | "cc", | 9935 | "cc", |
| 9905 | "regex", | 9936 | "regex", |
| @@ -10147,6 +10178,15 @@ dependencies = [ · 241 unmodified lines | |||
| 10147 | "tree-sitter", | 10178 | "tree-sitter", |
| 10148 | ] | 10179 | ] |
| 10149 | 10180 | ||
| 10181 | [[package]] | ||
| 10182 | name = "tree-sitter-uiua" | ||
| 10183 | version = "0.3.3" | ||
| 10184 | source = "git+https://github.com/shnarazk/tree-sitter-uiua?rev=9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2#9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2" | ||
| 10185 | dependencies = [ | ||
| 10186 | "cc", | ||
| 10187 | "tree-sitter", | ||
| 10188 | ] | ||
| 10189 | |||
| 10150 | [[package]] | 10190 | [[package]] |
| 10151 | name = "tree-sitter-vue" | 10191 | name = "tree-sitter-vue" |
| 10152 | version = "0.0.1" | 10192 | version = "0.0.1" |
| @@ -10240,6 +10280,7 @@ dependencies = [ · 87 unmodified lines | |||
| 10240 | "serde", | 10280 | "serde", |
| 10241 | "settings2", | 10281 | "settings2", |
| 10242 | "smallvec", | 10282 | "smallvec", |
| 10283 | "story", | ||
| 10243 | "strum", | 10284 | "strum", |
| 10244 | "theme2", | 10285 | "theme2", |
| 10245 | ] | 10286 | ] |
| @@ -11354,6 +11395,7 @@ version = "0.1.0" · 1108 unmodified lines | |||
| 11354 | dependencies = [ | 11395 | dependencies = [ |
| 11355 | "anyhow", | 11396 | "anyhow", |
| 11356 | "async-recursion 1.0.5", | 11397 | "async-recursion 1.0.5", |
| 11398 | "async-trait", | ||
| 11357 | "bincode", | 11399 | "bincode", |
| 11358 | "call2", | 11400 | "call2", |
| 11359 | "client2", | 11401 | "client2", |
| @@ -11466,7 +11508,7 @@ dependencies = [ · 106 unmodified lines | |||
| 11466 | 11508 | ||
| 11467 | [[package]] | 11509 | [[package]] |
| 11468 | name = "zed" | 11510 | name = "zed" |
| 11469 | version = "0.114.0" | 11511 | version = "0.115.0" |
| 11470 | dependencies = [ | 11512 | dependencies = [ |
| 11471 | "activity_indicator", | 11513 | "activity_indicator", |
| 11472 | "ai", | 11514 | "ai", |
| @@ -11583,6 +11625,7 @@ dependencies = [ · 110 unmodified lines | |||
| 11583 | "tree-sitter-svelte", | 11625 | "tree-sitter-svelte", |
| 11584 | "tree-sitter-toml", | 11626 | "tree-sitter-toml", |
| 11585 | "tree-sitter-typescript", | 11627 | "tree-sitter-typescript", |
| 11628 | "tree-sitter-uiua", | ||
| 11586 | "tree-sitter-vue", | 11629 | "tree-sitter-vue", |
| 11587 | "tree-sitter-yaml", | 11630 | "tree-sitter-yaml", |
| 11588 | "unindent", | 11631 | "unindent", |
| @@ -11614,9 +11657,11 @@ dependencies = [ · 25 unmodified lines | |||
| 11614 | "async-recursion 0.3.2", | 11657 | "async-recursion 0.3.2", |
| 11615 | "async-tar", | 11658 | "async-tar", |
| 11616 | "async-trait", | 11659 | "async-trait", |
| 11660 | "audio2", | ||
| 11617 | "auto_update2", | 11661 | "auto_update2", |
| 11618 | "backtrace", | 11662 | "backtrace", |
| 11619 | "call2", | 11663 | "call2", |
| 11664 | "channel2", | ||
| 11620 | "chrono", | 11665 | "chrono", |
| 11621 | "cli", | 11666 | "cli", |
| 11622 | "client2", | 11667 | "client2", |
| @@ -11634,7 +11679,6 @@ dependencies = [ · 11 unmodified lines | |||
| 11634 | "fs2", | 11679 | "fs2", |
| 11635 | "fsevent", | 11680 | "fsevent", |
| 11636 | "futures 0.3.28", | 11681 | "futures 0.3.28", |
| 11637 | "fuzzy", | ||
| 11638 | "go_to_line2", | 11682 | "go_to_line2", |
| 11639 | "gpui2", | 11683 | "gpui2", |
| 11640 | "ignore", | 11684 | "ignore", |
| @@ -11662,6 +11706,7 @@ dependencies = [ · 21 unmodified lines | |||
| 11662 | "rsa 0.4.0", | 11706 | "rsa 0.4.0", |
| 11663 | "rust-embed", | 11707 | "rust-embed", |
| 11664 | "schemars", | 11708 | "schemars", |
| 11709 | "search2", | ||
| 11665 | "serde", | 11710 | "serde", |
| 11666 | "serde_derive", | 11711 | "serde_derive", |
| 11667 | "serde_json", | 11712 | "serde_json", |
| @@ -11704,6 +11749,7 @@ dependencies = [ · 36 unmodified lines | |||
| 11704 | "tree-sitter-svelte", | 11749 | "tree-sitter-svelte", |
| 11705 | "tree-sitter-toml", | 11750 | "tree-sitter-toml", |
| 11706 | "tree-sitter-typescript", | 11751 | "tree-sitter-typescript", |
| 11752 | "tree-sitter-uiua", | ||
| 11707 | "tree-sitter-vue", | 11753 | "tree-sitter-vue", |
| 11708 | "tree-sitter-yaml", | 11754 | "tree-sitter-yaml", |
| 11709 | "unindent", | 11755 | "unindent", |
| @@ -90,6 +90,7 @@ members = [ · 89 unmodified lines | |||
| 90 | "crates/rpc", | 90 | "crates/rpc", |
| 91 | "crates/rpc2", | 91 | "crates/rpc2", |
| 92 | "crates/search", | 92 | "crates/search", |
| 93 | "crates/search2", | ||
| 93 | "crates/settings", | 94 | "crates/settings", |
| 94 | "crates/settings2", | 95 | "crates/settings2", |
| 95 | "crates/snippet", | 96 | "crates/snippet", |
| @@ -97,7 +98,6 @@ members = [ · 1 unmodified line | |||
| 97 | "crates/sqlez_macros", | 98 | "crates/sqlez_macros", |
| 98 | "crates/rich_text", | 99 | "crates/rich_text", |
| 99 | "crates/storybook2", | 100 | "crates/storybook2", |
| 100 | "crates/storybook3", | ||
| 101 | "crates/sum_tree", | 101 | "crates/sum_tree", |
| 102 | "crates/terminal", | 102 | "crates/terminal", |
| 103 | "crates/terminal2", | 103 | "crates/terminal2", |
| @@ -110,6 +110,7 @@ members = [ · 6 unmodified lines | |||
| 110 | "crates/ui2", | 110 | "crates/ui2", |
| 111 | "crates/util", | 111 | "crates/util", |
| 112 | "crates/semantic_index", | 112 | "crates/semantic_index", |
| 113 | "crates/story", | ||
| 113 | "crates/vim", | 114 | "crates/vim", |
| 114 | "crates/vcs_menu", | 115 | "crates/vcs_menu", |
| 115 | "crates/workspace2", | 116 | "crates/workspace2", |
| @@ -194,8 +195,10 @@ tree-sitter-lua = "0.0.14" · 78 unmodified lines | |||
| 194 | tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } | 195 | tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } |
| 195 | tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} | 196 | tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} |
| 196 | tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"} | 197 | tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"} |
| 198 | tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"} | ||
| 199 | |||
| 197 | [patch.crates-io] | 200 | [patch.crates-io] |
| 198 | tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } | 201 | tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" } |
| 199 | async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } | 202 | async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } |
| 200 | 203 | ||
| 201 | # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 | 204 | # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 |
| @@ -207,11 +210,12 @@ core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "07 · 5 unmodified lines | |||
| 207 | 210 | ||
| 208 | [profile.dev] | 211 | [profile.dev] |
| 209 | split-debuginfo = "unpacked" | 212 | split-debuginfo = "unpacked" |
| 213 | debug = "limited" | ||
| 210 | 214 | ||
| 211 | [profile.dev.package.taffy] | 215 | [profile.dev.package.taffy] |
| 212 | opt-level = 3 | 216 | opt-level = 3 |
| 213 | 217 | ||
| 214 | [profile.release] | 218 | [profile.release] |
| 215 | debug = true | 219 | debug = "limited" |
| 216 | lto = "thin" | 220 | lto = "thin" |
| 217 | codegen-units = 1 | 221 | codegen-units = 1 |
| @@ -0,0 +1,4 @@ | |||
| 1 | web: cd ../zed.dev && PORT=3000 npm run dev | ||
| 2 | collab: cd crates/collab2 && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve | ||
| 3 | livekit: livekit-server --dev | ||
| 4 | postgrest: postgrest crates/collab2/admin_api.conf | ||
| @@ -43,7 +43,7 @@ · 42 unmodified lines | |||
| 43 | "calt": false | 43 | "calt": false |
| 44 | }, | 44 | }, |
| 45 | // The default font size for text in the UI | 45 | // The default font size for text in the UI |
| 46 | "ui_font_size": 14, | 46 | "ui_font_size": 16, |
| 47 | // The factor to grow the active pane by. Defaults to 1.0 | 47 | // The factor to grow the active pane by. Defaults to 1.0 |
| 48 | // which gives the same size as all other panes. | 48 | // which gives the same size as all other panes. |
| 49 | "active_pane_magnification": 1.0, | 49 | "active_pane_magnification": 1.0, |
| @@ -7,5 +7,6 @@ · 6 unmodified lines | |||
| 7 | // custom settings, run the `open default settings` command | 7 | // custom settings, run the `open default settings` command |
| 8 | // from the command palette or from `Zed` application menu. | 8 | // from the command palette or from `Zed` application menu. |
| 9 | { | 9 | { |
| 10 | "buffer_font_size": 15 | 10 | "ui_font_size": 16, |
| 11 | "buffer_font_size": 16 | ||
| 11 | } | 12 | } |
| @@ -84,8 +84,8 @@ impl Settings for AutoUpdateSetting { · 83 unmodified lines | |||
| 84 | pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) { | 84 | pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) { |
| 85 | AutoUpdateSetting::register(cx); | 85 | AutoUpdateSetting::register(cx); |
| 86 | 86 | ||
| 87 | cx.observe_new_views(|wokrspace: &mut Workspace, _cx| { | 87 | cx.observe_new_views(|workspace: &mut Workspace, _cx| { |
| 88 | wokrspace | 88 | workspace |
| 89 | .register_action(|_, action: &Check, cx| check(action, cx)) | 89 | .register_action(|_, action: &Check, cx| check(action, cx)) |
| 90 | .register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| { | 90 | .register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| { |
| 91 | let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]); | 91 | let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]); |
| @@ -94,6 +94,11 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo · 2 unmodified lines | |||
| 94 | }) | 94 | }) |
| 95 | .detach(); | 95 | .detach(); |
| 96 | }); | 96 | }); |
| 97 | |||
| 98 | // @nate - code to trigger update notification on launch | ||
| 99 | // workspace.show_notification(0, _cx, |cx| { | ||
| 100 | // cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap())) | ||
| 101 | // }); | ||
| 97 | }) | 102 | }) |
| 98 | .detach(); | 103 | .detach(); |
| 99 | 104 | ||
| @@ -131,7 +136,7 @@ pub fn check(_: &Check, cx: &mut AppContext) { · 31 unmodified lines | |||
| 131 | } | 136 | } |
| 132 | } | 137 | } |
| 133 | 138 | ||
| 134 | fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { | 139 | pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { |
| 135 | if let Some(auto_updater) = AutoUpdater::get(cx) { | 140 | if let Some(auto_updater) = AutoUpdater::get(cx) { |
| 136 | let auto_updater = auto_updater.read(cx); | 141 | let auto_updater = auto_updater.read(cx); |
| 137 | let server_url = &auto_updater.server_url; | 142 | let server_url = &auto_updater.server_url; |
| @@ -1,87 +1,56 @@ | |||
| 1 | use gpui::{div, Div, EventEmitter, ParentComponent, Render, SemanticVersion, ViewContext}; | 1 | use gpui::{ |
| 2 | use menu::Cancel; | 2 | div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render, |
| 3 | use workspace::notifications::NotificationEvent; | 3 | SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, |
| 4 | }; | ||
| 5 | use util::channel::ReleaseChannel; | ||
| 6 | use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt}; | ||
| 4 | 7 | ||
| 5 | pub struct UpdateNotification { | 8 | pub struct UpdateNotification { |
| 6 | _version: SemanticVersion, | 9 | version: SemanticVersion, |
| 7 | } | 10 | } |
| 8 | 11 | ||
| 9 | impl EventEmitter<NotificationEvent> for UpdateNotification {} | 12 | impl EventEmitter<DismissEvent> for UpdateNotification {} |
| 10 | 13 | ||
| 11 | impl Render for UpdateNotification { | 14 | impl Render for UpdateNotification { |
| 12 | type Element = Div<Self>; | 15 | type Element = Div; |
| 13 | 16 | ||
| 14 | fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element { | 17 | fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { |
| 15 | div().child("Updated zed!") | 18 | let app_name = cx.global::<ReleaseChannel>().display_name(); |
| 16 | // let theme = theme::current(cx).clone(); | 19 | |
| 17 | // let theme = &theme.update_notification; | 20 | v_stack() |
| 18 | 21 | .elevation_3(cx) | |
| 19 | // let app_name = cx.global::<ReleaseChannel>().display_name(); | 22 | .p_4() |
| 20 | 23 | .child( | |
| 21 | // MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| { | 24 | h_stack() |
| 22 | // Flex::column() | 25 | .justify_between() |
| 23 | // .with_child( | 26 | .child(Label::new(format!( |
| 24 | // Flex::row() | 27 | "Updated to {app_name} {}", |
| 25 | // .with_child( | 28 | self.version |
| 26 | // Text::new( | 29 | ))) |
| 27 | // format!("Updated to {app_name} {}", self.version), | 30 | .child( |
| 28 | // theme.message.text.clone(), | 31 | div() |
| 29 | // ) | 32 | .id("cancel") |
| 30 | // .contained() | 33 | .child(IconElement::new(Icon::Close)) |
| 31 | // .with_style(theme.message.container) | 34 | .cursor_pointer() |
| 32 | // .aligned() | 35 | .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), |
| 33 | // .top() | 36 | ), |
| 34 | // .left() | 37 | ) |
| 35 | // .flex(1., true), | 38 | .child( |
| 36 | // ) | 39 | div() |
| 37 | // .with_child( | 40 | .id("notes") |
| 38 | // MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| { | 41 | .child(Label::new("View the release notes")) |
| 39 | // let style = theme.dismiss_button.style_for(state); | 42 | .cursor_pointer() |
| 40 | // Svg::new("icons/x.svg") | 43 | .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)), |
| 41 | // .with_color(style.color) | 44 | ) |
| 42 | // .constrained() | ||
| 43 | // .with_width(style.icon_width) | ||
| 44 | // .aligned() | ||
| 45 | // .contained() | ||
| 46 | // .with_style(style.container) | ||
| 47 | // .constrained() | ||
| 48 | // .with_width(style.button_width) | ||
| 49 | // .with_height(style.button_width) | ||
| 50 | // }) | ||
| 51 | // .with_padding(Padding::uniform(5.)) | ||
| 52 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 53 | // this.dismiss(&Default::default(), cx) | ||
| 54 | // }) | ||
| 55 | // .aligned() | ||
| 56 | // .constrained() | ||
| 57 | // .with_height(cx.font_cache().line_height(theme.message.text.font_size)) | ||
| 58 | // .aligned() | ||
| 59 | // .top() | ||
| 60 | // .flex_float(), | ||
| 61 | // ), | ||
| 62 | // ) | ||
| 63 | // .with_child({ | ||
| 64 | // let style = theme.action_message.style_for(state); | ||
| 65 | // Text::new("View the release notes", style.text.clone()) | ||
| 66 | // .contained() | ||
| 67 | // .with_style(style.container) | ||
| 68 | // }) | ||
| 69 | // .contained() | ||
| 70 | // }) | ||
| 71 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 72 | // .on_click(MouseButton::Left, |_, _, cx| { | ||
| 73 | // crate::view_release_notes(&Default::default(), cx) | ||
| 74 | // }) | ||
| 75 | // .into_any_named("update notification") | ||
| 76 | } | 45 | } |
| 77 | } | 46 | } |
| 78 | 47 | ||
| 79 | impl UpdateNotification { | 48 | impl UpdateNotification { |
| 80 | pub fn new(version: SemanticVersion) -> Self { | 49 | pub fn new(version: SemanticVersion) -> Self { |
| 81 | Self { _version: version } | 50 | Self { version } |
| 82 | } | 51 | } |
| 83 | 52 | ||
| 84 | pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { | 53 | pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) { |
| 85 | cx.emit(NotificationEvent::Dismiss); | 54 | cx.emit(DismissEvent::Dismiss); |
| 86 | } | 55 | } |
| 87 | } | 56 | } |
| @@ -31,15 +31,19 @@ media = { path = "../media" } · 30 unmodified lines | |||
| 31 | project = { package = "project2", path = "../project2" } | 31 | project = { package = "project2", path = "../project2" } |
| 32 | settings = { package = "settings2", path = "../settings2" } | 32 | settings = { package = "settings2", path = "../settings2" } |
| 33 | util = { path = "../util" } | 33 | util = { path = "../util" } |
| 34 | 34 | ui = {package = "ui2", path = "../ui2"} | |
| 35 | workspace = {package = "workspace2", path = "../workspace2"} | ||
| 36 | async-trait.workspace = true | ||
| 35 | anyhow.workspace = true | 37 | anyhow.workspace = true |
| 36 | async-broadcast = "0.4" | 38 | async-broadcast = "0.4" |
| 37 | futures.workspace = true | 39 | futures.workspace = true |
| 40 | image = "0.23" | ||
| 38 | postage.workspace = true | 41 | postage.workspace = true |
| 39 | schemars.workspace = true | 42 | schemars.workspace = true |
| 40 | serde.workspace = true | 43 | serde.workspace = true |
| 41 | serde_json.workspace = true | 44 | serde_json.workspace = true |
| 42 | serde_derive.workspace = true | 45 | serde_derive.workspace = true |
| 46 | smallvec.workspace = true | ||
| 43 | 47 | ||
| 44 | [dev-dependencies] | 48 | [dev-dependencies] |
| 45 | client = { package = "client2", path = "../client2", features = ["test-support"] } | 49 | client = { package = "client2", path = "../client2", features = ["test-support"] } |
| @@ -1,25 +1,32 @@ | |||
| 1 | pub mod call_settings; | 1 | pub mod call_settings; |
| 2 | pub mod participant; | 2 | pub mod participant; |
| 3 | pub mod room; | 3 | pub mod room; |
| 4 | mod shared_screen; | ||
| 4 | 5 | ||
| 5 | use anyhow::{anyhow, Result}; | 6 | use anyhow::{anyhow, Result}; |
| 7 | use async_trait::async_trait; | ||
| 6 | use audio::Audio; | 8 | use audio::Audio; |
| 7 | use call_settings::CallSettings; | 9 | use call_settings::CallSettings; |
| 8 | use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; | 10 | use client::{ |
| 11 | proto::{self, PeerId}, | ||
| 12 | Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE, | ||
| 13 | }; | ||
| 9 | use collections::HashSet; | 14 | use collections::HashSet; |
| 10 | use futures::{channel::oneshot, future::Shared, Future, FutureExt}; | 15 | use futures::{channel::oneshot, future::Shared, Future, FutureExt}; |
| 11 | use gpui::{ | 16 | use gpui::{ |
| 12 | AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, | 17 | AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, |
| 13 | WeakModel, | 18 | View, ViewContext, VisualContext, WeakModel, WeakView, |
| 14 | }; | 19 | }; |
| 20 | pub use participant::ParticipantLocation; | ||
| 15 | use postage::watch; | 21 | use postage::watch; |
| 16 | use project::Project; | 22 | use project::Project; |
| 17 | use room::Event; | 23 | use room::Event; |
| 24 | pub use room::Room; | ||
| 18 | use settings::Settings; | 25 | use settings::Settings; |
| 26 | use shared_screen::SharedScreen; | ||
| 19 | use std::sync::Arc; | 27 | use std::sync::Arc; |
| 20 | 28 | use util::ResultExt; | |
| 21 | pub use participant::ParticipantLocation; | 29 | use workspace::{item::ItemHandle, CallHandler, Pane, Workspace}; |
| 22 | pub use room::Room; | ||
| 23 | 30 | ||
| 24 | pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { | 31 | pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { |
| 25 | CallSettings::register(cx); | 32 | CallSettings::register(cx); |
| @@ -464,7 +471,7 @@ impl ActiveCall { · 438 unmodified lines | |||
| 464 | &self.pending_invites | 471 | &self.pending_invites |
| 465 | } | 472 | } |
| 466 | 473 | ||
| 467 | pub fn report_call_event(&self, operation: &'static str, cx: &AppContext) { | 474 | pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) { |
| 468 | if let Some(room) = self.room() { | 475 | if let Some(room) = self.room() { |
| 469 | let room = room.read(cx); | 476 | let room = room.read(cx); |
| 470 | report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx); | 477 | report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx); |
| @@ -477,7 +484,7 @@ pub fn report_call_event_for_room( · 6 unmodified lines | |||
| 477 | room_id: u64, | 484 | room_id: u64, |
| 478 | channel_id: Option<u64>, | 485 | channel_id: Option<u64>, |
| 479 | client: &Arc<Client>, | 486 | client: &Arc<Client>, |
| 480 | cx: &AppContext, | 487 | cx: &mut AppContext, |
| 481 | ) { | 488 | ) { |
| 482 | let telemetry = client.telemetry(); | 489 | let telemetry = client.telemetry(); |
| 483 | let telemetry_settings = *TelemetrySettings::get_global(cx); | 490 | let telemetry_settings = *TelemetrySettings::get_global(cx); |
| @@ -505,6 +512,208 @@ pub fn report_call_event_for_channel( · 21 unmodified lines | |||
| 505 | ) | 512 | ) |
| 506 | } | 513 | } |
| 507 | 514 | ||
| 515 | pub struct Call { | ||
| 516 | active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>, | ||
| 517 | parent_workspace: WeakView<Workspace>, | ||
| 518 | } | ||
| 519 | |||
| 520 | impl Call { | ||
| 521 | pub fn new( | ||
| 522 | parent_workspace: WeakView<Workspace>, | ||
| 523 | cx: &mut ViewContext<'_, Workspace>, | ||
| 524 | ) -> Box<dyn CallHandler> { | ||
| 525 | let mut active_call = None; | ||
| 526 | if cx.has_global::<Model<ActiveCall>>() { | ||
| 527 | let call = cx.global::<Model<ActiveCall>>().clone(); | ||
| 528 | let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; | ||
| 529 | active_call = Some((call, subscriptions)); | ||
| 530 | } | ||
| 531 | Box::new(Self { | ||
| 532 | active_call, | ||
| 533 | parent_workspace, | ||
| 534 | }) | ||
| 535 | } | ||
| 536 | fn on_active_call_event( | ||
| 537 | workspace: &mut Workspace, | ||
| 538 | _: Model<ActiveCall>, | ||
| 539 | event: &room::Event, | ||
| 540 | cx: &mut ViewContext<Workspace>, | ||
| 541 | ) { | ||
| 542 | match event { | ||
| 543 | room::Event::ParticipantLocationChanged { participant_id } | ||
| 544 | | room::Event::RemoteVideoTracksChanged { participant_id } => { | ||
| 545 | workspace.leader_updated(*participant_id, cx); | ||
| 546 | } | ||
| 547 | _ => {} | ||
| 548 | } | ||
| 549 | } | ||
| 550 | } | ||
| 551 | |||
| 552 | #[async_trait(?Send)] | ||
| 553 | impl CallHandler for Call { | ||
| 554 | fn peer_state( | ||
| 555 | &mut self, | ||
| 556 | leader_id: PeerId, | ||
| 557 | cx: &mut ViewContext<Workspace>, | ||
| 558 | ) -> Option<(bool, bool)> { | ||
| 559 | let (call, _) = self.active_call.as_ref()?; | ||
| 560 | let room = call.read(cx).room()?.read(cx); | ||
| 561 | let participant = room.remote_participant_for_peer_id(leader_id)?; | ||
| 562 | |||
| 563 | let leader_in_this_app; | ||
| 564 | let leader_in_this_project; | ||
| 565 | match participant.location { | ||
| 566 | ParticipantLocation::SharedProject { project_id } => { | ||
| 567 | leader_in_this_app = true; | ||
| 568 | leader_in_this_project = Some(project_id) | ||
| 569 | == self | ||
| 570 | .parent_workspace | ||
| 571 | .update(cx, |this, cx| this.project().read(cx).remote_id()) | ||
| 572 | .log_err() | ||
| 573 | .flatten(); | ||
| 574 | } | ||
| 575 | ParticipantLocation::UnsharedProject => { | ||
| 576 | leader_in_this_app = true; | ||
| 577 | leader_in_this_project = false; | ||
| 578 | } | ||
| 579 | ParticipantLocation::External => { | ||
| 580 | leader_in_this_app = false; | ||
| 581 | leader_in_this_project = false; | ||
| 582 | } | ||
| 583 | }; | ||
| 584 | |||
| 585 | Some((leader_in_this_project, leader_in_this_app)) | ||
| 586 | } | ||
| 587 | |||
| 588 | fn shared_screen_for_peer( | ||
| 589 | &self, | ||
| 590 | peer_id: PeerId, | ||
| 591 | pane: &View<Pane>, | ||
| 592 | cx: &mut ViewContext<Workspace>, | ||
| 593 | ) -> Option<Box<dyn ItemHandle>> { | ||
| 594 | let (call, _) = self.active_call.as_ref()?; | ||
| 595 | let room = call.read(cx).room()?.read(cx); | ||
| 596 | let participant = room.remote_participant_for_peer_id(peer_id)?; | ||
| 597 | let track = participant.video_tracks.values().next()?.clone(); | ||
| 598 | let user = participant.user.clone(); | ||
| 599 | for item in pane.read(cx).items_of_type::<SharedScreen>() { | ||
| 600 | if item.read(cx).peer_id == peer_id { | ||
| 601 | return Some(Box::new(item)); | ||
| 602 | } | ||
| 603 | } | ||
| 604 | |||
| 605 | Some(Box::new(cx.build_view(|cx| { | ||
| 606 | SharedScreen::new(&track, peer_id, user.clone(), cx) | ||
| 607 | }))) | ||
| 608 | } | ||
| 609 | fn room_id(&self, cx: &AppContext) -> Option<u64> { | ||
| 610 | Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) | ||
| 611 | } | ||
| 612 | fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> { | ||
| 613 | let Some((call, _)) = self.active_call.as_ref() else { | ||
| 614 | return Task::ready(Err(anyhow!("Cannot exit a call; not in a call"))); | ||
| 615 | }; | ||
| 616 | |||
| 617 | call.update(cx, |this, cx| this.hang_up(cx)) | ||
| 618 | } | ||
| 619 | fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> { | ||
| 620 | ActiveCall::global(cx).read(cx).location().cloned() | ||
| 621 | } | ||
| 622 | fn invite( | ||
| 623 | &mut self, | ||
| 624 | called_user_id: u64, | ||
| 625 | initial_project: Option<Model<Project>>, | ||
| 626 | cx: &mut AppContext, | ||
| 627 | ) -> Task<Result<()>> { | ||
| 628 | ActiveCall::global(cx).update(cx, |this, cx| { | ||
| 629 | this.invite(called_user_id, initial_project, cx) | ||
| 630 | }) | ||
| 631 | } | ||
| 632 | fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> { | ||
| 633 | self.active_call | ||
| 634 | .as_ref() | ||
| 635 | .map(|call| { | ||
| 636 | call.0.read(cx).room().map(|room| { | ||
| 637 | room.read(cx) | ||
| 638 | .remote_participants() | ||
| 639 | .iter() | ||
| 640 | .map(|participant| { | ||
| 641 | (participant.1.user.clone(), participant.1.peer_id.clone()) | ||
| 642 | }) | ||
| 643 | .collect() | ||
| 644 | }) | ||
| 645 | }) | ||
| 646 | .flatten() | ||
| 647 | } | ||
| 648 | fn is_muted(&self, cx: &AppContext) -> Option<bool> { | ||
| 649 | self.active_call | ||
| 650 | .as_ref() | ||
| 651 | .map(|call| { | ||
| 652 | call.0 | ||
| 653 | .read(cx) | ||
| 654 | .room() | ||
| 655 | .map(|room| room.read(cx).is_muted(cx)) | ||
| 656 | }) | ||
| 657 | .flatten() | ||
| 658 | } | ||
| 659 | fn toggle_mute(&self, cx: &mut AppContext) { | ||
| 660 | self.active_call.as_ref().map(|call| { | ||
| 661 | call.0.update(cx, |this, cx| { | ||
| 662 | this.room().map(|room| { | ||
| 663 | let room = room.clone(); | ||
| 664 | cx.spawn(|_, mut cx| async move { | ||
| 665 | room.update(&mut cx, |this, cx| this.toggle_mute(cx))?? | ||
| 666 | .await | ||
| 667 | }) | ||
| 668 | .detach_and_log_err(cx); | ||
| 669 | }) | ||
| 670 | }) | ||
| 671 | }); | ||
| 672 | } | ||
| 673 | fn toggle_screen_share(&self, cx: &mut AppContext) { | ||
| 674 | self.active_call.as_ref().map(|call| { | ||
| 675 | call.0.update(cx, |this, cx| { | ||
| 676 | this.room().map(|room| { | ||
| 677 | room.update(cx, |this, cx| { | ||
| 678 | if this.is_screen_sharing() { | ||
| 679 | this.unshare_screen(cx).log_err(); | ||
| 680 | } else { | ||
| 681 | let t = this.share_screen(cx); | ||
| 682 | cx.spawn(move |_, _| async move { | ||
| 683 | t.await.log_err(); | ||
| 684 | }) | ||
| 685 | .detach(); | ||
| 686 | } | ||
| 687 | }) | ||
| 688 | }) | ||
| 689 | }) | ||
| 690 | }); | ||
| 691 | } | ||
| 692 | fn toggle_deafen(&self, cx: &mut AppContext) { | ||
| 693 | self.active_call.as_ref().map(|call| { | ||
| 694 | call.0.update(cx, |this, cx| { | ||
| 695 | this.room().map(|room| { | ||
| 696 | room.update(cx, |this, cx| { | ||
| 697 | this.toggle_deafen(cx).log_err(); | ||
| 698 | }) | ||
| 699 | }) | ||
| 700 | }) | ||
| 701 | }); | ||
| 702 | } | ||
| 703 | fn is_deafened(&self, cx: &AppContext) -> Option<bool> { | ||
| 704 | self.active_call | ||
| 705 | .as_ref() | ||
| 706 | .map(|call| { | ||
| 707 | call.0 | ||
| 708 | .read(cx) | ||
| 709 | .room() | ||
| 710 | .map(|room| room.read(cx).is_deafened()) | ||
| 711 | }) | ||
| 712 | .flatten() | ||
| 713 | .flatten() | ||
| 714 | } | ||
| 715 | } | ||
| 716 | |||
| 508 | #[cfg(test)] | 717 | #[cfg(test)] |
| 509 | mod test { | 718 | mod test { |
| 510 | use gpui::TestAppContext; | 719 | use gpui::TestAppContext; |
| @@ -4,7 +4,7 @@ use client::{proto, User}; · 3 unmodified lines | |||
| 4 | use collections::HashMap; | 4 | use collections::HashMap; |
| 5 | use gpui::WeakModel; | 5 | use gpui::WeakModel; |
| 6 | pub use live_kit_client::Frame; | 6 | pub use live_kit_client::Frame; |
| 7 | use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; | 7 | pub(crate) use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; |
| 8 | use project::Project; | 8 | use project::Project; |
| 9 | use std::sync::Arc; | 9 | use std::sync::Arc; |
| 10 | 10 | ||
| @@ -21,7 +21,7 @@ use live_kit_client::{ · 20 unmodified lines | |||
| 21 | }; | 21 | }; |
| 22 | use postage::{sink::Sink, stream::Stream, watch}; | 22 | use postage::{sink::Sink, stream::Stream, watch}; |
| 23 | use project::Project; | 23 | use project::Project; |
| 24 | use settings::Settings; | 24 | use settings::Settings as _; |
| 25 | use std::{future::Future, mem, sync::Arc, time::Duration}; | 25 | use std::{future::Future, mem, sync::Arc, time::Duration}; |
| 26 | use util::{post_inc, ResultExt, TryFutureExt}; | 26 | use util::{post_inc, ResultExt, TryFutureExt}; |
| 27 | 27 | ||
| @@ -1267,7 +1267,6 @@ impl Room { · 1239 unmodified lines | |||
| 1267 | .ok_or_else(|| anyhow!("live-kit was not initialized"))? | 1267 | .ok_or_else(|| anyhow!("live-kit was not initialized"))? |
| 1268 | .await | 1268 | .await |
| 1269 | }; | 1269 | }; |
| 1270 | |||
| 1271 | let publication = publish_track.await; | 1270 | let publication = publish_track.await; |
| 1272 | this.upgrade() | 1271 | this.upgrade() |
| 1273 | .ok_or_else(|| anyhow!("room was dropped"))? | 1272 | .ok_or_else(|| anyhow!("room was dropped"))? |
| @@ -0,0 +1,157 @@ | |||
| 1 | use crate::participant::{Frame, RemoteVideoTrack}; | ||
| 2 | use anyhow::Result; | ||
| 3 | use client::{proto::PeerId, User}; | ||
| 4 | use futures::StreamExt; | ||
| 5 | use gpui::{ | ||
| 6 | div, AppContext, Div, Element, EventEmitter, FocusHandle, FocusableView, ParentElement, Render, | ||
| 7 | SharedString, Task, View, ViewContext, VisualContext, WindowContext, | ||
| 8 | }; | ||
| 9 | use std::sync::{Arc, Weak}; | ||
| 10 | use workspace::{item::Item, ItemNavHistory, WorkspaceId}; | ||
| 11 | |||
| 12 | pub enum Event { | ||
| 13 | Close, | ||
| 14 | } | ||
| 15 | |||
| 16 | pub struct SharedScreen { | ||
| 17 | track: Weak<RemoteVideoTrack>, | ||
| 18 | frame: Option<Frame>, | ||
| 19 | // temporary addition just to render something interactive. | ||
| 20 | current_frame_id: usize, | ||
| 21 | pub peer_id: PeerId, | ||
| 22 | user: Arc<User>, | ||
| 23 | nav_history: Option<ItemNavHistory>, | ||
| 24 | _maintain_frame: Task<Result<()>>, | ||
| 25 | focus: FocusHandle, | ||
| 26 | } | ||
| 27 | |||
| 28 | impl SharedScreen { | ||
| 29 | pub fn new( | ||
| 30 | track: &Arc<RemoteVideoTrack>, | ||
| 31 | peer_id: PeerId, | ||
| 32 | user: Arc<User>, | ||
| 33 | cx: &mut ViewContext<Self>, | ||
| 34 | ) -> Self { | ||
| 35 | cx.focus_handle(); | ||
| 36 | let mut frames = track.frames(); | ||
| 37 | Self { | ||
| 38 | track: Arc::downgrade(track), | ||
| 39 | frame: None, | ||
| 40 | peer_id, | ||
| 41 | user, | ||
| 42 | nav_history: Default::default(), | ||
| 43 | _maintain_frame: cx.spawn(|this, mut cx| async move { | ||
| 44 | while let Some(frame) = frames.next().await { | ||
| 45 | this.update(&mut cx, |this, cx| { | ||
| 46 | this.frame = Some(frame); | ||
| 47 | cx.notify(); | ||
| 48 | })?; | ||
| 49 | } | ||
| 50 | this.update(&mut cx, |_, cx| cx.emit(Event::Close))?; | ||
| 51 | Ok(()) | ||
| 52 | }), | ||
| 53 | focus: cx.focus_handle(), | ||
| 54 | current_frame_id: 0, | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | impl EventEmitter<Event> for SharedScreen {} | ||
| 60 | impl EventEmitter<workspace::item::ItemEvent> for SharedScreen {} | ||
| 61 | |||
| 62 | impl FocusableView for SharedScreen { | ||
| 63 | fn focus_handle(&self, _: &AppContext) -> FocusHandle { | ||
| 64 | self.focus.clone() | ||
| 65 | } | ||
| 66 | } | ||
| 67 | impl Render for SharedScreen { | ||
| 68 | type Element = Div; | ||
| 69 | fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element { | ||
| 70 | let frame = self.frame.clone(); | ||
| 71 | let frame_id = self.current_frame_id; | ||
| 72 | self.current_frame_id = self.current_frame_id.wrapping_add(1); | ||
| 73 | div().children(frame.map(|_| { | ||
| 74 | ui::Label::new(frame_id.to_string()).color(ui::Color::Error) | ||
| 75 | // img().data(Arc::new(ImageData::new(image::ImageBuffer::new( | ||
| 76 | // frame.width() as u32, | ||
| 77 | // frame.height() as u32, | ||
| 78 | // )))) | ||
| 79 | })) | ||
| 80 | } | ||
| 81 | } | ||
| 82 | // impl View for SharedScreen { | ||
| 83 | // fn ui_name() -> &'static str { | ||
| 84 | // "SharedScreen" | ||
| 85 | // } | ||
| 86 | |||
| 87 | // fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { | ||
| 88 | // enum Focus {} | ||
| 89 | |||
| 90 | // let frame = self.frame.clone(); | ||
| 91 | // MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| { | ||
| 92 | // Canvas::new(move |bounds, _, _, cx| { | ||
| 93 | // if let Some(frame) = frame.clone() { | ||
| 94 | // let size = constrain_size_preserving_aspect_ratio( | ||
| 95 | // bounds.size(), | ||
| 96 | // vec2f(frame.width() as f32, frame.height() as f32), | ||
| 97 | // ); | ||
| 98 | // let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.; | ||
| 99 | // cx.scene().push_surface(gpui::platform::mac::Surface { | ||
| 100 | // bounds: RectF::new(origin, size), | ||
| 101 | // image_buffer: frame.image(), | ||
| 102 | // }); | ||
| 103 | // } | ||
| 104 | // }) | ||
| 105 | // .contained() | ||
| 106 | // .with_style(theme::current(cx).shared_screen) | ||
| 107 | // }) | ||
| 108 | // .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent()) | ||
| 109 | // .into_any() | ||
| 110 | // } | ||
| 111 | // } | ||
| 112 | |||
| 113 | impl Item for SharedScreen { | ||
| 114 | fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> { | ||
| 115 | Some(format!("{}'s screen", self.user.github_login).into()) | ||
| 116 | } | ||
| 117 | fn deactivated(&mut self, cx: &mut ViewContext<Self>) { | ||
| 118 | if let Some(nav_history) = self.nav_history.as_mut() { | ||
| 119 | nav_history.push::<()>(None, cx); | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | fn tab_content(&self, _: Option<usize>, _: &WindowContext<'_>) -> gpui::AnyElement { | ||
| 124 | div().child("Shared screen").into_any() | ||
| 125 | // Flex::row() | ||
| 126 | // .with_child( | ||
| 127 | // Svg::new("icons/desktop.svg") | ||
| 128 | // .with_color(style.label.text.color) | ||
| 129 | // .constrained() | ||
| 130 | // .with_width(style.type_icon_width) | ||
| 131 | // .aligned() | ||
| 132 | // .contained() | ||
| 133 | // .with_margin_right(style.spacing), | ||
| 134 | // ) | ||
| 135 | // .with_child( | ||
| 136 | // Label::new( | ||
| 137 | // format!("{}'s screen", self.user.github_login), | ||
| 138 | // style.label.clone(), | ||
| 139 | // ) | ||
| 140 | // .aligned(), | ||
| 141 | // ) | ||
| 142 | // .into_any() | ||
| 143 | } | ||
| 144 | |||
| 145 | fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) { | ||
| 146 | self.nav_history = Some(history); | ||
| 147 | } | ||
| 148 | |||
| 149 | fn clone_on_split( | ||
| 150 | &self, | ||
| 151 | _workspace_id: WorkspaceId, | ||
| 152 | cx: &mut ViewContext<Self>, | ||
| 153 | ) -> Option<View<Self>> { | ||
| 154 | let track = self.track.upgrade()?; | ||
| 155 | Some(cx.build_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx))) | ||
| 156 | } | ||
| 157 | } | ||
| @@ -18,7 +18,7 @@ db = { package = "db2", path = "../db2" } · 17 unmodified lines | |||
| 18 | gpui = { package = "gpui2", path = "../gpui2" } | 18 | gpui = { package = "gpui2", path = "../gpui2" } |
| 19 | util = { path = "../util" } | 19 | util = { path = "../util" } |
| 20 | rpc = { package = "rpc2", path = "../rpc2" } | 20 | rpc = { package = "rpc2", path = "../rpc2" } |
| 21 | text = { path = "../text" } | 21 | text = { package = "text2", path = "../text2" } |
| 22 | language = { package = "language2", path = "../language2" } | 22 | language = { package = "language2", path = "../language2" } |
| 23 | settings = { package = "settings2", path = "../settings2" } | 23 | settings = { package = "settings2", path = "../settings2" } |
| 24 | feature_flags = { package = "feature_flags2", path = "../feature_flags2" } | 24 | feature_flags = { package = "feature_flags2", path = "../feature_flags2" } |
| @@ -109,6 +109,10 @@ pub enum ClickhouseEvent { · 108 unmodified lines | |||
| 109 | virtual_memory_in_bytes: u64, | 109 | virtual_memory_in_bytes: u64, |
| 110 | milliseconds_since_first_event: i64, | 110 | milliseconds_since_first_event: i64, |
| 111 | }, | 111 | }, |
| 112 | App { | ||
| 113 | operation: &'static str, | ||
| 114 | milliseconds_since_first_event: i64, | ||
| 115 | }, | ||
| 112 | } | 116 | } |
| 113 | 117 | ||
| 114 | #[cfg(debug_assertions)] | 118 | #[cfg(debug_assertions)] |
| @@ -168,13 +172,8 @@ impl Telemetry { · 53 unmodified lines | |||
| 168 | let mut state = self.state.lock(); | 172 | let mut state = self.state.lock(); |
| 169 | state.installation_id = installation_id.map(|id| id.into()); | 173 | state.installation_id = installation_id.map(|id| id.into()); |
| 170 | state.session_id = Some(session_id.into()); | 174 | state.session_id = Some(session_id.into()); |
| 171 | let has_clickhouse_events = !state.clickhouse_events_queue.is_empty(); | ||
| 172 | drop(state); | 175 | drop(state); |
| 173 | 176 | ||
| 174 | if has_clickhouse_events { | ||
| 175 | self.flush_clickhouse_events(); | ||
| 176 | } | ||
| 177 | |||
| 178 | let this = self.clone(); | 177 | let this = self.clone(); |
| 179 | cx.spawn(|mut cx| async move { | 178 | cx.spawn(|mut cx| async move { |
| 180 | // Avoiding calling `System::new_all()`, as there have been crashes related to it | 179 | // Avoiding calling `System::new_all()`, as there have been crashes related to it |
| @@ -256,7 +255,7 @@ impl Telemetry { · 75 unmodified lines | |||
| 256 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 255 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 257 | }; | 256 | }; |
| 258 | 257 | ||
| 259 | self.report_clickhouse_event(event, telemetry_settings) | 258 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 260 | } | 259 | } |
| 261 | 260 | ||
| 262 | pub fn report_copilot_event( | 261 | pub fn report_copilot_event( |
| @@ -273,7 +272,7 @@ impl Telemetry { · 10 unmodified lines | |||
| 273 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 272 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 274 | }; | 273 | }; |
| 275 | 274 | ||
| 276 | self.report_clickhouse_event(event, telemetry_settings) | 275 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 277 | } | 276 | } |
| 278 | 277 | ||
| 279 | pub fn report_assistant_event( | 278 | pub fn report_assistant_event( |
| @@ -290,7 +289,7 @@ impl Telemetry { · 10 unmodified lines | |||
| 290 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 289 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 291 | }; | 290 | }; |
| 292 | 291 | ||
| 293 | self.report_clickhouse_event(event, telemetry_settings) | 292 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 294 | } | 293 | } |
| 295 | 294 | ||
| 296 | pub fn report_call_event( | 295 | pub fn report_call_event( |
| @@ -307,7 +306,7 @@ impl Telemetry { · 10 unmodified lines | |||
| 307 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 306 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 308 | }; | 307 | }; |
| 309 | 308 | ||
| 310 | self.report_clickhouse_event(event, telemetry_settings) | 309 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 311 | } | 310 | } |
| 312 | 311 | ||
| 313 | pub fn report_cpu_event( | 312 | pub fn report_cpu_event( |
| @@ -322,7 +321,7 @@ impl Telemetry { · 8 unmodified lines | |||
| 322 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 321 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 323 | }; | 322 | }; |
| 324 | 323 | ||
| 325 | self.report_clickhouse_event(event, telemetry_settings) | 324 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 326 | } | 325 | } |
| 327 | 326 | ||
| 328 | pub fn report_memory_event( | 327 | pub fn report_memory_event( |
| @@ -337,7 +336,21 @@ impl Telemetry { · 8 unmodified lines | |||
| 337 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 336 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 338 | }; | 337 | }; |
| 339 | 338 | ||
| 340 | self.report_clickhouse_event(event, telemetry_settings) | 339 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 340 | } | ||
| 341 | |||
| 342 | // app_events are called at app open and app close, so flush is set to immediately send | ||
| 343 | pub fn report_app_event( | ||
| 344 | self: &Arc<Self>, | ||
| 345 | telemetry_settings: TelemetrySettings, | ||
| 346 | operation: &'static str, | ||
| 347 | ) { | ||
| 348 | let event = ClickhouseEvent::App { | ||
| 349 | operation, | ||
| 350 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | ||
| 351 | }; | ||
| 352 | |||
| 353 | self.report_clickhouse_event(event, telemetry_settings, true) | ||
| 341 | } | 354 | } |
| 342 | 355 | ||
| 343 | fn milliseconds_since_first_event(&self) -> i64 { | 356 | fn milliseconds_since_first_event(&self) -> i64 { |
| @@ -358,6 +371,7 @@ impl Telemetry { · 14 unmodified lines | |||
| 358 | self: &Arc<Self>, | 371 | self: &Arc<Self>, |
| 359 | event: ClickhouseEvent, | 372 | event: ClickhouseEvent, |
| 360 | telemetry_settings: TelemetrySettings, | 373 | telemetry_settings: TelemetrySettings, |
| 374 | immediate_flush: bool, | ||
| 361 | ) { | 375 | ) { |
| 362 | if !telemetry_settings.metrics { | 376 | if !telemetry_settings.metrics { |
| 363 | return; | 377 | return; |
| @@ -370,7 +384,7 @@ impl Telemetry { · 6 unmodified lines | |||
| 370 | .push(ClickhouseEventWrapper { signed_in, event }); | 384 | .push(ClickhouseEventWrapper { signed_in, event }); |
| 371 | 385 | ||
| 372 | if state.installation_id.is_some() { | 386 | if state.installation_id.is_some() { |
| 373 | if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN { | 387 | if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN { |
| 374 | drop(state); | 388 | drop(state); |
| 375 | self.flush_clickhouse_events(); | 389 | self.flush_clickhouse_events(); |
| 376 | } else { | 390 | } else { |
| @@ -382,7 +382,7 @@ impl settings::Settings for TelemetrySettings { · 381 unmodified lines | |||
| 382 | } | 382 | } |
| 383 | 383 | ||
| 384 | impl Client { | 384 | impl Client { |
| 385 | pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> { | 385 | pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> { |
| 386 | Arc::new(Self { | 386 | Arc::new(Self { |
| 387 | id: AtomicU64::new(0), | 387 | id: AtomicU64::new(0), |
| 388 | peer: Peer::new(0), | 388 | peer: Peer::new(0), |
| @@ -551,7 +551,6 @@ impl Client { · 162 unmodified lines | |||
| 551 | F: 'static + Future<Output = Result<()>>, | 551 | F: 'static + Future<Output = Result<()>>, |
| 552 | { | 552 | { |
| 553 | let message_type_id = TypeId::of::<M>(); | 553 | let message_type_id = TypeId::of::<M>(); |
| 554 | |||
| 555 | let mut state = self.state.write(); | 554 | let mut state = self.state.write(); |
| 556 | state | 555 | state |
| 557 | .models_by_message_type | 556 | .models_by_message_type |
| @@ -1,5 +1,6 @@ | |||
| 1 | use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; | 1 | use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; |
| 2 | use chrono::{DateTime, Utc}; | 2 | use chrono::{DateTime, Utc}; |
| 3 | use futures::Future; | ||
| 3 | use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task}; | 4 | use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task}; |
| 4 | use lazy_static::lazy_static; | 5 | use lazy_static::lazy_static; |
| 5 | use parking_lot::Mutex; | 6 | use parking_lot::Mutex; |
| @@ -107,6 +108,10 @@ pub enum ClickhouseEvent { · 101 unmodified lines | |||
| 107 | virtual_memory_in_bytes: u64, | 108 | virtual_memory_in_bytes: u64, |
| 108 | milliseconds_since_first_event: i64, | 109 | milliseconds_since_first_event: i64, |
| 109 | }, | 110 | }, |
| 111 | App { | ||
| 112 | operation: &'static str, | ||
| 113 | milliseconds_since_first_event: i64, | ||
| 114 | }, | ||
| 110 | } | 115 | } |
| 111 | 116 | ||
| 112 | #[cfg(debug_assertions)] | 117 | #[cfg(debug_assertions)] |
| @@ -122,12 +127,13 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); · 9 unmodified lines | |||
| 122 | const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30); | 127 | const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30); |
| 123 | 128 | ||
| 124 | impl Telemetry { | 129 | impl Telemetry { |
| 125 | pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> { | 130 | pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> { |
| 126 | let release_channel = if cx.has_global::<ReleaseChannel>() { | 131 | let release_channel = if cx.has_global::<ReleaseChannel>() { |
| 127 | Some(cx.global::<ReleaseChannel>().display_name()) | 132 | Some(cx.global::<ReleaseChannel>().display_name()) |
| 128 | } else { | 133 | } else { |
| 129 | None | 134 | None |
| 130 | }; | 135 | }; |
| 136 | |||
| 131 | // TODO: Replace all hardware stuff with nested SystemSpecs json | 137 | // TODO: Replace all hardware stuff with nested SystemSpecs json |
| 132 | let this = Arc::new(Self { | 138 | let this = Arc::new(Self { |
| 133 | http_client: client, | 139 | http_client: client, |
| @@ -147,9 +153,30 @@ impl Telemetry { · 13 unmodified lines | |||
| 147 | }), | 153 | }), |
| 148 | }); | 154 | }); |
| 149 | 155 | ||
| 156 | // We should only ever have one instance of Telemetry, leak the subscription to keep it alive | ||
| 157 | // rather than store in TelemetryState, complicating spawn as subscriptions are not Send | ||
| 158 | std::mem::forget(cx.on_app_quit({ | ||
| 159 | let this = this.clone(); | ||
| 160 | move |cx| this.shutdown_telemetry(cx) | ||
| 161 | })); | ||
| 162 | |||
| 150 | this | 163 | this |
| 151 | } | 164 | } |
| 152 | 165 | ||
| 166 | #[cfg(any(test, feature = "test-support"))] | ||
| 167 | fn shutdown_telemetry(self: &Arc<Self>, _: &mut AppContext) -> impl Future<Output = ()> { | ||
| 168 | Task::ready(()) | ||
| 169 | } | ||
| 170 | |||
| 171 | // Skip calling this function in tests. | ||
| 172 | // TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings | ||
| 173 | #[cfg(not(any(test, feature = "test-support")))] | ||
| 174 | fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> { | ||
| 175 | let telemetry_settings = TelemetrySettings::get_global(cx).clone(); | ||
| 176 | self.report_app_event(telemetry_settings, "close"); | ||
| 177 | Task::ready(()) | ||
| 178 | } | ||
| 179 | |||
| 153 | pub fn log_file_path(&self) -> Option<PathBuf> { | 180 | pub fn log_file_path(&self) -> Option<PathBuf> { |
| 154 | Some(self.state.lock().log_file.as_ref()?.path().to_path_buf()) | 181 | Some(self.state.lock().log_file.as_ref()?.path().to_path_buf()) |
| 155 | } | 182 | } |
| @@ -163,13 +190,8 @@ impl Telemetry { · 7 unmodified lines | |||
| 163 | let mut state = self.state.lock(); | 190 | let mut state = self.state.lock(); |
| 164 | state.installation_id = installation_id.map(|id| id.into()); | 191 | state.installation_id = installation_id.map(|id| id.into()); |
| 165 | state.session_id = Some(session_id.into()); | 192 | state.session_id = Some(session_id.into()); |
| 166 | let has_clickhouse_events = !state.clickhouse_events_queue.is_empty(); | ||
| 167 | drop(state); | 193 | drop(state); |
| 168 | 194 | ||
| 169 | if has_clickhouse_events { | ||
| 170 | self.flush_clickhouse_events(); | ||
| 171 | } | ||
| 172 | |||
| 173 | let this = self.clone(); | 195 | let this = self.clone(); |
| 174 | cx.spawn(|cx| async move { | 196 | cx.spawn(|cx| async move { |
| 175 | // Avoiding calling `System::new_all()`, as there have been crashes related to it | 197 | // Avoiding calling `System::new_all()`, as there have been crashes related to it |
| @@ -257,7 +279,7 @@ impl Telemetry { · 81 unmodified lines | |||
| 257 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 279 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 258 | }; | 280 | }; |
| 259 | 281 | ||
| 260 | self.report_clickhouse_event(event, telemetry_settings) | 282 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 261 | } | 283 | } |
| 262 | 284 | ||
| 263 | pub fn report_copilot_event( | 285 | pub fn report_copilot_event( |
| @@ -274,7 +296,7 @@ impl Telemetry { · 10 unmodified lines | |||
| 274 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 296 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 275 | }; | 297 | }; |
| 276 | 298 | ||
| 277 | self.report_clickhouse_event(event, telemetry_settings) | 299 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 278 | } | 300 | } |
| 279 | 301 | ||
| 280 | pub fn report_assistant_event( | 302 | pub fn report_assistant_event( |
| @@ -291,7 +313,7 @@ impl Telemetry { · 10 unmodified lines | |||
| 291 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 313 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 292 | }; | 314 | }; |
| 293 | 315 | ||
| 294 | self.report_clickhouse_event(event, telemetry_settings) | 316 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 295 | } | 317 | } |
| 296 | 318 | ||
| 297 | pub fn report_call_event( | 319 | pub fn report_call_event( |
| @@ -308,7 +330,7 @@ impl Telemetry { · 10 unmodified lines | |||
| 308 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 330 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 309 | }; | 331 | }; |
| 310 | 332 | ||
| 311 | self.report_clickhouse_event(event, telemetry_settings) | 333 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 312 | } | 334 | } |
| 313 | 335 | ||
| 314 | pub fn report_cpu_event( | 336 | pub fn report_cpu_event( |
| @@ -323,7 +345,7 @@ impl Telemetry { · 8 unmodified lines | |||
| 323 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 345 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 324 | }; | 346 | }; |
| 325 | 347 | ||
| 326 | self.report_clickhouse_event(event, telemetry_settings) | 348 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 327 | } | 349 | } |
| 328 | 350 | ||
| 329 | pub fn report_memory_event( | 351 | pub fn report_memory_event( |
| @@ -338,7 +360,21 @@ impl Telemetry { · 8 unmodified lines | |||
| 338 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | 360 | milliseconds_since_first_event: self.milliseconds_since_first_event(), |
| 339 | }; | 361 | }; |
| 340 | 362 | ||
| 341 | self.report_clickhouse_event(event, telemetry_settings) | 363 | self.report_clickhouse_event(event, telemetry_settings, false) |
| 364 | } | ||
| 365 | |||
| 366 | // app_events are called at app open and app close, so flush is set to immediately send | ||
| 367 | pub fn report_app_event( | ||
| 368 | self: &Arc<Self>, | ||
| 369 | telemetry_settings: TelemetrySettings, | ||
| 370 | operation: &'static str, | ||
| 371 | ) { | ||
| 372 | let event = ClickhouseEvent::App { | ||
| 373 | operation, | ||
| 374 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | ||
| 375 | }; | ||
| 376 | |||
| 377 | self.report_clickhouse_event(event, telemetry_settings, true) | ||
| 342 | } | 378 | } |
| 343 | 379 | ||
| 344 | fn milliseconds_since_first_event(&self) -> i64 { | 380 | fn milliseconds_since_first_event(&self) -> i64 { |
| @@ -359,6 +395,7 @@ impl Telemetry { · 14 unmodified lines | |||
| 359 | self: &Arc<Self>, | 395 | self: &Arc<Self>, |
| 360 | event: ClickhouseEvent, | 396 | event: ClickhouseEvent, |
| 361 | telemetry_settings: TelemetrySettings, | 397 | telemetry_settings: TelemetrySettings, |
| 398 | immediate_flush: bool, | ||
| 362 | ) { | 399 | ) { |
| 363 | if !telemetry_settings.metrics { | 400 | if !telemetry_settings.metrics { |
| 364 | return; | 401 | return; |
| @@ -371,7 +408,7 @@ impl Telemetry { · 6 unmodified lines | |||
| 371 | .push(ClickhouseEventWrapper { signed_in, event }); | 408 | .push(ClickhouseEventWrapper { signed_in, event }); |
| 372 | 409 | ||
| 373 | if state.installation_id.is_some() { | 410 | if state.installation_id.is_some() { |
| 374 | if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN { | 411 | if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN { |
| 375 | drop(state); | 412 | drop(state); |
| 376 | self.flush_clickhouse_events(); | 413 | self.flush_clickhouse_events(); |
| 377 | } else { | 414 | } else { |
| @@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"] · 2 unmodified lines | |||
| 3 | default-run = "collab" | 3 | default-run = "collab" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | name = "collab" | 5 | name = "collab" |
| 6 | version = "0.28.0" | 6 | version = "0.29.0" |
| 7 | publish = false | 7 | publish = false |
| 8 | 8 | ||
| 9 | [[bin]] | 9 | [[bin]] |
| @@ -10,7 +10,7 @@ publish = false · 9 unmodified lines | |||
| 10 | name = "collab2" | 10 | name = "collab2" |
| 11 | 11 | ||
| 12 | [[bin]] | 12 | [[bin]] |
| 13 | name = "seed" | 13 | name = "seed2" |
| 14 | required-features = ["seed-support"] | 14 | required-features = ["seed-support"] |
| 15 | 15 | ||
| 16 | [dependencies] | 16 | [dependencies] |
File renamed without changes.
File renamed without changes.
| @@ -149,7 +149,7 @@ impl TestServer { · 148 unmodified lines | |||
| 149 | .user_id | 149 | .user_id |
| 150 | }; | 150 | }; |
| 151 | let client_name = name.to_string(); | 151 | let client_name = name.to_string(); |
| 152 | let mut client = cx.read(|cx| Client::new(http.clone(), cx)); | 152 | let mut client = cx.update(|cx| Client::new(http.clone(), cx)); |
| 153 | let server = self.server.clone(); | 153 | let server = self.server.clone(); |
| 154 | let db = self.app_state.db.clone(); | 154 | let db = self.app_state.db.clone(); |
| 155 | let connection_killers = self.connection_killers.clone(); | 155 | let connection_killers = self.connection_killers.clone(); |
| @@ -221,6 +221,7 @@ impl TestServer { · 65 unmodified lines | |||
| 221 | fs: fs.clone(), | 221 | fs: fs.clone(), |
| 222 | build_window_options: |_, _, _| Default::default(), | 222 | build_window_options: |_, _, _| Default::default(), |
| 223 | node_runtime: FakeNodeRuntime::new(), | 223 | node_runtime: FakeNodeRuntime::new(), |
| 224 | call_factory: |_, _| Box::new(workspace::TestCallHandler), | ||
| 224 | }); | 225 | }); |
| 225 | 226 | ||
| 226 | cx.update(|cx| { | 227 | cx.update(|cx| { |
| @@ -33,7 +33,7 @@ collections = { path = "../collections" } · 32 unmodified lines | |||
| 33 | # drag_and_drop = { path = "../drag_and_drop" } | 33 | # drag_and_drop = { path = "../drag_and_drop" } |
| 34 | editor = { package="editor2", path = "../editor2" } | 34 | editor = { package="editor2", path = "../editor2" } |
| 35 | #feedback = { path = "../feedback" } | 35 | #feedback = { path = "../feedback" } |
| 36 | fuzzy = { path = "../fuzzy" } | 36 | fuzzy = { package = "fuzzy2", path = "../fuzzy2" } |
| 37 | gpui = { package = "gpui2", path = "../gpui2" } | 37 | gpui = { package = "gpui2", path = "../gpui2" } |
| 38 | language = { package = "language2", path = "../language2" } | 38 | language = { package = "language2", path = "../language2" } |
| 39 | menu = { package = "menu2", path = "../menu2" } | 39 | menu = { package = "menu2", path = "../menu2" } |
| @@ -1,5 +1,6 @@ | |||
| 1 | #![allow(unused)] | ||
| 1 | // mod channel_modal; | 2 | // mod channel_modal; |
| 2 | // mod contact_finder; | 3 | mod contact_finder; |
| 3 | 4 | ||
| 4 | // use crate::{ | 5 | // use crate::{ |
| 5 | // channel_view::{self, ChannelView}, | 6 | // channel_view::{self, ChannelView}, |
| @@ -15,7 +16,7 @@ · 9 unmodified lines | |||
| 15 | // proto::{self, PeerId}, | 16 | // proto::{self, PeerId}, |
| 16 | // Client, Contact, User, UserStore, | 17 | // Client, Contact, User, UserStore, |
| 17 | // }; | 18 | // }; |
| 18 | // use contact_finder::ContactFinder; | 19 | use contact_finder::ContactFinder; |
| 19 | // use context_menu::{ContextMenu, ContextMenuItem}; | 20 | // use context_menu::{ContextMenu, ContextMenuItem}; |
| 20 | // use db::kvp::KEY_VALUE_STORE; | 21 | // use db::kvp::KEY_VALUE_STORE; |
| 21 | // use drag_and_drop::{DragAndDrop, Draggable}; | 22 | // use drag_and_drop::{DragAndDrop, Draggable}; |
| @@ -155,20 +156,30 @@ actions!( · 133 unmodified lines | |||
| 155 | 156 | ||
| 156 | const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; | 157 | const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; |
| 157 | 158 | ||
| 158 | use std::sync::Arc; | 159 | use std::{iter::once, mem, sync::Arc}; |
| 159 | 160 | ||
| 161 | use call::ActiveCall; | ||
| 162 | use channel::{Channel, ChannelId, ChannelStore}; | ||
| 163 | use client::{Client, Contact, User, UserStore}; | ||
| 160 | use db::kvp::KEY_VALUE_STORE; | 164 | use db::kvp::KEY_VALUE_STORE; |
| 165 | use editor::Editor; | ||
| 166 | use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; | ||
| 167 | use fuzzy::{match_strings, StringMatchCandidate}; | ||
| 161 | use gpui::{ | 168 | use gpui::{ |
| 162 | actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, | 169 | actions, div, img, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, |
| 163 | Focusable, FocusableView, InteractiveComponent, ParentComponent, Render, View, ViewContext, | 170 | Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, |
| 164 | VisualContext, WeakView, | 171 | RenderOnce, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, |
| 165 | }; | 172 | }; |
| 166 | use project::Fs; | 173 | use project::Fs; |
| 167 | use serde_derive::{Deserialize, Serialize}; | 174 | use serde_derive::{Deserialize, Serialize}; |
| 168 | use settings::Settings; | 175 | use settings::Settings; |
| 169 | use util::ResultExt; | 176 | use ui::{ |
| 177 | h_stack, v_stack, Avatar, Button, Icon, IconButton, Label, List, ListHeader, ListItem, Tooltip, | ||
| 178 | }; | ||
| 179 | use util::{maybe, ResultExt}; | ||
| 170 | use workspace::{ | 180 | use workspace::{ |
| 171 | dock::{DockPosition, Panel, PanelEvent}, | 181 | dock::{DockPosition, Panel, PanelEvent}, |
| 182 | notifications::NotifyResultExt, | ||
| 172 | Workspace, | 183 | Workspace, |
| 173 | }; | 184 | }; |
| 174 | 185 | ||
| @@ -266,26 +277,26 @@ pub fn init(cx: &mut AppContext) { · 91 unmodified lines | |||
| 266 | // ); | 277 | // ); |
| 267 | } | 278 | } |
| 268 | 279 | ||
| 269 | // #[derive(Debug)] | 280 | #[derive(Debug)] |
| 270 | // pub enum ChannelEditingState { | 281 | pub enum ChannelEditingState { |
| 271 | // Create { | 282 | Create { |
| 272 | // location: Option<ChannelId>, | 283 | location: Option<ChannelId>, |
| 273 | // pending_name: Option<String>, | 284 | pending_name: Option<String>, |
| 274 | // }, | 285 | }, |
| 275 | // Rename { | 286 | Rename { |
| 276 | // location: ChannelId, | 287 | location: ChannelId, |
| 277 | // pending_name: Option<String>, | 288 | pending_name: Option<String>, |
| 278 | // }, | 289 | }, |
| 279 | // } | 290 | } |
| 280 | 291 | ||
| 281 | // impl ChannelEditingState { | 292 | impl ChannelEditingState { |
| 282 | // fn pending_name(&self) -> Option<&str> { | 293 | fn pending_name(&self) -> Option<&str> { |
| 283 | // match self { | 294 | match self { |
| 284 | // ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(), | 295 | ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(), |
| 285 | // ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(), | 296 | ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(), |
| 286 | // } | 297 | } |
| 287 | // } | 298 | } |
| 288 | // } | 299 | } |
| 289 | 300 | ||
| 290 | pub struct CollabPanel { | 301 | pub struct CollabPanel { |
| 291 | width: Option<f32>, | 302 | width: Option<f32>, |
| @@ -294,22 +305,22 @@ pub struct CollabPanel { · 2 unmodified lines | |||
| 294 | // channel_clipboard: Option<ChannelMoveClipboard>, | 305 | // channel_clipboard: Option<ChannelMoveClipboard>, |
| 295 | // pending_serialization: Task<Option<()>>, | 306 | // pending_serialization: Task<Option<()>>, |
| 296 | // context_menu: ViewHandle<ContextMenu>, | 307 | // context_menu: ViewHandle<ContextMenu>, |
| 297 | // filter_editor: ViewHandle<Editor>, | 308 | filter_editor: View<Editor>, |
| 298 | // channel_name_editor: ViewHandle<Editor>, | 309 | // channel_name_editor: ViewHandle<Editor>, |
| 299 | // channel_editing_state: Option<ChannelEditingState>, | 310 | channel_editing_state: Option<ChannelEditingState>, |
| 300 | // entries: Vec<ListEntry>, | 311 | entries: Vec<ListEntry>, |
| 301 | // selection: Option<usize>, | 312 | // selection: Option<usize>, |
| 302 | // user_store: ModelHandle<UserStore>, | 313 | channel_store: Model<ChannelStore>, |
| 303 | // client: Arc<Client>, | 314 | user_store: Model<UserStore>, |
| 304 | // channel_store: ModelHandle<ChannelStore>, | 315 | client: Arc<Client>, |
| 305 | // project: ModelHandle<Project>, | 316 | // project: ModelHandle<Project>, |
| 306 | // match_candidates: Vec<StringMatchCandidate>, | 317 | match_candidates: Vec<StringMatchCandidate>, |
| 307 | // list_state: ListState<Self>, | 318 | // list_state: ListState<Self>, |
| 308 | // subscriptions: Vec<Subscription>, | 319 | subscriptions: Vec<Subscription>, |
| 309 | // collapsed_sections: Vec<Section>, | 320 | collapsed_sections: Vec<Section>, |
| 310 | // collapsed_channels: Vec<ChannelId>, | 321 | collapsed_channels: Vec<ChannelId>, |
| 311 | // drag_target_channel: ChannelDragTarget, | 322 | // drag_target_channel: ChannelDragTarget, |
| 312 | _workspace: WeakView<Workspace>, | 323 | workspace: WeakView<Workspace>, |
| 313 | // context_menu_on_selected: bool, | 324 | // context_menu_on_selected: bool, |
| 314 | } | 325 | } |
| 315 | 326 | ||
| @@ -333,58 +344,58 @@ struct SerializedCollabPanel { · 17 unmodified lines | |||
| 333 | // Dismissed, | 344 | // Dismissed, |
| 334 | // } | 345 | // } |
| 335 | 346 | ||
| 336 | // #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] | 347 | #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] |
| 337 | // enum Section { | 348 | enum Section { |
| 338 | // ActiveCall, | 349 | ActiveCall, |
| 339 | // Channels, | 350 | Channels, |
| 340 | // ChannelInvites, | 351 | ChannelInvites, |
| 341 | // ContactRequests, | 352 | ContactRequests, |
| 342 | // Contacts, | 353 | Contacts, |
| 343 | // Online, | 354 | Online, |
| 344 | // Offline, | 355 | Offline, |
| 345 | // } | 356 | } |
| 346 | 357 | ||
| 347 | // #[derive(Clone, Debug)] | 358 | #[derive(Clone, Debug)] |
| 348 | // enum ListEntry { | 359 | enum ListEntry { |
| 349 | // Header(Section), | 360 | Header(Section), |
| 350 | // CallParticipant { | 361 | // CallParticipant { |
| 351 | // user: Arc<User>, | 362 | // user: Arc<User>, |
| 352 | // peer_id: Option<PeerId>, | 363 | // peer_id: Option<PeerId>, |
| 353 | // is_pending: bool, | 364 | // is_pending: bool, |
| 354 | // }, | 365 | // }, |
| 355 | // ParticipantProject { | 366 | // ParticipantProject { |
| 356 | // project_id: u64, | 367 | // project_id: u64, |
| 357 | // worktree_root_names: Vec<String>, | 368 | // worktree_root_names: Vec<String>, |
| 358 | // host_user_id: u64, | 369 | // host_user_id: u64, |
| 359 | // is_last: bool, | 370 | // is_last: bool, |
| 360 | // }, | 371 | // }, |
| 361 | // ParticipantScreen { | 372 | // ParticipantScreen { |
| 362 | // peer_id: Option<PeerId>, | 373 | // peer_id: Option<PeerId>, |
| 363 | // is_last: bool, | 374 | // is_last: bool, |
| 364 | // }, | 375 | // }, |
| 365 | // IncomingRequest(Arc<User>), | 376 | IncomingRequest(Arc<User>), |
| 366 | // OutgoingRequest(Arc<User>), | 377 | OutgoingRequest(Arc<User>), |
| 367 | // ChannelInvite(Arc<Channel>), | 378 | // ChannelInvite(Arc<Channel>), |
| 368 | // Channel { | 379 | Channel { |
| 369 | // channel: Arc<Channel>, | 380 | channel: Arc<Channel>, |
| 370 | // depth: usize, | 381 | depth: usize, |
| 371 | // has_children: bool, | 382 | has_children: bool, |
| 372 | // }, | 383 | }, |
| 373 | // ChannelNotes { | 384 | // ChannelNotes { |
| 374 | // channel_id: ChannelId, | 385 | // channel_id: ChannelId, |
| 375 | // }, | 386 | // }, |
| 376 | // ChannelChat { | 387 | // ChannelChat { |
| 377 | // channel_id: ChannelId, | 388 | // channel_id: ChannelId, |
| 378 | // }, | 389 | // }, |
| 379 | // ChannelEditor { | 390 | ChannelEditor { |
| 380 | // depth: usize, | 391 | depth: usize, |
| 381 | // }, | 392 | }, |
| 382 | // Contact { | 393 | Contact { |
| 383 | // contact: Arc<Contact>, | 394 | contact: Arc<Contact>, |
| 384 | // calling: bool, | 395 | calling: bool, |
| 385 | // }, | 396 | }, |
| 386 | // ContactPlaceholder, | 397 | ContactPlaceholder, |
| 387 | // } | 398 | } |
| 388 | 399 | ||
| 389 | // impl Entity for CollabPanel { | 400 | // impl Entity for CollabPanel { |
| 390 | // type Event = Event; | 401 | // type Event = Event; |
| @@ -395,16 +406,11 @@ impl CollabPanel { · 4 unmodified lines | |||
| 395 | cx.build_view(|cx| { | 406 | cx.build_view(|cx| { |
| 396 | // let view_id = cx.view_id(); | 407 | // let view_id = cx.view_id(); |
| 397 | 408 | ||
| 398 | // let filter_editor = cx.add_view(|cx| { | 409 | let filter_editor = cx.build_view(|cx| { |
| 399 | // let mut editor = Editor::single_line( | 410 | let mut editor = Editor::single_line(cx); |
| 400 | // Some(Arc::new(|theme| { | 411 | editor.set_placeholder_text("Filter channels, contacts", cx); |
| 401 | // theme.collab_panel.user_query_editor.clone() | 412 | editor |
| 402 | // })), | 413 | }); |
| 403 | // cx, | ||
| 404 | // ); | ||
| 405 | // editor.set_placeholder_text("Filter channels, contacts", cx); | ||
| 406 | // editor | ||
| 407 | // }); | ||
| 408 | 414 | ||
| 409 | // cx.subscribe(&filter_editor, |this, _, event, cx| { | 415 | // cx.subscribe(&filter_editor, |this, _, event, cx| { |
| 410 | // if let editor::Event::BufferEdited = event { | 416 | // if let editor::Event::BufferEdited = event { |
| @@ -583,7 +589,7 @@ impl CollabPanel { · 172 unmodified lines | |||
| 583 | // } | 589 | // } |
| 584 | // }); | 590 | // }); |
| 585 | 591 | ||
| 586 | let this = Self { | 592 | let mut this = Self { |
| 587 | width: None, | 593 | width: None, |
| 588 | focus_handle: cx.focus_handle(), | 594 | focus_handle: cx.focus_handle(), |
| 589 | // channel_clipboard: None, | 595 | // channel_clipboard: None, |
| @@ -591,25 +597,25 @@ impl CollabPanel { · 1 unmodified line | |||
| 591 | // pending_serialization: Task::ready(None), | 597 | // pending_serialization: Task::ready(None), |
| 592 | // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), | 598 | // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), |
| 593 | // channel_name_editor, | 599 | // channel_name_editor, |
| 594 | // filter_editor, | 600 | filter_editor, |
| 595 | // entries: Vec::default(), | 601 | entries: Vec::default(), |
| 596 | // channel_editing_state: None, | 602 | channel_editing_state: None, |
| 597 | // selection: None, | 603 | // selection: None, |
| 598 | // user_store: workspace.user_store().clone(), | 604 | channel_store: ChannelStore::global(cx), |
| 599 | // channel_store: ChannelStore::global(cx), | 605 | user_store: workspace.user_store().clone(), |
| 600 | // project: workspace.project().clone(), | 606 | // project: workspace.project().clone(), |
| 601 | // subscriptions: Vec::default(), | 607 | subscriptions: Vec::default(), |
| 602 | // match_candidates: Vec::default(), | 608 | match_candidates: Vec::default(), |
| 603 | // collapsed_sections: vec![Section::Offline], | 609 | collapsed_sections: vec![Section::Offline], |
| 604 | // collapsed_channels: Vec::default(), | 610 | collapsed_channels: Vec::default(), |
| 605 | _workspace: workspace.weak_handle(), | 611 | workspace: workspace.weak_handle(), |
| 606 | // client: workspace.app_state().client.clone(), | 612 | client: workspace.app_state().client.clone(), |
| 607 | // context_menu_on_selected: true, | 613 | // context_menu_on_selected: true, |
| 608 | // drag_target_channel: ChannelDragTarget::None, | 614 | // drag_target_channel: ChannelDragTarget::None, |
| 609 | // list_state, | 615 | // list_state, |
| 610 | }; | 616 | }; |
| 611 | 617 | ||
| 612 | // this.update_entries(false, cx); | 618 | this.update_entries(false, cx); |
| 613 | 619 | ||
| 614 | // // Update the dock position when the setting changes. | 620 | // // Update the dock position when the setting changes. |
| 615 | // let mut old_dock_position = this.position(cx); | 621 | // let mut old_dock_position = this.position(cx); |
| @@ -626,10 +632,10 @@ impl CollabPanel { · 10 unmodified lines | |||
| 626 | // ); | 632 | // ); |
| 627 | 633 | ||
| 628 | // let active_call = ActiveCall::global(cx); | 634 | // let active_call = ActiveCall::global(cx); |
| 629 | // this.subscriptions | 635 | this.subscriptions |
| 630 | // .push(cx.observe(&this.user_store, |this, _, cx| { | 636 | .push(cx.observe(&this.user_store, |this, _, cx| { |
| 631 | // this.update_entries(true, cx) | 637 | this.update_entries(true, cx) |
| 632 | // })); | 638 | })); |
| 633 | // this.subscriptions | 639 | // this.subscriptions |
| 634 | // .push(cx.observe(&this.channel_store, |this, _, cx| { | 640 | // .push(cx.observe(&this.channel_store, |this, _, cx| { |
| 635 | // this.update_entries(true, cx) | 641 | // this.update_entries(true, cx) |
| @@ -663,6 +669,9 @@ impl CollabPanel { · 27 unmodified lines | |||
| 663 | }) | 669 | }) |
| 664 | } | 670 | } |
| 665 | 671 | ||
| 672 | fn contacts(&self, cx: &AppContext) -> Option<Vec<Arc<Contact>>> { | ||
| 673 | Some(self.user_store.read(cx).contacts().to_owned()) | ||
| 674 | } | ||
| 666 | pub async fn load( | 675 | pub async fn load( |
| 667 | workspace: WeakView<Workspace>, | 676 | workspace: WeakView<Workspace>, |
| 668 | mut cx: AsyncWindowContext, | 677 | mut cx: AsyncWindowContext, |
| @@ -715,449 +724,449 @@ impl CollabPanel { · 46 unmodified lines | |||
| 715 | // ); | 724 | // ); |
| 716 | // } | 725 | // } |
| 717 | 726 | ||
| 718 | // fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) { | 727 | fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) { |
| 719 | // let channel_store = self.channel_store.read(cx); | 728 | let channel_store = self.channel_store.read(cx); |
| 720 | // let user_store = self.user_store.read(cx); | 729 | let user_store = self.user_store.read(cx); |
| 721 | // let query = self.filter_editor.read(cx).text(cx); | 730 | let query = self.filter_editor.read(cx).text(cx); |
| 722 | // let executor = cx.background().clone(); | 731 | let executor = cx.background_executor().clone(); |
| 723 | 732 | ||
| 724 | // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned()); | 733 | // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned()); |
| 725 | // let old_entries = mem::take(&mut self.entries); | 734 | let _old_entries = mem::take(&mut self.entries); |
| 726 | // let mut scroll_to_top = false; | 735 | // let mut scroll_to_top = false; |
| 727 | 736 | ||
| 728 | // if let Some(room) = ActiveCall::global(cx).read(cx).room() { | 737 | // if let Some(room) = ActiveCall::global(cx).read(cx).room() { |
| 729 | // self.entries.push(ListEntry::Header(Section::ActiveCall)); | 738 | // self.entries.push(ListEntry::Header(Section::ActiveCall)); |
| 730 | // if !old_entries | 739 | // if !old_entries |
| 731 | // .iter() | 740 | // .iter() |
| 732 | // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall))) | 741 | // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall))) |
| 733 | // { | 742 | // { |
| 734 | // scroll_to_top = true; | 743 | // scroll_to_top = true; |
| 735 | // } | 744 | // } |
| 736 | 745 | ||
| 737 | // if !self.collapsed_sections.contains(&Section::ActiveCall) { | 746 | // if !self.collapsed_sections.contains(&Section::ActiveCall) { |
| 738 | // let room = room.read(cx); | 747 | // let room = room.read(cx); |
| 739 | 748 | ||
| 740 | // if let Some(channel_id) = room.channel_id() { | 749 | // if let Some(channel_id) = room.channel_id() { |
| 741 | // self.entries.push(ListEntry::ChannelNotes { channel_id }); | 750 | // self.entries.push(ListEntry::ChannelNotes { channel_id }); |
| 742 | // self.entries.push(ListEntry::ChannelChat { channel_id }) | 751 | // self.entries.push(ListEntry::ChannelChat { channel_id }) |
| 743 | // } | 752 | // } |
| 744 | 753 | ||
| 745 | // // Populate the active user. | 754 | // // Populate the active user. |
| 746 | // if let Some(user) = user_store.current_user() { | 755 | // if let Some(user) = user_store.current_user() { |
| 747 | // self.match_candidates.clear(); | 756 | // self.match_candidates.clear(); |
| 748 | // self.match_candidates.push(StringMatchCandidate { | 757 | // self.match_candidates.push(StringMatchCandidate { |
| 749 | // id: 0, | 758 | // id: 0, |
| 750 | // string: user.github_login.clone(), | 759 | // string: user.github_login.clone(), |
| 751 | // char_bag: user.github_login.chars().collect(), | 760 | // char_bag: user.github_login.chars().collect(), |
| 752 | // }); | 761 | // }); |
| 753 | // let matches = executor.block(match_strings( | 762 | // let matches = executor.block(match_strings( |
| 754 | // &self.match_candidates, | 763 | // &self.match_candidates, |
| 755 | // &query, | 764 | // &query, |
| 756 | // true, | 765 | // true, |
| 757 | // usize::MAX, | 766 | // usize::MAX, |
| 758 | // &Default::default(), | 767 | // &Default::default(), |
| 759 | // executor.clone(), | 768 | // executor.clone(), |
| 760 | // )); | 769 | // )); |
| 761 | // if !matches.is_empty() { | 770 | // if !matches.is_empty() { |
| 762 | // let user_id = user.id; | 771 | // let user_id = user.id; |
| 763 | // self.entries.push(ListEntry::CallParticipant { | 772 | // self.entries.push(ListEntry::CallParticipant { |
| 764 | // user, | 773 | // user, |
| 765 | // peer_id: None, | 774 | // peer_id: None, |
| 766 | // is_pending: false, | 775 | // is_pending: false, |
| 767 | // }); | 776 | // }); |
| 768 | // let mut projects = room.local_participant().projects.iter().peekable(); | 777 | // let mut projects = room.local_participant().projects.iter().peekable(); |
| 769 | // while let Some(project) = projects.next() { | 778 | // while let Some(project) = projects.next() { |
| 770 | // self.entries.push(ListEntry::ParticipantProject { | 779 | // self.entries.push(ListEntry::ParticipantProject { |
| 771 | // project_id: project.id, | 780 | // project_id: project.id, |
| 772 | // worktree_root_names: project.worktree_root_names.clone(), | 781 | // worktree_root_names: project.worktree_root_names.clone(), |
| 773 | // host_user_id: user_id, | 782 | // host_user_id: user_id, |
| 774 | // is_last: projects.peek().is_none() && !room.is_screen_sharing(), | 783 | // is_last: projects.peek().is_none() && !room.is_screen_sharing(), |
| 775 | // }); | 784 | // }); |
| 776 | // } | 785 | // } |
| 777 | // if room.is_screen_sharing() { | 786 | // if room.is_screen_sharing() { |
| 778 | // self.entries.push(ListEntry::ParticipantScreen { | 787 | // self.entries.push(ListEntry::ParticipantScreen { |
| 779 | // peer_id: None, | 788 | // peer_id: None, |
| 780 | // is_last: true, | 789 | // is_last: true, |
| 781 | // }); | 790 | // }); |
| 782 | // } | 791 | // } |
| 783 | // } | 792 | // } |
| 784 | // } | 793 | // } |
| 785 | 794 | ||
| 786 | // // Populate remote participants. | 795 | // // Populate remote participants. |
| 787 | // self.match_candidates.clear(); | 796 | // self.match_candidates.clear(); |
| 788 | // self.match_candidates | 797 | // self.match_candidates |
| 789 | // .extend(room.remote_participants().iter().map(|(_, participant)| { | 798 | // .extend(room.remote_participants().iter().map(|(_, participant)| { |
| 790 | // StringMatchCandidate { | 799 | // StringMatchCandidate { |
| 791 | // id: participant.user.id as usize, | 800 | // id: participant.user.id as usize, |
| 792 | // string: participant.user.github_login.clone(), | 801 | // string: participant.user.github_login.clone(), |
| 793 | // char_bag: participant.user.github_login.chars().collect(), | 802 | // char_bag: participant.user.github_login.chars().collect(), |
| 794 | // } | 803 | // } |
| 795 | // })); | 804 | // })); |
| 796 | // let matches = executor.block(match_strings( | 805 | // let matches = executor.block(match_strings( |
| 797 | // &self.match_candidates, | 806 | // &self.match_candidates, |
| 798 | // &query, | 807 | // &query, |
| 799 | // true, | 808 | // true, |
| 800 | // usize::MAX, | 809 | // usize::MAX, |
| 801 | // &Default::default(), | 810 | // &Default::default(), |
| 802 | // executor.clone(), | 811 | // executor.clone(), |
| 803 | // )); | 812 | // )); |
| 804 | // for mat in matches { | 813 | // for mat in matches { |
| 805 | // let user_id = mat.candidate_id as u64; | 814 | // let user_id = mat.candidate_id as u64; |
| 806 | // let participant = &room.remote_participants()[&user_id]; | 815 | // let participant = &room.remote_participants()[&user_id]; |
| 807 | // self.entries.push(ListEntry::CallParticipant { | 816 | // self.entries.push(ListEntry::CallParticipant { |
| 808 | // user: participant.user.clone(), | 817 | // user: participant.user.clone(), |
| 809 | // peer_id: Some(participant.peer_id), | 818 | // peer_id: Some(participant.peer_id), |
| 810 | // is_pending: false, | 819 | // is_pending: false, |
| 811 | // }); | 820 | // }); |
| 812 | // let mut projects = participant.projects.iter().peekable(); | 821 | // let mut projects = participant.projects.iter().peekable(); |
| 813 | // while let Some(project) = projects.next() { | 822 | // while let Some(project) = projects.next() { |
| 814 | // self.entries.push(ListEntry::ParticipantProject { | 823 | // self.entries.push(ListEntry::ParticipantProject { |
| 815 | // project_id: project.id, | 824 | // project_id: project.id, |
| 816 | // worktree_root_names: project.worktree_root_names.clone(), | 825 | // worktree_root_names: project.worktree_root_names.clone(), |
| 817 | // host_user_id: participant.user.id, | 826 | // host_user_id: participant.user.id, |
| 818 | // is_last: projects.peek().is_none() | 827 | // is_last: projects.peek().is_none() |
| 819 | // && participant.video_tracks.is_empty(), | 828 | // && participant.video_tracks.is_empty(), |
| 820 | // }); | 829 | // }); |
| 821 | // } | 830 | // } |
| 822 | // if !participant.video_tracks.is_empty() { | 831 | // if !participant.video_tracks.is_empty() { |
| 823 | // self.entries.push(ListEntry::ParticipantScreen { | 832 | // self.entries.push(ListEntry::ParticipantScreen { |
| 824 | // peer_id: Some(participant.peer_id), | 833 | // peer_id: Some(participant.peer_id), |
| 825 | // is_last: true, | 834 | // is_last: true, |
| 826 | // }); | 835 | // }); |
| 827 | // } | 836 | // } |
| 828 | // } | 837 | // } |
| 829 | 838 | ||
| 830 | // // Populate pending participants. | 839 | // // Populate pending participants. |
| 831 | // self.match_candidates.clear(); | 840 | // self.match_candidates.clear(); |
| 832 | // self.match_candidates | 841 | // self.match_candidates |
| 833 | // .extend(room.pending_participants().iter().enumerate().map( | 842 | // .extend(room.pending_participants().iter().enumerate().map( |
| 834 | // |(id, participant)| StringMatchCandidate { | 843 | // |(id, participant)| StringMatchCandidate { |
| 835 | // id, | 844 | // id, |
| 836 | // string: participant.github_login.clone(), | 845 | // string: participant.github_login.clone(), |
| 837 | // char_bag: participant.github_login.chars().collect(), | 846 | // char_bag: participant.github_login.chars().collect(), |
| 838 | // }, | 847 | // }, |
| 839 | // )); | 848 | // )); |
| 840 | // let matches = executor.block(match_strings( | 849 | // let matches = executor.block(match_strings( |
| 841 | // &self.match_candidates, | 850 | // &self.match_candidates, |
| 842 | // &query, | 851 | // &query, |
| 843 | // true, | 852 | // true, |
| 844 | // usize::MAX, | 853 | // usize::MAX, |
| 845 | // &Default::default(), | 854 | // &Default::default(), |
| 846 | // executor.clone(), | 855 | // executor.clone(), |
| 847 | // )); | 856 | // )); |
| 848 | // self.entries | 857 | // self.entries |
| 849 | // .extend(matches.iter().map(|mat| ListEntry::CallParticipant { | 858 | // .extend(matches.iter().map(|mat| ListEntry::CallParticipant { |
| 850 | // user: room.pending_participants()[mat.candidate_id].clone(), | 859 | // user: room.pending_participants()[mat.candidate_id].clone(), |
| 851 | // peer_id: None, | 860 | // peer_id: None, |
| 852 | // is_pending: true, | 861 | // is_pending: true, |
| 853 | // })); | 862 | // })); |
| 854 | // } | 863 | // } |
| 855 | // } | 864 | // } |
| 856 | 865 | ||
| 857 | // let mut request_entries = Vec::new(); | 866 | let mut request_entries = Vec::new(); |
| 858 | 867 | ||
| 859 | // if cx.has_flag::<ChannelsAlpha>() { | 868 | if cx.has_flag::<ChannelsAlpha>() { |
| 860 | // self.entries.push(ListEntry::Header(Section::Channels)); | 869 | self.entries.push(ListEntry::Header(Section::Channels)); |
| 861 | 870 | ||
| 862 | // if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { | 871 | if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { |
| 863 | // self.match_candidates.clear(); | 872 | self.match_candidates.clear(); |
| 864 | // self.match_candidates | 873 | self.match_candidates |
| 865 | // .extend(channel_store.ordered_channels().enumerate().map( | 874 | .extend(channel_store.ordered_channels().enumerate().map( |
| 866 | // |(ix, (_, channel))| StringMatchCandidate { | 875 | |(ix, (_, channel))| StringMatchCandidate { |
| 867 | // id: ix, | 876 | id: ix, |
| 868 | // string: channel.name.clone(), | 877 | string: channel.name.clone(), |
| 869 | // char_bag: channel.name.chars().collect(), | 878 | char_bag: channel.name.chars().collect(), |
| 870 | // }, | 879 | }, |
| 871 | // )); | 880 | )); |
| 872 | // let matches = executor.block(match_strings( | 881 | let matches = executor.block(match_strings( |
| 873 | // &self.match_candidates, | 882 | &self.match_candidates, |
| 874 | // &query, | 883 | &query, |
| 875 | // true, | 884 | true, |
| 876 | // usize::MAX, | 885 | usize::MAX, |
| 877 | // &Default::default(), | 886 | &Default::default(), |
| 878 | // executor.clone(), | 887 | executor.clone(), |
| 879 | // )); | 888 | )); |
| 880 | // if let Some(state) = &self.channel_editing_state { | 889 | if let Some(state) = &self.channel_editing_state { |
| 881 | // if matches!(state, ChannelEditingState::Create { location: None, .. }) { | 890 | if matches!(state, ChannelEditingState::Create { location: None, .. }) { |
| 882 | // self.entries.push(ListEntry::ChannelEditor { depth: 0 }); | 891 | self.entries.push(ListEntry::ChannelEditor { depth: 0 }); |
| 883 | // } | 892 | } |
| 884 | // } | 893 | } |
| 885 | // let mut collapse_depth = None; | 894 | let mut collapse_depth = None; |
| 886 | // for mat in matches { | 895 | for mat in matches { |
| 887 | // let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); | 896 | let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); |
| 888 | // let depth = channel.parent_path.len(); | 897 | let depth = channel.parent_path.len(); |
| 889 | 898 | ||
| 890 | // if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { | 899 | if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { |
| 891 | // collapse_depth = Some(depth); | 900 | collapse_depth = Some(depth); |
| 892 | // } else if let Some(collapsed_depth) = collapse_depth { | 901 | } else if let Some(collapsed_depth) = collapse_depth { |
| 893 | // if depth > collapsed_depth { | 902 | if depth > collapsed_depth { |
| 894 | // continue; | 903 | continue; |
| 895 | // } | 904 | } |
| 896 | // if self.is_channel_collapsed(channel.id) { | 905 | if self.is_channel_collapsed(channel.id) { |
| 897 | // collapse_depth = Some(depth); | 906 | collapse_depth = Some(depth); |
| 898 | // } else { | 907 | } else { |
| 899 | // collapse_depth = None; | 908 | collapse_depth = None; |
| 900 | // } | 909 | } |
| 901 | // } | 910 | } |
| 902 | 911 | ||
| 903 | // let has_children = channel_store | 912 | let has_children = channel_store |
| 904 | // .channel_at_index(mat.candidate_id + 1) | 913 | .channel_at_index(mat.candidate_id + 1) |
| 905 | // .map_or(false, |next_channel| { | 914 | .map_or(false, |next_channel| { |
| 906 | // next_channel.parent_path.ends_with(&[channel.id]) | 915 | next_channel.parent_path.ends_with(&[channel.id]) |
| 907 | // }); | 916 | }); |
| 908 | 917 | ||
| 909 | // match &self.channel_editing_state { | 918 | match &self.channel_editing_state { |
| 910 | // Some(ChannelEditingState::Create { | 919 | Some(ChannelEditingState::Create { |
| 911 | // location: parent_id, | 920 | location: parent_id, |
| 912 | // .. | 921 | .. |
| 913 | // }) if *parent_id == Some(channel.id) => { | 922 | }) if *parent_id == Some(channel.id) => { |
| 914 | // self.entries.push(ListEntry::Channel { | 923 | self.entries.push(ListEntry::Channel { |
| 915 | // channel: channel.clone(), | 924 | channel: channel.clone(), |
| 916 | // depth, | 925 | depth, |
| 917 | // has_children: false, | 926 | has_children: false, |
| 918 | // }); | 927 | }); |
| 919 | // self.entries | 928 | self.entries |
| 920 | // .push(ListEntry::ChannelEditor { depth: depth + 1 }); | 929 | .push(ListEntry::ChannelEditor { depth: depth + 1 }); |
| 921 | // } | 930 | } |
| 922 | // Some(ChannelEditingState::Rename { | 931 | Some(ChannelEditingState::Rename { |
| 923 | // location: parent_id, | 932 | location: parent_id, |
| 924 | // .. | 933 | .. |
| 925 | // }) if parent_id == &channel.id => { | 934 | }) if parent_id == &channel.id => { |
| 926 | // self.entries.push(ListEntry::ChannelEditor { depth }); | 935 | self.entries.push(ListEntry::ChannelEditor { depth }); |
| 927 | // } | 936 | } |
| 928 | // _ => { | 937 | _ => { |
| 929 | // self.entries.push(ListEntry::Channel { | 938 | self.entries.push(ListEntry::Channel { |
| 930 | // channel: channel.clone(), | 939 | channel: channel.clone(), |
| 931 | // depth, | 940 | depth, |
| 932 | // has_children, | 941 | has_children, |
| 933 | // }); | 942 | }); |
| 934 | // } | 943 | } |
| 935 | // } | 944 | } |
| 936 | // } | 945 | } |
| 937 | // } | 946 | } |
| 938 | |||
| 939 | // let channel_invites = channel_store.channel_invitations(); | ||
| 940 | // if !channel_invites.is_empty() { | ||
| 941 | // self.match_candidates.clear(); | ||
| 942 | // self.match_candidates | ||
| 943 | // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { | ||
| 944 | // StringMatchCandidate { | ||
| 945 | // id: ix, | ||
| 946 | // string: channel.name.clone(), | ||
| 947 | // char_bag: channel.name.chars().collect(), | ||
| 948 | // } | ||
| 949 | // })); | ||
| 950 | // let matches = executor.block(match_strings( | ||
| 951 | // &self.match_candidates, | ||
| 952 | // &query, | ||
| 953 | // true, | ||
| 954 | // usize::MAX, | ||
| 955 | // &Default::default(), | ||
| 956 | // executor.clone(), | ||
| 957 | // )); | ||
| 958 | // request_entries.extend(matches.iter().map(|mat| { | ||
| 959 | // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) | ||
| 960 | // })); | ||
| 961 | |||
| 962 | // if !request_entries.is_empty() { | ||
| 963 | // self.entries | ||
| 964 | // .push(ListEntry::Header(Section::ChannelInvites)); | ||
| 965 | // if !self.collapsed_sections.contains(&Section::ChannelInvites) { | ||
| 966 | // self.entries.append(&mut request_entries); | ||
| 967 | // } | ||
| 968 | // } | ||
| 969 | // } | ||
| 970 | // } | ||
| 971 | |||
| 972 | // self.entries.push(ListEntry::Header(Section::Contacts)); | ||
| 973 | |||
| 974 | // request_entries.clear(); | ||
| 975 | // let incoming = user_store.incoming_contact_requests(); | ||
| 976 | // if !incoming.is_empty() { | ||
| 977 | // self.match_candidates.clear(); | ||
| 978 | // self.match_candidates | ||
| 979 | // .extend( | ||
| 980 | // incoming | ||
| 981 | // .iter() | ||
| 982 | // .enumerate() | ||
| 983 | // .map(|(ix, user)| StringMatchCandidate { | ||
| 984 | // id: ix, | ||
| 985 | // string: user.github_login.clone(), | ||
| 986 | // char_bag: user.github_login.chars().collect(), | ||
| 987 | // }), | ||
| 988 | // ); | ||
| 989 | // let matches = executor.block(match_strings( | ||
| 990 | // &self.match_candidates, | ||
| 991 | // &query, | ||
| 992 | // true, | ||
| 993 | // usize::MAX, | ||
| 994 | // &Default::default(), | ||
| 995 | // executor.clone(), | ||
| 996 | // )); | ||
| 997 | // request_entries.extend( | ||
| 998 | // matches | ||
| 999 | // .iter() | ||
| 1000 | // .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())), | ||
| 1001 | // ); | ||
| 1002 | // } | ||
| 1003 | |||
| 1004 | // let outgoing = user_store.outgoing_contact_requests(); | ||
| 1005 | // if !outgoing.is_empty() { | ||
| 1006 | // self.match_candidates.clear(); | ||
| 1007 | // self.match_candidates | ||
| 1008 | // .extend( | ||
| 1009 | // outgoing | ||
| 1010 | // .iter() | ||
| 1011 | // .enumerate() | ||
| 1012 | // .map(|(ix, user)| StringMatchCandidate { | ||
| 1013 | // id: ix, | ||
| 1014 | // string: user.github_login.clone(), | ||
| 1015 | // char_bag: user.github_login.chars().collect(), | ||
| 1016 | // }), | ||
| 1017 | // ); | ||
| 1018 | // let matches = executor.block(match_strings( | ||
| 1019 | // &self.match_candidates, | ||
| 1020 | // &query, | ||
| 1021 | // true, | ||
| 1022 | // usize::MAX, | ||
| 1023 | // &Default::default(), | ||
| 1024 | // executor.clone(), | ||
| 1025 | // )); | ||
| 1026 | // request_entries.extend( | ||
| 1027 | // matches | ||
| 1028 | // .iter() | ||
| 1029 | // .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())), | ||
| 1030 | // ); | ||
| 1031 | // } | ||
| 1032 | |||
| 1033 | // if !request_entries.is_empty() { | ||
| 1034 | // self.entries | ||
| 1035 | // .push(ListEntry::Header(Section::ContactRequests)); | ||
| 1036 | // if !self.collapsed_sections.contains(&Section::ContactRequests) { | ||
| 1037 | // self.entries.append(&mut request_entries); | ||
| 1038 | // } | ||
| 1039 | // } | ||
| 1040 | |||
| 1041 | // let contacts = user_store.contacts(); | ||
| 1042 | // if !contacts.is_empty() { | ||
| 1043 | // self.match_candidates.clear(); | ||
| 1044 | // self.match_candidates | ||
| 1045 | // .extend( | ||
| 1046 | // contacts | ||
| 1047 | // .iter() | ||
| 1048 | // .enumerate() | ||
| 1049 | // .map(|(ix, contact)| StringMatchCandidate { | ||
| 1050 | // id: ix, | ||
| 1051 | // string: contact.user.github_login.clone(), | ||
| 1052 | // char_bag: contact.user.github_login.chars().collect(), | ||
| 1053 | // }), | ||
| 1054 | // ); | ||
| 1055 | |||
| 1056 | // let matches = executor.block(match_strings( | ||
| 1057 | // &self.match_candidates, | ||
| 1058 | // &query, | ||
| 1059 | // true, | ||
| 1060 | // usize::MAX, | ||
| 1061 | // &Default::default(), | ||
| 1062 | // executor.clone(), | ||
| 1063 | // )); | ||
| 1064 | |||
| 1065 | // let (online_contacts, offline_contacts) = matches | ||
| 1066 | // .iter() | ||
| 1067 | // .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online); | ||
| 1068 | |||
| 1069 | // for (matches, section) in [ | ||
| 1070 | // (online_contacts, Section::Online), | ||
| 1071 | // (offline_contacts, Section::Offline), | ||
| 1072 | // ] { | ||
| 1073 | // if !matches.is_empty() { | ||
| 1074 | // self.entries.push(ListEntry::Header(section)); | ||
| 1075 | // if !self.collapsed_sections.contains(§ion) { | ||
| 1076 | // let active_call = &ActiveCall::global(cx).read(cx); | ||
| 1077 | // for mat in matches { | ||
| 1078 | // let contact = &contacts[mat.candidate_id]; | ||
| 1079 | // self.entries.push(ListEntry::Contact { | ||
| 1080 | // contact: contact.clone(), | ||
| 1081 | // calling: active_call.pending_invites().contains(&contact.user.id), | ||
| 1082 | // }); | ||
| 1083 | // } | ||
| 1084 | // } | ||
| 1085 | // } | ||
| 1086 | // } | ||
| 1087 | // } | ||
| 1088 | |||
| 1089 | // if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() { | ||
| 1090 | // self.entries.push(ListEntry::ContactPlaceholder); | ||
| 1091 | // } | ||
| 1092 | |||
| 1093 | // if select_same_item { | ||
| 1094 | // if let Some(prev_selected_entry) = prev_selected_entry { | ||
| 1095 | // self.selection.take(); | ||
| 1096 | // for (ix, entry) in self.entries.iter().enumerate() { | ||
| 1097 | // if *entry == prev_selected_entry { | ||
| 1098 | // self.selection = Some(ix); | ||
| 1099 | // break; | ||
| 1100 | // } | ||
| 1101 | // } | ||
| 1102 | // } | ||
| 1103 | // } else { | ||
| 1104 | // self.selection = self.selection.and_then(|prev_selection| { | ||
| 1105 | // if self.entries.is_empty() { | ||
| 1106 | // None | ||
| 1107 | // } else { | ||
| 1108 | // Some(prev_selection.min(self.entries.len() - 1)) | ||
| 1109 | // } | ||
| 1110 | // }); | ||
| 1111 | // } | ||
| 1112 | |||
| 1113 | // let old_scroll_top = self.list_state.logical_scroll_top(); | ||
| 1114 | |||
| 1115 | // self.list_state.reset(self.entries.len()); | ||
| 1116 | 947 | ||
| 1117 | // if scroll_to_top { | 948 | // let channel_invites = channel_store.channel_invitations(); |
| 1118 | // self.list_state.scroll_to(ListOffset::default()); | 949 | // if !channel_invites.is_empty() { |
| 1119 | // } else { | 950 | // self.match_candidates.clear(); |
| 1120 | // // Attempt to maintain the same scroll position. | 951 | // self.match_candidates |
| 1121 | // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) { | 952 | // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { |
| 1122 | // let new_scroll_top = self | 953 | // StringMatchCandidate { |
| 1123 | // .entries | 954 | // id: ix, |
| 1124 | // .iter() | 955 | // string: channel.name.clone(), |
| 1125 | // .position(|entry| entry == old_top_entry) | 956 | // char_bag: channel.name.chars().collect(), |
| 1126 | // .map(|item_ix| ListOffset { | 957 | // } |
| 1127 | // item_ix, | 958 | // })); |
| 1128 | // offset_in_item: old_scroll_top.offset_in_item, | 959 | // let matches = executor.block(match_strings( |
| 1129 | // }) | 960 | // &self.match_candidates, |
| 1130 | // .or_else(|| { | 961 | // &query, |
| 1131 | // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?; | 962 | // true, |
| 1132 | // let item_ix = self | 963 | // usize::MAX, |
| 1133 | // .entries | 964 | // &Default::default(), |
| 1134 | // .iter() | 965 | // executor.clone(), |
| 1135 | // .position(|entry| entry == entry_after_old_top)?; | 966 | // )); |
| 1136 | // Some(ListOffset { | 967 | // request_entries.extend(matches.iter().map(|mat| { |
| 1137 | // item_ix, | 968 | // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) |
| 1138 | // offset_in_item: 0., | 969 | // })); |
| 1139 | // }) | ||
| 1140 | // }) | ||
| 1141 | // .or_else(|| { | ||
| 1142 | // let entry_before_old_top = | ||
| 1143 | // old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?; | ||
| 1144 | // let item_ix = self | ||
| 1145 | // .entries | ||
| 1146 | // .iter() | ||
| 1147 | // .position(|entry| entry == entry_before_old_top)?; | ||
| 1148 | // Some(ListOffset { | ||
| 1149 | // item_ix, | ||
| 1150 | // offset_in_item: 0., | ||
| 1151 | // }) | ||
| 1152 | // }); | ||
| 1153 | 970 | ||
| 1154 | // self.list_state | 971 | // if !request_entries.is_empty() { |
| 1155 | // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top)); | 972 | // self.entries |
| 1156 | // } | 973 | // .push(ListEntry::Header(Section::ChannelInvites)); |
| 1157 | // } | 974 | // if !self.collapsed_sections.contains(&Section::ChannelInvites) { |
| 975 | // self.entries.append(&mut request_entries); | ||
| 976 | // } | ||
| 977 | // } | ||
| 978 | // } | ||
| 979 | } | ||
| 980 | |||
| 981 | self.entries.push(ListEntry::Header(Section::Contacts)); | ||
| 982 | |||
| 983 | request_entries.clear(); | ||
| 984 | let incoming = user_store.incoming_contact_requests(); | ||
| 985 | if !incoming.is_empty() { | ||
| 986 | self.match_candidates.clear(); | ||
| 987 | self.match_candidates | ||
| 988 | .extend( | ||
| 989 | incoming | ||
| 990 | .iter() | ||
| 991 | .enumerate() | ||
| 992 | .map(|(ix, user)| StringMatchCandidate { | ||
| 993 | id: ix, | ||
| 994 | string: user.github_login.clone(), | ||
| 995 | char_bag: user.github_login.chars().collect(), | ||
| 996 | }), | ||
| 997 | ); | ||
| 998 | let matches = executor.block(match_strings( | ||
| 999 | &self.match_candidates, | ||
| 1000 | &query, | ||
| 1001 | true, | ||
| 1002 | usize::MAX, | ||
| 1003 | &Default::default(), | ||
| 1004 | executor.clone(), | ||
| 1005 | )); | ||
| 1006 | request_entries.extend( | ||
| 1007 | matches | ||
| 1008 | .iter() | ||
| 1009 | .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())), | ||
| 1010 | ); | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | let outgoing = user_store.outgoing_contact_requests(); | ||
| 1014 | if !outgoing.is_empty() { | ||
| 1015 | self.match_candidates.clear(); | ||
| 1016 | self.match_candidates | ||
| 1017 | .extend( | ||
| 1018 | outgoing | ||
| 1019 | .iter() | ||
| 1020 | .enumerate() | ||
| 1021 | .map(|(ix, user)| StringMatchCandidate { | ||
| 1022 | id: ix, | ||
| 1023 | string: user.github_login.clone(), | ||
| 1024 | char_bag: user.github_login.chars().collect(), | ||
| 1025 | }), | ||
| 1026 | ); | ||
| 1027 | let matches = executor.block(match_strings( | ||
| 1028 | &self.match_candidates, | ||
| 1029 | &query, | ||
| 1030 | true, | ||
| 1031 | usize::MAX, | ||
| 1032 | &Default::default(), | ||
| 1033 | executor.clone(), | ||
| 1034 | )); | ||
| 1035 | request_entries.extend( | ||
| 1036 | matches | ||
| 1037 | .iter() | ||
| 1038 | .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())), | ||
| 1039 | ); | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | if !request_entries.is_empty() { | ||
| 1043 | self.entries | ||
| 1044 | .push(ListEntry::Header(Section::ContactRequests)); | ||
| 1045 | if !self.collapsed_sections.contains(&Section::ContactRequests) { | ||
| 1046 | self.entries.append(&mut request_entries); | ||
| 1047 | } | ||
| 1048 | } | ||
| 1049 | |||
| 1050 | let contacts = user_store.contacts(); | ||
| 1051 | if !contacts.is_empty() { | ||
| 1052 | self.match_candidates.clear(); | ||
| 1053 | self.match_candidates | ||
| 1054 | .extend( | ||
| 1055 | contacts | ||
| 1056 | .iter() | ||
| 1057 | .enumerate() | ||
| 1058 | .map(|(ix, contact)| StringMatchCandidate { | ||
| 1059 | id: ix, | ||
| 1060 | string: contact.user.github_login.clone(), | ||
| 1061 | char_bag: contact.user.github_login.chars().collect(), | ||
| 1062 | }), | ||
| 1063 | ); | ||
| 1064 | |||
| 1065 | let matches = executor.block(match_strings( | ||
| 1066 | &self.match_candidates, | ||
| 1067 | &query, | ||
| 1068 | true, | ||
| 1069 | usize::MAX, | ||
| 1070 | &Default::default(), | ||
| 1071 | executor.clone(), | ||
| 1072 | )); | ||
| 1073 | |||
| 1074 | let (online_contacts, offline_contacts) = matches | ||
| 1075 | .iter() | ||
| 1076 | .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online); | ||
| 1077 | |||
| 1078 | for (matches, section) in [ | ||
| 1079 | (online_contacts, Section::Online), | ||
| 1080 | (offline_contacts, Section::Offline), | ||
| 1081 | ] { | ||
| 1082 | if !matches.is_empty() { | ||
| 1083 | self.entries.push(ListEntry::Header(section)); | ||
| 1084 | if !self.collapsed_sections.contains(§ion) { | ||
| 1085 | let active_call = &ActiveCall::global(cx).read(cx); | ||
| 1086 | for mat in matches { | ||
| 1087 | let contact = &contacts[mat.candidate_id]; | ||
| 1088 | self.entries.push(ListEntry::Contact { | ||
| 1089 | contact: contact.clone(), | ||
| 1090 | calling: active_call.pending_invites().contains(&contact.user.id), | ||
| 1091 | }); | ||
| 1092 | } | ||
| 1093 | } | ||
| 1094 | } | ||
| 1095 | } | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() { | ||
| 1099 | self.entries.push(ListEntry::ContactPlaceholder); | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | // if select_same_item { | ||
| 1103 | // if let Some(prev_selected_entry) = prev_selected_entry { | ||
| 1104 | // self.selection.take(); | ||
| 1105 | // for (ix, entry) in self.entries.iter().enumerate() { | ||
| 1106 | // if *entry == prev_selected_entry { | ||
| 1107 | // self.selection = Some(ix); | ||
| 1108 | // break; | ||
| 1109 | // } | ||
| 1110 | // } | ||
| 1111 | // } | ||
| 1112 | // } else { | ||
| 1113 | // self.selection = self.selection.and_then(|prev_selection| { | ||
| 1114 | // if self.entries.is_empty() { | ||
| 1115 | // None | ||
| 1116 | // } else { | ||
| 1117 | // Some(prev_selection.min(self.entries.len() - 1)) | ||
| 1118 | // } | ||
| 1119 | // }); | ||
| 1120 | // } | ||
| 1121 | |||
| 1122 | // let old_scroll_top = self.list_state.logical_scroll_top(); | ||
| 1123 | |||
| 1124 | // self.list_state.reset(self.entries.len()); | ||
| 1125 | |||
| 1126 | // if scroll_to_top { | ||
| 1127 | // self.list_state.scroll_to(ListOffset::default()); | ||
| 1128 | // } else { | ||
| 1129 | // // Attempt to maintain the same scroll position. | ||
| 1130 | // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) { | ||
| 1131 | // let new_scroll_top = self | ||
| 1132 | // .entries | ||
| 1133 | // .iter() | ||
| 1134 | // .position(|entry| entry == old_top_entry) | ||
| 1135 | // .map(|item_ix| ListOffset { | ||
| 1136 | // item_ix, | ||
| 1137 | // offset_in_item: old_scroll_top.offset_in_item, | ||
| 1138 | // }) | ||
| 1139 | // .or_else(|| { | ||
| 1140 | // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?; | ||
| 1141 | // let item_ix = self | ||
| 1142 | // .entries | ||
| 1143 | // .iter() | ||
| 1144 | // .position(|entry| entry == entry_after_old_top)?; | ||
| 1145 | // Some(ListOffset { | ||
| 1146 | // item_ix, | ||
| 1147 | // offset_in_item: 0., | ||
| 1148 | // }) | ||
| 1149 | // }) | ||
| 1150 | // .or_else(|| { | ||
| 1151 | // let entry_before_old_top = | ||
| 1152 | // old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?; | ||
| 1153 | // let item_ix = self | ||
| 1154 | // .entries | ||
| 1155 | // .iter() | ||
| 1156 | // .position(|entry| entry == entry_before_old_top)?; | ||
| 1157 | // Some(ListOffset { | ||
| 1158 | // item_ix, | ||
| 1159 | // offset_in_item: 0., | ||
| 1160 | // }) | ||
| 1161 | // }); | ||
| 1162 | |||
| 1163 | // self.list_state | ||
| 1164 | // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top)); | ||
| 1165 | // } | ||
| 1166 | // } | ||
| 1158 | 1167 | ||
| 1159 | // cx.notify(); | 1168 | cx.notify(); |
| 1160 | // } | 1169 | } |
| 1161 | 1170 | ||
| 1162 | // fn render_call_participant( | 1171 | // fn render_call_participant( |
| 1163 | // user: &User, | 1172 | // user: &User, |
| @@ -1455,389 +1464,6 @@ impl CollabPanel { · 291 unmodified lines | |||
| 1455 | // } | 1464 | // } |
| 1456 | // } | 1465 | // } |
| 1457 | 1466 | ||
| 1458 | // fn render_header( | ||
| 1459 | // &self, | ||
| 1460 | // section: Section, | ||
| 1461 | // theme: &theme::Theme, | ||
| 1462 | // is_selected: bool, | ||
| 1463 | // is_collapsed: bool, | ||
| 1464 | // cx: &mut ViewContext<Self>, | ||
| 1465 | // ) -> AnyElement<Self> { | ||
| 1466 | // enum Header {} | ||
| 1467 | // enum LeaveCallContactList {} | ||
| 1468 | // enum AddChannel {} | ||
| 1469 | |||
| 1470 | // let tooltip_style = &theme.tooltip; | ||
| 1471 | // let mut channel_link = None; | ||
| 1472 | // let mut channel_tooltip_text = None; | ||
| 1473 | // let mut channel_icon = None; | ||
| 1474 | // let mut is_dragged_over = false; | ||
| 1475 | |||
| 1476 | // let text = match section { | ||
| 1477 | // Section::ActiveCall => { | ||
| 1478 | // let channel_name = maybe!({ | ||
| 1479 | // let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?; | ||
| 1480 | |||
| 1481 | // let channel = self.channel_store.read(cx).channel_for_id(channel_id)?; | ||
| 1482 | |||
| 1483 | // channel_link = Some(channel.link()); | ||
| 1484 | // (channel_icon, channel_tooltip_text) = match channel.visibility { | ||
| 1485 | // proto::ChannelVisibility::Public => { | ||
| 1486 | // (Some("icons/public.svg"), Some("Copy public channel link.")) | ||
| 1487 | // } | ||
| 1488 | // proto::ChannelVisibility::Members => { | ||
| 1489 | // (Some("icons/hash.svg"), Some("Copy private channel link.")) | ||
| 1490 | // } | ||
| 1491 | // }; | ||
| 1492 | |||
| 1493 | // Some(channel.name.as_str()) | ||
| 1494 | // }); | ||
| 1495 | |||
| 1496 | // if let Some(name) = channel_name { | ||
| 1497 | // Cow::Owned(format!("{}", name)) | ||
| 1498 | // } else { | ||
| 1499 | // Cow::Borrowed("Current Call") | ||
| 1500 | // } | ||
| 1501 | // } | ||
| 1502 | // Section::ContactRequests => Cow::Borrowed("Requests"), | ||
| 1503 | // Section::Contacts => Cow::Borrowed("Contacts"), | ||
| 1504 | // Section::Channels => Cow::Borrowed("Channels"), | ||
| 1505 | // Section::ChannelInvites => Cow::Borrowed("Invites"), | ||
| 1506 | // Section::Online => Cow::Borrowed("Online"), | ||
| 1507 | // Section::Offline => Cow::Borrowed("Offline"), | ||
| 1508 | // }; | ||
| 1509 | |||
| 1510 | // enum AddContact {} | ||
| 1511 | // let button = match section { | ||
| 1512 | // Section::ActiveCall => channel_link.map(|channel_link| { | ||
| 1513 | // let channel_link_copy = channel_link.clone(); | ||
| 1514 | // MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| { | ||
| 1515 | // render_icon_button( | ||
| 1516 | // theme | ||
| 1517 | // .collab_panel | ||
| 1518 | // .leave_call_button | ||
| 1519 | // .style_for(is_selected, state), | ||
| 1520 | // "icons/link.svg", | ||
| 1521 | // ) | ||
| 1522 | // }) | ||
| 1523 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 1524 | // .on_click(MouseButton::Left, move |_, _, cx| { | ||
| 1525 | // let item = ClipboardItem::new(channel_link_copy.clone()); | ||
| 1526 | // cx.write_to_clipboard(item) | ||
| 1527 | // }) | ||
| 1528 | // .with_tooltip::<AddContact>( | ||
| 1529 | // 0, | ||
| 1530 | // channel_tooltip_text.unwrap(), | ||
| 1531 | // None, | ||
| 1532 | // tooltip_style.clone(), | ||
| 1533 | // cx, | ||
| 1534 | // ) | ||
| 1535 | // }), | ||
| 1536 | // Section::Contacts => Some( | ||
| 1537 | // MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| { | ||
| 1538 | // render_icon_button( | ||
| 1539 | // theme | ||
| 1540 | // .collab_panel | ||
| 1541 | // .add_contact_button | ||
| 1542 | // .style_for(is_selected, state), | ||
| 1543 | // "icons/plus.svg", | ||
| 1544 | // ) | ||
| 1545 | // }) | ||
| 1546 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 1547 | // .on_click(MouseButton::Left, |_, this, cx| { | ||
| 1548 | // this.toggle_contact_finder(cx); | ||
| 1549 | // }) | ||
| 1550 | // .with_tooltip::<LeaveCallContactList>( | ||
| 1551 | // 0, | ||
| 1552 | // "Search for new contact", | ||
| 1553 | // None, | ||
| 1554 | // tooltip_style.clone(), | ||
| 1555 | // cx, | ||
| 1556 | // ), | ||
| 1557 | // ), | ||
| 1558 | // Section::Channels => { | ||
| 1559 | // if cx | ||
| 1560 | // .global::<DragAndDrop<Workspace>>() | ||
| 1561 | // .currently_dragged::<Channel>(cx.window()) | ||
| 1562 | // .is_some() | ||
| 1563 | // && self.drag_target_channel == ChannelDragTarget::Root | ||
| 1564 | // { | ||
| 1565 | // is_dragged_over = true; | ||
| 1566 | // } | ||
| 1567 | |||
| 1568 | // Some( | ||
| 1569 | // MouseEventHandler::new::<AddChannel, _>(0, cx, |state, _| { | ||
| 1570 | // render_icon_button( | ||
| 1571 | // theme | ||
| 1572 | // .collab_panel | ||
| 1573 | // .add_contact_button | ||
| 1574 | // .style_for(is_selected, state), | ||
| 1575 | // "icons/plus.svg", | ||
| 1576 | // ) | ||
| 1577 | // }) | ||
| 1578 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 1579 | // .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx)) | ||
| 1580 | // .with_tooltip::<AddChannel>( | ||
| 1581 | // 0, | ||
| 1582 | // "Create a channel", | ||
| 1583 | // None, | ||
| 1584 | // tooltip_style.clone(), | ||
| 1585 | // cx, | ||
| 1586 | // ), | ||
| 1587 | // ) | ||
| 1588 | // } | ||
| 1589 | // _ => None, | ||
| 1590 | // }; | ||
| 1591 | |||
| 1592 | // let can_collapse = match section { | ||
| 1593 | // Section::ActiveCall | Section::Channels | Section::Contacts => false, | ||
| 1594 | // Section::ChannelInvites | ||
| 1595 | // | Section::ContactRequests | ||
| 1596 | // | Section::Online | ||
| 1597 | // | Section::Offline => true, | ||
| 1598 | // }; | ||
| 1599 | // let icon_size = (&theme.collab_panel).section_icon_size; | ||
| 1600 | // let mut result = MouseEventHandler::new::<Header, _>(section as usize, cx, |state, _| { | ||
| 1601 | // let header_style = if can_collapse { | ||
| 1602 | // theme | ||
| 1603 | // .collab_panel | ||
| 1604 | // .subheader_row | ||
| 1605 | // .in_state(is_selected) | ||
| 1606 | // .style_for(state) | ||
| 1607 | // } else { | ||
| 1608 | // &theme.collab_panel.header_row | ||
| 1609 | // }; | ||
| 1610 | |||
| 1611 | // Flex::row() | ||
| 1612 | // .with_children(if can_collapse { | ||
| 1613 | // Some( | ||
| 1614 | // Svg::new(if is_collapsed { | ||
| 1615 | // "icons/chevron_right.svg" | ||
| 1616 | // } else { | ||
| 1617 | // "icons/chevron_down.svg" | ||
| 1618 | // }) | ||
| 1619 | // .with_color(header_style.text.color) | ||
| 1620 | // .constrained() | ||
| 1621 | // .with_max_width(icon_size) | ||
| 1622 | // .with_max_height(icon_size) | ||
| 1623 | // .aligned() | ||
| 1624 | // .constrained() | ||
| 1625 | // .with_width(icon_size) | ||
| 1626 | // .contained() | ||
| 1627 | // .with_margin_right( | ||
| 1628 | // theme.collab_panel.contact_username.container.margin.left, | ||
| 1629 | // ), | ||
| 1630 | // ) | ||
| 1631 | // } else if let Some(channel_icon) = channel_icon { | ||
| 1632 | // Some( | ||
| 1633 | // Svg::new(channel_icon) | ||
| 1634 | // .with_color(header_style.text.color) | ||
| 1635 | // .constrained() | ||
| 1636 | // .with_max_width(icon_size) | ||
| 1637 | // .with_max_height(icon_size) | ||
| 1638 | // .aligned() | ||
| 1639 | // .constrained() | ||
| 1640 | // .with_width(icon_size) | ||
| 1641 | // .contained() | ||
| 1642 | // .with_margin_right( | ||
| 1643 | // theme.collab_panel.contact_username.container.margin.left, | ||
| 1644 | // ), | ||
| 1645 | // ) | ||
| 1646 | // } else { | ||
| 1647 | // None | ||
| 1648 | // }) | ||
| 1649 | // .with_child( | ||
| 1650 | // Label::new(text, header_style.text.clone()) | ||
| 1651 | // .aligned() | ||
| 1652 | // .left() | ||
| 1653 | // .flex(1., true), | ||
| 1654 | // ) | ||
| 1655 | // .with_children(button.map(|button| button.aligned().right())) | ||
| 1656 | // .constrained() | ||
| 1657 | // .with_height(theme.collab_panel.row_height) | ||
| 1658 | // .contained() | ||
| 1659 | // .with_style(if is_dragged_over { | ||
| 1660 | // theme.collab_panel.dragged_over_header | ||
| 1661 | // } else { | ||
| 1662 | // header_style.container | ||
| 1663 | // }) | ||
| 1664 | // }); | ||
| 1665 | |||
| 1666 | // result = result | ||
| 1667 | // .on_move(move |_, this, cx| { | ||
| 1668 | // if cx | ||
| 1669 | // .global::<DragAndDrop<Workspace>>() | ||
| 1670 | // .currently_dragged::<Channel>(cx.window()) | ||
| 1671 | // .is_some() | ||
| 1672 | // { | ||
| 1673 | // this.drag_target_channel = ChannelDragTarget::Root; | ||
| 1674 | // cx.notify() | ||
| 1675 | // } | ||
| 1676 | // }) | ||
| 1677 | // .on_up(MouseButton::Left, move |_, this, cx| { | ||
| 1678 | // if let Some((_, dragged_channel)) = cx | ||
| 1679 | // .global::<DragAndDrop<Workspace>>() | ||
| 1680 | // .currently_dragged::<Channel>(cx.window()) | ||
| 1681 | // { | ||
| 1682 | // this.channel_store | ||
| 1683 | // .update(cx, |channel_store, cx| { | ||
| 1684 | // channel_store.move_channel(dragged_channel.id, None, cx) | ||
| 1685 | // }) | ||
| 1686 | // .detach_and_log_err(cx) | ||
| 1687 | // } | ||
| 1688 | // }); | ||
| 1689 | |||
| 1690 | // if can_collapse { | ||
| 1691 | // result = result | ||
| 1692 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 1693 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 1694 | // if can_collapse { | ||
| 1695 | // this.toggle_section_expanded(section, cx); | ||
| 1696 | // } | ||
| 1697 | // }) | ||
| 1698 | // } | ||
| 1699 | |||
| 1700 | // result.into_any() | ||
| 1701 | // } | ||
| 1702 | |||
| 1703 | // fn render_contact( | ||
| 1704 | // contact: &Contact, | ||
| 1705 | // calling: bool, | ||
| 1706 | // project: &ModelHandle<Project>, | ||
| 1707 | // theme: &theme::Theme, | ||
| 1708 | // is_selected: bool, | ||
| 1709 | // cx: &mut ViewContext<Self>, | ||
| 1710 | // ) -> AnyElement<Self> { | ||
| 1711 | // enum ContactTooltip {} | ||
| 1712 | |||
| 1713 | // let collab_theme = &theme.collab_panel; | ||
| 1714 | // let online = contact.online; | ||
| 1715 | // let busy = contact.busy || calling; | ||
| 1716 | // let user_id = contact.user.id; | ||
| 1717 | // let github_login = contact.user.github_login.clone(); | ||
| 1718 | // let initial_project = project.clone(); | ||
| 1719 | |||
| 1720 | // let event_handler = | ||
| 1721 | // MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| { | ||
| 1722 | // Flex::row() | ||
| 1723 | // .with_children(contact.user.avatar.clone().map(|avatar| { | ||
| 1724 | // let status_badge = if contact.online { | ||
| 1725 | // Some( | ||
| 1726 | // Empty::new() | ||
| 1727 | // .collapsed() | ||
| 1728 | // .contained() | ||
| 1729 | // .with_style(if busy { | ||
| 1730 | // collab_theme.contact_status_busy | ||
| 1731 | // } else { | ||
| 1732 | // collab_theme.contact_status_free | ||
| 1733 | // }) | ||
| 1734 | // .aligned(), | ||
| 1735 | // ) | ||
| 1736 | // } else { | ||
| 1737 | // None | ||
| 1738 | // }; | ||
| 1739 | // Stack::new() | ||
| 1740 | // .with_child( | ||
| 1741 | // Image::from_data(avatar) | ||
| 1742 | // .with_style(collab_theme.contact_avatar) | ||
| 1743 | // .aligned() | ||
| 1744 | // .left(), | ||
| 1745 | // ) | ||
| 1746 | // .with_children(status_badge) | ||
| 1747 | // })) | ||
| 1748 | // .with_child( | ||
| 1749 | // Label::new( | ||
| 1750 | // contact.user.github_login.clone(), | ||
| 1751 | // collab_theme.contact_username.text.clone(), | ||
| 1752 | // ) | ||
| 1753 | // .contained() | ||
| 1754 | // .with_style(collab_theme.contact_username.container) | ||
| 1755 | // .aligned() | ||
| 1756 | // .left() | ||
| 1757 | // .flex(1., true), | ||
| 1758 | // ) | ||
| 1759 | // .with_children(if state.hovered() { | ||
| 1760 | // Some( | ||
| 1761 | // MouseEventHandler::new::<Cancel, _>( | ||
| 1762 | // contact.user.id as usize, | ||
| 1763 | // cx, | ||
| 1764 | // |mouse_state, _| { | ||
| 1765 | // let button_style = | ||
| 1766 | // collab_theme.contact_button.style_for(mouse_state); | ||
| 1767 | // render_icon_button(button_style, "icons/x.svg") | ||
| 1768 | // .aligned() | ||
| 1769 | // .flex_float() | ||
| 1770 | // }, | ||
| 1771 | // ) | ||
| 1772 | // .with_padding(Padding::uniform(2.)) | ||
| 1773 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 1774 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 1775 | // this.remove_contact(user_id, &github_login, cx); | ||
| 1776 | // }) | ||
| 1777 | // .flex_float(), | ||
| 1778 | // ) | ||
| 1779 | // } else { | ||
| 1780 | // None | ||
| 1781 | // }) | ||
| 1782 | // .with_children(if calling { | ||
| 1783 | // Some( | ||
| 1784 | // Label::new("Calling", collab_theme.calling_indicator.text.clone()) | ||
| 1785 | // .contained() | ||
| 1786 | // .with_style(collab_theme.calling_indicator.container) | ||
| 1787 | // .aligned(), | ||
| 1788 | // ) | ||
| 1789 | // } else { | ||
| 1790 | // None | ||
| 1791 | // }) | ||
| 1792 | // .constrained() | ||
| 1793 | // .with_height(collab_theme.row_height) | ||
| 1794 | // .contained() | ||
| 1795 | // .with_style( | ||
| 1796 | // *collab_theme | ||
| 1797 | // .contact_row | ||
| 1798 | // .in_state(is_selected) | ||
| 1799 | // .style_for(state), | ||
| 1800 | // ) | ||
| 1801 | // }); | ||
| 1802 | |||
| 1803 | // if online && !busy { | ||
| 1804 | // let room = ActiveCall::global(cx).read(cx).room(); | ||
| 1805 | // let label = if room.is_some() { | ||
| 1806 | // format!("Invite {} to join call", contact.user.github_login) | ||
| 1807 | // } else { | ||
| 1808 | // format!("Call {}", contact.user.github_login) | ||
| 1809 | // }; | ||
| 1810 | |||
| 1811 | // event_handler | ||
| 1812 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 1813 | // this.call(user_id, Some(initial_project.clone()), cx); | ||
| 1814 | // }) | ||
| 1815 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 1816 | // .with_tooltip::<ContactTooltip>( | ||
| 1817 | // contact.user.id as usize, | ||
| 1818 | // label, | ||
| 1819 | // None, | ||
| 1820 | // theme.tooltip.clone(), | ||
| 1821 | // cx, | ||
| 1822 | // ) | ||
| 1823 | // .into_any() | ||
| 1824 | // } else { | ||
| 1825 | // event_handler | ||
| 1826 | // .with_tooltip::<ContactTooltip>( | ||
| 1827 | // contact.user.id as usize, | ||
| 1828 | // format!( | ||
| 1829 | // "{} is {}", | ||
| 1830 | // contact.user.github_login, | ||
| 1831 | // if busy { "on a call" } else { "offline" } | ||
| 1832 | // ), | ||
| 1833 | // None, | ||
| 1834 | // theme.tooltip.clone(), | ||
| 1835 | // cx, | ||
| 1836 | // ) | ||
| 1837 | // .into_any() | ||
| 1838 | // } | ||
| 1839 | // } | ||
| 1840 | |||
| 1841 | // fn render_contact_placeholder( | 1467 | // fn render_contact_placeholder( |
| 1842 | // &self, | 1468 | // &self, |
| 1843 | // theme: &theme::CollabPanel, | 1469 | // theme: &theme::CollabPanel, |
| @@ -1933,335 +1559,6 @@ impl CollabPanel { · 89 unmodified lines | |||
| 1933 | // .into_any() | 1559 | // .into_any() |
| 1934 | // } | 1560 | // } |
| 1935 | 1561 | ||
| 1936 | // fn render_channel( | ||
| 1937 | // &self, | ||
| 1938 | // channel: &Channel, | ||
| 1939 | // depth: usize, | ||
| 1940 | // theme: &theme::Theme, | ||
| 1941 | // is_selected: bool, | ||
| 1942 | // has_children: bool, | ||
| 1943 | // ix: usize, | ||
| 1944 | // cx: &mut ViewContext<Self>, | ||
| 1945 | // ) -> AnyElement<Self> { | ||
| 1946 | // let channel_id = channel.id; | ||
| 1947 | // let collab_theme = &theme.collab_panel; | ||
| 1948 | // let is_public = self | ||
| 1949 | // .channel_store | ||
| 1950 | // .read(cx) | ||
| 1951 | // .channel_for_id(channel_id) | ||
| 1952 | // .map(|channel| channel.visibility) | ||
| 1953 | // == Some(proto::ChannelVisibility::Public); | ||
| 1954 | // let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id); | ||
| 1955 | // let disclosed = | ||
| 1956 | // has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok()); | ||
| 1957 | |||
| 1958 | // let is_active = maybe!({ | ||
| 1959 | // let call_channel = ActiveCall::global(cx) | ||
| 1960 | // .read(cx) | ||
| 1961 | // .room()? | ||
| 1962 | // .read(cx) | ||
| 1963 | // .channel_id()?; | ||
| 1964 | // Some(call_channel == channel_id) | ||
| 1965 | // }) | ||
| 1966 | // .unwrap_or(false); | ||
| 1967 | |||
| 1968 | // const FACEPILE_LIMIT: usize = 3; | ||
| 1969 | |||
| 1970 | // enum ChannelCall {} | ||
| 1971 | // enum ChannelNote {} | ||
| 1972 | // enum NotesTooltip {} | ||
| 1973 | // enum ChatTooltip {} | ||
| 1974 | // enum ChannelTooltip {} | ||
| 1975 | |||
| 1976 | // let mut is_dragged_over = false; | ||
| 1977 | // if cx | ||
| 1978 | // .global::<DragAndDrop<Workspace>>() | ||
| 1979 | // .currently_dragged::<Channel>(cx.window()) | ||
| 1980 | // .is_some() | ||
| 1981 | // && self.drag_target_channel == ChannelDragTarget::Channel(channel_id) | ||
| 1982 | // { | ||
| 1983 | // is_dragged_over = true; | ||
| 1984 | // } | ||
| 1985 | |||
| 1986 | // let has_messages_notification = channel.unseen_message_id.is_some(); | ||
| 1987 | |||
| 1988 | // MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| { | ||
| 1989 | // let row_hovered = state.hovered(); | ||
| 1990 | |||
| 1991 | // let mut select_state = |interactive: &Interactive<ContainerStyle>| { | ||
| 1992 | // if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() { | ||
| 1993 | // interactive.clicked.as_ref().unwrap().clone() | ||
| 1994 | // } else if state.hovered() || other_selected { | ||
| 1995 | // interactive | ||
| 1996 | // .hovered | ||
| 1997 | // .as_ref() | ||
| 1998 | // .unwrap_or(&interactive.default) | ||
| 1999 | // .clone() | ||
| 2000 | // } else { | ||
| 2001 | // interactive.default.clone() | ||
| 2002 | // } | ||
| 2003 | // }; | ||
| 2004 | |||
| 2005 | // Flex::<Self>::row() | ||
| 2006 | // .with_child( | ||
| 2007 | // Svg::new(if is_public { | ||
| 2008 | // "icons/public.svg" | ||
| 2009 | // } else { | ||
| 2010 | // "icons/hash.svg" | ||
| 2011 | // }) | ||
| 2012 | // .with_color(collab_theme.channel_hash.color) | ||
| 2013 | // .constrained() | ||
| 2014 | // .with_width(collab_theme.channel_hash.width) | ||
| 2015 | // .aligned() | ||
| 2016 | // .left(), | ||
| 2017 | // ) | ||
| 2018 | // .with_child({ | ||
| 2019 | // let style = collab_theme.channel_name.inactive_state(); | ||
| 2020 | // Flex::row() | ||
| 2021 | // .with_child( | ||
| 2022 | // Label::new(channel.name.clone(), style.text.clone()) | ||
| 2023 | // .contained() | ||
| 2024 | // .with_style(style.container) | ||
| 2025 | // .aligned() | ||
| 2026 | // .left() | ||
| 2027 | // .with_tooltip::<ChannelTooltip>( | ||
| 2028 | // ix, | ||
| 2029 | // "Join channel", | ||
| 2030 | // None, | ||
| 2031 | // theme.tooltip.clone(), | ||
| 2032 | // cx, | ||
| 2033 | // ), | ||
| 2034 | // ) | ||
| 2035 | // .with_children({ | ||
| 2036 | // let participants = | ||
| 2037 | // self.channel_store.read(cx).channel_participants(channel_id); | ||
| 2038 | |||
| 2039 | // if !participants.is_empty() { | ||
| 2040 | // let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT); | ||
| 2041 | |||
| 2042 | // let result = FacePile::new(collab_theme.face_overlap) | ||
| 2043 | // .with_children( | ||
| 2044 | // participants | ||
| 2045 | // .iter() | ||
| 2046 | // .filter_map(|user| { | ||
| 2047 | // Some( | ||
| 2048 | // Image::from_data(user.avatar.clone()?) | ||
| 2049 | // .with_style(collab_theme.channel_avatar), | ||
| 2050 | // ) | ||
| 2051 | // }) | ||
| 2052 | // .take(FACEPILE_LIMIT), | ||
| 2053 | // ) | ||
| 2054 | // .with_children((extra_count > 0).then(|| { | ||
| 2055 | // Label::new( | ||
| 2056 | // format!("+{}", extra_count), | ||
| 2057 | // collab_theme.extra_participant_label.text.clone(), | ||
| 2058 | // ) | ||
| 2059 | // .contained() | ||
| 2060 | // .with_style(collab_theme.extra_participant_label.container) | ||
| 2061 | // })); | ||
| 2062 | |||
| 2063 | // Some(result) | ||
| 2064 | // } else { | ||
| 2065 | // None | ||
| 2066 | // } | ||
| 2067 | // }) | ||
| 2068 | // .with_spacing(8.) | ||
| 2069 | // .align_children_center() | ||
| 2070 | // .flex(1., true) | ||
| 2071 | // }) | ||
| 2072 | // .with_child( | ||
| 2073 | // MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| { | ||
| 2074 | // let container_style = collab_theme | ||
| 2075 | // .disclosure | ||
| 2076 | // .button | ||
| 2077 | // .style_for(mouse_state) | ||
| 2078 | // .container; | ||
| 2079 | |||
| 2080 | // if channel.unseen_message_id.is_some() { | ||
| 2081 | // Svg::new("icons/conversations.svg") | ||
| 2082 | // .with_color(collab_theme.channel_note_active_color) | ||
| 2083 | // .constrained() | ||
| 2084 | // .with_width(collab_theme.channel_hash.width) | ||
| 2085 | // .contained() | ||
| 2086 | // .with_style(container_style) | ||
| 2087 | // .with_uniform_padding(4.) | ||
| 2088 | // .into_any() | ||
| 2089 | // } else if row_hovered { | ||
| 2090 | // Svg::new("icons/conversations.svg") | ||
| 2091 | // .with_color(collab_theme.channel_hash.color) | ||
| 2092 | // .constrained() | ||
| 2093 | // .with_width(collab_theme.channel_hash.width) | ||
| 2094 | // .contained() | ||
| 2095 | // .with_style(container_style) | ||
| 2096 | // .with_uniform_padding(4.) | ||
| 2097 | // .into_any() | ||
| 2098 | // } else { | ||
| 2099 | // Empty::new().into_any() | ||
| 2100 | // } | ||
| 2101 | // }) | ||
| 2102 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 2103 | // this.join_channel_chat(&JoinChannelChat { channel_id }, cx); | ||
| 2104 | // }) | ||
| 2105 | // .with_tooltip::<ChatTooltip>( | ||
| 2106 | // ix, | ||
| 2107 | // "Open channel chat", | ||
| 2108 | // None, | ||
| 2109 | // theme.tooltip.clone(), | ||
| 2110 | // cx, | ||
| 2111 | // ) | ||
| 2112 | // .contained() | ||
| 2113 | // .with_margin_right(4.), | ||
| 2114 | // ) | ||
| 2115 | // .with_child( | ||
| 2116 | // MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| { | ||
| 2117 | // let container_style = collab_theme | ||
| 2118 | // .disclosure | ||
| 2119 | // .button | ||
| 2120 | // .style_for(mouse_state) | ||
| 2121 | // .container; | ||
| 2122 | // if row_hovered || channel.unseen_note_version.is_some() { | ||
| 2123 | // Svg::new("icons/file.svg") | ||
| 2124 | // .with_color(if channel.unseen_note_version.is_some() { | ||
| 2125 | // collab_theme.channel_note_active_color | ||
| 2126 | // } else { | ||
| 2127 | // collab_theme.channel_hash.color | ||
| 2128 | // }) | ||
| 2129 | // .constrained() | ||
| 2130 | // .with_width(collab_theme.channel_hash.width) | ||
| 2131 | // .contained() | ||
| 2132 | // .with_style(container_style) | ||
| 2133 | // .with_uniform_padding(4.) | ||
| 2134 | // .with_margin_right(collab_theme.channel_hash.container.margin.left) | ||
| 2135 | // .with_tooltip::<NotesTooltip>( | ||
| 2136 | // ix as usize, | ||
| 2137 | // "Open channel notes", | ||
| 2138 | // None, | ||
| 2139 | // theme.tooltip.clone(), | ||
| 2140 | // cx, | ||
| 2141 | // ) | ||
| 2142 | // .into_any() | ||
| 2143 | // } else if has_messages_notification { | ||
| 2144 | // Empty::new() | ||
| 2145 | // .constrained() | ||
| 2146 | // .with_width(collab_theme.channel_hash.width) | ||
| 2147 | // .contained() | ||
| 2148 | // .with_uniform_padding(4.) | ||
| 2149 | // .with_margin_right(collab_theme.channel_hash.container.margin.left) | ||
| 2150 | // .into_any() | ||
| 2151 | // } else { | ||
| 2152 | // Empty::new().into_any() | ||
| 2153 | // } | ||
| 2154 | // }) | ||
| 2155 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 2156 | // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx); | ||
| 2157 | // }), | ||
| 2158 | // ) | ||
| 2159 | // .align_children_center() | ||
| 2160 | // .styleable_component() | ||
| 2161 | // .disclosable( | ||
| 2162 | // disclosed, | ||
| 2163 | // Box::new(ToggleCollapse { | ||
| 2164 | // location: channel.id.clone(), | ||
| 2165 | // }), | ||
| 2166 | // ) | ||
| 2167 | // .with_id(ix) | ||
| 2168 | // .with_style(collab_theme.disclosure.clone()) | ||
| 2169 | // .element() | ||
| 2170 | // .constrained() | ||
| 2171 | // .with_height(collab_theme.row_height) | ||
| 2172 | // .contained() | ||
| 2173 | // .with_style(select_state( | ||
| 2174 | // collab_theme | ||
| 2175 | // .channel_row | ||
| 2176 | // .in_state(is_selected || is_active || is_dragged_over), | ||
| 2177 | // )) | ||
| 2178 | // .with_padding_left( | ||
| 2179 | // collab_theme.channel_row.default_style().padding.left | ||
| 2180 | // + collab_theme.channel_indent * depth as f32, | ||
| 2181 | // ) | ||
| 2182 | // }) | ||
| 2183 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 2184 | // if this.drag_target_channel == ChannelDragTarget::None { | ||
| 2185 | // if is_active { | ||
| 2186 | // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx) | ||
| 2187 | // } else { | ||
| 2188 | // this.join_channel(channel_id, cx) | ||
| 2189 | // } | ||
| 2190 | // } | ||
| 2191 | // }) | ||
| 2192 | // .on_click(MouseButton::Right, { | ||
| 2193 | // let channel = channel.clone(); | ||
| 2194 | // move |e, this, cx| { | ||
| 2195 | // this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx); | ||
| 2196 | // } | ||
| 2197 | // }) | ||
| 2198 | // .on_up(MouseButton::Left, move |_, this, cx| { | ||
| 2199 | // if let Some((_, dragged_channel)) = cx | ||
| 2200 | // .global::<DragAndDrop<Workspace>>() | ||
| 2201 | // .currently_dragged::<Channel>(cx.window()) | ||
| 2202 | // { | ||
| 2203 | // this.channel_store | ||
| 2204 | // .update(cx, |channel_store, cx| { | ||
| 2205 | // channel_store.move_channel(dragged_channel.id, Some(channel_id), cx) | ||
| 2206 | // }) | ||
| 2207 | // .detach_and_log_err(cx) | ||
| 2208 | // } | ||
| 2209 | // }) | ||
| 2210 | // .on_move({ | ||
| 2211 | // let channel = channel.clone(); | ||
| 2212 | // move |_, this, cx| { | ||
| 2213 | // if let Some((_, dragged_channel)) = cx | ||
| 2214 | // .global::<DragAndDrop<Workspace>>() | ||
| 2215 | // .currently_dragged::<Channel>(cx.window()) | ||
| 2216 | // { | ||
| 2217 | // if channel.id != dragged_channel.id { | ||
| 2218 | // this.drag_target_channel = ChannelDragTarget::Channel(channel.id); | ||
| 2219 | // } | ||
| 2220 | // cx.notify() | ||
| 2221 | // } | ||
| 2222 | // } | ||
| 2223 | // }) | ||
| 2224 | // .as_draggable::<_, Channel>( | ||
| 2225 | // channel.clone(), | ||
| 2226 | // move |_, channel, cx: &mut ViewContext<Workspace>| { | ||
| 2227 | // let theme = &theme::current(cx).collab_panel; | ||
| 2228 | |||
| 2229 | // Flex::<Workspace>::row() | ||
| 2230 | // .with_child( | ||
| 2231 | // Svg::new("icons/hash.svg") | ||
| 2232 | // .with_color(theme.channel_hash.color) | ||
| 2233 | // .constrained() | ||
| 2234 | // .with_width(theme.channel_hash.width) | ||
| 2235 | // .aligned() | ||
| 2236 | // .left(), | ||
| 2237 | // ) | ||
| 2238 | // .with_child( | ||
| 2239 | // Label::new(channel.name.clone(), theme.channel_name.text.clone()) | ||
| 2240 | // .contained() | ||
| 2241 | // .with_style(theme.channel_name.container) | ||
| 2242 | // .aligned() | ||
| 2243 | // .left(), | ||
| 2244 | // ) | ||
| 2245 | // .align_children_center() | ||
| 2246 | // .contained() | ||
| 2247 | // .with_background_color( | ||
| 2248 | // theme | ||
| 2249 | // .container | ||
| 2250 | // .background_color | ||
| 2251 | // .unwrap_or(gpui::color::Color::transparent_black()), | ||
| 2252 | // ) | ||
| 2253 | // .contained() | ||
| 2254 | // .with_padding_left( | ||
| 2255 | // theme.channel_row.default_style().padding.left | ||
| 2256 | // + theme.channel_indent * depth as f32, | ||
| 2257 | // ) | ||
| 2258 | // .into_any() | ||
| 2259 | // }, | ||
| 2260 | // ) | ||
| 2261 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 2262 | // .into_any() | ||
| 2263 | // } | ||
| 2264 | |||
| 2265 | // fn render_channel_notes( | 1562 | // fn render_channel_notes( |
| 2266 | // &self, | 1563 | // &self, |
| 2267 | // channel_id: ChannelId, | 1564 | // channel_id: ChannelId, |
| @@ -2948,9 +2245,9 @@ impl CollabPanel { · 680 unmodified lines | |||
| 2948 | // cx.focus_self(); | 2245 | // cx.focus_self(); |
| 2949 | // } | 2246 | // } |
| 2950 | 2247 | ||
| 2951 | // fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool { | 2248 | fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool { |
| 2952 | // self.collapsed_channels.binary_search(&channel_id).is_ok() | 2249 | self.collapsed_channels.binary_search(&channel_id).is_ok() |
| 2953 | // } | 2250 | } |
| 2954 | 2251 | ||
| 2955 | // fn leave_call(cx: &mut ViewContext<Self>) { | 2252 | // fn leave_call(cx: &mut ViewContext<Self>) { |
| 2956 | // ActiveCall::global(cx) | 2253 | // ActiveCall::global(cx) |
| @@ -2958,19 +2255,17 @@ impl CollabPanel { · 1 unmodified line | |||
| 2958 | // .detach_and_log_err(cx); | 2255 | // .detach_and_log_err(cx); |
| 2959 | // } | 2256 | // } |
| 2960 | 2257 | ||
| 2961 | // fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) { | 2258 | fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) { |
| 2962 | // if let Some(workspace) = self.workspace.upgrade(cx) { | 2259 | if let Some(workspace) = self.workspace.upgrade() { |
| 2963 | // workspace.update(cx, |workspace, cx| { | 2260 | workspace.update(cx, |workspace, cx| { |
| 2964 | // workspace.toggle_modal(cx, |_, cx| { | 2261 | workspace.toggle_modal(cx, |cx| { |
| 2965 | // cx.add_view(|cx| { | 2262 | let mut finder = ContactFinder::new(self.user_store.clone(), cx); |
| 2966 | // let mut finder = ContactFinder::new(self.user_store.clone(), cx); | 2263 | finder.set_query(self.filter_editor.read(cx).text(cx), cx); |
| 2967 | // finder.set_query(self.filter_editor.read(cx).text(cx), cx); | 2264 | finder |
| 2968 | // finder | 2265 | }); |
| 2969 | // }) | 2266 | }); |
| 2970 | // }); | 2267 | } |
| 2971 | // }); | 2268 | } |
| 2972 | // } | ||
| 2973 | // } | ||
| 2974 | 2269 | ||
| 2975 | // fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) { | 2270 | // fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) { |
| 2976 | // self.channel_editing_state = Some(ChannelEditingState::Create { | 2271 | // self.channel_editing_state = Some(ChannelEditingState::Create { |
| @@ -3247,6 +2542,802 @@ impl CollabPanel { · 270 unmodified lines | |||
| 3247 | // let item = ClipboardItem::new(channel.link()); | 2542 | // let item = ClipboardItem::new(channel.link()); |
| 3248 | // cx.write_to_clipboard(item) | 2543 | // cx.write_to_clipboard(item) |
| 3249 | // } | 2544 | // } |
| 2545 | |||
| 2546 | fn render_signed_out(&mut self, cx: &mut ViewContext<Self>) -> Div { | ||
| 2547 | v_stack().child(Button::new("Sign in to collaborate").on_click(cx.listener( | ||
| 2548 | |this, _, cx| { | ||
| 2549 | let client = this.client.clone(); | ||
| 2550 | cx.spawn(|_, mut cx| async move { | ||
| 2551 | client | ||
| 2552 | .authenticate_and_connect(true, &cx) | ||
| 2553 | .await | ||
| 2554 | .notify_async_err(&mut cx); | ||
| 2555 | }) | ||
| 2556 | .detach() | ||
| 2557 | }, | ||
| 2558 | ))) | ||
| 2559 | } | ||
| 2560 | |||
| 2561 | fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> List { | ||
| 2562 | let is_selected = false; // todo!() this.selection == Some(ix); | ||
| 2563 | |||
| 2564 | List::new().children(self.entries.clone().into_iter().map(|entry| { | ||
| 2565 | match entry { | ||
| 2566 | ListEntry::Header(section) => { | ||
| 2567 | let is_collapsed = self.collapsed_sections.contains(§ion); | ||
| 2568 | self.render_header(section, is_selected, is_collapsed, cx) | ||
| 2569 | .into_any_element() | ||
| 2570 | } | ||
| 2571 | ListEntry::Contact { contact, calling } => self | ||
| 2572 | .render_contact(&*contact, calling, is_selected, cx) | ||
| 2573 | .into_any_element(), | ||
| 2574 | ListEntry::ContactPlaceholder => self | ||
| 2575 | .render_contact_placeholder(is_selected, cx) | ||
| 2576 | .into_any_element(), | ||
| 2577 | ListEntry::IncomingRequest(user) => self | ||
| 2578 | .render_contact_request(user, true, is_selected, cx) | ||
| 2579 | .into_any_element(), | ||
| 2580 | ListEntry::OutgoingRequest(user) => self | ||
| 2581 | .render_contact_request(user, false, is_selected, cx) | ||
| 2582 | .into_any_element(), | ||
| 2583 | ListEntry::Channel { | ||
| 2584 | channel, | ||
| 2585 | depth, | ||
| 2586 | has_children, | ||
| 2587 | } => self | ||
| 2588 | .render_channel(&*channel, depth, has_children, is_selected, cx) | ||
| 2589 | .into_any_element(), | ||
| 2590 | ListEntry::ChannelEditor { depth } => todo!(), | ||
| 2591 | } | ||
| 2592 | })) | ||
| 2593 | } | ||
| 2594 | |||
| 2595 | fn render_header( | ||
| 2596 | &mut self, | ||
| 2597 | section: Section, | ||
| 2598 | is_selected: bool, | ||
| 2599 | is_collapsed: bool, | ||
| 2600 | cx: &ViewContext<Self>, | ||
| 2601 | ) -> impl IntoElement { | ||
| 2602 | // let mut channel_link = None; | ||
| 2603 | // let mut channel_tooltip_text = None; | ||
| 2604 | // let mut channel_icon = None; | ||
| 2605 | // let mut is_dragged_over = false; | ||
| 2606 | |||
| 2607 | let text = match section { | ||
| 2608 | Section::ActiveCall => { | ||
| 2609 | // let channel_name = maybe!({ | ||
| 2610 | // let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?; | ||
| 2611 | |||
| 2612 | // let channel = self.channel_store.read(cx).channel_for_id(channel_id)?; | ||
| 2613 | |||
| 2614 | // channel_link = Some(channel.link()); | ||
| 2615 | // (channel_icon, channel_tooltip_text) = match channel.visibility { | ||
| 2616 | // proto::ChannelVisibility::Public => { | ||
| 2617 | // (Some("icons/public.svg"), Some("Copy public channel link.")) | ||
| 2618 | // } | ||
| 2619 | // proto::ChannelVisibility::Members => { | ||
| 2620 | // (Some("icons/hash.svg"), Some("Copy private channel link.")) | ||
| 2621 | // } | ||
| 2622 | // }; | ||
| 2623 | |||
| 2624 | // Some(channel.name.as_str()) | ||
| 2625 | // }); | ||
| 2626 | |||
| 2627 | // if let Some(name) = channel_name { | ||
| 2628 | // SharedString::from(format!("{}", name)) | ||
| 2629 | // } else { | ||
| 2630 | // SharedString::from("Current Call") | ||
| 2631 | // } | ||
| 2632 | todo!() | ||
| 2633 | } | ||
| 2634 | Section::ContactRequests => SharedString::from("Requests"), | ||
| 2635 | Section::Contacts => SharedString::from("Contacts"), | ||
| 2636 | Section::Channels => SharedString::from("Channels"), | ||
| 2637 | Section::ChannelInvites => SharedString::from("Invites"), | ||
| 2638 | Section::Online => SharedString::from("Online"), | ||
| 2639 | Section::Offline => SharedString::from("Offline"), | ||
| 2640 | }; | ||
| 2641 | |||
| 2642 | let button = match section { | ||
| 2643 | Section::ActiveCall => | ||
| 2644 | // channel_link.map(|channel_link| { | ||
| 2645 | // let channel_link_copy = channel_link.clone(); | ||
| 2646 | // MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| { | ||
| 2647 | // render_icon_button( | ||
| 2648 | // theme | ||
| 2649 | // .collab_panel | ||
| 2650 | // .leave_call_button | ||
| 2651 | // .style_for(is_selected, state), | ||
| 2652 | // "icons/link.svg", | ||
| 2653 | // ) | ||
| 2654 | // }) | ||
| 2655 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 2656 | // .on_click(MouseButton::Left, move |_, _, cx| { | ||
| 2657 | // let item = ClipboardItem::new(channel_link_copy.clone()); | ||
| 2658 | // cx.write_to_clipboard(item) | ||
| 2659 | // }) | ||
| 2660 | // .with_tooltip::<AddContact>( | ||
| 2661 | // 0, | ||
| 2662 | // channel_tooltip_text.unwrap(), | ||
| 2663 | // None, | ||
| 2664 | // tooltip_style.clone(), | ||
| 2665 | // cx, | ||
| 2666 | // ) | ||
| 2667 | // }), | ||
| 2668 | { | ||
| 2669 | todo!() | ||
| 2670 | } | ||
| 2671 | Section::Contacts => Some( | ||
| 2672 | IconButton::new("add-contact", Icon::Plus) | ||
| 2673 | .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) | ||
| 2674 | .tooltip(|cx| Tooltip::text("Search for new contact", cx)), | ||
| 2675 | ), | ||
| 2676 | Section::Channels => { | ||
| 2677 | // todo!() | ||
| 2678 | // if cx | ||
| 2679 | // .global::<DragAndDrop<Workspace>>() | ||
| 2680 | // .currently_dragged::<Channel>(cx.window()) | ||
| 2681 | // .is_some() | ||
| 2682 | // && self.drag_target_channel == ChannelDragTarget::Root | ||
| 2683 | // { | ||
| 2684 | // is_dragged_over = true; | ||
| 2685 | // } | ||
| 2686 | |||
| 2687 | Some( | ||
| 2688 | IconButton::new("add-channel", Icon::Plus) | ||
| 2689 | .on_click(cx.listener(|this, _, cx| { | ||
| 2690 | todo!() | ||
| 2691 | // this.new_root_channel(cx) | ||
| 2692 | })) | ||
| 2693 | .tooltip(|cx| Tooltip::text("Create a channel", cx)), | ||
| 2694 | ) | ||
| 2695 | } | ||
| 2696 | _ => None, | ||
| 2697 | }; | ||
| 2698 | |||
| 2699 | let can_collapse = match section { | ||
| 2700 | Section::ActiveCall | Section::Channels | Section::Contacts => false, | ||
| 2701 | Section::ChannelInvites | ||
| 2702 | | Section::ContactRequests | ||
| 2703 | | Section::Online | ||
| 2704 | | Section::Offline => true, | ||
| 2705 | }; | ||
| 2706 | |||
| 2707 | let mut header = ListHeader::new(text); | ||
| 2708 | if let Some(button) = button { | ||
| 2709 | header = header.right_button(button) | ||
| 2710 | } | ||
| 2711 | // todo!() is selected | ||
| 2712 | if can_collapse { | ||
| 2713 | // todo!() on click to toggle | ||
| 2714 | header = header.toggle(ui::Toggle::Toggled(is_collapsed)); | ||
| 2715 | } | ||
| 2716 | |||
| 2717 | header | ||
| 2718 | } | ||
| 2719 | fn render_contact( | ||
| 2720 | &mut self, | ||
| 2721 | contact: &Contact, | ||
| 2722 | calling: bool, | ||
| 2723 | is_selected: bool, | ||
| 2724 | cx: &mut ViewContext<Self>, | ||
| 2725 | ) -> impl IntoElement { | ||
| 2726 | enum ContactTooltip {} | ||
| 2727 | |||
| 2728 | let online = contact.online; | ||
| 2729 | let busy = contact.busy || calling; | ||
| 2730 | let user_id = contact.user.id; | ||
| 2731 | let github_login = SharedString::from(contact.user.github_login.clone()); | ||
| 2732 | let mut item = ListItem::new(github_login.clone()) | ||
| 2733 | .on_click(cx.listener(move |this, _, cx| { | ||
| 2734 | this.workspace | ||
| 2735 | .update(cx, |this, cx| { | ||
| 2736 | this.call_state() | ||
| 2737 | .invite(user_id, None, cx) | ||
| 2738 | .detach_and_log_err(cx) | ||
| 2739 | }) | ||
| 2740 | .log_err(); | ||
| 2741 | })) | ||
| 2742 | .child(Label::new(github_login.clone())); | ||
| 2743 | if let Some(avatar) = contact.user.avatar.clone() { | ||
| 2744 | //item = item.left_avatar(avatar); | ||
| 2745 | } | ||
| 2746 | // let event_handler = | ||
| 2747 | // MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| { | ||
| 2748 | // Flex::row() | ||
| 2749 | // .with_children(contact.user.avatar.clone().map(|avatar| { | ||
| 2750 | // let status_badge = if contact.online { | ||
| 2751 | // Some( | ||
| 2752 | // Empty::new() | ||
| 2753 | // .collapsed() | ||
| 2754 | // .contained() | ||
| 2755 | // .with_style(if busy { | ||
| 2756 | // collab_theme.contact_status_busy | ||
| 2757 | // } else { | ||
| 2758 | // collab_theme.contact_status_free | ||
| 2759 | // }) | ||
| 2760 | // .aligned(), | ||
| 2761 | // ) | ||
| 2762 | // } else { | ||
| 2763 | // None | ||
| 2764 | // }; | ||
| 2765 | // Stack::new() | ||
| 2766 | // .with_child( | ||
| 2767 | // Image::from_data(avatar) | ||
| 2768 | // .with_style(collab_theme.contact_avatar) | ||
| 2769 | // .aligned() | ||
| 2770 | // .left(), | ||
| 2771 | // ) | ||
| 2772 | // .with_children(status_badge) | ||
| 2773 | // })) | ||
| 2774 | // .with_child( | ||
| 2775 | // Label::new( | ||
| 2776 | // contact.user.github_login.clone(), | ||
| 2777 | // collab_theme.contact_username.text.clone(), | ||
| 2778 | // ) | ||
| 2779 | // .contained() | ||
| 2780 | // .with_style(collab_theme.contact_username.container) | ||
| 2781 | // .aligned() | ||
| 2782 | // .left() | ||
| 2783 | // .flex(1., true), | ||
| 2784 | // ) | ||
| 2785 | // .with_children(if state.hovered() { | ||
| 2786 | // Some( | ||
| 2787 | // MouseEventHandler::new::<Cancel, _>( | ||
| 2788 | // contact.user.id as usize, | ||
| 2789 | // cx, | ||
| 2790 | // |mouse_state, _| { | ||
| 2791 | // let button_style = | ||
| 2792 | // collab_theme.contact_button.style_for(mouse_state); | ||
| 2793 | // render_icon_button(button_style, "icons/x.svg") | ||
| 2794 | // .aligned() | ||
| 2795 | // .flex_float() | ||
| 2796 | // }, | ||
| 2797 | // ) | ||
| 2798 | // .with_padding(Padding::uniform(2.)) | ||
| 2799 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 2800 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 2801 | // this.remove_contact(user_id, &github_login, cx); | ||
| 2802 | // }) | ||
| 2803 | // .flex_float(), | ||
| 2804 | // ) | ||
| 2805 | // } else { | ||
| 2806 | // None | ||
| 2807 | // }) | ||
| 2808 | // .with_children(if calling { | ||
| 2809 | // Some( | ||
| 2810 | // Label::new("Calling", collab_theme.calling_indicator.text.clone()) | ||
| 2811 | // .contained() | ||
| 2812 | // .with_style(collab_theme.calling_indicator.container) | ||
| 2813 | // .aligned(), | ||
| 2814 | // ) | ||
| 2815 | // } else { | ||
| 2816 | // None | ||
| 2817 | // }) | ||
| 2818 | // .constrained() | ||
| 2819 | // .with_height(collab_theme.row_height) | ||
| 2820 | // .contained() | ||
| 2821 | // .with_style( | ||
| 2822 | // *collab_theme | ||
| 2823 | // .contact_row | ||
| 2824 | // .in_state(is_selected) | ||
| 2825 | // .style_for(state), | ||
| 2826 | // ) | ||
| 2827 | // }); | ||
| 2828 | |||
| 2829 | // if online && !busy { | ||
| 2830 | // let room = ActiveCall::global(cx).read(cx).room(); | ||
| 2831 | // let label = if room.is_some() { | ||
| 2832 | // format!("Invite {} to join call", contact.user.github_login) | ||
| 2833 | // } else { | ||
| 2834 | // format!("Call {}", contact.user.github_login) | ||
| 2835 | // }; | ||
| 2836 | |||
| 2837 | // event_handler | ||
| 2838 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 2839 | // this.call(user_id, Some(initial_project.clone()), cx); | ||
| 2840 | // }) | ||
| 2841 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 2842 | // .with_tooltip::<ContactTooltip>( | ||
| 2843 | // contact.user.id as usize, | ||
| 2844 | // label, | ||
| 2845 | // None, | ||
| 2846 | // theme.tooltip.clone(), | ||
| 2847 | // cx, | ||
| 2848 | // ) | ||
| 2849 | // .into_any() | ||
| 2850 | // } else { | ||
| 2851 | // event_handler | ||
| 2852 | // .with_tooltip::<ContactTooltip>( | ||
| 2853 | // contact.user.id as usize, | ||
| 2854 | // format!( | ||
| 2855 | // "{} is {}", | ||
| 2856 | // contact.user.github_login, | ||
| 2857 | // if busy { "on a call" } else { "offline" } | ||
| 2858 | // ), | ||
| 2859 | // None, | ||
| 2860 | // theme.tooltip.clone(), | ||
| 2861 | // cx, | ||
| 2862 | // ) | ||
| 2863 | // .into_any() | ||
| 2864 | // }; | ||
| 2865 | |||
| 2866 | item | ||
| 2867 | } | ||
| 2868 | |||
| 2869 | fn render_contact_request( | ||
| 2870 | &mut self, | ||
| 2871 | user: Arc<User>, | ||
| 2872 | is_incoming: bool, | ||
| 2873 | is_selected: bool, | ||
| 2874 | cx: &mut ViewContext<Self>, | ||
| 2875 | ) -> impl IntoElement { | ||
| 2876 | let github_login = SharedString::from(user.github_login.clone()); | ||
| 2877 | |||
| 2878 | let mut item = ListItem::new(github_login.clone()) | ||
| 2879 | .child(Label::new(github_login.clone())) | ||
| 2880 | .on_click(cx.listener(|this, _, cx| { | ||
| 2881 | todo!(); | ||
| 2882 | })); | ||
| 2883 | if let Some(avatar) = user.avatar.clone() { | ||
| 2884 | item = item.left_avatar(avatar); | ||
| 2885 | } | ||
| 2886 | // .with_children(user.avatar.clone().map(|avatar| { | ||
| 2887 | // Image::from_data(avatar) | ||
| 2888 | // .with_style(theme.contact_avatar) | ||
| 2889 | // .aligned() | ||
| 2890 | // .left() | ||
| 2891 | // })) | ||
| 2892 | // .with_child( | ||
| 2893 | // Label::new( | ||
| 2894 | // user.github_login.clone(), | ||
| 2895 | // theme.contact_username.text.clone(), | ||
| 2896 | // ) | ||
| 2897 | // .contained() | ||
| 2898 | // .with_style(theme.contact_username.container) | ||
| 2899 | // .aligned() | ||
| 2900 | // .left() | ||
| 2901 | // .flex(1., true), | ||
| 2902 | // ); | ||
| 2903 | |||
| 2904 | // let user_id = user.id; | ||
| 2905 | // let github_login = user.github_login.clone(); | ||
| 2906 | // let is_contact_request_pending = user_store.read(cx).is_contact_request_pending(&user); | ||
| 2907 | // let button_spacing = theme.contact_button_spacing; | ||
| 2908 | |||
| 2909 | // if is_incoming { | ||
| 2910 | // row.add_child( | ||
| 2911 | // MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| { | ||
| 2912 | // let button_style = if is_contact_request_pending { | ||
| 2913 | // &theme.disabled_button | ||
| 2914 | // } else { | ||
| 2915 | // theme.contact_button.style_for(mouse_state) | ||
| 2916 | // }; | ||
| 2917 | // render_icon_button(button_style, "icons/x.svg").aligned() | ||
| 2918 | // }) | ||
| 2919 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 2920 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 2921 | // this.respond_to_contact_request(user_id, false, cx); | ||
| 2922 | // }) | ||
| 2923 | // .contained() | ||
| 2924 | // .with_margin_right(button_spacing), | ||
| 2925 | // ); | ||
| 2926 | |||
| 2927 | // row.add_child( | ||
| 2928 | // MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| { | ||
| 2929 | // let button_style = if is_contact_request_pending { | ||
| 2930 | // &theme.disabled_button | ||
| 2931 | // } else { | ||
| 2932 | // theme.contact_button.style_for(mouse_state) | ||
| 2933 | // }; | ||
| 2934 | // render_icon_button(button_style, "icons/check.svg") | ||
| 2935 | // .aligned() | ||
| 2936 | // .flex_float() | ||
| 2937 | // }) | ||
| 2938 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 2939 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 2940 | // this.respond_to_contact_request(user_id, true, cx); | ||
| 2941 | // }), | ||
| 2942 | // ); | ||
| 2943 | // } else { | ||
| 2944 | // row.add_child( | ||
| 2945 | // MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| { | ||
| 2946 | // let button_style = if is_contact_request_pending { | ||
| 2947 | // &theme.disabled_button | ||
| 2948 | // } else { | ||
| 2949 | // theme.contact_button.style_for(mouse_state) | ||
| 2950 | // }; | ||
| 2951 | // render_icon_button(button_style, "icons/x.svg") | ||
| 2952 | // .aligned() | ||
| 2953 | // .flex_float() | ||
| 2954 | // }) | ||
| 2955 | // .with_padding(Padding::uniform(2.)) | ||
| 2956 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 2957 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 2958 | // this.remove_contact(user_id, &github_login, cx); | ||
| 2959 | // }) | ||
| 2960 | // .flex_float(), | ||
| 2961 | // ); | ||
| 2962 | // } | ||
| 2963 | |||
| 2964 | // row.constrained() | ||
| 2965 | // .with_height(theme.row_height) | ||
| 2966 | // .contained() | ||
| 2967 | // .with_style( | ||
| 2968 | // *theme | ||
| 2969 | // .contact_row | ||
| 2970 | // .in_state(is_selected) | ||
| 2971 | // .style_for(&mut Default::default()), | ||
| 2972 | // ) | ||
| 2973 | // .into_any() | ||
| 2974 | item | ||
| 2975 | } | ||
| 2976 | |||
| 2977 | fn render_contact_placeholder( | ||
| 2978 | &self, | ||
| 2979 | is_selected: bool, | ||
| 2980 | cx: &mut ViewContext<Self>, | ||
| 2981 | ) -> impl IntoElement { | ||
| 2982 | ListItem::new("contact-placeholder") | ||
| 2983 | .child(Label::new("Add a Contact")) | ||
| 2984 | .on_click(cx.listener(|this, _, cx| todo!())) | ||
| 2985 | // enum AddContacts {} | ||
| 2986 | // MouseEventHandler::new::<AddContacts, _>(0, cx, |state, _| { | ||
| 2987 | // let style = theme.list_empty_state.style_for(is_selected, state); | ||
| 2988 | // Flex::row() | ||
| 2989 | // .with_child( | ||
| 2990 | // Svg::new("icons/plus.svg") | ||
| 2991 | // .with_color(theme.list_empty_icon.color) | ||
| 2992 | // .constrained() | ||
| 2993 | // .with_width(theme.list_empty_icon.width) | ||
| 2994 | // .aligned() | ||
| 2995 | // .left(), | ||
| 2996 | // ) | ||
| 2997 | // .with_child( | ||
| 2998 | // Label::new("Add a contact", style.text.clone()) | ||
| 2999 | // .contained() | ||
| 3000 | // .with_style(theme.list_empty_label_container), | ||
| 3001 | // ) | ||
| 3002 | // .align_children_center() | ||
| 3003 | // .contained() | ||
| 3004 | // .with_style(style.container) | ||
| 3005 | // .into_any() | ||
| 3006 | // }) | ||
| 3007 | // .on_click(MouseButton::Left, |_, this, cx| { | ||
| 3008 | // this.toggle_contact_finder(cx); | ||
| 3009 | // }) | ||
| 3010 | // .into_any() | ||
| 3011 | } | ||
| 3012 | |||
| 3013 | fn render_channel( | ||
| 3014 | &self, | ||
| 3015 | channel: &Channel, | ||
| 3016 | depth: usize, | ||
| 3017 | has_children: bool, | ||
| 3018 | is_selected: bool, | ||
| 3019 | cx: &mut ViewContext<Self>, | ||
| 3020 | ) -> impl IntoElement { | ||
| 3021 | let channel_id = channel.id; | ||
| 3022 | ListItem::new(channel_id as usize).child(Label::new(channel.name.clone())) | ||
| 3023 | // let channel_id = channel.id; | ||
| 3024 | // let collab_theme = &theme.collab_panel; | ||
| 3025 | // let is_public = self | ||
| 3026 | // .channel_store | ||
| 3027 | // .read(cx) | ||
| 3028 | // .channel_for_id(channel_id) | ||
| 3029 | // .map(|channel| channel.visibility) | ||
| 3030 | // == Some(proto::ChannelVisibility::Public); | ||
| 3031 | // let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id); | ||
| 3032 | // let disclosed = | ||
| 3033 | // has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok()); | ||
| 3034 | |||
| 3035 | // let is_active = maybe!({ | ||
| 3036 | // let call_channel = ActiveCall::global(cx) | ||
| 3037 | // .read(cx) | ||
| 3038 | // .room()? | ||
| 3039 | // .read(cx) | ||
| 3040 | // .channel_id()?; | ||
| 3041 | // Some(call_channel == channel_id) | ||
| 3042 | // }) | ||
| 3043 | // .unwrap_or(false); | ||
| 3044 | |||
| 3045 | // const FACEPILE_LIMIT: usize = 3; | ||
| 3046 | |||
| 3047 | // enum ChannelCall {} | ||
| 3048 | // enum ChannelNote {} | ||
| 3049 | // enum NotesTooltip {} | ||
| 3050 | // enum ChatTooltip {} | ||
| 3051 | // enum ChannelTooltip {} | ||
| 3052 | |||
| 3053 | // let mut is_dragged_over = false; | ||
| 3054 | // if cx | ||
| 3055 | // .global::<DragAndDrop<Workspace>>() | ||
| 3056 | // .currently_dragged::<Channel>(cx.window()) | ||
| 3057 | // .is_some() | ||
| 3058 | // && self.drag_target_channel == ChannelDragTarget::Channel(channel_id) | ||
| 3059 | // { | ||
| 3060 | // is_dragged_over = true; | ||
| 3061 | // } | ||
| 3062 | |||
| 3063 | // let has_messages_notification = channel.unseen_message_id.is_some(); | ||
| 3064 | |||
| 3065 | // MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| { | ||
| 3066 | // let row_hovered = state.hovered(); | ||
| 3067 | |||
| 3068 | // let mut select_state = |interactive: &Interactive<ContainerStyle>| { | ||
| 3069 | // if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() { | ||
| 3070 | // interactive.clicked.as_ref().unwrap().clone() | ||
| 3071 | // } else if state.hovered() || other_selected { | ||
| 3072 | // interactive | ||
| 3073 | // .hovered | ||
| 3074 | // .as_ref() | ||
| 3075 | // .unwrap_or(&interactive.default) | ||
| 3076 | // .clone() | ||
| 3077 | // } else { | ||
| 3078 | // interactive.default.clone() | ||
| 3079 | // } | ||
| 3080 | // }; | ||
| 3081 | |||
| 3082 | // Flex::<Self>::row() | ||
| 3083 | // .with_child( | ||
| 3084 | // Svg::new(if is_public { | ||
| 3085 | // "icons/public.svg" | ||
| 3086 | // } else { | ||
| 3087 | // "icons/hash.svg" | ||
| 3088 | // }) | ||
| 3089 | // .with_color(collab_theme.channel_hash.color) | ||
| 3090 | // .constrained() | ||
| 3091 | // .with_width(collab_theme.channel_hash.width) | ||
| 3092 | // .aligned() | ||
| 3093 | // .left(), | ||
| 3094 | // ) | ||
| 3095 | // .with_child({ | ||
| 3096 | // let style = collab_theme.channel_name.inactive_state(); | ||
| 3097 | // Flex::row() | ||
| 3098 | // .with_child( | ||
| 3099 | // Label::new(channel.name.clone(), style.text.clone()) | ||
| 3100 | // .contained() | ||
| 3101 | // .with_style(style.container) | ||
| 3102 | // .aligned() | ||
| 3103 | // .left() | ||
| 3104 | // .with_tooltip::<ChannelTooltip>( | ||
| 3105 | // ix, | ||
| 3106 | // "Join channel", | ||
| 3107 | // None, | ||
| 3108 | // theme.tooltip.clone(), | ||
| 3109 | // cx, | ||
| 3110 | // ), | ||
| 3111 | // ) | ||
| 3112 | // .with_children({ | ||
| 3113 | // let participants = | ||
| 3114 | // self.channel_store.read(cx).channel_participants(channel_id); | ||
| 3115 | |||
| 3116 | // if !participants.is_empty() { | ||
| 3117 | // let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT); | ||
| 3118 | |||
| 3119 | // let result = FacePile::new(collab_theme.face_overlap) | ||
| 3120 | // .with_children( | ||
| 3121 | // participants | ||
| 3122 | // .iter() | ||
| 3123 | // .filter_map(|user| { | ||
| 3124 | // Some( | ||
| 3125 | // Image::from_data(user.avatar.clone()?) | ||
| 3126 | // .with_style(collab_theme.channel_avatar), | ||
| 3127 | // ) | ||
| 3128 | // }) | ||
| 3129 | // .take(FACEPILE_LIMIT), | ||
| 3130 | // ) | ||
| 3131 | // .with_children((extra_count > 0).then(|| { | ||
| 3132 | // Label::new( | ||
| 3133 | // format!("+{}", extra_count), | ||
| 3134 | // collab_theme.extra_participant_label.text.clone(), | ||
| 3135 | // ) | ||
| 3136 | // .contained() | ||
| 3137 | // .with_style(collab_theme.extra_participant_label.container) | ||
| 3138 | // })); | ||
| 3139 | |||
| 3140 | // Some(result) | ||
| 3141 | // } else { | ||
| 3142 | // None | ||
| 3143 | // } | ||
| 3144 | // }) | ||
| 3145 | // .with_spacing(8.) | ||
| 3146 | // .align_children_center() | ||
| 3147 | // .flex(1., true) | ||
| 3148 | // }) | ||
| 3149 | // .with_child( | ||
| 3150 | // MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| { | ||
| 3151 | // let container_style = collab_theme | ||
| 3152 | // .disclosure | ||
| 3153 | // .button | ||
| 3154 | // .style_for(mouse_state) | ||
| 3155 | // .container; | ||
| 3156 | |||
| 3157 | // if channel.unseen_message_id.is_some() { | ||
| 3158 | // Svg::new("icons/conversations.svg") | ||
| 3159 | // .with_color(collab_theme.channel_note_active_color) | ||
| 3160 | // .constrained() | ||
| 3161 | // .with_width(collab_theme.channel_hash.width) | ||
| 3162 | // .contained() | ||
| 3163 | // .with_style(container_style) | ||
| 3164 | // .with_uniform_padding(4.) | ||
| 3165 | // .into_any() | ||
| 3166 | // } else if row_hovered { | ||
| 3167 | // Svg::new("icons/conversations.svg") | ||
| 3168 | // .with_color(collab_theme.channel_hash.color) | ||
| 3169 | // .constrained() | ||
| 3170 | // .with_width(collab_theme.channel_hash.width) | ||
| 3171 | // .contained() | ||
| 3172 | // .with_style(container_style) | ||
| 3173 | // .with_uniform_padding(4.) | ||
| 3174 | // .into_any() | ||
| 3175 | // } else { | ||
| 3176 | // Empty::new().into_any() | ||
| 3177 | // } | ||
| 3178 | // }) | ||
| 3179 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 3180 | // this.join_channel_chat(&JoinChannelChat { channel_id }, cx); | ||
| 3181 | // }) | ||
| 3182 | // .with_tooltip::<ChatTooltip>( | ||
| 3183 | // ix, | ||
| 3184 | // "Open channel chat", | ||
| 3185 | // None, | ||
| 3186 | // theme.tooltip.clone(), | ||
| 3187 | // cx, | ||
| 3188 | // ) | ||
| 3189 | // .contained() | ||
| 3190 | // .with_margin_right(4.), | ||
| 3191 | // ) | ||
| 3192 | // .with_child( | ||
| 3193 | // MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| { | ||
| 3194 | // let container_style = collab_theme | ||
| 3195 | // .disclosure | ||
| 3196 | // .button | ||
| 3197 | // .style_for(mouse_state) | ||
| 3198 | // .container; | ||
| 3199 | // if row_hovered || channel.unseen_note_version.is_some() { | ||
| 3200 | // Svg::new("icons/file.svg") | ||
| 3201 | // .with_color(if channel.unseen_note_version.is_some() { | ||
| 3202 | // collab_theme.channel_note_active_color | ||
| 3203 | // } else { | ||
| 3204 | // collab_theme.channel_hash.color | ||
| 3205 | // }) | ||
| 3206 | // .constrained() | ||
| 3207 | // .with_width(collab_theme.channel_hash.width) | ||
| 3208 | // .contained() | ||
| 3209 | // .with_style(container_style) | ||
| 3210 | // .with_uniform_padding(4.) | ||
| 3211 | // .with_margin_right(collab_theme.channel_hash.container.margin.left) | ||
| 3212 | // .with_tooltip::<NotesTooltip>( | ||
| 3213 | // ix as usize, | ||
| 3214 | // "Open channel notes", | ||
| 3215 | // None, | ||
| 3216 | // theme.tooltip.clone(), | ||
| 3217 | // cx, | ||
| 3218 | // ) | ||
| 3219 | // .into_any() | ||
| 3220 | // } else if has_messages_notification { | ||
| 3221 | // Empty::new() | ||
| 3222 | // .constrained() | ||
| 3223 | // .with_width(collab_theme.channel_hash.width) | ||
| 3224 | // .contained() | ||
| 3225 | // .with_uniform_padding(4.) | ||
| 3226 | // .with_margin_right(collab_theme.channel_hash.container.margin.left) | ||
| 3227 | // .into_any() | ||
| 3228 | // } else { | ||
| 3229 | // Empty::new().into_any() | ||
| 3230 | // } | ||
| 3231 | // }) | ||
| 3232 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 3233 | // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx); | ||
| 3234 | // }), | ||
| 3235 | // ) | ||
| 3236 | // .align_children_center() | ||
| 3237 | // .styleable_component() | ||
| 3238 | // .disclosable( | ||
| 3239 | // disclosed, | ||
| 3240 | // Box::new(ToggleCollapse { | ||
| 3241 | // location: channel.id.clone(), | ||
| 3242 | // }), | ||
| 3243 | // ) | ||
| 3244 | // .with_id(ix) | ||
| 3245 | // .with_style(collab_theme.disclosure.clone()) | ||
| 3246 | // .element() | ||
| 3247 | // .constrained() | ||
| 3248 | // .with_height(collab_theme.row_height) | ||
| 3249 | // .contained() | ||
| 3250 | // .with_style(select_state( | ||
| 3251 | // collab_theme | ||
| 3252 | // .channel_row | ||
| 3253 | // .in_state(is_selected || is_active || is_dragged_over), | ||
| 3254 | // )) | ||
| 3255 | // .with_padding_left( | ||
| 3256 | // collab_theme.channel_row.default_style().padding.left | ||
| 3257 | // + collab_theme.channel_indent * depth as f32, | ||
| 3258 | // ) | ||
| 3259 | // }) | ||
| 3260 | // .on_click(MouseButton::Left, move |_, this, cx| { | ||
| 3261 | // if this.drag_target_channel == ChannelDragTarget::None { | ||
| 3262 | // if is_active { | ||
| 3263 | // this.open_channel_notes(&OpenChannelNotes { channel_id }, cx) | ||
| 3264 | // } else { | ||
| 3265 | // this.join_channel(channel_id, cx) | ||
| 3266 | // } | ||
| 3267 | // } | ||
| 3268 | // }) | ||
| 3269 | // .on_click(MouseButton::Right, { | ||
| 3270 | // let channel = channel.clone(); | ||
| 3271 | // move |e, this, cx| { | ||
| 3272 | // this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx); | ||
| 3273 | // } | ||
| 3274 | // }) | ||
| 3275 | // .on_up(MouseButton::Left, move |_, this, cx| { | ||
| 3276 | // if let Some((_, dragged_channel)) = cx | ||
| 3277 | // .global::<DragAndDrop<Workspace>>() | ||
| 3278 | // .currently_dragged::<Channel>(cx.window()) | ||
| 3279 | // { | ||
| 3280 | // this.channel_store | ||
| 3281 | // .update(cx, |channel_store, cx| { | ||
| 3282 | // channel_store.move_channel(dragged_channel.id, Some(channel_id), cx) | ||
| 3283 | // }) | ||
| 3284 | // .detach_and_log_err(cx) | ||
| 3285 | // } | ||
| 3286 | // }) | ||
| 3287 | // .on_move({ | ||
| 3288 | // let channel = channel.clone(); | ||
| 3289 | // move |_, this, cx| { | ||
| 3290 | // if let Some((_, dragged_channel)) = cx | ||
| 3291 | // .global::<DragAndDrop<Workspace>>() | ||
| 3292 | // .currently_dragged::<Channel>(cx.window()) | ||
| 3293 | // { | ||
| 3294 | // if channel.id != dragged_channel.id { | ||
| 3295 | // this.drag_target_channel = ChannelDragTarget::Channel(channel.id); | ||
| 3296 | // } | ||
| 3297 | // cx.notify() | ||
| 3298 | // } | ||
| 3299 | // } | ||
| 3300 | // }) | ||
| 3301 | // .as_draggable::<_, Channel>( | ||
| 3302 | // channel.clone(), | ||
| 3303 | // move |_, channel, cx: &mut ViewContext<Workspace>| { | ||
| 3304 | // let theme = &theme::current(cx).collab_panel; | ||
| 3305 | |||
| 3306 | // Flex::<Workspace>::row() | ||
| 3307 | // .with_child( | ||
| 3308 | // Svg::new("icons/hash.svg") | ||
| 3309 | // .with_color(theme.channel_hash.color) | ||
| 3310 | // .constrained() | ||
| 3311 | // .with_width(theme.channel_hash.width) | ||
| 3312 | // .aligned() | ||
| 3313 | // .left(), | ||
| 3314 | // ) | ||
| 3315 | // .with_child( | ||
| 3316 | // Label::new(channel.name.clone(), theme.channel_name.text.clone()) | ||
| 3317 | // .contained() | ||
| 3318 | // .with_style(theme.channel_name.container) | ||
| 3319 | // .aligned() | ||
| 3320 | // .left(), | ||
| 3321 | // ) | ||
| 3322 | // .align_children_center() | ||
| 3323 | // .contained() | ||
| 3324 | // .with_background_color( | ||
| 3325 | // theme | ||
| 3326 | // .container | ||
| 3327 | // .background_color | ||
| 3328 | // .unwrap_or(gpui::color::Color::transparent_black()), | ||
| 3329 | // ) | ||
| 3330 | // .contained() | ||
| 3331 | // .with_padding_left( | ||
| 3332 | // theme.channel_row.default_style().padding.left | ||
| 3333 | // + theme.channel_indent * depth as f32, | ||
| 3334 | // ) | ||
| 3335 | // .into_any() | ||
| 3336 | // }, | ||
| 3337 | // ) | ||
| 3338 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 3339 | // .into_any() | ||
| 3340 | } | ||
| 3250 | } | 3341 | } |
| 3251 | 3342 | ||
| 3252 | // fn render_tree_branch( | 3343 | // fn render_tree_branch( |
| @@ -3295,13 +3386,19 @@ impl CollabPanel { · 42 unmodified lines | |||
| 3295 | // } | 3386 | // } |
| 3296 | 3387 | ||
| 3297 | impl Render for CollabPanel { | 3388 | impl Render for CollabPanel { |
| 3298 | type Element = Focusable<Self, Div<Self>>; | 3389 | type Element = Focusable<Div>; |
| 3299 | 3390 | ||
| 3300 | fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { | 3391 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 3301 | div() | 3392 | div() |
| 3302 | .key_context("CollabPanel") | 3393 | .key_context("CollabPanel") |
| 3303 | .track_focus(&self.focus_handle) | 3394 | .track_focus(&self.focus_handle) |
| 3304 | .child("COLLAB PANEL") | 3395 | .map(|el| { |
| 3396 | if self.user_store.read(cx).current_user().is_none() { | ||
| 3397 | el.child(self.render_signed_out(cx)) | ||
| 3398 | } else { | ||
| 3399 | el.child(self.render_signed_in(cx)) | ||
| 3400 | } | ||
| 3401 | }) | ||
| 3305 | } | 3402 | } |
| 3306 | } | 3403 | } |
| 3307 | 3404 | ||
| @@ -1,37 +1,34 @@ | |||
| 1 | use client::{ContactRequestStatus, User, UserStore}; | 1 | use client::{ContactRequestStatus, User, UserStore}; |
| 2 | use gpui::{ | 2 | use gpui::{ |
| 3 | elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle, | 3 | div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle, |
| 4 | FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View, | ||
| 5 | ViewContext, VisualContext, WeakView, | ||
| 4 | }; | 6 | }; |
| 5 | use picker::{Picker, PickerDelegate, PickerEvent}; | 7 | use picker::{Picker, PickerDelegate}; |
| 6 | use std::sync::Arc; | 8 | use std::sync::Arc; |
| 7 | use util::TryFutureExt; | 9 | use theme::ActiveTheme as _; |
| 8 | use workspace::Modal; | 10 | use ui::{h_stack, v_stack, Label}; |
| 11 | use util::{ResultExt as _, TryFutureExt}; | ||
| 9 | 12 | ||
| 10 | pub fn init(cx: &mut AppContext) { | 13 | pub fn init(cx: &mut AppContext) { |
| 11 | Picker::<ContactFinderDelegate>::init(cx); | 14 | //Picker::<ContactFinderDelegate>::init(cx); |
| 12 | cx.add_action(ContactFinder::dismiss) | 15 | //cx.add_action(ContactFinder::dismiss) |
| 13 | } | 16 | } |
| 14 | 17 | ||
| 15 | pub struct ContactFinder { | 18 | pub struct ContactFinder { |
| 16 | picker: ViewHandle<Picker<ContactFinderDelegate>>, | 19 | picker: View<Picker<ContactFinderDelegate>>, |
| 17 | has_focus: bool, | 20 | has_focus: bool, |
| 18 | } | 21 | } |
| 19 | 22 | ||
| 20 | impl ContactFinder { | 23 | impl ContactFinder { |
| 21 | pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self { | 24 | pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self { |
| 22 | let picker = cx.add_view(|cx| { | 25 | let delegate = ContactFinderDelegate { |
| 23 | Picker::new( | 26 | parent: cx.view().downgrade(), |
| 24 | ContactFinderDelegate { | 27 | user_store, |
| 25 | user_store, | 28 | potential_contacts: Arc::from([]), |
| 26 | potential_contacts: Arc::from([]), | 29 | selected_index: 0, |
| 27 | selected_index: 0, | 30 | }; |
| 28 | }, | 31 | let picker = cx.build_view(|cx| Picker::new(delegate, cx)); |
| 29 | cx, | ||
| 30 | ) | ||
| 31 | .with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone()) | ||
| 32 | }); | ||
| 33 | |||
| 34 | cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach(); | ||
| 35 | 32 | ||
| 36 | Self { | 33 | Self { |
| 37 | picker, | 34 | picker, |
| @@ -41,105 +38,72 @@ impl ContactFinder { · 3 unmodified lines | |||
| 41 | 38 | ||
| 42 | pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) { | 39 | pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) { |
| 43 | self.picker.update(cx, |picker, cx| { | 40 | self.picker.update(cx, |picker, cx| { |
| 44 | picker.set_query(query, cx); | 41 | // todo!() |
| 42 | // picker.set_query(query, cx); | ||
| 45 | }); | 43 | }); |
| 46 | } | 44 | } |
| 47 | |||
| 48 | fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { | ||
| 49 | cx.emit(PickerEvent::Dismiss); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | impl Entity for ContactFinder { | ||
| 54 | type Event = PickerEvent; | ||
| 55 | } | 45 | } |
| 56 | 46 | ||
| 57 | impl View for ContactFinder { | 47 | impl Render for ContactFinder { |
| 58 | fn ui_name() -> &'static str { | 48 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 59 | "ContactFinder" | 49 | fn render_mode_button(text: &'static str) -> AnyElement { |
| 60 | } | 50 | Label::new(text).into_any_element() |
| 61 | |||
| 62 | fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { | ||
| 63 | let full_theme = &theme::current(cx); | ||
| 64 | let theme = &full_theme.collab_panel.tabbed_modal; | ||
| 65 | |||
| 66 | fn render_mode_button( | ||
| 67 | text: &'static str, | ||
| 68 | theme: &theme::TabbedModal, | ||
| 69 | _cx: &mut ViewContext<ContactFinder>, | ||
| 70 | ) -> AnyElement<ContactFinder> { | ||
| 71 | let contained_text = &theme.tab_button.active_state().default; | ||
| 72 | Label::new(text, contained_text.text.clone()) | ||
| 73 | .contained() | ||
| 74 | .with_style(contained_text.container.clone()) | ||
| 75 | .into_any() | ||
| 76 | } | 51 | } |
| 77 | 52 | ||
| 78 | Flex::column() | 53 | v_stack() |
| 79 | .with_child( | 54 | .child( |
| 80 | Flex::column() | 55 | v_stack() |
| 81 | .with_child( | 56 | .child(Label::new("Contacts")) |
| 82 | Label::new("Contacts", theme.title.text.clone()) | 57 | .child(h_stack().children([render_mode_button("Invite new contacts")])) |
| 83 | .contained() | 58 | .bg(cx.theme().colors().element_background), |
| 84 | .with_style(theme.title.container.clone()), | ||
| 85 | ) | ||
| 86 | .with_child(Flex::row().with_children([render_mode_button( | ||
| 87 | "Invite new contacts", | ||
| 88 | &theme, | ||
| 89 | cx, | ||
| 90 | )])) | ||
| 91 | .expanded() | ||
| 92 | .contained() | ||
| 93 | .with_style(theme.header), | ||
| 94 | ) | ||
| 95 | .with_child( | ||
| 96 | ChildView::new(&self.picker, cx) | ||
| 97 | .contained() | ||
| 98 | .with_style(theme.body), | ||
| 99 | ) | 59 | ) |
| 100 | .constrained() | 60 | .child(self.picker.clone()) |
| 101 | .with_max_height(theme.max_height) | 61 | .w_96() |
| 102 | .with_max_width(theme.max_width) | ||
| 103 | .contained() | ||
| 104 | .with_style(theme.modal) | ||
| 105 | .into_any() | ||
| 106 | } | 62 | } |
| 107 | 63 | ||
| 108 | fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) { | 64 | // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) { |
| 109 | self.has_focus = true; | 65 | // self.has_focus = true; |
| 110 | if cx.is_self_focused() { | 66 | // if cx.is_self_focused() { |
| 111 | cx.focus(&self.picker) | 67 | // cx.focus(&self.picker) |
| 112 | } | 68 | // } |
| 113 | } | 69 | // } |
| 114 | 70 | ||
| 115 | fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) { | 71 | // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) { |
| 116 | self.has_focus = false; | 72 | // self.has_focus = false; |
| 117 | } | 73 | // } |
| 74 | |||
| 75 | type Element = Div; | ||
| 118 | } | 76 | } |
| 119 | 77 | ||
| 120 | impl Modal for ContactFinder { | 78 | // impl Modal for ContactFinder { |
| 121 | fn has_focus(&self) -> bool { | 79 | // fn has_focus(&self) -> bool { |
| 122 | self.has_focus | 80 | // self.has_focus |
| 123 | } | 81 | // } |
| 124 | 82 | ||
| 125 | fn dismiss_on_event(event: &Self::Event) -> bool { | 83 | // fn dismiss_on_event(event: &Self::Event) -> bool { |
| 126 | match event { | 84 | // match event { |
| 127 | PickerEvent::Dismiss => true, | 85 | // PickerEvent::Dismiss => true, |
| 128 | } | 86 | // } |
| 129 | } | 87 | // } |
| 130 | } | 88 | // } |
| 131 | 89 | ||
| 132 | pub struct ContactFinderDelegate { | 90 | pub struct ContactFinderDelegate { |
| 91 | parent: WeakView<ContactFinder>, | ||
| 133 | potential_contacts: Arc<[Arc<User>]>, | 92 | potential_contacts: Arc<[Arc<User>]>, |
| 134 | user_store: ModelHandle<UserStore>, | 93 | user_store: Model<UserStore>, |
| 135 | selected_index: usize, | 94 | selected_index: usize, |
| 136 | } | 95 | } |
| 137 | 96 | ||
| 138 | impl PickerDelegate for ContactFinderDelegate { | 97 | impl EventEmitter<DismissEvent> for ContactFinder {} |
| 139 | fn placeholder_text(&self) -> Arc<str> { | 98 | |
| 140 | "Search collaborator by username...".into() | 99 | impl FocusableView for ContactFinder { |
| 100 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle { | ||
| 101 | self.picker.focus_handle(cx) | ||
| 141 | } | 102 | } |
| 103 | } | ||
| 142 | 104 | ||
| 105 | impl PickerDelegate for ContactFinderDelegate { | ||
| 106 | type ListItem = Div; | ||
| 143 | fn match_count(&self) -> usize { | 107 | fn match_count(&self) -> usize { |
| 144 | self.potential_contacts.len() | 108 | self.potential_contacts.len() |
| 145 | } | 109 | } |
| @@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate { · 6 unmodified lines | |||
| 152 | self.selected_index = ix; | 116 | self.selected_index = ix; |
| 153 | } | 117 | } |
| 154 | 118 | ||
| 119 | fn placeholder_text(&self) -> Arc<str> { | ||
| 120 | "Search collaborator by username...".into() | ||
| 121 | } | ||
| 122 | |||
| 155 | fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> { | 123 | fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> { |
| 156 | let search_users = self | 124 | let search_users = self |
| 157 | .user_store | 125 | .user_store |
| @@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate { · 3 unmodified lines | |||
| 161 | async { | 129 | async { |
| 162 | let potential_contacts = search_users.await?; | 130 | let potential_contacts = search_users.await?; |
| 163 | picker.update(&mut cx, |picker, cx| { | 131 | picker.update(&mut cx, |picker, cx| { |
| 164 | picker.delegate_mut().potential_contacts = potential_contacts.into(); | 132 | picker.delegate.potential_contacts = potential_contacts.into(); |
| 165 | cx.notify(); | 133 | cx.notify(); |
| 166 | })?; | 134 | })?; |
| 167 | anyhow::Ok(()) | 135 | anyhow::Ok(()) |
| @@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate { · 23 unmodified lines | |||
| 191 | } | 159 | } |
| 192 | 160 | ||
| 193 | fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) { | 161 | fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) { |
| 194 | cx.emit(PickerEvent::Dismiss); | 162 | //cx.emit(PickerEvent::Dismiss); |
| 163 | self.parent | ||
| 164 | .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) | ||
| 165 | .log_err(); | ||
| 195 | } | 166 | } |
| 196 | 167 | ||
| 197 | fn render_match( | 168 | fn render_match( |
| 198 | &self, | 169 | &self, |
| 199 | ix: usize, | 170 | ix: usize, |
| 200 | mouse_state: &mut MouseState, | ||
| 201 | selected: bool, | 171 | selected: bool, |
| 202 | cx: &gpui::AppContext, | 172 | cx: &mut ViewContext<Picker<Self>>, |
| 203 | ) -> AnyElement<Picker<Self>> { | 173 | ) -> Option<Self::ListItem> { |
| 204 | let full_theme = &theme::current(cx); | ||
| 205 | let theme = &full_theme.collab_panel.contact_finder; | ||
| 206 | let tabbed_modal = &full_theme.collab_panel.tabbed_modal; | ||
| 207 | let user = &self.potential_contacts[ix]; | 174 | let user = &self.potential_contacts[ix]; |
| 208 | let request_status = self.user_store.read(cx).contact_request_status(user); | 175 | let request_status = self.user_store.read(cx).contact_request_status(user); |
| 209 | 176 | ||
| @@ -214,48 +181,47 @@ impl PickerDelegate for ContactFinderDelegate { · 4 unmodified lines | |||
| 214 | ContactRequestStatus::RequestSent => Some("icons/x.svg"), | 181 | ContactRequestStatus::RequestSent => Some("icons/x.svg"), |
| 215 | ContactRequestStatus::RequestAccepted => None, | 182 | ContactRequestStatus::RequestAccepted => None, |
| 216 | }; | 183 | }; |
| 217 | let button_style = if self.user_store.read(cx).is_contact_request_pending(user) { | 184 | dbg!(icon_path); |
| 218 | &theme.disabled_contact_button | 185 | Some( |
| 219 | } else { | 186 | div() |
| 220 | &theme.contact_button | 187 | .flex_1() |
| 221 | }; | 188 | .justify_between() |
| 222 | let style = tabbed_modal | 189 | .children(user.avatar.clone().map(|avatar| img().data(avatar))) |
| 223 | .picker | 190 | .child(Label::new(user.github_login.clone())) |
| 224 | .item | 191 | .children(icon_path.map(|icon_path| svg().path(icon_path))), |
| 225 | .in_state(selected) | 192 | ) |
| 226 | .style_for(mouse_state); | 193 | // Flex::row() |
| 227 | Flex::row() | 194 | // .with_children(user.avatar.clone().map(|avatar| { |
| 228 | .with_children(user.avatar.clone().map(|avatar| { | 195 | // Image::from_data(avatar) |
| 229 | Image::from_data(avatar) | 196 | // .with_style(theme.contact_avatar) |
| 230 | .with_style(theme.contact_avatar) | 197 | // .aligned() |
| 231 | .aligned() | 198 | // .left() |
| 232 | .left() | 199 | // })) |
| 233 | })) | 200 | // .with_child( |
| 234 | .with_child( | 201 | // Label::new(user.github_login.clone(), style.label.clone()) |
| 235 | Label::new(user.github_login.clone(), style.label.clone()) | 202 | // .contained() |
| 236 | .contained() | 203 | // .with_style(theme.contact_username) |
| 237 | .with_style(theme.contact_username) | 204 | // .aligned() |
| 238 | .aligned() | 205 | // .left(), |
| 239 | .left(), | 206 | // ) |
| 240 | ) | 207 | // .with_children(icon_path.map(|icon_path| { |
| 241 | .with_children(icon_path.map(|icon_path| { | 208 | // Svg::new(icon_path) |
| 242 | Svg::new(icon_path) | 209 | // .with_color(button_style.color) |
| 243 | .with_color(button_style.color) | 210 | // .constrained() |
| 244 | .constrained() | 211 | // .with_width(button_style.icon_width) |
| 245 | .with_width(button_style.icon_width) | 212 | // .aligned() |
| 246 | .aligned() | 213 | // .contained() |
| 247 | .contained() | 214 | // .with_style(button_style.container) |
| 248 | .with_style(button_style.container) | 215 | // .constrained() |
| 249 | .constrained() | 216 | // .with_width(button_style.button_width) |
| 250 | .with_width(button_style.button_width) | 217 | // .with_height(button_style.button_width) |
| 251 | .with_height(button_style.button_width) | 218 | // .aligned() |
| 252 | .aligned() | 219 | // .flex_float() |
| 253 | .flex_float() | 220 | // })) |
| 254 | })) | 221 | // .contained() |
| 255 | .contained() | 222 | // .with_style(style.container) |
| 256 | .with_style(style.container) | 223 | // .constrained() |
| 257 | .constrained() | 224 | // .with_height(tabbed_modal.row_height) |
| 258 | .with_height(tabbed_modal.row_height) | 225 | // .into_any() |
| 259 | .into_any() | ||
| 260 | } | 226 | } |
| 261 | } | 227 | } |
| @@ -31,14 +31,17 @@ use std::sync::Arc; · 30 unmodified lines | |||
| 31 | use call::ActiveCall; | 31 | use call::ActiveCall; |
| 32 | use client::{Client, UserStore}; | 32 | use client::{Client, UserStore}; |
| 33 | use gpui::{ | 33 | use gpui::{ |
| 34 | div, px, rems, AppContext, Component, Div, InteractiveComponent, Model, ParentComponent, | 34 | div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model, MouseButton, |
| 35 | Render, Stateful, StatefulInteractiveComponent, Styled, Subscription, ViewContext, | 35 | ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription, |
| 36 | VisualContext, WeakView, WindowBounds, | 36 | ViewContext, VisualContext, WeakView, WindowBounds, |
| 37 | }; | 37 | }; |
| 38 | use project::Project; | 38 | use project::Project; |
| 39 | use theme::ActiveTheme; | 39 | use theme::ActiveTheme; |
| 40 | use ui::{h_stack, Button, ButtonVariant, KeyBinding, Label, TextColor, Tooltip}; | 40 | use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip}; |
| 41 | use workspace::Workspace; | 41 | use util::ResultExt; |
| 42 | use workspace::{notifications::NotifyResultExt, Workspace}; | ||
| 43 | |||
| 44 | use crate::face_pile::FacePile; | ||
| 42 | 45 | ||
| 43 | // const MAX_PROJECT_NAME_LENGTH: usize = 40; | 46 | // const MAX_PROJECT_NAME_LENGTH: usize = 40; |
| 44 | // const MAX_BRANCH_NAME_LENGTH: usize = 40; | 47 | // const MAX_BRANCH_NAME_LENGTH: usize = 40; |
| @@ -82,9 +85,44 @@ pub struct CollabTitlebarItem { · 37 unmodified lines | |||
| 82 | } | 85 | } |
| 83 | 86 | ||
| 84 | impl Render for CollabTitlebarItem { | 87 | impl Render for CollabTitlebarItem { |
| 85 | type Element = Stateful<Self, Div<Self>>; | 88 | type Element = Stateful<Div>; |
| 86 | 89 | ||
| 87 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | 90 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 91 | let is_in_room = self | ||
| 92 | .workspace | ||
| 93 | .update(cx, |this, cx| this.call_state().is_in_room(cx)) | ||
| 94 | .unwrap_or_default(); | ||
| 95 | let is_shared = is_in_room && self.project.read(cx).is_shared(); | ||
| 96 | let current_user = self.user_store.read(cx).current_user(); | ||
| 97 | let client = self.client.clone(); | ||
| 98 | let users = self | ||
| 99 | .workspace | ||
| 100 | .update(cx, |this, cx| this.call_state().remote_participants(cx)) | ||
| 101 | .log_err() | ||
| 102 | .flatten(); | ||
| 103 | let mic_icon = if self | ||
| 104 | .workspace | ||
| 105 | .update(cx, |this, cx| this.call_state().is_muted(cx)) | ||
| 106 | .log_err() | ||
| 107 | .flatten() | ||
| 108 | .unwrap_or_default() | ||
| 109 | { | ||
| 110 | ui::Icon::MicMute | ||
| 111 | } else { | ||
| 112 | ui::Icon::Mic | ||
| 113 | }; | ||
| 114 | let speakers_icon = if self | ||
| 115 | .workspace | ||
| 116 | .update(cx, |this, cx| this.call_state().is_deafened(cx)) | ||
| 117 | .log_err() | ||
| 118 | .flatten() | ||
| 119 | .unwrap_or_default() | ||
| 120 | { | ||
| 121 | ui::Icon::AudioOff | ||
| 122 | } else { | ||
| 123 | ui::Icon::AudioOn | ||
| 124 | }; | ||
| 125 | let workspace = self.workspace.clone(); | ||
| 88 | h_stack() | 126 | h_stack() |
| 89 | .id("titlebar") | 127 | .id("titlebar") |
| 90 | .justify_between() | 128 | .justify_between() |
| @@ -100,7 +138,7 @@ impl Render for CollabTitlebarItem { · 9 unmodified lines | |||
| 100 | |s| s.pl(px(68.)), | 138 | |s| s.pl(px(68.)), |
| 101 | ) | 139 | ) |
| 102 | .bg(cx.theme().colors().title_bar_background) | 140 | .bg(cx.theme().colors().title_bar_background) |
| 103 | .on_click(|_, event, cx| { | 141 | .on_click(|event, cx| { |
| 104 | if event.up.click_count == 2 { | 142 | if event.up.click_count == 2 { |
| 105 | cx.zoom_window(); | 143 | cx.zoom_window(); |
| 106 | } | 144 | } |
| @@ -111,31 +149,37 @@ impl Render for CollabTitlebarItem { · 4 unmodified lines | |||
| 111 | // TODO - Add player menu | 149 | // TODO - Add player menu |
| 112 | .child( | 150 | .child( |
| 113 | div() | 151 | div() |
| 152 | .border() | ||
| 153 | .border_color(gpui::red()) | ||
| 114 | .id("project_owner_indicator") | 154 | .id("project_owner_indicator") |
| 115 | .child( | 155 | .child( |
| 116 | Button::new("player") | 156 | Button::new("player") |
| 117 | .variant(ButtonVariant::Ghost) | 157 | .variant(ButtonVariant::Ghost) |
| 118 | .color(Some(TextColor::Player(0))), | 158 | .color(Some(Color::Player(0))), |
| 119 | ) | 159 | ) |
| 120 | .tooltip(move |_, cx| Tooltip::text("Toggle following", cx)), | 160 | .tooltip(move |cx| Tooltip::text("Toggle following", cx)), |
| 121 | ) | 161 | ) |
| 122 | // TODO - Add project menu | 162 | // TODO - Add project menu |
| 123 | .child( | 163 | .child( |
| 124 | div() | 164 | div() |
| 165 | .border() | ||
| 166 | .border_color(gpui::red()) | ||
| 125 | .id("titlebar_project_menu_button") | 167 | .id("titlebar_project_menu_button") |
| 126 | .child(Button::new("project_name").variant(ButtonVariant::Ghost)) | 168 | .child(Button::new("project_name").variant(ButtonVariant::Ghost)) |
| 127 | .tooltip(move |_, cx| Tooltip::text("Recent Projects", cx)), | 169 | .tooltip(move |cx| Tooltip::text("Recent Projects", cx)), |
| 128 | ) | 170 | ) |
| 129 | // TODO - Add git menu | 171 | // TODO - Add git menu |
| 130 | .child( | 172 | .child( |
| 131 | div() | 173 | div() |
| 174 | .border() | ||
| 175 | .border_color(gpui::red()) | ||
| 132 | .id("titlebar_git_menu_button") | 176 | .id("titlebar_git_menu_button") |
| 133 | .child( | 177 | .child( |
| 134 | Button::new("branch_name") | 178 | Button::new("branch_name") |
| 135 | .variant(ButtonVariant::Ghost) | 179 | .variant(ButtonVariant::Ghost) |
| 136 | .color(Some(TextColor::Muted)), | 180 | .color(Some(Color::Muted)), |
| 137 | ) | 181 | ) |
| 138 | .tooltip(move |_, cx| { | 182 | .tooltip(move |cx| { |
| 139 | cx.build_view(|_| { | 183 | cx.build_view(|_| { |
| 140 | Tooltip::new("Recent Branches") | 184 | Tooltip::new("Recent Branches") |
| 141 | .key_binding(KeyBinding::new(gpui::KeyBinding::new( | 185 | .key_binding(KeyBinding::new(gpui::KeyBinding::new( |
| @@ -149,8 +193,113 @@ impl Render for CollabTitlebarItem { · 7 unmodified lines | |||
| 149 | .into() | 193 | .into() |
| 150 | }), | 194 | }), |
| 151 | ), | 195 | ), |
| 152 | ) // self.titlebar_item | 196 | ) |
| 153 | .child(h_stack().child(Label::new("Right side titlebar item"))) | 197 | .when_some( |
| 198 | users.zip(current_user.clone()), | ||
| 199 | |this, (remote_participants, current_user)| { | ||
| 200 | let mut pile = FacePile::default(); | ||
| 201 | pile.extend( | ||
| 202 | current_user | ||
| 203 | .avatar | ||
| 204 | .clone() | ||
| 205 | .map(|avatar| { | ||
| 206 | div().child(Avatar::data(avatar.clone())).into_any_element() | ||
| 207 | }) | ||
| 208 | .into_iter() | ||
| 209 | .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| { | ||
| 210 | user.avatar.as_ref().map(|avatar| { | ||
| 211 | div() | ||
| 212 | .child( | ||
| 213 | Avatar::data(avatar.clone()).into_element().into_any(), | ||
| 214 | ) | ||
| 215 | .on_mouse_down(MouseButton::Left, { | ||
| 216 | let workspace = workspace.clone(); | ||
| 217 | move |_, cx| { | ||
| 218 | workspace | ||
| 219 | .update(cx, |this, cx| { | ||
| 220 | this.open_shared_screen(peer_id, cx); | ||
| 221 | }) | ||
| 222 | .log_err(); | ||
| 223 | } | ||
| 224 | }) | ||
| 225 | .into_any_element() | ||
| 226 | }) | ||
| 227 | })), | ||
| 228 | ); | ||
| 229 | this.child(pile.render(cx)) | ||
| 230 | }, | ||
| 231 | ) | ||
| 232 | .child(div().flex_1()) | ||
| 233 | .when(is_in_room, |this| { | ||
| 234 | this.child( | ||
| 235 | h_stack() | ||
| 236 | .child( | ||
| 237 | h_stack() | ||
| 238 | .child(Button::new(if is_shared { "Unshare" } else { "Share" })) | ||
| 239 | .child(IconButton::new("leave-call", ui::Icon::Exit).on_click({ | ||
| 240 | let workspace = workspace.clone(); | ||
| 241 | move |_, cx| { | ||
| 242 | workspace | ||
| 243 | .update(cx, |this, cx| { | ||
| 244 | this.call_state().hang_up(cx).detach(); | ||
| 245 | }) | ||
| 246 | .log_err(); | ||
| 247 | } | ||
| 248 | })), | ||
| 249 | ) | ||
| 250 | .child( | ||
| 251 | h_stack() | ||
| 252 | .child(IconButton::new("mute-microphone", mic_icon).on_click({ | ||
| 253 | let workspace = workspace.clone(); | ||
| 254 | move |_, cx| { | ||
| 255 | workspace | ||
| 256 | .update(cx, |this, cx| { | ||
| 257 | this.call_state().toggle_mute(cx); | ||
| 258 | }) | ||
| 259 | .log_err(); | ||
| 260 | } | ||
| 261 | })) | ||
| 262 | .child(IconButton::new("mute-sound", speakers_icon).on_click({ | ||
| 263 | let workspace = workspace.clone(); | ||
| 264 | move |_, cx| { | ||
| 265 | workspace | ||
| 266 | .update(cx, |this, cx| { | ||
| 267 | this.call_state().toggle_deafen(cx); | ||
| 268 | }) | ||
| 269 | .log_err(); | ||
| 270 | } | ||
| 271 | })) | ||
| 272 | .child(IconButton::new("screen-share", ui::Icon::Screen).on_click( | ||
| 273 | move |_, cx| { | ||
| 274 | workspace | ||
| 275 | .update(cx, |this, cx| { | ||
| 276 | this.call_state().toggle_screen_share(cx); | ||
| 277 | }) | ||
| 278 | .log_err(); | ||
| 279 | }, | ||
| 280 | )) | ||
| 281 | .pl_2(), | ||
| 282 | ), | ||
| 283 | ) | ||
| 284 | }) | ||
| 285 | .map(|this| { | ||
| 286 | if let Some(user) = current_user { | ||
| 287 | this.when_some(user.avatar.clone(), |this, avatar| { | ||
| 288 | this.child(ui::Avatar::data(avatar)) | ||
| 289 | }) | ||
| 290 | } else { | ||
| 291 | this.child(Button::new("Sign in").on_click(move |_, cx| { | ||
| 292 | let client = client.clone(); | ||
| 293 | cx.spawn(move |mut cx| async move { | ||
| 294 | client | ||
| 295 | .authenticate_and_connect(true, &cx) | ||
| 296 | .await | ||
| 297 | .notify_async_err(&mut cx); | ||
| 298 | }) | ||
| 299 | .detach(); | ||
| 300 | })) | ||
| 301 | } | ||
| 302 | }) | ||
| 154 | } | 303 | } |
| 155 | } | 304 | } |
| 156 | 305 | ||
| @@ -7,11 +7,14 @@ pub mod notification_panel; · 6 unmodified lines | |||
| 7 | pub mod notifications; | 7 | pub mod notifications; |
| 8 | mod panel_settings; | 8 | mod panel_settings; |
| 9 | 9 | ||
| 10 | use std::sync::Arc; | 10 | use std::{rc::Rc, sync::Arc}; |
| 11 | 11 | ||
| 12 | pub use collab_panel::CollabPanel; | 12 | pub use collab_panel::CollabPanel; |
| 13 | pub use collab_titlebar_item::CollabTitlebarItem; | 13 | pub use collab_titlebar_item::CollabTitlebarItem; |
| 14 | use gpui::AppContext; | 14 | use gpui::{ |
| 15 | point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind, | ||
| 16 | WindowOptions, | ||
| 17 | }; | ||
| 15 | pub use panel_settings::{ | 18 | pub use panel_settings::{ |
| 16 | ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings, | 19 | ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings, |
| 17 | }; | 20 | }; |
| @@ -23,7 +26,7 @@ use workspace::AppState; · 5 unmodified lines | |||
| 23 | // [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall] | 26 | // [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall] |
| 24 | // ); | 27 | // ); |
| 25 | 28 | ||
| 26 | pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) { | 29 | pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { |
| 27 | CollaborationPanelSettings::register(cx); | 30 | CollaborationPanelSettings::register(cx); |
| 28 | ChatPanelSettings::register(cx); | 31 | ChatPanelSettings::register(cx); |
| 29 | NotificationPanelSettings::register(cx); | 32 | NotificationPanelSettings::register(cx); |
| @@ -32,7 +35,7 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) { · 2 unmodified lines | |||
| 32 | collab_titlebar_item::init(cx); | 35 | collab_titlebar_item::init(cx); |
| 33 | collab_panel::init(cx); | 36 | collab_panel::init(cx); |
| 34 | // chat_panel::init(cx); | 37 | // chat_panel::init(cx); |
| 35 | // notifications::init(&app_state, cx); | 38 | notifications::init(&app_state, cx); |
| 36 | 39 | ||
| 37 | // cx.add_global_action(toggle_screen_sharing); | 40 | // cx.add_global_action(toggle_screen_sharing); |
| 38 | // cx.add_global_action(toggle_mute); | 41 | // cx.add_global_action(toggle_mute); |
| @@ -95,31 +98,36 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) { · 56 unmodified lines | |||
| 95 | // } | 98 | // } |
| 96 | // } | 99 | // } |
| 97 | 100 | ||
| 98 | // fn notification_window_options( | 101 | fn notification_window_options( |
| 99 | // screen: Rc<dyn Screen>, | 102 | screen: Rc<dyn PlatformDisplay>, |
| 100 | // window_size: Vector2F, | 103 | window_size: Size<Pixels>, |
| 101 | // ) -> WindowOptions<'static> { | 104 | ) -> WindowOptions { |
| 102 | // const NOTIFICATION_PADDING: f32 = 16.; | 105 | let notification_margin_width = GlobalPixels::from(16.); |
| 106 | let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.); | ||
| 103 | 107 | ||
| 104 | // let screen_bounds = screen.content_bounds(); | 108 | let screen_bounds = screen.bounds(); |
| 105 | // WindowOptions { | 109 | let size: Size<GlobalPixels> = window_size.into(); |
| 106 | // bounds: WindowBounds::Fixed(RectF::new( | 110 | |
| 107 | // screen_bounds.upper_right() | 111 | // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument. |
| 108 | // + vec2f( | 112 | let bounds = gpui::Bounds::<GlobalPixels> { |
| 109 | // -NOTIFICATION_PADDING - window_size.x(), | 113 | origin: screen_bounds.upper_right() |
| 110 | // NOTIFICATION_PADDING, | 114 | - point( |
| 111 | // ), | 115 | size.width + notification_margin_width, |
| 112 | // window_size, | 116 | notification_margin_height, |
| 113 | // )), | 117 | ), |
| 114 | // titlebar: None, | 118 | size: window_size.into(), |
| 115 | // center: false, | 119 | }; |
| 116 | // focus: false, | 120 | WindowOptions { |
| 117 | // show: true, | 121 | bounds: WindowBounds::Fixed(bounds), |
| 118 | // kind: WindowKind::PopUp, | 122 | titlebar: None, |
| 119 | // is_movable: false, | 123 | center: false, |
| 120 | // screen: Some(screen), | 124 | focus: false, |
| 121 | // } | 125 | show: true, |
| 122 | // } | 126 | kind: WindowKind::PopUp, |
| 127 | is_movable: false, | ||
| 128 | display_id: Some(screen.id()), | ||
| 129 | } | ||
| 130 | } | ||
| 123 | 131 | ||
| 124 | // fn render_avatar<T: 'static>( | 132 | // fn render_avatar<T: 'static>( |
| 125 | // avatar: Option<Arc<ImageData>>, | 133 | // avatar: Option<Arc<ImageData>>, |
| @@ -1,54 +1,48 @@ | |||
| 1 | // use std::ops::Range; | 1 | use gpui::{ |
| 2 | div, AnyElement, Div, IntoElement as _, ParentElement as _, RenderOnce, Styled, WindowContext, | ||
| 3 | }; | ||
| 2 | 4 | ||
| 3 | // use gpui::{ | 5 | #[derive(Default)] |
| 4 | // geometry::{ | 6 | pub(crate) struct FacePile { |
| 5 | // rect::RectF, | 7 | faces: Vec<AnyElement>, |
| 6 | // vector::{vec2f, Vector2F}, | 8 | } |
| 7 | // }, | ||
| 8 | // json::ToJson, | ||
| 9 | // serde_json::{self, json}, | ||
| 10 | // AnyElement, Axis, Element, View, ViewContext, | ||
| 11 | // }; | ||
| 12 | 9 | ||
| 13 | // pub(crate) struct FacePile<V: View> { | 10 | impl RenderOnce for FacePile { |
| 14 | // overlap: f32, | 11 | type Rendered = Div; |
| 15 | // faces: Vec<AnyElement<V>>, | ||
| 16 | // } | ||
| 17 | 12 | ||
| 18 | // impl<V: View> FacePile<V> { | 13 | fn render(self, _: &mut WindowContext) -> Self::Rendered { |
| 19 | // pub fn new(overlap: f32) -> Self { | 14 | let player_count = self.faces.len(); |
| 20 | // Self { | 15 | let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| { |
| 21 | // overlap, | 16 | let isnt_last = ix < player_count - 1; |
| 22 | // faces: Vec::new(), | ||
| 23 | // } | ||
| 24 | // } | ||
| 25 | // } | ||
| 26 | 17 | ||
| 27 | // impl<V: View> Element<V> for FacePile<V> { | 18 | div().when(isnt_last, |div| div.neg_mr_1()).child(player) |
| 28 | // type LayoutState = (); | 19 | }); |
| 29 | // type PaintState = (); | 20 | div().p_1().flex().items_center().children(player_list) |
| 21 | } | ||
| 22 | } | ||
| 30 | 23 | ||
| 24 | // impl Element for FacePile { | ||
| 25 | // type State = (); | ||
| 31 | // fn layout( | 26 | // fn layout( |
| 32 | // &mut self, | 27 | // &mut self, |
| 33 | // constraint: gpui::SizeConstraint, | 28 | // state: Option<Self::State>, |
| 34 | // view: &mut V, | 29 | // cx: &mut WindowContext, |
| 35 | // cx: &mut ViewContext<V>, | 30 | // ) -> (LayoutId, Self::State) { |
| 36 | // ) -> (Vector2F, Self::LayoutState) { | ||
| 37 | // debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY); | ||
| 38 | |||
| 39 | // let mut width = 0.; | 31 | // let mut width = 0.; |
| 40 | // let mut max_height = 0.; | 32 | // let mut max_height = 0.; |
| 33 | // let mut faces = Vec::with_capacity(self.faces.len()); | ||
| 41 | // for face in &mut self.faces { | 34 | // for face in &mut self.faces { |
| 42 | // let layout = face.layout(constraint, view, cx); | 35 | // let layout = face.layout(cx); |
| 43 | // width += layout.x(); | 36 | // width += layout.x(); |
| 44 | // max_height = f32::max(max_height, layout.y()); | 37 | // max_height = f32::max(max_height, layout.y()); |
| 38 | // faces.push(layout); | ||
| 45 | // } | 39 | // } |
| 46 | // width -= self.overlap * self.faces.len().saturating_sub(1) as f32; | 40 | // width -= self.overlap * self.faces.len().saturating_sub(1) as f32; |
| 47 | 41 | // (cx.request_layout(&Style::default(), faces), ()) | |
| 48 | // ( | 42 | // // ( |
| 49 | // Vector2F::new(width, max_height.clamp(1., constraint.max.y())), | 43 | // // Vector2F::new(width, max_height.clamp(1., constraint.max.y())), |
| 50 | // (), | 44 | // // (), |
| 51 | // ) | 45 | // // )) |
| 52 | // } | 46 | // } |
| 53 | 47 | ||
| 54 | // fn paint( | 48 | // fn paint( |
| @@ -77,37 +71,10 @@ · 22 unmodified lines | |||
| 77 | 71 | ||
| 78 | // () | 72 | // () |
| 79 | // } | 73 | // } |
| 80 | |||
| 81 | // fn rect_for_text_range( | ||
| 82 | // &self, | ||
| 83 | // _: Range<usize>, | ||
| 84 | // _: RectF, | ||
| 85 | // _: RectF, | ||
| 86 | // _: &Self::LayoutState, | ||
| 87 | // _: &Self::PaintState, | ||
| 88 | // _: &V, | ||
| 89 | // _: &ViewContext<V>, | ||
| 90 | // ) -> Option<RectF> { | ||
| 91 | // None | ||
| 92 | // } | ||
| 93 | |||
| 94 | // fn debug( | ||
| 95 | // &self, | ||
| 96 | // bounds: RectF, | ||
| 97 | // _: &Self::LayoutState, | ||
| 98 | // _: &Self::PaintState, | ||
| 99 | // _: &V, | ||
| 100 | // _: &ViewContext<V>, | ||
| 101 | // ) -> serde_json::Value { | ||
| 102 | // json!({ | ||
| 103 | // "type": "FacePile", | ||
| 104 | // "bounds": bounds.to_json() | ||
| 105 | // }) | ||
| 106 | // } | ||
| 107 | // } | 74 | // } |
| 108 | 75 | ||
| 109 | // impl<V: View> Extend<AnyElement<V>> for FacePile<V> { | 76 | impl Extend<AnyElement> for FacePile { |
| 110 | // fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) { | 77 | fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) { |
| 111 | // self.faces.extend(children); | 78 | self.faces.extend(children); |
| 112 | // } | 79 | } |
| 113 | // } | 80 | } |
| @@ -1,11 +1,11 @@ | |||
| 1 | // use gpui::AppContext; | 1 | use gpui::AppContext; |
| 2 | // use std::sync::Arc; | 2 | use std::sync::Arc; |
| 3 | // use workspace::AppState; | 3 | use workspace::AppState; |
| 4 | 4 | ||
| 5 | // pub mod incoming_call_notification; | 5 | pub mod incoming_call_notification; |
| 6 | // pub mod project_shared_notification; | 6 | // pub mod project_shared_notification; |
| 7 | 7 | ||
| 8 | // pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { | 8 | pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { |
| 9 | // incoming_call_notification::init(app_state, cx); | 9 | incoming_call_notification::init(app_state, cx); |
| 10 | // project_shared_notification::init(app_state, cx); | 10 | //project_shared_notification::init(app_state, cx); |
| 11 | // } | 11 | } |
| @@ -1,14 +1,12 @@ | |||
| 1 | use crate::notification_window_options; | 1 | use crate::notification_window_options; |
| 2 | use call::{ActiveCall, IncomingCall}; | 2 | use call::{ActiveCall, IncomingCall}; |
| 3 | use client::proto; | ||
| 4 | use futures::StreamExt; | 3 | use futures::StreamExt; |
| 5 | use gpui::{ | 4 | use gpui::{ |
| 6 | elements::*, | 5 | div, green, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce, |
| 7 | geometry::vector::vec2f, | 6 | StatefulInteractiveElement, Styled, ViewContext, VisualContext as _, WindowHandle, |
| 8 | platform::{CursorStyle, MouseButton}, | ||
| 9 | AnyElement, AppContext, Entity, View, ViewContext, WindowHandle, | ||
| 10 | }; | 7 | }; |
| 11 | use std::sync::{Arc, Weak}; | 8 | use std::sync::{Arc, Weak}; |
| 9 | use ui::{h_stack, v_stack, Avatar, Button, Label}; | ||
| 12 | use util::ResultExt; | 10 | use util::ResultExt; |
| 13 | use workspace::AppState; | 11 | use workspace::AppState; |
| 14 | 12 | ||
| @@ -19,23 +17,44 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { · 4 unmodified lines | |||
| 19 | let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new(); | 17 | let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new(); |
| 20 | while let Some(incoming_call) = incoming_call.next().await { | 18 | while let Some(incoming_call) = incoming_call.next().await { |
| 21 | for window in notification_windows.drain(..) { | 19 | for window in notification_windows.drain(..) { |
| 22 | window.remove(&mut cx); | 20 | window |
| 21 | .update(&mut cx, |_, cx| { | ||
| 22 | // todo!() | ||
| 23 | cx.remove_window(); | ||
| 24 | }) | ||
| 25 | .log_err(); | ||
| 23 | } | 26 | } |
| 24 | 27 | ||
| 25 | if let Some(incoming_call) = incoming_call { | 28 | if let Some(incoming_call) = incoming_call { |
| 26 | let window_size = cx.read(|cx| { | 29 | let unique_screens = cx.update(|cx| cx.displays()).unwrap(); |
| 27 | let theme = &theme::current(cx).incoming_call_notification; | 30 | let window_size = gpui::Size { |
| 28 | vec2f(theme.window_width, theme.window_height) | 31 | width: px(380.), |
| 29 | }); | 32 | height: px(64.), |
| 33 | }; | ||
| 30 | 34 | ||
| 31 | for screen in cx.platform().screens() { | 35 | for window in unique_screens { |
| 36 | let options = notification_window_options(window, window_size); | ||
| 32 | let window = cx | 37 | let window = cx |
| 33 | .add_window(notification_window_options(screen, window_size), |_| { | 38 | .open_window(options, |cx| { |
| 34 | IncomingCallNotification::new(incoming_call.clone(), app_state.clone()) | 39 | cx.build_view(|_| { |
| 35 | }); | 40 | IncomingCallNotification::new( |
| 36 | 41 | incoming_call.clone(), | |
| 42 | app_state.clone(), | ||
| 43 | ) | ||
| 44 | }) | ||
| 45 | }) | ||
| 46 | .unwrap(); | ||
| 37 | notification_windows.push(window); | 47 | notification_windows.push(window); |
| 38 | } | 48 | } |
| 49 | |||
| 50 | // for screen in cx.platform().screens() { | ||
| 51 | // let window = cx | ||
| 52 | // .add_window(notification_window_options(screen, window_size), |_| { | ||
| 53 | // IncomingCallNotification::new(incoming_call.clone(), app_state.clone()) | ||
| 54 | // }); | ||
| 55 | |||
| 56 | // notification_windows.push(window); | ||
| 57 | // } | ||
| 39 | } | 58 | } |
| 40 | } | 59 | } |
| 41 | }) | 60 | }) |
| @@ -47,167 +66,196 @@ struct RespondToCall { · 5 unmodified lines | |||
| 47 | accept: bool, | 66 | accept: bool, |
| 48 | } | 67 | } |
| 49 | 68 | ||
| 50 | pub struct IncomingCallNotification { | 69 | struct IncomingCallNotificationState { |
| 51 | call: IncomingCall, | 70 | call: IncomingCall, |
| 52 | app_state: Weak<AppState>, | 71 | app_state: Weak<AppState>, |
| 53 | } | 72 | } |
| 54 | 73 | ||
| 55 | impl IncomingCallNotification { | 74 | pub struct IncomingCallNotification { |
| 75 | state: Arc<IncomingCallNotificationState>, | ||
| 76 | } | ||
| 77 | impl IncomingCallNotificationState { | ||
| 56 | pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self { | 78 | pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self { |
| 57 | Self { call, app_state } | 79 | Self { call, app_state } |
| 58 | } | 80 | } |
| 59 | 81 | ||
| 60 | fn respond(&mut self, accept: bool, cx: &mut ViewContext<Self>) { | 82 | fn respond(&self, accept: bool, cx: &mut AppContext) { |
| 61 | let active_call = ActiveCall::global(cx); | 83 | let active_call = ActiveCall::global(cx); |
| 62 | if accept { | 84 | if accept { |
| 63 | let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx)); | 85 | let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx)); |
| 64 | let caller_user_id = self.call.calling_user.id; | ||
| 65 | let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id); | 86 | let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id); |
| 66 | let app_state = self.app_state.clone(); | 87 | let app_state = self.app_state.clone(); |
| 67 | cx.app_context() | 88 | let cx: &mut AppContext = cx; |
| 68 | .spawn(|mut cx| async move { | 89 | cx.spawn(|cx| async move { |
| 69 | join.await?; | 90 | join.await?; |
| 70 | if let Some(project_id) = initial_project_id { | 91 | if let Some(_project_id) = initial_project_id { |
| 71 | cx.update(|cx| { | 92 | cx.update(|_cx| { |
| 72 | if let Some(app_state) = app_state.upgrade() { | 93 | if let Some(_app_state) = app_state.upgrade() { |
| 73 | workspace::join_remote_project( | 94 | // workspace::join_remote_project( |
| 74 | project_id, | 95 | // project_id, |
| 75 | caller_user_id, | 96 | // caller_user_id, |
| 76 | app_state, | 97 | // app_state, |
| 77 | cx, | 98 | // cx, |
| 78 | ) | 99 | // ) |
| 79 | .detach_and_log_err(cx); | 100 | // .detach_and_log_err(cx); |
| 80 | } | 101 | } |
| 81 | }); | 102 | }) |
| 82 | } | 103 | .log_err(); |
| 83 | anyhow::Ok(()) | 104 | } |
| 84 | }) | 105 | anyhow::Ok(()) |
| 85 | .detach_and_log_err(cx); | 106 | }) |
| 107 | .detach_and_log_err(cx); | ||
| 86 | } else { | 108 | } else { |
| 87 | active_call.update(cx, |active_call, cx| { | 109 | active_call.update(cx, |active_call, cx| { |
| 88 | active_call.decline_incoming(cx).log_err(); | 110 | active_call.decline_incoming(cx).log_err(); |
| 89 | }); | 111 | }); |
| 90 | } | 112 | } |
| 91 | } | 113 | } |
| 114 | } | ||
| 92 | 115 | ||
| 93 | fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { | 116 | impl IncomingCallNotification { |
| 94 | let theme = &theme::current(cx).incoming_call_notification; | 117 | pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self { |
| 95 | let default_project = proto::ParticipantProject::default(); | 118 | Self { |
| 96 | let initial_project = self | 119 | state: Arc::new(IncomingCallNotificationState::new(call, app_state)), |
| 97 | .call | 120 | } |
| 98 | .initial_project | ||
| 99 | .as_ref() | ||
| 100 | .unwrap_or(&default_project); | ||
| 101 | Flex::row() | ||
| 102 | .with_children(self.call.calling_user.avatar.clone().map(|avatar| { | ||
| 103 | Image::from_data(avatar) | ||
| 104 | .with_style(theme.caller_avatar) | ||
| 105 | .aligned() | ||
| 106 | })) | ||
| 107 | .with_child( | ||
| 108 | Flex::column() | ||
| 109 | .with_child( | ||
| 110 | Label::new( | ||
| 111 | self.call.calling_user.github_login.clone(), | ||
| 112 | theme.caller_username.text.clone(), | ||
| 113 | ) | ||
| 114 | .contained() | ||
| 115 | .with_style(theme.caller_username.container), | ||
| 116 | ) | ||
| 117 | .with_child( | ||
| 118 | Label::new( | ||
| 119 | format!( | ||
| 120 | "is sharing a project in Zed{}", | ||
| 121 | if initial_project.worktree_root_names.is_empty() { | ||
| 122 | "" | ||
| 123 | } else { | ||
| 124 | ":" | ||
| 125 | } | ||
| 126 | ), | ||
| 127 | theme.caller_message.text.clone(), | ||
| 128 | ) | ||
| 129 | .contained() | ||
| 130 | .with_style(theme.caller_message.container), | ||
| 131 | ) | ||
| 132 | .with_children(if initial_project.worktree_root_names.is_empty() { | ||
| 133 | None | ||
| 134 | } else { | ||
| 135 | Some( | ||
| 136 | Label::new( | ||
| 137 | initial_project.worktree_root_names.join(", "), | ||
| 138 | theme.worktree_roots.text.clone(), | ||
| 139 | ) | ||
| 140 | .contained() | ||
| 141 | .with_style(theme.worktree_roots.container), | ||
| 142 | ) | ||
| 143 | }) | ||
| 144 | .contained() | ||
| 145 | .with_style(theme.caller_metadata) | ||
| 146 | .aligned(), | ||
| 147 | ) | ||
| 148 | .contained() | ||
| 149 | .with_style(theme.caller_container) | ||
| 150 | .flex(1., true) | ||
| 151 | .into_any() | ||
| 152 | } | 121 | } |
| 153 | 122 | fn render_caller(&self, cx: &mut ViewContext<Self>) -> impl Element { | |
| 154 | fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { | 123 | h_stack() |
| 155 | enum Accept {} | 124 | .children( |
| 156 | enum Decline {} | 125 | self.state |
| 157 | 126 | .call | |
| 158 | let theme = theme::current(cx); | 127 | .calling_user |
| 159 | Flex::column() | 128 | .avatar |
| 160 | .with_child( | 129 | .as_ref() |
| 161 | MouseEventHandler::new::<Accept, _>(0, cx, |_, _| { | 130 | .map(|avatar| Avatar::data(avatar.clone())), |
| 162 | let theme = &theme.incoming_call_notification; | ||
| 163 | Label::new("Accept", theme.accept_button.text.clone()) | ||
| 164 | .aligned() | ||
| 165 | .contained() | ||
| 166 | .with_style(theme.accept_button.container) | ||
| 167 | }) | ||
| 168 | .with_cursor_style(CursorStyle::PointingHand) | ||
| 169 | .on_click(MouseButton::Left, |_, this, cx| { | ||
| 170 | this.respond(true, cx); | ||
| 171 | }) | ||
| 172 | .flex(1., true), | ||
| 173 | ) | 131 | ) |
| 174 | .with_child( | 132 | .child( |
| 175 | MouseEventHandler::new::<Decline, _>(0, cx, |_, _| { | 133 | v_stack() |
| 176 | let theme = &theme.incoming_call_notification; | 134 | .child(Label::new(format!( |
| 177 | Label::new("Decline", theme.decline_button.text.clone()) | 135 | "{} is sharing a project in Zed", |
| 178 | .aligned() | 136 | self.state.call.calling_user.github_login |
| 179 | .contained() | 137 | ))) |
| 180 | .with_style(theme.decline_button.container) | 138 | .child(self.render_buttons(cx)), |
| 181 | }) | ||
| 182 | .with_cursor_style(CursorStyle::PointingHand) | ||
| 183 | .on_click(MouseButton::Left, |_, this, cx| { | ||
| 184 | this.respond(false, cx); | ||
| 185 | }) | ||
| 186 | .flex(1., true), | ||
| 187 | ) | 139 | ) |
| 188 | .constrained() | 140 | // let theme = &theme::current(cx).incoming_call_notification; |
| 189 | .with_width(theme.incoming_call_notification.button_width) | 141 | // let default_project = proto::ParticipantProject::default(); |
| 190 | .into_any() | 142 | // let initial_project = self |
| 143 | // .call | ||
| 144 | // .initial_project | ||
| 145 | // .as_ref() | ||
| 146 | // .unwrap_or(&default_project); | ||
| 147 | // Flex::row() | ||
| 148 | // .with_children(self.call.calling_user.avatar.clone().map(|avatar| { | ||
| 149 | // Image::from_data(avatar) | ||
| 150 | // .with_style(theme.caller_avatar) | ||
| 151 | // .aligned() | ||
| 152 | // })) | ||
| 153 | // .with_child( | ||
| 154 | // Flex::column() | ||
| 155 | // .with_child( | ||
| 156 | // Label::new( | ||
| 157 | // self.call.calling_user.github_login.clone(), | ||
| 158 | // theme.caller_username.text.clone(), | ||
| 159 | // ) | ||
| 160 | // .contained() | ||
| 161 | // .with_style(theme.caller_username.container), | ||
| 162 | // ) | ||
| 163 | // .with_child( | ||
| 164 | // Label::new( | ||
| 165 | // format!( | ||
| 166 | // "is sharing a project in Zed{}", | ||
| 167 | // if initial_project.worktree_root_names.is_empty() { | ||
| 168 | // "" | ||
| 169 | // } else { | ||
| 170 | // ":" | ||
| 171 | // } | ||
| 172 | // ), | ||
| 173 | // theme.caller_message.text.clone(), | ||
| 174 | // ) | ||
| 175 | // .contained() | ||
| 176 | // .with_style(theme.caller_message.container), | ||
| 177 | // ) | ||
| 178 | // .with_children(if initial_project.worktree_root_names.is_empty() { | ||
| 179 | // None | ||
| 180 | // } else { | ||
| 181 | // Some( | ||
| 182 | // Label::new( | ||
| 183 | // initial_project.worktree_root_names.join(", "), | ||
| 184 | // theme.worktree_roots.text.clone(), | ||
| 185 | // ) | ||
| 186 | // .contained() | ||
| 187 | // .with_style(theme.worktree_roots.container), | ||
| 188 | // ) | ||
| 189 | // }) | ||
| 190 | // .contained() | ||
| 191 | // .with_style(theme.caller_metadata) | ||
| 192 | // .aligned(), | ||
| 193 | // ) | ||
| 194 | // .contained() | ||
| 195 | // .with_style(theme.caller_container) | ||
| 196 | // .flex(1., true) | ||
| 197 | // .into_any() | ||
| 191 | } | 198 | } |
| 192 | } | ||
| 193 | 199 | ||
| 194 | impl Entity for IncomingCallNotification { | 200 | fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element { |
| 195 | type Event = (); | 201 | h_stack() |
| 196 | } | 202 | .child(Button::new("Accept").render(cx).bg(green()).on_click({ |
| 203 | let state = self.state.clone(); | ||
| 204 | move |_, cx| state.respond(true, cx) | ||
| 205 | })) | ||
| 206 | .child(Button::new("Decline").render(cx).bg(red()).on_click({ | ||
| 207 | let state = self.state.clone(); | ||
| 208 | move |_, cx| state.respond(false, cx) | ||
| 209 | })) | ||
| 197 | 210 | ||
| 198 | impl View for IncomingCallNotification { | 211 | // enum Accept {} |
| 199 | fn ui_name() -> &'static str { | 212 | // enum Decline {} |
| 200 | "IncomingCallNotification" | ||
| 201 | } | ||
| 202 | 213 | ||
| 203 | fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { | 214 | // let theme = theme::current(cx); |
| 204 | let background = theme::current(cx).incoming_call_notification.background; | 215 | // Flex::column() |
| 205 | Flex::row() | 216 | // .with_child( |
| 206 | .with_child(self.render_caller(cx)) | 217 | // MouseEventHandler::new::<Accept, _>(0, cx, |_, _| { |
| 207 | .with_child(self.render_buttons(cx)) | 218 | // let theme = &theme.incoming_call_notification; |
| 208 | .contained() | 219 | // Label::new("Accept", theme.accept_button.text.clone()) |
| 209 | .with_background_color(background) | 220 | // .aligned() |
| 210 | .expanded() | 221 | // .contained() |
| 211 | .into_any() | 222 | // .with_style(theme.accept_button.container) |
| 223 | // }) | ||
| 224 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 225 | // .on_click(MouseButton::Left, |_, this, cx| { | ||
| 226 | // this.respond(true, cx); | ||
| 227 | // }) | ||
| 228 | // .flex(1., true), | ||
| 229 | // ) | ||
| 230 | // .with_child( | ||
| 231 | // MouseEventHandler::new::<Decline, _>(0, cx, |_, _| { | ||
| 232 | // let theme = &theme.incoming_call_notification; | ||
| 233 | // Label::new("Decline", theme.decline_button.text.clone()) | ||
| 234 | // .aligned() | ||
| 235 | // .contained() | ||
| 236 | // .with_style(theme.decline_button.container) | ||
| 237 | // }) | ||
| 238 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 239 | // .on_click(MouseButton::Left, |_, this, cx| { | ||
| 240 | // this.respond(false, cx); | ||
| 241 | // }) | ||
| 242 | // .flex(1., true), | ||
| 243 | // ) | ||
| 244 | // .constrained() | ||
| 245 | // .with_width(theme.incoming_call_notification.button_width) | ||
| 246 | // .into_any() | ||
| 247 | } | ||
| 248 | } | ||
| 249 | impl Render for IncomingCallNotification { | ||
| 250 | type Element = Div; | ||
| 251 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | ||
| 252 | div().bg(red()).flex_none().child(self.render_caller(cx)) | ||
| 253 | // Flex::row() | ||
| 254 | // .with_child() | ||
| 255 | // .with_child(self.render_buttons(cx)) | ||
| 256 | // .contained() | ||
| 257 | // .with_background_color(background) | ||
| 258 | // .expanded() | ||
| 259 | // .into_any() | ||
| 212 | } | 260 | } |
| 213 | } | 261 | } |
| @@ -1,17 +1,15 @@ | |||
| 1 | use collections::{CommandPaletteFilter, HashMap}; | 1 | use collections::{CommandPaletteFilter, HashMap}; |
| 2 | use fuzzy::{StringMatch, StringMatchCandidate}; | 2 | use fuzzy::{StringMatch, StringMatchCandidate}; |
| 3 | use gpui::{ | 3 | use gpui::{ |
| 4 | actions, div, prelude::*, Action, AppContext, Component, Div, EventEmitter, FocusHandle, | 4 | actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, |
| 5 | FocusableView, Keystroke, Manager, ParentComponent, Render, Styled, View, ViewContext, | 5 | Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, |
| 6 | VisualContext, WeakView, | ||
| 7 | }; | 6 | }; |
| 8 | use picker::{Picker, PickerDelegate}; | 7 | use picker::{Picker, PickerDelegate}; |
| 9 | use std::{ | 8 | use std::{ |
| 10 | cmp::{self, Reverse}, | 9 | cmp::{self, Reverse}, |
| 11 | sync::Arc, | 10 | sync::Arc, |
| 12 | }; | 11 | }; |
| 13 | use theme::ActiveTheme; | 12 | use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem}; |
| 14 | use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt}; | ||
| 15 | use util::{ | 13 | use util::{ |
| 16 | channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, | 14 | channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, |
| 17 | ResultExt, | 15 | ResultExt, |
| @@ -69,7 +67,7 @@ impl CommandPalette { · 51 unmodified lines | |||
| 69 | } | 67 | } |
| 70 | } | 68 | } |
| 71 | 69 | ||
| 72 | impl EventEmitter<Manager> for CommandPalette {} | 70 | impl EventEmitter<DismissEvent> for CommandPalette {} |
| 73 | 71 | ||
| 74 | impl FocusableView for CommandPalette { | 72 | impl FocusableView for CommandPalette { |
| 75 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle { | 73 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle { |
| @@ -78,7 +76,7 @@ impl FocusableView for CommandPalette { · 2 unmodified lines | |||
| 78 | } | 76 | } |
| 79 | 77 | ||
| 80 | impl Render for CommandPalette { | 78 | impl Render for CommandPalette { |
| 81 | type Element = Div<Self>; | 79 | type Element = Div; |
| 82 | 80 | ||
| 83 | fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { | 81 | fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { |
| 84 | v_stack().w_96().child(self.picker.clone()) | 82 | v_stack().w_96().child(self.picker.clone()) |
| @@ -141,7 +139,7 @@ impl CommandPaletteDelegate { · 56 unmodified lines | |||
| 141 | } | 139 | } |
| 142 | 140 | ||
| 143 | impl PickerDelegate for CommandPaletteDelegate { | 141 | impl PickerDelegate for CommandPaletteDelegate { |
| 144 | type ListItem = Div<Picker<Self>>; | 142 | type ListItem = ListItem; |
| 145 | 143 | ||
| 146 | fn placeholder_text(&self) -> Arc<str> { | 144 | fn placeholder_text(&self) -> Arc<str> { |
| 147 | "Execute a command...".into() | 145 | "Execute a command...".into() |
| @@ -269,7 +267,7 @@ impl PickerDelegate for CommandPaletteDelegate { · 121 unmodified lines | |||
| 269 | 267 | ||
| 270 | fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) { | 268 | fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) { |
| 271 | self.command_palette | 269 | self.command_palette |
| 272 | .update(cx, |_, cx| cx.emit(Manager::Dismiss)) | 270 | .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) |
| 273 | .log_err(); | 271 | .log_err(); |
| 274 | } | 272 | } |
| 275 | 273 | ||
| @@ -294,24 +292,16 @@ impl PickerDelegate for CommandPaletteDelegate { · 18 unmodified lines | |||
| 294 | ix: usize, | 292 | ix: usize, |
| 295 | selected: bool, | 293 | selected: bool, |
| 296 | cx: &mut ViewContext<Picker<Self>>, | 294 | cx: &mut ViewContext<Picker<Self>>, |
| 297 | ) -> Self::ListItem { | 295 | ) -> Option<Self::ListItem> { |
| 298 | let colors = cx.theme().colors(); | ||
| 299 | let Some(r#match) = self.matches.get(ix) else { | 296 | let Some(r#match) = self.matches.get(ix) else { |
| 300 | return div(); | 297 | return None; |
| 301 | }; | 298 | }; |
| 302 | let Some(command) = self.commands.get(r#match.candidate_id) else { | 299 | let Some(command) = self.commands.get(r#match.candidate_id) else { |
| 303 | return div(); | 300 | return None; |
| 304 | }; | 301 | }; |
| 305 | 302 | ||
| 306 | div() | 303 | Some( |
| 307 | .px_1() | 304 | ListItem::new(ix).selected(selected).child( |
| 308 | .text_color(colors.text) | ||
| 309 | .text_ui() | ||
| 310 | .bg(colors.ghost_element_background) | ||
| 311 | .rounded_md() | ||
| 312 | .when(selected, |this| this.bg(colors.ghost_element_selected)) | ||
| 313 | .hover(|this| this.bg(colors.ghost_element_hover)) | ||
| 314 | .child( | ||
| 315 | h_stack() | 305 | h_stack() |
| 316 | .justify_between() | 306 | .justify_between() |
| 317 | .child(HighlightedLabel::new( | 307 | .child(HighlightedLabel::new( |
| @@ -319,7 +309,8 @@ impl PickerDelegate for CommandPaletteDelegate { · 1 unmodified line | |||
| 319 | r#match.positions.clone(), | 309 | r#match.positions.clone(), |
| 320 | )) | 310 | )) |
| 321 | .children(KeyBinding::for_action(&*command.action, cx)), | 311 | .children(KeyBinding::for_action(&*command.action, cx)), |
| 322 | ) | 312 | ), |
| 313 | ) | ||
| 323 | } | 314 | } |
| 324 | } | 315 | } |
| 325 | 316 | ||
| @@ -13,10 +13,10 @@ use editor::{ · 12 unmodified lines | |||
| 13 | }; | 13 | }; |
| 14 | use futures::future::try_join_all; | 14 | use futures::future::try_join_all; |
| 15 | use gpui::{ | 15 | use gpui::{ |
| 16 | actions, div, AnyElement, AnyView, AppContext, Component, Context, Div, EventEmitter, | 16 | actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusEvent, |
| 17 | FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, InteractiveComponent, | 17 | FocusHandle, Focusable, FocusableElement, FocusableView, InteractiveElement, IntoElement, |
| 18 | Model, ParentComponent, Render, SharedString, Styled, Subscription, Task, View, ViewContext, | 18 | Model, ParentElement, Render, SharedString, Styled, Subscription, Task, View, ViewContext, |
| 19 | VisualContext, WeakView, | 19 | VisualContext, WeakView, WindowContext, |
| 20 | }; | 20 | }; |
| 21 | use language::{ | 21 | use language::{ |
| 22 | Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, | 22 | Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, |
| @@ -36,7 +36,7 @@ use std::{ · 13 unmodified lines | |||
| 36 | }; | 36 | }; |
| 37 | use theme::ActiveTheme; | 37 | use theme::ActiveTheme; |
| 38 | pub use toolbar_controls::ToolbarControls; | 38 | pub use toolbar_controls::ToolbarControls; |
| 39 | use ui::{h_stack, HighlightedLabel, Icon, IconElement, Label, TextColor}; | 39 | use ui::{h_stack, Color, HighlightedLabel, Icon, IconElement, Label}; |
| 40 | use util::TryFutureExt; | 40 | use util::TryFutureExt; |
| 41 | use workspace::{ | 41 | use workspace::{ |
| 42 | item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, | 42 | item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, |
| @@ -91,7 +91,7 @@ struct DiagnosticGroupState { · 48 unmodified lines | |||
| 91 | impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {} | 91 | impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {} |
| 92 | 92 | ||
| 93 | impl Render for ProjectDiagnosticsEditor { | 93 | impl Render for ProjectDiagnosticsEditor { |
| 94 | type Element = Focusable<Self, Div<Self>>; | 94 | type Element = Focusable<Div>; |
| 95 | 95 | ||
| 96 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | 96 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 97 | let child = if self.path_states.is_empty() { | 97 | let child = if self.path_states.is_empty() { |
| @@ -109,8 +109,8 @@ impl Render for ProjectDiagnosticsEditor { · 11 unmodified lines | |||
| 109 | div() | 109 | div() |
| 110 | .track_focus(&self.focus_handle) | 110 | .track_focus(&self.focus_handle) |
| 111 | .size_full() | 111 | .size_full() |
| 112 | .on_focus_in(Self::focus_in) | 112 | .on_focus_in(cx.listener(Self::focus_in)) |
| 113 | .on_action(Self::toggle_warnings) | 113 | .on_action(cx.listener(Self::toggle_warnings)) |
| 114 | .child(child) | 114 | .child(child) |
| 115 | } | 115 | } |
| 116 | } | 116 | } |
| @@ -662,7 +662,7 @@ impl Item for ProjectDiagnosticsEditor { · 545 unmodified lines | |||
| 662 | Some("Project Diagnostics".into()) | 662 | Some("Project Diagnostics".into()) |
| 663 | } | 663 | } |
| 664 | 664 | ||
| 665 | fn tab_content<T: 'static>(&self, _detail: Option<usize>, _: &AppContext) -> AnyElement<T> { | 665 | fn tab_content(&self, _detail: Option<usize>, _: &WindowContext) -> AnyElement { |
| 666 | render_summary(&self.summary) | 666 | render_summary(&self.summary) |
| 667 | } | 667 | } |
| 668 | 668 | ||
| @@ -742,7 +742,7 @@ impl Item for ProjectDiagnosticsEditor { · 73 unmodified lines | |||
| 742 | } | 742 | } |
| 743 | 743 | ||
| 744 | fn breadcrumb_location(&self) -> ToolbarItemLocation { | 744 | fn breadcrumb_location(&self) -> ToolbarItemLocation { |
| 745 | ToolbarItemLocation::PrimaryLeft { flex: None } | 745 | ToolbarItemLocation::PrimaryLeft |
| 746 | } | 746 | } |
| 747 | 747 | ||
| 748 | fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> { | 748 | fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> { |
| @@ -778,27 +778,28 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { · 29 unmodified lines | |||
| 778 | .bg(gpui::red()) | 778 | .bg(gpui::red()) |
| 779 | .map(|stack| { | 779 | .map(|stack| { |
| 780 | let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { | 780 | let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { |
| 781 | IconElement::new(Icon::XCircle).color(TextColor::Error) | 781 | IconElement::new(Icon::XCircle).color(Color::Error) |
| 782 | } else { | 782 | } else { |
| 783 | IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning) | 783 | IconElement::new(Icon::ExclamationTriangle).color(Color::Warning) |
| 784 | }; | 784 | }; |
| 785 | 785 | ||
| 786 | stack.child(div().pl_8().child(icon)) | 786 | stack.child(div().pl_8().child(icon)) |
| 787 | }) | 787 | }) |
| 788 | .when_some(diagnostic.source.as_ref(), |stack, source| { | 788 | .when_some(diagnostic.source.as_ref(), |stack, source| { |
| 789 | stack.child(Label::new(format!("{source}:")).color(TextColor::Accent)) | 789 | stack.child(Label::new(format!("{source}:")).color(Color::Accent)) |
| 790 | }) | 790 | }) |
| 791 | .child(HighlightedLabel::new(message.clone(), highlights.clone())) | 791 | .child(HighlightedLabel::new(message.clone(), highlights.clone())) |
| 792 | .when_some(diagnostic.code.as_ref(), |stack, code| { | 792 | .when_some(diagnostic.code.as_ref(), |stack, code| { |
| 793 | stack.child(Label::new(code.clone())) | 793 | stack.child(Label::new(code.clone())) |
| 794 | }) | 794 | }) |
| 795 | .render() | 795 | .into_any_element() |
| 796 | }) | 796 | }) |
| 797 | } | 797 | } |
| 798 | 798 | ||
| 799 | pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElement<T> { | 799 | pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement { |
| 800 | if summary.error_count == 0 && summary.warning_count == 0 { | 800 | if summary.error_count == 0 && summary.warning_count == 0 { |
| 801 | Label::new("No problems").render() | 801 | let label = Label::new("No problems"); |
| 802 | label.into_any_element() | ||
| 802 | } else { | 803 | } else { |
| 803 | h_stack() | 804 | h_stack() |
| 804 | .bg(gpui::red()) | 805 | .bg(gpui::red()) |
| @@ -806,7 +807,7 @@ pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElem · 1 unmodified line | |||
| 806 | .child(Label::new(summary.error_count.to_string())) | 807 | .child(Label::new(summary.error_count.to_string())) |
| 807 | .child(IconElement::new(Icon::ExclamationTriangle)) | 808 | .child(IconElement::new(Icon::ExclamationTriangle)) |
| 808 | .child(Label::new(summary.warning_count.to_string())) | 809 | .child(Label::new(summary.warning_count.to_string())) |
| 809 | .render() | 810 | .into_any_element() |
| 810 | } | 811 | } |
| 811 | } | 812 | } |
| 812 | 813 | ||
| @@ -1549,7 +1550,7 @@ mod tests { · 736 unmodified lines | |||
| 1549 | block_id: ix, | 1550 | block_id: ix, |
| 1550 | editor_style: &editor::EditorStyle::default(), | 1551 | editor_style: &editor::EditorStyle::default(), |
| 1551 | }) | 1552 | }) |
| 1552 | .element_id()? | 1553 | .inner_id()? |
| 1553 | .try_into() | 1554 | .try_into() |
| 1554 | .ok()?, | 1555 | .ok()?, |
| 1555 | 1556 | ||
| @@ -1,13 +1,13 @@ | |||
| 1 | use collections::HashSet; | 1 | use collections::HashSet; |
| 2 | use editor::{Editor, GoToDiagnostic}; | 2 | use editor::{Editor, GoToDiagnostic}; |
| 3 | use gpui::{ | 3 | use gpui::{ |
| 4 | rems, Div, EventEmitter, InteractiveComponent, ParentComponent, Render, Stateful, | 4 | rems, Div, EventEmitter, InteractiveElement, ParentElement, Render, Stateful, |
| 5 | StatefulInteractiveComponent, Styled, Subscription, View, ViewContext, WeakView, | 5 | StatefulInteractiveElement, Styled, Subscription, View, ViewContext, WeakView, |
| 6 | }; | 6 | }; |
| 7 | use language::Diagnostic; | 7 | use language::Diagnostic; |
| 8 | use lsp::LanguageServerId; | 8 | use lsp::LanguageServerId; |
| 9 | use theme::ActiveTheme; | 9 | use theme::ActiveTheme; |
| 10 | use ui::{h_stack, Icon, IconElement, Label, TextColor, Tooltip}; | 10 | use ui::{h_stack, Color, Icon, IconElement, Label, Tooltip}; |
| 11 | use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; | 11 | use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; |
| 12 | 12 | ||
| 13 | use crate::ProjectDiagnosticsEditor; | 13 | use crate::ProjectDiagnosticsEditor; |
| @@ -22,30 +22,30 @@ pub struct DiagnosticIndicator { · 8 unmodified lines | |||
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | impl Render for DiagnosticIndicator { | 24 | impl Render for DiagnosticIndicator { |
| 25 | type Element = Stateful<Self, Div<Self>>; | 25 | type Element = Stateful<Div>; |
| 26 | 26 | ||
| 27 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | 27 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 28 | let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { | 28 | let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { |
| 29 | (0, 0) => h_stack().child(IconElement::new(Icon::Check).color(TextColor::Success)), | 29 | (0, 0) => h_stack().child(IconElement::new(Icon::Check).color(Color::Success)), |
| 30 | (0, warning_count) => h_stack() | 30 | (0, warning_count) => h_stack() |
| 31 | .gap_1() | 31 | .gap_1() |
| 32 | .child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning)) | 32 | .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)) |
| 33 | .child(Label::new(warning_count.to_string())), | 33 | .child(Label::new(warning_count.to_string())), |
| 34 | (error_count, 0) => h_stack() | 34 | (error_count, 0) => h_stack() |
| 35 | .gap_1() | 35 | .gap_1() |
| 36 | .child(IconElement::new(Icon::XCircle).color(TextColor::Error)) | 36 | .child(IconElement::new(Icon::XCircle).color(Color::Error)) |
| 37 | .child(Label::new(error_count.to_string())), | 37 | .child(Label::new(error_count.to_string())), |
| 38 | (error_count, warning_count) => h_stack() | 38 | (error_count, warning_count) => h_stack() |
| 39 | .gap_1() | 39 | .gap_1() |
| 40 | .child(IconElement::new(Icon::XCircle).color(TextColor::Error)) | 40 | .child(IconElement::new(Icon::XCircle).color(Color::Error)) |
| 41 | .child(Label::new(error_count.to_string())) | 41 | .child(Label::new(error_count.to_string())) |
| 42 | .child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning)) | 42 | .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)) |
| 43 | .child(Label::new(warning_count.to_string())), | 43 | .child(Label::new(warning_count.to_string())), |
| 44 | }; | 44 | }; |
| 45 | 45 | ||
| 46 | h_stack() | 46 | h_stack() |
| 47 | .id(cx.entity_id()) | 47 | .id("diagnostic-indicator") |
| 48 | .on_action(Self::go_to_next_diagnostic) | 48 | .on_action(cx.listener(Self::go_to_next_diagnostic)) |
| 49 | .rounded_md() | 49 | .rounded_md() |
| 50 | .flex_none() | 50 | .flex_none() |
| 51 | .h(rems(1.375)) | 51 | .h(rems(1.375)) |
| @@ -54,14 +54,14 @@ impl Render for DiagnosticIndicator { · 2 unmodified lines | |||
| 54 | .bg(cx.theme().colors().ghost_element_background) | 54 | .bg(cx.theme().colors().ghost_element_background) |
| 55 | .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) | 55 | .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) |
| 56 | .active(|style| style.bg(cx.theme().colors().ghost_element_active)) | 56 | .active(|style| style.bg(cx.theme().colors().ghost_element_active)) |
| 57 | .tooltip(|_, cx| Tooltip::text("Project Diagnostics", cx)) | 57 | .tooltip(|cx| Tooltip::text("Project Diagnostics", cx)) |
| 58 | .on_click(|this, _, cx| { | 58 | .on_click(cx.listener(|this, _, cx| { |
| 59 | if let Some(workspace) = this.workspace.upgrade() { | 59 | if let Some(workspace) = this.workspace.upgrade() { |
| 60 | workspace.update(cx, |workspace, cx| { | 60 | workspace.update(cx, |workspace, cx| { |
| 61 | ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx) | 61 | ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx) |
| 62 | }) | 62 | }) |
| 63 | } | 63 | } |
| 64 | }) | 64 | })) |
| 65 | .child(diagnostic_indicator) | 65 | .child(diagnostic_indicator) |
| 66 | } | 66 | } |
| 67 | } | 67 | } |
| @@ -1,5 +1,5 @@ | |||
| 1 | use crate::ProjectDiagnosticsEditor; | 1 | use crate::ProjectDiagnosticsEditor; |
| 2 | use gpui::{div, Div, EventEmitter, ParentComponent, Render, ViewContext, WeakView}; | 2 | use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView}; |
| 3 | use ui::{Icon, IconButton, Tooltip}; | 3 | use ui::{Icon, IconButton, Tooltip}; |
| 4 | use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; | 4 | use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; |
| 5 | 5 | ||
| @@ -8,7 +8,7 @@ pub struct ToolbarControls { · 2 unmodified lines | |||
| 8 | } | 8 | } |
| 9 | 9 | ||
| 10 | impl Render for ToolbarControls { | 10 | impl Render for ToolbarControls { |
| 11 | type Element = Div<Self>; | 11 | type Element = Div; |
| 12 | 12 | ||
| 13 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | 13 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 14 | let include_warnings = self | 14 | let include_warnings = self |
| @@ -26,14 +26,14 @@ impl Render for ToolbarControls { · 11 unmodified lines | |||
| 26 | 26 | ||
| 27 | div().child( | 27 | div().child( |
| 28 | IconButton::new("toggle-warnings", Icon::ExclamationTriangle) | 28 | IconButton::new("toggle-warnings", Icon::ExclamationTriangle) |
| 29 | .tooltip(move |_, cx| Tooltip::text(tooltip, cx)) | 29 | .tooltip(move |cx| Tooltip::text(tooltip, cx)) |
| 30 | .on_click(|this: &mut Self, cx| { | 30 | .on_click(cx.listener(|this, _, cx| { |
| 31 | if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) { | 31 | if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) { |
| 32 | editor.update(cx, |editor, cx| { | 32 | editor.update(cx, |editor, cx| { |
| 33 | editor.toggle_warnings(&Default::default(), cx); | 33 | editor.toggle_warnings(&Default::default(), cx); |
| 34 | }); | 34 | }); |
| 35 | } | 35 | } |
| 36 | }), | 36 | })), |
| 37 | ) | 37 | ) |
| 38 | } | 38 | } |
| 39 | } | 39 | } |
| @@ -49,7 +49,7 @@ impl ToolbarItemView for ToolbarControls { · 9 unmodified lines | |||
| 49 | if let Some(pane_item) = active_pane_item.as_ref() { | 49 | if let Some(pane_item) = active_pane_item.as_ref() { |
| 50 | if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() { | 50 | if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() { |
| 51 | self.editor = Some(editor.downgrade()); | 51 | self.editor = Some(editor.downgrade()); |
| 52 | ToolbarItemLocation::PrimaryRight { flex: None } | 52 | ToolbarItemLocation::PrimaryRight |
| 53 | } else { | 53 | } else { |
| 54 | ToolbarItemLocation::Hidden | 54 | ToolbarItemLocation::Hidden |
| 55 | } | 55 | } |
| @@ -1001,17 +1001,18 @@ impl CompletionsMenu { · 1000 unmodified lines | |||
| 1001 | 1001 | ||
| 1002 | fn pre_resolve_completion_documentation( | 1002 | fn pre_resolve_completion_documentation( |
| 1003 | &self, | 1003 | &self, |
| 1004 | project: Option<ModelHandle<Project>>, | 1004 | editor: &Editor, |
| 1005 | cx: &mut ViewContext<Editor>, | 1005 | cx: &mut ViewContext<Editor>, |
| 1006 | ) { | 1006 | ) -> Option<Task<()>> { |
| 1007 | let settings = settings::get::<EditorSettings>(cx); | 1007 | let settings = settings::get::<EditorSettings>(cx); |
| 1008 | if !settings.show_completion_documentation { | 1008 | if !settings.show_completion_documentation { |
| 1009 | return; | 1009 | return None; |
| 1010 | } | 1010 | } |
| 1011 | 1011 | ||
| 1012 | let Some(project) = project else { | 1012 | let Some(project) = editor.project.clone() else { |
| 1013 | return; | 1013 | return None; |
| 1014 | }; | 1014 | }; |
| 1015 | |||
| 1015 | let client = project.read(cx).client(); | 1016 | let client = project.read(cx).client(); |
| 1016 | let language_registry = project.read(cx).languages().clone(); | 1017 | let language_registry = project.read(cx).languages().clone(); |
| 1017 | 1018 | ||
| @@ -1021,7 +1022,7 @@ impl CompletionsMenu { · 3 unmodified lines | |||
| 1021 | let completions = self.completions.clone(); | 1022 | let completions = self.completions.clone(); |
| 1022 | let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); | 1023 | let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); |
| 1023 | 1024 | ||
| 1024 | cx.spawn(move |this, mut cx| async move { | 1025 | Some(cx.spawn(move |this, mut cx| async move { |
| 1025 | if is_remote { | 1026 | if is_remote { |
| 1026 | let Some(project_id) = project_id else { | 1027 | let Some(project_id) = project_id else { |
| 1027 | log::error!("Remote project without remote_id"); | 1028 | log::error!("Remote project without remote_id"); |
| @@ -1083,8 +1084,7 @@ impl CompletionsMenu { · 55 unmodified lines | |||
| 1083 | _ = this.update(&mut cx, |_, cx| cx.notify()); | 1084 | _ = this.update(&mut cx, |_, cx| cx.notify()); |
| 1084 | } | 1085 | } |
| 1085 | } | 1086 | } |
| 1086 | }) | 1087 | })) |
| 1087 | .detach(); | ||
| 1088 | } | 1088 | } |
| 1089 | 1089 | ||
| 1090 | fn attempt_resolve_selected_completion_documentation( | 1090 | fn attempt_resolve_selected_completion_documentation( |
| @@ -3423,7 +3423,7 @@ impl Editor { · 2332 unmodified lines | |||
| 3423 | to_insert, | 3423 | to_insert, |
| 3424 | }) = self.inlay_hint_cache.spawn_hint_refresh( | 3424 | }) = self.inlay_hint_cache.spawn_hint_refresh( |
| 3425 | reason_description, | 3425 | reason_description, |
| 3426 | self.excerpt_visible_offsets(required_languages.as_ref(), cx), | 3426 | self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx), |
| 3427 | invalidate_cache, | 3427 | invalidate_cache, |
| 3428 | cx, | 3428 | cx, |
| 3429 | ) { | 3429 | ) { |
| @@ -3442,11 +3442,15 @@ impl Editor { · 12 unmodified lines | |||
| 3442 | .collect() | 3442 | .collect() |
| 3443 | } | 3443 | } |
| 3444 | 3444 | ||
| 3445 | pub fn excerpt_visible_offsets( | 3445 | pub fn excerpts_for_inlay_hints_query( |
| 3446 | &self, | 3446 | &self, |
| 3447 | restrict_to_languages: Option<&HashSet<Arc<Language>>>, | 3447 | restrict_to_languages: Option<&HashSet<Arc<Language>>>, |
| 3448 | cx: &mut ViewContext<'_, '_, Editor>, | 3448 | cx: &mut ViewContext<'_, '_, Editor>, |
| 3449 | ) -> HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)> { | 3449 | ) -> HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)> { |
| 3450 | let Some(project) = self.project.as_ref() else { | ||
| 3451 | return HashMap::default(); | ||
| 3452 | }; | ||
| 3453 | let project = project.read(cx); | ||
| 3450 | let multi_buffer = self.buffer().read(cx); | 3454 | let multi_buffer = self.buffer().read(cx); |
| 3451 | let multi_buffer_snapshot = multi_buffer.snapshot(cx); | 3455 | let multi_buffer_snapshot = multi_buffer.snapshot(cx); |
| 3452 | let multi_buffer_visible_start = self | 3456 | let multi_buffer_visible_start = self |
| @@ -3466,6 +3470,14 @@ impl Editor { · 13 unmodified lines | |||
| 3466 | .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) | 3470 | .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) |
| 3467 | .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| { | 3471 | .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| { |
| 3468 | let buffer = buffer_handle.read(cx); | 3472 | let buffer = buffer_handle.read(cx); |
| 3473 | let buffer_file = project::worktree::File::from_dyn(buffer.file())?; | ||
| 3474 | let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?; | ||
| 3475 | let worktree_entry = buffer_worktree | ||
| 3476 | .read(cx) | ||
| 3477 | .entry_for_id(buffer_file.project_entry_id(cx)?)?; | ||
| 3478 | if worktree_entry.is_ignored { | ||
| 3479 | return None; | ||
| 3480 | } | ||
| 3469 | let language = buffer.language()?; | 3481 | let language = buffer.language()?; |
| 3470 | if let Some(restrict_to_languages) = restrict_to_languages { | 3482 | if let Some(restrict_to_languages) = restrict_to_languages { |
| 3471 | if !restrict_to_languages.contains(language) { | 3483 | if !restrict_to_languages.contains(language) { |
| @@ -3580,7 +3592,8 @@ impl Editor { · 108 unmodified lines | |||
| 3580 | let id = post_inc(&mut self.next_completion_id); | 3592 | let id = post_inc(&mut self.next_completion_id); |
| 3581 | let task = cx.spawn(|this, mut cx| { | 3593 | let task = cx.spawn(|this, mut cx| { |
| 3582 | async move { | 3594 | async move { |
| 3583 | let menu = if let Some(completions) = completions.await.log_err() { | 3595 | let completions = completions.await.log_err(); |
| 3596 | let (menu, pre_resolve_task) = if let Some(completions) = completions { | ||
| 3584 | let mut menu = CompletionsMenu { | 3597 | let mut menu = CompletionsMenu { |
| 3585 | id, | 3598 | id, |
| 3586 | initial_position: position, | 3599 | initial_position: position, |
| @@ -3601,21 +3614,26 @@ impl Editor { · 14 unmodified lines | |||
| 3601 | selected_item: 0, | 3614 | selected_item: 0, |
| 3602 | list: Default::default(), | 3615 | list: Default::default(), |
| 3603 | }; | 3616 | }; |
| 3617 | |||
| 3604 | menu.filter(query.as_deref(), cx.background()).await; | 3618 | menu.filter(query.as_deref(), cx.background()).await; |
| 3619 | |||
| 3605 | if menu.matches.is_empty() { | 3620 | if menu.matches.is_empty() { |
| 3606 | None | 3621 | (None, None) |
| 3607 | } else { | 3622 | } else { |
| 3608 | _ = this.update(&mut cx, |editor, cx| { | 3623 | let pre_resolve_task = this |
| 3609 | menu.pre_resolve_completion_documentation(editor.project.clone(), cx); | 3624 | .update(&mut cx, |editor, cx| { |
| 3610 | }); | 3625 | menu.pre_resolve_completion_documentation(editor, cx) |
| 3611 | Some(menu) | 3626 | }) |
| 3627 | .ok() | ||
| 3628 | .flatten(); | ||
| 3629 | (Some(menu), pre_resolve_task) | ||
| 3612 | } | 3630 | } |
| 3613 | } else { | 3631 | } else { |
| 3614 | None | 3632 | (None, None) |
| 3615 | }; | 3633 | }; |
| 3616 | 3634 | ||
| 3617 | this.update(&mut cx, |this, cx| { | 3635 | this.update(&mut cx, |this, cx| { |
| 3618 | this.completion_tasks.retain(|(task_id, _)| *task_id > id); | 3636 | this.completion_tasks.retain(|(task_id, _)| *task_id >= id); |
| 3619 | 3637 | ||
| 3620 | let mut context_menu = this.context_menu.write(); | 3638 | let mut context_menu = this.context_menu.write(); |
| 3621 | match context_menu.as_ref() { | 3639 | match context_menu.as_ref() { |
| @@ -3636,10 +3654,10 @@ impl Editor { · 14 unmodified lines | |||
| 3636 | drop(context_menu); | 3654 | drop(context_menu); |
| 3637 | this.discard_copilot_suggestion(cx); | 3655 | this.discard_copilot_suggestion(cx); |
| 3638 | cx.notify(); | 3656 | cx.notify(); |
| 3639 | } else if this.completion_tasks.is_empty() { | 3657 | } else if this.completion_tasks.len() <= 1 { |
| 3640 | // If there are no more completion tasks and the last menu was | 3658 | // If there are no more completion tasks (omitting ourself) and |
| 3641 | // empty, we should hide it. If it was already hidden, we should | 3659 | // the last menu was empty, we should hide it. If it was already |
| 3642 | // also show the copilot suggestion when available. | 3660 | // hidden, we should also show the copilot suggestion when available. |
| 3643 | drop(context_menu); | 3661 | drop(context_menu); |
| 3644 | if this.hide_context_menu(cx).is_none() { | 3662 | if this.hide_context_menu(cx).is_none() { |
| 3645 | this.update_visible_copilot_suggestion(cx); | 3663 | this.update_visible_copilot_suggestion(cx); |
| @@ -3647,10 +3665,15 @@ impl Editor { · 1 unmodified line | |||
| 3647 | } | 3665 | } |
| 3648 | })?; | 3666 | })?; |
| 3649 | 3667 | ||
| 3668 | if let Some(pre_resolve_task) = pre_resolve_task { | ||
| 3669 | pre_resolve_task.await; | ||
| 3670 | } | ||
| 3671 | |||
| 3650 | Ok::<_, anyhow::Error>(()) | 3672 | Ok::<_, anyhow::Error>(()) |
| 3651 | } | 3673 | } |
| 3652 | .log_err() | 3674 | .log_err() |
| 3653 | }); | 3675 | }); |
| 3676 | |||
| 3654 | self.completion_tasks.push((id, task)); | 3677 | self.completion_tasks.push((id, task)); |
| 3655 | } | 3678 | } |
| 3656 | 3679 | ||
| @@ -861,7 +861,7 @@ async fn fetch_and_update_hints( · 860 unmodified lines | |||
| 861 | let inlay_hints_fetch_task = editor | 861 | let inlay_hints_fetch_task = editor |
| 862 | .update(&mut cx, |editor, cx| { | 862 | .update(&mut cx, |editor, cx| { |
| 863 | if got_throttled { | 863 | if got_throttled { |
| 864 | let query_not_around_visible_range = match editor.excerpt_visible_offsets(None, cx).remove(&query.excerpt_id) { | 864 | let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) { |
| 865 | Some((_, _, current_visible_range)) => { | 865 | Some((_, _, current_visible_range)) => { |
| 866 | let visible_offset_length = current_visible_range.len(); | 866 | let visible_offset_length = current_visible_range.len(); |
| 867 | let double_visible_range = current_visible_range | 867 | let double_visible_range = current_visible_range |
| @@ -2237,7 +2237,9 @@ pub mod tests { · 1369 unmodified lines | |||
| 2237 | editor: &ViewHandle<Editor>, | 2237 | editor: &ViewHandle<Editor>, |
| 2238 | cx: &mut gpui::TestAppContext, | 2238 | cx: &mut gpui::TestAppContext, |
| 2239 | ) -> Range<Point> { | 2239 | ) -> Range<Point> { |
| 2240 | let ranges = editor.update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx)); | 2240 | let ranges = editor.update(cx, |editor, cx| { |
| 2241 | editor.excerpts_for_inlay_hints_query(None, cx) | ||
| 2242 | }); | ||
| 2241 | assert_eq!( | 2243 | assert_eq!( |
| 2242 | ranges.len(), | 2244 | ranges.len(), |
| 2243 | 1, | 2245 | 1, |
| @@ -50,7 +50,7 @@ struct BlockRow(u32); · 49 unmodified lines | |||
| 50 | #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] | 50 | #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] |
| 51 | struct WrapRow(u32); | 51 | struct WrapRow(u32); |
| 52 | 52 | ||
| 53 | pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>; | 53 | pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement>; |
| 54 | 54 | ||
| 55 | pub struct Block { | 55 | pub struct Block { |
| 56 | id: BlockId, | 56 | id: BlockId, |
| @@ -69,7 +69,7 @@ where · 12 unmodified lines | |||
| 69 | pub position: P, | 69 | pub position: P, |
| 70 | pub height: u8, | 70 | pub height: u8, |
| 71 | pub style: BlockStyle, | 71 | pub style: BlockStyle, |
| 72 | pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>, | 72 | pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>, |
| 73 | pub disposition: BlockDisposition, | 73 | pub disposition: BlockDisposition, |
| 74 | } | 74 | } |
| 75 | 75 | ||
| @@ -947,7 +947,7 @@ impl DerefMut for BlockContext<'_, '_> { · 871 unmodified lines | |||
| 947 | } | 947 | } |
| 948 | 948 | ||
| 949 | impl Block { | 949 | impl Block { |
| 950 | pub fn render(&self, cx: &mut BlockContext) -> AnyElement<Editor> { | 950 | pub fn render(&self, cx: &mut BlockContext) -> AnyElement { |
| 951 | self.render.lock()(cx) | 951 | self.render.lock()(cx) |
| 952 | } | 952 | } |
| 953 | 953 | ||
| @@ -40,11 +40,12 @@ use fuzzy::{StringMatch, StringMatchCandidate}; · 39 unmodified lines | |||
| 40 | use git::diff_hunk_to_display; | 40 | use git::diff_hunk_to_display; |
| 41 | use gpui::{ | 41 | use gpui::{ |
| 42 | actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, | 42 | actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, |
| 43 | AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context, | 43 | AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, |
| 44 | EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, | 44 | DispatchPhase, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures, |
| 45 | Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled, | 45 | FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, |
| 46 | Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, | 46 | MouseButton, ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText, |
| 47 | WeakView, WindowContext, | 47 | Subscription, Task, TextRun, TextStyle, UniformListScrollHandle, View, ViewContext, |
| 48 | VisualContext, WeakView, WhiteSpace, WindowContext, | ||
| 48 | }; | 49 | }; |
| 49 | use highlight_matching_bracket::refresh_matching_bracket_highlights; | 50 | use highlight_matching_bracket::refresh_matching_bracket_highlights; |
| 50 | use hover_popover::{hide_hover, HoverState}; | 51 | use hover_popover::{hide_hover, HoverState}; |
| @@ -54,13 +55,14 @@ use itertools::Itertools; · 3 unmodified lines | |||
| 54 | pub use language::{char_kind, CharKind}; | 55 | pub use language::{char_kind, CharKind}; |
| 55 | use language::{ | 56 | use language::{ |
| 56 | language_settings::{self, all_language_settings, InlayHintSettings}, | 57 | language_settings::{self, all_language_settings, InlayHintSettings}, |
| 57 | point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape, | 58 | markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, |
| 58 | Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName, | 59 | Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, |
| 59 | OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, | 60 | LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, |
| 61 | TransactionId, | ||
| 60 | }; | 62 | }; |
| 61 | use lazy_static::lazy_static; | 63 | use lazy_static::lazy_static; |
| 62 | use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; | 64 | use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; |
| 63 | use lsp::{DiagnosticSeverity, Documentation, LanguageServerId}; | 65 | use lsp::{DiagnosticSeverity, LanguageServerId}; |
| 64 | use movement::TextLayoutDetails; | 66 | use movement::TextLayoutDetails; |
| 65 | use multi_buffer::ToOffsetUtf16; | 67 | use multi_buffer::ToOffsetUtf16; |
| 66 | pub use multi_buffer::{ | 68 | pub use multi_buffer::{ |
| @@ -97,12 +99,12 @@ use text::{OffsetUtf16, Rope}; · 30 unmodified lines | |||
| 97 | use theme::{ | 99 | use theme::{ |
| 98 | ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, | 100 | ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, |
| 99 | }; | 101 | }; |
| 100 | use ui::{v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip}; | 102 | use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, StyledExt, Tooltip}; |
| 101 | use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; | 103 | use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; |
| 102 | use workspace::{ | 104 | use workspace::{ |
| 103 | item::{ItemEvent, ItemHandle}, | 105 | item::{ItemEvent, ItemHandle}, |
| 104 | searchable::SearchEvent, | 106 | searchable::SearchEvent, |
| 105 | ItemNavHistory, SplitDirection, ViewId, Workspace, | 107 | ItemNavHistory, Pane, SplitDirection, ViewId, Workspace, |
| 106 | }; | 108 | }; |
| 107 | 109 | ||
| 108 | const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); | 110 | const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); |
| @@ -115,70 +117,70 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis · 6 unmodified lines | |||
| 115 | 117 | ||
| 116 | pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); | 118 | pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); |
| 117 | 119 | ||
| 118 | // pub fn render_parsed_markdown<Tag: 'static>( | 120 | pub fn render_parsed_markdown( |
| 119 | // parsed: &language::ParsedMarkdown, | 121 | element_id: impl Into<ElementId>, |
| 120 | // editor_style: &EditorStyle, | 122 | parsed: &language::ParsedMarkdown, |
| 121 | // workspace: Option<WeakView<Workspace>>, | 123 | editor_style: &EditorStyle, |
| 122 | // cx: &mut ViewContext<Editor>, | 124 | workspace: Option<WeakView<Workspace>>, |
| 123 | // ) -> Text { | 125 | cx: &mut ViewContext<Editor>, |
| 124 | // enum RenderedMarkdown {} | 126 | ) -> InteractiveText { |
| 125 | 127 | let code_span_background_color = cx | |
| 126 | // let parsed = parsed.clone(); | 128 | .theme() |
| 127 | // let view_id = cx.view_id(); | 129 | .colors() |
| 128 | // let code_span_background_color = editor_style.document_highlight_read_background; | 130 | .editor_document_highlight_read_background; |
| 129 | 131 | ||
| 130 | // let mut region_id = 0; | 132 | let highlights = gpui::combine_highlights( |
| 131 | 133 | parsed.highlights.iter().filter_map(|(range, highlight)| { | |
| 132 | // todo!() | 134 | let highlight = highlight.to_highlight_style(&editor_style.syntax)?; |
| 133 | // // Text::new(parsed.text, editor_style.text.clone()) | 135 | Some((range.clone(), highlight)) |
| 134 | // // .with_highlights( | 136 | }), |
| 135 | // // parsed | 137 | parsed |
| 136 | // // .highlights | 138 | .regions |
| 137 | // // .iter() | 139 | .iter() |
| 138 | // // .filter_map(|(range, highlight)| { | 140 | .zip(&parsed.region_ranges) |
| 139 | // // let highlight = highlight.to_highlight_style(&editor_style.syntax)?; | 141 | .filter_map(|(region, range)| { |
| 140 | // // Some((range.clone(), highlight)) | 142 | if region.code { |
| 141 | // // }) | 143 | Some(( |
| 142 | // // .collect::<Vec<_>>(), | 144 | range.clone(), |
| 143 | // // ) | 145 | HighlightStyle { |
| 144 | // // .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| { | 146 | background_color: Some(code_span_background_color), |
| 145 | // // region_id += 1; | 147 | ..Default::default() |
| 146 | // // let region = parsed.regions[ix].clone(); | 148 | }, |
| 147 | 149 | )) | |
| 148 | // // if let Some(link) = region.link { | 150 | } else { |
| 149 | // // cx.scene().push_cursor_region(CursorRegion { | 151 | None |
| 150 | // // bounds, | 152 | } |
| 151 | // // style: CursorStyle::PointingHand, | 153 | }), |
| 152 | // // }); | 154 | ); |
| 153 | // // cx.scene().push_mouse_region( | 155 | let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights); |
| 154 | // // MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) | 156 | |
| 155 | // // .on_down::<Editor, _>(MouseButton::Left, move |_, _, cx| match &link { | 157 | // todo!("add the ability to change cursor style for link ranges") |
| 156 | // // markdown::Link::Web { url } => cx.platform().open_url(url), | 158 | let mut links = Vec::new(); |
| 157 | // // markdown::Link::Path { path } => { | 159 | let mut link_ranges = Vec::new(); |
| 158 | // // if let Some(workspace) = &workspace { | 160 | for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) { |
| 159 | // // _ = workspace.update(cx, |workspace, cx| { | 161 | if let Some(link) = region.link.clone() { |
| 160 | // // workspace.open_abs_path(path.clone(), false, cx).detach(); | 162 | links.push(link); |
| 161 | // // }); | 163 | link_ranges.push(range.clone()); |
| 162 | // // } | 164 | } |
| 163 | // // } | 165 | } |
| 164 | // // }), | 166 | |
| 165 | // // ); | 167 | InteractiveText::new( |
| 166 | // // } | 168 | element_id, |
| 167 | 169 | StyledText::new(parsed.text.clone()).with_runs(runs), | |
| 168 | // // if region.code { | 170 | ) |
| 169 | // // cx.draw_quad(Quad { | 171 | .on_click(link_ranges, move |clicked_range_ix, cx| { |
| 170 | // // bounds, | 172 | match &links[clicked_range_ix] { |
| 171 | // // background: Some(code_span_background_color), | 173 | markdown::Link::Web { url } => cx.open_url(url), |
| 172 | // // corner_radii: (2.0).into(), | 174 | markdown::Link::Path { path } => { |
| 173 | // // order: todo!(), | 175 | if let Some(workspace) = &workspace { |
| 174 | // // content_mask: todo!(), | 176 | _ = workspace.update(cx, |workspace, cx| { |
| 175 | // // border_color: todo!(), | 177 | workspace.open_abs_path(path.clone(), false, cx).detach(); |
| 176 | // // border_widths: todo!(), | 178 | }); |
| 177 | // // }); | 179 | } |
| 178 | // // } | 180 | } |
| 179 | // // }) | 181 | } |
| 180 | // // .with_soft_wrap(true) | 182 | }) |
| 181 | // } | 183 | } |
| 182 | 184 | ||
| 183 | #[derive(PartialEq, Clone, Deserialize, Default, Action)] | 185 | #[derive(PartialEq, Clone, Deserialize, Default, Action)] |
| 184 | pub struct SelectNext { | 186 | pub struct SelectNext { |
| @@ -529,8 +531,6 @@ pub fn init(cx: &mut AppContext) { · 344 unmodified lines | |||
| 529 | // cx.register_action_type(Editor::context_menu_next); | 531 | // cx.register_action_type(Editor::context_menu_next); |
| 530 | // cx.register_action_type(Editor::context_menu_last); | 532 | // cx.register_action_type(Editor::context_menu_last); |
| 531 | 533 | ||
| 532 | hover_popover::init(cx); | ||
| 533 | |||
| 534 | workspace::register_project_item::<Editor>(cx); | 534 | workspace::register_project_item::<Editor>(cx); |
| 535 | workspace::register_followable_item::<Editor>(cx); | 535 | workspace::register_followable_item::<Editor>(cx); |
| 536 | workspace::register_deserializable_item::<Editor>(cx); | 536 | workspace::register_deserializable_item::<Editor>(cx); |
| @@ -663,6 +663,7 @@ pub struct Editor { · 126 unmodified lines | |||
| 663 | pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>, | 663 | pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>, |
| 664 | gutter_width: Pixels, | 664 | gutter_width: Pixels, |
| 665 | style: Option<EditorStyle>, | 665 | style: Option<EditorStyle>, |
| 666 | editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>, | ||
| 666 | } | 667 | } |
| 667 | 668 | ||
| 668 | pub struct EditorSnapshot { | 669 | pub struct EditorSnapshot { |
| @@ -905,12 +906,16 @@ impl ContextMenu { · 236 unmodified lines | |||
| 905 | &self, | 906 | &self, |
| 906 | cursor_position: DisplayPoint, | 907 | cursor_position: DisplayPoint, |
| 907 | style: &EditorStyle, | 908 | style: &EditorStyle, |
| 909 | max_height: Pixels, | ||
| 908 | workspace: Option<WeakView<Workspace>>, | 910 | workspace: Option<WeakView<Workspace>>, |
| 909 | cx: &mut ViewContext<Editor>, | 911 | cx: &mut ViewContext<Editor>, |
| 910 | ) -> (DisplayPoint, AnyElement<Editor>) { | 912 | ) -> (DisplayPoint, AnyElement) { |
| 911 | match self { | 913 | match self { |
| 912 | ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)), | 914 | ContextMenu::Completions(menu) => ( |
| 913 | ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), | 915 | cursor_position, |
| 916 | menu.render(style, max_height, workspace, cx), | ||
| 917 | ), | ||
| 918 | ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx), | ||
| 914 | } | 919 | } |
| 915 | } | 920 | } |
| 916 | } | 921 | } |
| @@ -966,20 +971,22 @@ impl CompletionsMenu { · 49 unmodified lines | |||
| 966 | 971 | ||
| 967 | fn pre_resolve_completion_documentation( | 972 | fn pre_resolve_completion_documentation( |
| 968 | &self, | 973 | &self, |
| 969 | project: Option<Model<Project>>, | 974 | _editor: &Editor, |
| 970 | cx: &mut ViewContext<Editor>, | 975 | _cx: &mut ViewContext<Editor>, |
| 971 | ) { | 976 | ) -> Option<Task<()>> { |
| 972 | // todo!("implementation below "); | 977 | // todo!("implementation below "); |
| 978 | None | ||
| 973 | } | 979 | } |
| 974 | // ) { | 980 | // { |
| 975 | // let settings = EditorSettings::get_global(cx); | 981 | // let settings = EditorSettings::get_global(cx); |
| 976 | // if !settings.show_completion_documentation { | 982 | // if !settings.show_completion_documentation { |
| 977 | // return; | 983 | // return None; |
| 978 | // } | 984 | // } |
| 979 | 985 | ||
| 980 | // let Some(project) = project else { | 986 | // let Some(project) = editor.project.clone() else { |
| 981 | // return; | 987 | // return None; |
| 982 | // }; | 988 | // }; |
| 989 | |||
| 983 | // let client = project.read(cx).client(); | 990 | // let client = project.read(cx).client(); |
| 984 | // let language_registry = project.read(cx).languages().clone(); | 991 | // let language_registry = project.read(cx).languages().clone(); |
| 985 | 992 | ||
| @@ -989,7 +996,7 @@ impl CompletionsMenu { · 3 unmodified lines | |||
| 989 | // let completions = self.completions.clone(); | 996 | // let completions = self.completions.clone(); |
| 990 | // let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); | 997 | // let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); |
| 991 | 998 | ||
| 992 | // cx.spawn(move |this, mut cx| async move { | 999 | // Some(cx.spawn(move |this, mut cx| async move { |
| 993 | // if is_remote { | 1000 | // if is_remote { |
| 994 | // let Some(project_id) = project_id else { | 1001 | // let Some(project_id) = project_id else { |
| 995 | // log::error!("Remote project without remote_id"); | 1002 | // log::error!("Remote project without remote_id"); |
| @@ -1051,8 +1058,7 @@ impl CompletionsMenu { · 55 unmodified lines | |||
| 1051 | // _ = this.update(&mut cx, |_, cx| cx.notify()); | 1058 | // _ = this.update(&mut cx, |_, cx| cx.notify()); |
| 1052 | // } | 1059 | // } |
| 1053 | // } | 1060 | // } |
| 1054 | // }) | 1061 | // })) |
| 1055 | // .detach(); | ||
| 1056 | // } | 1062 | // } |
| 1057 | 1063 | ||
| 1058 | fn attempt_resolve_selected_completion_documentation( | 1064 | fn attempt_resolve_selected_completion_documentation( |
| @@ -1221,210 +1227,156 @@ impl CompletionsMenu { · 162 unmodified lines | |||
| 1221 | fn render( | 1227 | fn render( |
| 1222 | &self, | 1228 | &self, |
| 1223 | style: &EditorStyle, | 1229 | style: &EditorStyle, |
| 1230 | max_height: Pixels, | ||
| 1224 | workspace: Option<WeakView<Workspace>>, | 1231 | workspace: Option<WeakView<Workspace>>, |
| 1225 | cx: &mut ViewContext<Editor>, | 1232 | cx: &mut ViewContext<Editor>, |
| 1226 | ) -> AnyElement<Editor> { | 1233 | ) -> AnyElement { |
| 1227 | todo!("old implementation below") | 1234 | let settings = EditorSettings::get_global(cx); |
| 1228 | } | 1235 | let show_completion_documentation = settings.show_completion_documentation; |
| 1229 | 1236 | ||
| 1230 | // enum CompletionTag {} | 1237 | let widest_completion_ix = self |
| 1238 | .matches | ||
| 1239 | .iter() | ||
| 1240 | .enumerate() | ||
| 1241 | .max_by_key(|(_, mat)| { | ||
| 1242 | let completions = self.completions.read(); | ||
| 1243 | let completion = &completions[mat.candidate_id]; | ||
| 1244 | let documentation = &completion.documentation; | ||
| 1245 | |||
| 1246 | let mut len = completion.label.text.chars().count(); | ||
| 1247 | if let Some(Documentation::SingleLine(text)) = documentation { | ||
| 1248 | if show_completion_documentation { | ||
| 1249 | len += text.chars().count(); | ||
| 1250 | } | ||
| 1251 | } | ||
| 1231 | 1252 | ||
| 1232 | // let settings = EditorSettings>(cx); | 1253 | len |
| 1233 | // let show_completion_documentation = settings.show_completion_documentation; | 1254 | }) |
| 1255 | .map(|(ix, _)| ix); | ||
| 1234 | 1256 | ||
| 1235 | // let widest_completion_ix = self | 1257 | let completions = self.completions.clone(); |
| 1236 | // .matches | 1258 | let matches = self.matches.clone(); |
| 1237 | // .iter() | 1259 | let selected_item = self.selected_item; |
| 1238 | // .enumerate() | 1260 | let style = style.clone(); |
| 1239 | // .max_by_key(|(_, mat)| { | ||
| 1240 | // let completions = self.completions.read(); | ||
| 1241 | // let completion = &completions[mat.candidate_id]; | ||
| 1242 | // let documentation = &completion.documentation; | ||
| 1243 | 1261 | ||
| 1244 | // let mut len = completion.label.text.chars().count(); | 1262 | let multiline_docs = { |
| 1245 | // if let Some(Documentation::SingleLine(text)) = documentation { | 1263 | let mat = &self.matches[selected_item]; |
| 1246 | // if show_completion_documentation { | 1264 | let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation { |
| 1247 | // len += text.chars().count(); | 1265 | Some(Documentation::MultiLinePlainText(text)) => { |
| 1248 | // } | 1266 | Some(div().child(SharedString::from(text.clone()))) |
| 1249 | // } | 1267 | } |
| 1268 | Some(Documentation::MultiLineMarkdown(parsed)) => Some(div().child( | ||
| 1269 | render_parsed_markdown("completions_markdown", parsed, &style, workspace, cx), | ||
| 1270 | )), | ||
| 1271 | _ => None, | ||
| 1272 | }; | ||
| 1273 | multiline_docs.map(|div| { | ||
| 1274 | div.id("multiline_docs") | ||
| 1275 | .max_h(max_height) | ||
| 1276 | .flex_1() | ||
| 1277 | .px_1p5() | ||
| 1278 | .py_1() | ||
| 1279 | .min_w(px(260.)) | ||
| 1280 | .max_w(px(640.)) | ||
| 1281 | .w(px(500.)) | ||
| 1282 | .text_ui() | ||
| 1283 | .overflow_y_scroll() | ||
| 1284 | // Prevent a mouse down on documentation from being propagated to the editor, | ||
| 1285 | // because that would move the cursor. | ||
| 1286 | .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) | ||
| 1287 | }) | ||
| 1288 | }; | ||
| 1289 | let list = uniform_list( | ||
| 1290 | cx.view().clone(), | ||
| 1291 | "completions", | ||
| 1292 | matches.len(), | ||
| 1293 | move |editor, range, cx| { | ||
| 1294 | let start_ix = range.start; | ||
| 1295 | let completions_guard = completions.read(); | ||
| 1250 | 1296 | ||
| 1251 | // len | 1297 | matches[range] |
| 1252 | // }) | 1298 | .iter() |
| 1253 | // .map(|(ix, _)| ix); | 1299 | .enumerate() |
| 1300 | .map(|(ix, mat)| { | ||
| 1301 | let item_ix = start_ix + ix; | ||
| 1302 | let candidate_id = mat.candidate_id; | ||
| 1303 | let completion = &completions_guard[candidate_id]; | ||
| 1254 | 1304 | ||
| 1255 | // let completions = self.completions.clone(); | 1305 | let documentation = if show_completion_documentation { |
| 1256 | // let matches = self.matches.clone(); | 1306 | &completion.documentation |
| 1257 | // let selected_item = self.selected_item; | 1307 | } else { |
| 1258 | 1308 | &None | |
| 1259 | // let list = UniformList::new(self.list.clone(), matches.len(), cx, { | 1309 | }; |
| 1260 | // let style = style.clone(); | ||
| 1261 | // move |_, range, items, cx| { | ||
| 1262 | // let start_ix = range.start; | ||
| 1263 | // let completions_guard = completions.read(); | ||
| 1264 | |||
| 1265 | // for (ix, mat) in matches[range].iter().enumerate() { | ||
| 1266 | // let item_ix = start_ix + ix; | ||
| 1267 | // let candidate_id = mat.candidate_id; | ||
| 1268 | // let completion = &completions_guard[candidate_id]; | ||
| 1269 | |||
| 1270 | // let documentation = if show_completion_documentation { | ||
| 1271 | // &completion.documentation | ||
| 1272 | // } else { | ||
| 1273 | // &None | ||
| 1274 | // }; | ||
| 1275 | 1310 | ||
| 1276 | // items.push( | 1311 | let highlights = gpui::combine_highlights( |
| 1277 | // MouseEventHandler::new::<CompletionTag, _>( | 1312 | mat.ranges().map(|range| (range, FontWeight::BOLD.into())), |
| 1278 | // mat.candidate_id, | 1313 | styled_runs_for_code_label(&completion.label, &style.syntax).map( |
| 1279 | // cx, | 1314 | |(range, mut highlight)| { |
| 1280 | // |state, _| { | 1315 | // Ignore font weight for syntax highlighting, as we'll use it |
| 1281 | // let item_style = if item_ix == selected_item { | 1316 | // for fuzzy matches. |
| 1282 | // style.autocomplete.selected_item | 1317 | highlight.font_weight = None; |
| 1283 | // } else if state.hovered() { | 1318 | (range, highlight) |
| 1284 | // style.autocomplete.hovered_item | 1319 | }, |
| 1285 | // } else { | 1320 | ), |
| 1286 | // style.autocomplete.item | 1321 | ); |
| 1287 | // }; | 1322 | let completion_label = StyledText::new(completion.label.text.clone()) |
| 1288 | 1323 | .with_runs(text_runs_for_highlights( | |
| 1289 | // let completion_label = | 1324 | &completion.label.text, |
| 1290 | // Text::new(completion.label.text.clone(), style.text.clone()) | 1325 | &style.text, |
| 1291 | // .with_soft_wrap(false) | 1326 | highlights, |
| 1292 | // .with_highlights( | 1327 | )); |
| 1293 | // combine_syntax_and_fuzzy_match_highlights( | 1328 | let documentation_label = |
| 1294 | // &completion.label.text, | 1329 | if let Some(Documentation::SingleLine(text)) = documentation { |
| 1295 | // style.text.color.into(), | 1330 | Some(SharedString::from(text.clone())) |
| 1296 | // styled_runs_for_code_label( | 1331 | } else { |
| 1297 | // &completion.label, | 1332 | None |
| 1298 | // &style.syntax, | 1333 | }; |
| 1299 | // ), | 1334 | |
| 1300 | // &mat.positions, | 1335 | div() |
| 1301 | // ), | 1336 | .id(mat.candidate_id) |
| 1302 | // ); | 1337 | .min_w(px(220.)) |
| 1303 | 1338 | .max_w(px(540.)) | |
| 1304 | // if let Some(Documentation::SingleLine(text)) = documentation { | 1339 | .whitespace_nowrap() |
| 1305 | // Flex::row() | 1340 | .overflow_hidden() |
| 1306 | // .with_child(completion_label) | 1341 | .text_ui() |
| 1307 | // .with_children((|| { | 1342 | .px_1() |
| 1308 | // let text_style = TextStyle { | 1343 | .rounded(px(4.)) |
| 1309 | // color: style.autocomplete.inline_docs_color, | 1344 | .bg(cx.theme().colors().ghost_element_background) |
| 1310 | // font_size: style.text.font_size | 1345 | .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) |
| 1311 | // * style.autocomplete.inline_docs_size_percent, | 1346 | .when(item_ix == selected_item, |div| { |
| 1312 | // ..style.text.clone() | 1347 | div.bg(cx.theme().colors().ghost_element_selected) |
| 1313 | // }; | 1348 | }) |
| 1314 | 1349 | .on_mouse_down( | |
| 1315 | // let label = Text::new(text.clone(), text_style) | 1350 | MouseButton::Left, |
| 1316 | // .aligned() | 1351 | cx.listener(move |editor, event, cx| { |
| 1317 | // .constrained() | 1352 | cx.stop_propagation(); |
| 1318 | // .dynamically(move |constraint, _, _| { | 1353 | editor |
| 1319 | // gpui::SizeConstraint { | 1354 | .confirm_completion( |
| 1320 | // min: constraint.min, | 1355 | &ConfirmCompletion { |
| 1321 | // max: vec2f( | 1356 | item_ix: Some(item_ix), |
| 1322 | // constraint.max.x(), | 1357 | }, |
| 1323 | // constraint.min.y(), | 1358 | cx, |
| 1324 | // ), | 1359 | ) |
| 1325 | // } | 1360 | .map(|task| task.detach_and_log_err(cx)); |
| 1326 | // }); | 1361 | }), |
| 1327 | 1362 | ) | |
| 1328 | // if Some(item_ix) == widest_completion_ix { | 1363 | .child(completion_label) |
| 1329 | // Some( | 1364 | .children(documentation_label) |
| 1330 | // label | 1365 | }) |
| 1331 | // .contained() | 1366 | .collect() |
| 1332 | // .with_style( | 1367 | }, |
| 1333 | // style | 1368 | ) |
| 1334 | // .autocomplete | 1369 | .max_h(max_height) |
| 1335 | // .inline_docs_container, | 1370 | .track_scroll(self.scroll_handle.clone()) |
| 1336 | // ) | 1371 | .with_width_from_item(widest_completion_ix); |
| 1337 | // .into_any(), | 1372 | |
| 1338 | // ) | 1373 | Popover::new() |
| 1339 | // } else { | 1374 | .child(list) |
| 1340 | // Some(label.flex_float().into_any()) | 1375 | .when_some(multiline_docs, |popover, multiline_docs| { |
| 1341 | // } | 1376 | popover.aside(multiline_docs) |
| 1342 | // })()) | 1377 | }) |
| 1343 | // .into_any() | 1378 | .into_any_element() |
| 1344 | // } else { | 1379 | } |
| 1345 | // completion_label.into_any() | ||
| 1346 | // } | ||
| 1347 | // .contained() | ||
| 1348 | // .with_style(item_style) | ||
| 1349 | // .constrained() | ||
| 1350 | // .dynamically( | ||
| 1351 | // move |constraint, _, _| { | ||
| 1352 | // if Some(item_ix) == widest_completion_ix { | ||
| 1353 | // constraint | ||
| 1354 | // } else { | ||
| 1355 | // gpui::SizeConstraint { | ||
| 1356 | // min: constraint.min, | ||
| 1357 | // max: constraint.min, | ||
| 1358 | // } | ||
| 1359 | // } | ||
| 1360 | // }, | ||
| 1361 | // ) | ||
| 1362 | // }, | ||
| 1363 | // ) | ||
| 1364 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 1365 | // .on_down(MouseButton::Left, move |_, this, cx| { | ||
| 1366 | // this.confirm_completion( | ||
| 1367 | // &ConfirmCompletion { | ||
| 1368 | // item_ix: Some(item_ix), | ||
| 1369 | // }, | ||
| 1370 | // cx, | ||
| 1371 | // ) | ||
| 1372 | // .map(|task| task.detach()); | ||
| 1373 | // }) | ||
| 1374 | // .constrained() | ||
| 1375 | // .with_min_width(style.autocomplete.completion_min_width) | ||
| 1376 | // .with_max_width(style.autocomplete.completion_max_width) | ||
| 1377 | // .into_any(), | ||
| 1378 | // ); | ||
| 1379 | // } | ||
| 1380 | // } | ||
| 1381 | // }) | ||
| 1382 | // .with_width_from_item(widest_completion_ix); | ||
| 1383 | |||
| 1384 | // enum MultiLineDocumentation {} | ||
| 1385 | |||
| 1386 | // Flex::row() | ||
| 1387 | // .with_child(list.flex(1., false)) | ||
| 1388 | // .with_children({ | ||
| 1389 | // let mat = &self.matches[selected_item]; | ||
| 1390 | // let completions = self.completions.read(); | ||
| 1391 | // let completion = &completions[mat.candidate_id]; | ||
| 1392 | // let documentation = &completion.documentation; | ||
| 1393 | |||
| 1394 | // match documentation { | ||
| 1395 | // Some(Documentation::MultiLinePlainText(text)) => Some( | ||
| 1396 | // Flex::column() | ||
| 1397 | // .scrollable::<MultiLineDocumentation>(0, None, cx) | ||
| 1398 | // .with_child( | ||
| 1399 | // Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), | ||
| 1400 | // ) | ||
| 1401 | // .contained() | ||
| 1402 | // .with_style(style.autocomplete.alongside_docs_container) | ||
| 1403 | // .constrained() | ||
| 1404 | // .with_max_width(style.autocomplete.alongside_docs_max_width) | ||
| 1405 | // .flex(1., false), | ||
| 1406 | // ), | ||
| 1407 | |||
| 1408 | // Some(Documentation::MultiLineMarkdown(parsed)) => Some( | ||
| 1409 | // Flex::column() | ||
| 1410 | // .scrollable::<MultiLineDocumentation>(0, None, cx) | ||
| 1411 | // .with_child(render_parsed_markdown::<MultiLineDocumentation>( | ||
| 1412 | // parsed, &style, workspace, cx, | ||
| 1413 | // )) | ||
| 1414 | // .contained() | ||
| 1415 | // .with_style(style.autocomplete.alongside_docs_container) | ||
| 1416 | // .constrained() | ||
| 1417 | // .with_max_width(style.autocomplete.alongside_docs_max_width) | ||
| 1418 | // .flex(1., false), | ||
| 1419 | // ), | ||
| 1420 | |||
| 1421 | // _ => None, | ||
| 1422 | // } | ||
| 1423 | // }) | ||
| 1424 | // .contained() | ||
| 1425 | // .with_style(style.autocomplete.container) | ||
| 1426 | // .into_any() | ||
| 1427 | // } | ||
| 1428 | 1380 | ||
| 1429 | pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) { | 1381 | pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) { |
| 1430 | let mut matches = if let Some(query) = query { | 1382 | let mut matches = if let Some(query) = query { |
| @@ -1540,14 +1492,17 @@ impl CodeActionsMenu { · 109 unmodified lines | |||
| 1540 | &self, | 1492 | &self, |
| 1541 | mut cursor_position: DisplayPoint, | 1493 | mut cursor_position: DisplayPoint, |
| 1542 | style: &EditorStyle, | 1494 | style: &EditorStyle, |
| 1495 | max_height: Pixels, | ||
| 1543 | cx: &mut ViewContext<Editor>, | 1496 | cx: &mut ViewContext<Editor>, |
| 1544 | ) -> (DisplayPoint, AnyElement<Editor>) { | 1497 | ) -> (DisplayPoint, AnyElement) { |
| 1545 | let actions = self.actions.clone(); | 1498 | let actions = self.actions.clone(); |
| 1546 | let selected_item = self.selected_item; | 1499 | let selected_item = self.selected_item; |
| 1500 | |||
| 1547 | let element = uniform_list( | 1501 | let element = uniform_list( |
| 1502 | cx.view().clone(), | ||
| 1548 | "code_actions_menu", | 1503 | "code_actions_menu", |
| 1549 | self.actions.len(), | 1504 | self.actions.len(), |
| 1550 | move |editor, range, cx| { | 1505 | move |this, range, cx| { |
| 1551 | actions[range.clone()] | 1506 | actions[range.clone()] |
| 1552 | .iter() | 1507 | .iter() |
| 1553 | .enumerate() | 1508 | .enumerate() |
| @@ -1569,18 +1524,22 @@ impl CodeActionsMenu { · 15 unmodified lines | |||
| 1569 | .bg(colors.element_hover) | 1524 | .bg(colors.element_hover) |
| 1570 | .text_color(colors.text_accent) | 1525 | .text_color(colors.text_accent) |
| 1571 | }) | 1526 | }) |
| 1572 | .on_mouse_down(MouseButton::Left, move |editor: &mut Editor, _, cx| { | 1527 | .on_mouse_down( |
| 1573 | cx.stop_propagation(); | 1528 | MouseButton::Left, |
| 1574 | editor | 1529 | cx.listener(move |editor, _, cx| { |
| 1575 | .confirm_code_action( | 1530 | cx.stop_propagation(); |
| 1576 | &ConfirmCodeAction { | 1531 | editor |
| 1577 | item_ix: Some(item_ix), | 1532 | .confirm_code_action( |
| 1578 | }, | 1533 | &ConfirmCodeAction { |
| 1579 | cx, | 1534 | item_ix: Some(item_ix), |
| 1580 | ) | 1535 | }, |
| 1581 | .map(|task| task.detach_and_log_err(cx)); | 1536 | cx, |
| 1582 | }) | 1537 | ) |
| 1583 | .child(action.lsp_action.title.clone()) | 1538 | .map(|task| task.detach_and_log_err(cx)); |
| 1539 | }), | ||
| 1540 | ) | ||
| 1541 | // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here. | ||
| 1542 | .child(SharedString::from(action.lsp_action.title.clone())) | ||
| 1584 | }) | 1543 | }) |
| 1585 | .collect() | 1544 | .collect() |
| 1586 | }, | 1545 | }, |
| @@ -1588,6 +1547,8 @@ impl CodeActionsMenu { · 1 unmodified line | |||
| 1588 | .elevation_1(cx) | 1547 | .elevation_1(cx) |
| 1589 | .px_2() | 1548 | .px_2() |
| 1590 | .py_1() | 1549 | .py_1() |
| 1550 | .max_h(max_height) | ||
| 1551 | .track_scroll(self.scroll_handle.clone()) | ||
| 1591 | .with_width_from_item( | 1552 | .with_width_from_item( |
| 1592 | self.actions | 1553 | self.actions |
| 1593 | .iter() | 1554 | .iter() |
| @@ -1595,7 +1556,7 @@ impl CodeActionsMenu { · 1 unmodified line | |||
| 1595 | .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) | 1556 | .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) |
| 1596 | .map(|(ix, _)| ix), | 1557 | .map(|(ix, _)| ix), |
| 1597 | ) | 1558 | ) |
| 1598 | .render(); | 1559 | .into_any_element(); |
| 1599 | 1560 | ||
| 1600 | if self.deployed_from_indicator { | 1561 | if self.deployed_from_indicator { |
| 1601 | *cursor_position.column_mut() = 0; | 1562 | *cursor_position.column_mut() = 0; |
| @@ -1943,6 +1904,7 @@ impl Editor { · 341 unmodified lines | |||
| 1943 | pixel_position_of_newest_cursor: None, | 1904 | pixel_position_of_newest_cursor: None, |
| 1944 | gutter_width: Default::default(), | 1905 | gutter_width: Default::default(), |
| 1945 | style: None, | 1906 | style: None, |
| 1907 | editor_actions: Default::default(), | ||
| 1946 | _subscriptions: vec![ | 1908 | _subscriptions: vec![ |
| 1947 | cx.observe(&buffer, Self::on_buffer_changed), | 1909 | cx.observe(&buffer, Self::on_buffer_changed), |
| 1948 | cx.subscribe(&buffer, Self::on_buffer_event), | 1910 | cx.subscribe(&buffer, Self::on_buffer_event), |
| @@ -2074,10 +2036,14 @@ impl Editor { · 125 unmodified lines | |||
| 2074 | &self.buffer | 2036 | &self.buffer |
| 2075 | } | 2037 | } |
| 2076 | 2038 | ||
| 2077 | fn workspace(&self) -> Option<View<Workspace>> { | 2039 | pub fn workspace(&self) -> Option<View<Workspace>> { |
| 2078 | self.workspace.as_ref()?.0.upgrade() | 2040 | self.workspace.as_ref()?.0.upgrade() |
| 2079 | } | 2041 | } |
| 2080 | 2042 | ||
| 2043 | pub fn pane(&self, cx: &AppContext) -> Option<View<Pane>> { | ||
| 2044 | self.workspace()?.read(cx).pane_for(&self.handle.upgrade()?) | ||
| 2045 | } | ||
| 2046 | |||
| 2081 | pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> { | 2047 | pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> { |
| 2082 | self.buffer().read(cx).title(cx) | 2048 | self.buffer().read(cx).title(cx) |
| 2083 | } | 2049 | } |
| @@ -2319,6 +2285,7 @@ impl Editor { · 235 unmodified lines | |||
| 2319 | 2285 | ||
| 2320 | self.blink_manager.update(cx, BlinkManager::pause_blinking); | 2286 | self.blink_manager.update(cx, BlinkManager::pause_blinking); |
| 2321 | cx.emit(EditorEvent::SelectionsChanged { local }); | 2287 | cx.emit(EditorEvent::SelectionsChanged { local }); |
| 2288 | cx.emit(SearchEvent::MatchesInvalidated); | ||
| 2322 | 2289 | ||
| 2323 | if self.selections.disjoint_anchors().len() == 1 { | 2290 | if self.selections.disjoint_anchors().len() == 1 { |
| 2324 | cx.emit(SearchEvent::ActiveMatchChanged) | 2291 | cx.emit(SearchEvent::ActiveMatchChanged) |
| @@ -3437,7 +3404,7 @@ impl Editor { · 1112 unmodified lines | |||
| 3437 | to_insert, | 3404 | to_insert, |
| 3438 | }) = self.inlay_hint_cache.spawn_hint_refresh( | 3405 | }) = self.inlay_hint_cache.spawn_hint_refresh( |
| 3439 | reason_description, | 3406 | reason_description, |
| 3440 | self.excerpt_visible_offsets(required_languages.as_ref(), cx), | 3407 | self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx), |
| 3441 | invalidate_cache, | 3408 | invalidate_cache, |
| 3442 | cx, | 3409 | cx, |
| 3443 | ) { | 3410 | ) { |
| @@ -3456,11 +3423,15 @@ impl Editor { · 12 unmodified lines | |||
| 3456 | .collect() | 3423 | .collect() |
| 3457 | } | 3424 | } |
| 3458 | 3425 | ||
| 3459 | pub fn excerpt_visible_offsets( | 3426 | pub fn excerpts_for_inlay_hints_query( |
| 3460 | &self, | 3427 | &self, |
| 3461 | restrict_to_languages: Option<&HashSet<Arc<Language>>>, | 3428 | restrict_to_languages: Option<&HashSet<Arc<Language>>>, |
| 3462 | cx: &mut ViewContext<Editor>, | 3429 | cx: &mut ViewContext<Editor>, |
| 3463 | ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> { | 3430 | ) -> HashMap<ExcerptId, (Model<Buffer>, clock::Global, Range<usize>)> { |
| 3431 | let Some(project) = self.project.as_ref() else { | ||
| 3432 | return HashMap::default(); | ||
| 3433 | }; | ||
| 3434 | let project = project.read(cx); | ||
| 3464 | let multi_buffer = self.buffer().read(cx); | 3435 | let multi_buffer = self.buffer().read(cx); |
| 3465 | let multi_buffer_snapshot = multi_buffer.snapshot(cx); | 3436 | let multi_buffer_snapshot = multi_buffer.snapshot(cx); |
| 3466 | let multi_buffer_visible_start = self | 3437 | let multi_buffer_visible_start = self |
| @@ -3480,6 +3451,15 @@ impl Editor { · 13 unmodified lines | |||
| 3480 | .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) | 3451 | .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) |
| 3481 | .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| { | 3452 | .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| { |
| 3482 | let buffer = buffer_handle.read(cx); | 3453 | let buffer = buffer_handle.read(cx); |
| 3454 | let buffer_file = project::worktree::File::from_dyn(buffer.file())?; | ||
| 3455 | let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?; | ||
| 3456 | let worktree_entry = buffer_worktree | ||
| 3457 | .read(cx) | ||
| 3458 | .entry_for_id(buffer_file.project_entry_id(cx)?)?; | ||
| 3459 | if worktree_entry.is_ignored { | ||
| 3460 | return None; | ||
| 3461 | } | ||
| 3462 | |||
| 3483 | let language = buffer.language()?; | 3463 | let language = buffer.language()?; |
| 3484 | if let Some(restrict_to_languages) = restrict_to_languages { | 3464 | if let Some(restrict_to_languages) = restrict_to_languages { |
| 3485 | if !restrict_to_languages.contains(language) { | 3465 | if !restrict_to_languages.contains(language) { |
| @@ -3594,7 +3574,8 @@ impl Editor { · 108 unmodified lines | |||
| 3594 | let id = post_inc(&mut self.next_completion_id); | 3574 | let id = post_inc(&mut self.next_completion_id); |
| 3595 | let task = cx.spawn(|this, mut cx| { | 3575 | let task = cx.spawn(|this, mut cx| { |
| 3596 | async move { | 3576 | async move { |
| 3597 | let menu = if let Some(completions) = completions.await.log_err() { | 3577 | let completions = completions.await.log_err(); |
| 3578 | let (menu, pre_resolve_task) = if let Some(completions) = completions { | ||
| 3598 | let mut menu = CompletionsMenu { | 3579 | let mut menu = CompletionsMenu { |
| 3599 | id, | 3580 | id, |
| 3600 | initial_position: position, | 3581 | initial_position: position, |
| @@ -3617,20 +3598,24 @@ impl Editor { · 16 unmodified lines | |||
| 3617 | }; | 3598 | }; |
| 3618 | menu.filter(query.as_deref(), cx.background_executor().clone()) | 3599 | menu.filter(query.as_deref(), cx.background_executor().clone()) |
| 3619 | .await; | 3600 | .await; |
| 3601 | |||
| 3620 | if menu.matches.is_empty() { | 3602 | if menu.matches.is_empty() { |
| 3621 | None | 3603 | (None, None) |
| 3622 | } else { | 3604 | } else { |
| 3623 | _ = this.update(&mut cx, |editor, cx| { | 3605 | let pre_resolve_task = this |
| 3624 | menu.pre_resolve_completion_documentation(editor.project.clone(), cx); | 3606 | .update(&mut cx, |editor, cx| { |
| 3625 | }); | 3607 | menu.pre_resolve_completion_documentation(editor, cx) |
| 3626 | Some(menu) | 3608 | }) |
| 3609 | .ok() | ||
| 3610 | .flatten(); | ||
| 3611 | (Some(menu), pre_resolve_task) | ||
| 3627 | } | 3612 | } |
| 3628 | } else { | 3613 | } else { |
| 3629 | None | 3614 | (None, None) |
| 3630 | }; | 3615 | }; |
| 3631 | 3616 | ||
| 3632 | this.update(&mut cx, |this, cx| { | 3617 | this.update(&mut cx, |this, cx| { |
| 3633 | this.completion_tasks.retain(|(task_id, _)| *task_id > id); | 3618 | this.completion_tasks.retain(|(task_id, _)| *task_id >= id); |
| 3634 | 3619 | ||
| 3635 | let mut context_menu = this.context_menu.write(); | 3620 | let mut context_menu = this.context_menu.write(); |
| 3636 | match context_menu.as_ref() { | 3621 | match context_menu.as_ref() { |
| @@ -3662,142 +3647,147 @@ impl Editor { · 25 unmodified lines | |||
| 3662 | } | 3647 | } |
| 3663 | })?; | 3648 | })?; |
| 3664 | 3649 | ||
| 3650 | if let Some(pre_resolve_task) = pre_resolve_task { | ||
| 3651 | pre_resolve_task.await; | ||
| 3652 | } | ||
| 3653 | |||
| 3665 | Ok::<_, anyhow::Error>(()) | 3654 | Ok::<_, anyhow::Error>(()) |
| 3666 | } | 3655 | } |
| 3667 | .log_err() | 3656 | .log_err() |
| 3668 | }); | 3657 | }); |
| 3658 | |||
| 3669 | self.completion_tasks.push((id, task)); | 3659 | self.completion_tasks.push((id, task)); |
| 3670 | } | 3660 | } |
| 3671 | 3661 | ||
| 3672 | // pub fn confirm_completion( | 3662 | pub fn confirm_completion( |
| 3673 | // &mut self, | 3663 | &mut self, |
| 3674 | // action: &ConfirmCompletion, | 3664 | action: &ConfirmCompletion, |
| 3675 | // cx: &mut ViewContext<Self>, | 3665 | cx: &mut ViewContext<Self>, |
| 3676 | // ) -> Option<Task<Result<()>>> { | 3666 | ) -> Option<Task<Result<()>>> { |
| 3677 | // use language::ToOffset as _; | 3667 | use language::ToOffset as _; |
| 3678 | 3668 | ||
| 3679 | // let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? { | 3669 | let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? { |
| 3680 | // menu | 3670 | menu |
| 3681 | // } else { | 3671 | } else { |
| 3682 | // return None; | 3672 | return None; |
| 3683 | // }; | 3673 | }; |
| 3684 | 3674 | ||
| 3685 | // let mat = completions_menu | 3675 | let mat = completions_menu |
| 3686 | // .matches | 3676 | .matches |
| 3687 | // .get(action.item_ix.unwrap_or(completions_menu.selected_item))?; | 3677 | .get(action.item_ix.unwrap_or(completions_menu.selected_item))?; |
| 3688 | // let buffer_handle = completions_menu.buffer; | 3678 | let buffer_handle = completions_menu.buffer; |
| 3689 | // let completions = completions_menu.completions.read(); | 3679 | let completions = completions_menu.completions.read(); |
| 3690 | // let completion = completions.get(mat.candidate_id)?; | 3680 | let completion = completions.get(mat.candidate_id)?; |
| 3691 | 3681 | ||
| 3692 | // let snippet; | 3682 | let snippet; |
| 3693 | // let text; | 3683 | let text; |
| 3694 | // if completion.is_snippet() { | 3684 | if completion.is_snippet() { |
| 3695 | // snippet = Some(Snippet::parse(&completion.new_text).log_err()?); | 3685 | snippet = Some(Snippet::parse(&completion.new_text).log_err()?); |
| 3696 | // text = snippet.as_ref().unwrap().text.clone(); | 3686 | text = snippet.as_ref().unwrap().text.clone(); |
| 3697 | // } else { | 3687 | } else { |
| 3698 | // snippet = None; | 3688 | snippet = None; |
| 3699 | // text = completion.new_text.clone(); | 3689 | text = completion.new_text.clone(); |
| 3700 | // }; | 3690 | }; |
| 3701 | // let selections = self.selections.all::<usize>(cx); | 3691 | let selections = self.selections.all::<usize>(cx); |
| 3702 | // let buffer = buffer_handle.read(cx); | 3692 | let buffer = buffer_handle.read(cx); |
| 3703 | // let old_range = completion.old_range.to_offset(buffer); | 3693 | let old_range = completion.old_range.to_offset(buffer); |
| 3704 | // let old_text = buffer.text_for_range(old_range.clone()).collect::<String>(); | 3694 | let old_text = buffer.text_for_range(old_range.clone()).collect::<String>(); |
| 3705 | |||
| 3706 | // let newest_selection = self.selections.newest_anchor(); | ||
| 3707 | // if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) { | ||
| 3708 | // return None; | ||
| 3709 | // } | ||
| 3710 | 3695 | ||
| 3711 | // let lookbehind = newest_selection | 3696 | let newest_selection = self.selections.newest_anchor(); |
| 3712 | // .start | 3697 | if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) { |
| 3713 | // .text_anchor | 3698 | return None; |
| 3714 | // .to_offset(buffer) | 3699 | } |
| 3715 | // .saturating_sub(old_range.start); | ||
| 3716 | // let lookahead = old_range | ||
| 3717 | // .end | ||
| 3718 | // .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer)); | ||
| 3719 | // let mut common_prefix_len = old_text | ||
| 3720 | // .bytes() | ||
| 3721 | // .zip(text.bytes()) | ||
| 3722 | // .take_while(|(a, b)| a == b) | ||
| 3723 | // .count(); | ||
| 3724 | |||
| 3725 | // let snapshot = self.buffer.read(cx).snapshot(cx); | ||
| 3726 | // let mut range_to_replace: Option<Range<isize>> = None; | ||
| 3727 | // let mut ranges = Vec::new(); | ||
| 3728 | // for selection in &selections { | ||
| 3729 | // if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) { | ||
| 3730 | // let start = selection.start.saturating_sub(lookbehind); | ||
| 3731 | // let end = selection.end + lookahead; | ||
| 3732 | // if selection.id == newest_selection.id { | ||
| 3733 | // range_to_replace = Some( | ||
| 3734 | // ((start + common_prefix_len) as isize - selection.start as isize) | ||
| 3735 | // ..(end as isize - selection.start as isize), | ||
| 3736 | // ); | ||
| 3737 | // } | ||
| 3738 | // ranges.push(start + common_prefix_len..end); | ||
| 3739 | // } else { | ||
| 3740 | // common_prefix_len = 0; | ||
| 3741 | // ranges.clear(); | ||
| 3742 | // ranges.extend(selections.iter().map(|s| { | ||
| 3743 | // if s.id == newest_selection.id { | ||
| 3744 | // range_to_replace = Some( | ||
| 3745 | // old_range.start.to_offset_utf16(&snapshot).0 as isize | ||
| 3746 | // - selection.start as isize | ||
| 3747 | // ..old_range.end.to_offset_utf16(&snapshot).0 as isize | ||
| 3748 | // - selection.start as isize, | ||
| 3749 | // ); | ||
| 3750 | // old_range.clone() | ||
| 3751 | // } else { | ||
| 3752 | // s.start..s.end | ||
| 3753 | // } | ||
| 3754 | // })); | ||
| 3755 | // break; | ||
| 3756 | // } | ||
| 3757 | // } | ||
| 3758 | // let text = &text[common_prefix_len..]; | ||
| 3759 | 3700 | ||
| 3760 | // cx.emit(Event::InputHandled { | 3701 | let lookbehind = newest_selection |
| 3761 | // utf16_range_to_replace: range_to_replace, | 3702 | .start |
| 3762 | // text: text.into(), | 3703 | .text_anchor |
| 3763 | // }); | 3704 | .to_offset(buffer) |
| 3705 | .saturating_sub(old_range.start); | ||
| 3706 | let lookahead = old_range | ||
| 3707 | .end | ||
| 3708 | .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer)); | ||
| 3709 | let mut common_prefix_len = old_text | ||
| 3710 | .bytes() | ||
| 3711 | .zip(text.bytes()) | ||
| 3712 | .take_while(|(a, b)| a == b) | ||
| 3713 | .count(); | ||
| 3764 | 3714 | ||
| 3765 | // self.transact(cx, |this, cx| { | 3715 | let snapshot = self.buffer.read(cx).snapshot(cx); |
| 3766 | // if let Some(mut snippet) = snippet { | 3716 | let mut range_to_replace: Option<Range<isize>> = None; |
| 3767 | // snippet.text = text.to_string(); | 3717 | let mut ranges = Vec::new(); |
| 3768 | // for tabstop in snippet.tabstops.iter_mut().flatten() { | 3718 | for selection in &selections { |
| 3769 | // tabstop.start -= common_prefix_len as isize; | 3719 | if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) { |
| 3770 | // tabstop.end -= common_prefix_len as isize; | 3720 | let start = selection.start.saturating_sub(lookbehind); |
| 3771 | // } | 3721 | let end = selection.end + lookahead; |
| 3722 | if selection.id == newest_selection.id { | ||
| 3723 | range_to_replace = Some( | ||
| 3724 | ((start + common_prefix_len) as isize - selection.start as isize) | ||
| 3725 | ..(end as isize - selection.start as isize), | ||
| 3726 | ); | ||
| 3727 | } | ||
| 3728 | ranges.push(start + common_prefix_len..end); | ||
| 3729 | } else { | ||
| 3730 | common_prefix_len = 0; | ||
| 3731 | ranges.clear(); | ||
| 3732 | ranges.extend(selections.iter().map(|s| { | ||
| 3733 | if s.id == newest_selection.id { | ||
| 3734 | range_to_replace = Some( | ||
| 3735 | old_range.start.to_offset_utf16(&snapshot).0 as isize | ||
| 3736 | - selection.start as isize | ||
| 3737 | ..old_range.end.to_offset_utf16(&snapshot).0 as isize | ||
| 3738 | - selection.start as isize, | ||
| 3739 | ); | ||
| 3740 | old_range.clone() | ||
| 3741 | } else { | ||
| 3742 | s.start..s.end | ||
| 3743 | } | ||
| 3744 | })); | ||
| 3745 | break; | ||
| 3746 | } | ||
| 3747 | } | ||
| 3748 | let text = &text[common_prefix_len..]; | ||
| 3772 | 3749 | ||
| 3773 | // this.insert_snippet(&ranges, snippet, cx).log_err(); | 3750 | cx.emit(EditorEvent::InputHandled { |
| 3774 | // } else { | 3751 | utf16_range_to_replace: range_to_replace, |
| 3775 | // this.buffer.update(cx, |buffer, cx| { | 3752 | text: text.into(), |
| 3776 | // buffer.edit( | 3753 | }); |
| 3777 | // ranges.iter().map(|range| (range.clone(), text)), | ||
| 3778 | // this.autoindent_mode.clone(), | ||
| 3779 | // cx, | ||
| 3780 | // ); | ||
| 3781 | // }); | ||
| 3782 | // } | ||
| 3783 | 3754 | ||
| 3784 | // this.refresh_copilot_suggestions(true, cx); | 3755 | self.transact(cx, |this, cx| { |
| 3785 | // }); | 3756 | if let Some(mut snippet) = snippet { |
| 3757 | snippet.text = text.to_string(); | ||
| 3758 | for tabstop in snippet.tabstops.iter_mut().flatten() { | ||
| 3759 | tabstop.start -= common_prefix_len as isize; | ||
| 3760 | tabstop.end -= common_prefix_len as isize; | ||
| 3761 | } | ||
| 3786 | 3762 | ||
| 3787 | // let project = self.project.clone()?; | 3763 | this.insert_snippet(&ranges, snippet, cx).log_err(); |
| 3788 | // let apply_edits = project.update(cx, |project, cx| { | 3764 | } else { |
| 3789 | // project.apply_additional_edits_for_completion( | 3765 | this.buffer.update(cx, |buffer, cx| { |
| 3790 | // buffer_handle, | 3766 | buffer.edit( |
| 3791 | // completion.clone(), | 3767 | ranges.iter().map(|range| (range.clone(), text)), |
| 3792 | // true, | 3768 | this.autoindent_mode.clone(), |
| 3793 | // cx, | 3769 | cx, |
| 3794 | // ) | 3770 | ); |
| 3795 | // }); | 3771 | }); |
| 3796 | // Some(cx.foreground().spawn(async move { | 3772 | } |
| 3797 | // apply_edits.await?; | 3773 | |
| 3798 | // Ok(()) | 3774 | this.refresh_copilot_suggestions(true, cx); |
| 3799 | // })) | 3775 | }); |
| 3800 | // } | 3776 | |
| 3777 | let project = self.project.clone()?; | ||
| 3778 | let apply_edits = project.update(cx, |project, cx| { | ||
| 3779 | project.apply_additional_edits_for_completion( | ||
| 3780 | buffer_handle, | ||
| 3781 | completion.clone(), | ||
| 3782 | true, | ||
| 3783 | cx, | ||
| 3784 | ) | ||
| 3785 | }); | ||
| 3786 | Some(cx.foreground_executor().spawn(async move { | ||
| 3787 | apply_edits.await?; | ||
| 3788 | Ok(()) | ||
| 3789 | })) | ||
| 3790 | } | ||
| 3801 | 3791 | ||
| 3802 | pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) { | 3792 | pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) { |
| 3803 | let mut context_menu = self.context_menu.write(); | 3793 | let mut context_menu = self.context_menu.write(); |
| @@ -4353,19 +4343,19 @@ impl Editor { · 549 unmodified lines | |||
| 4353 | style: &EditorStyle, | 4343 | style: &EditorStyle, |
| 4354 | is_active: bool, | 4344 | is_active: bool, |
| 4355 | cx: &mut ViewContext<Self>, | 4345 | cx: &mut ViewContext<Self>, |
| 4356 | ) -> Option<AnyElement<Self>> { | 4346 | ) -> Option<IconButton> { |
| 4357 | if self.available_code_actions.is_some() { | 4347 | if self.available_code_actions.is_some() { |
| 4358 | Some( | 4348 | Some( |
| 4359 | IconButton::new("code_actions_indicator", ui::Icon::Bolt) | 4349 | IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(cx.listener( |
| 4360 | .on_click(|editor: &mut Editor, cx| { | 4350 | |editor, e, cx| { |
| 4361 | editor.toggle_code_actions( | 4351 | editor.toggle_code_actions( |
| 4362 | &ToggleCodeActions { | 4352 | &ToggleCodeActions { |
| 4363 | deployed_from_indicator: true, | 4353 | deployed_from_indicator: true, |
| 4364 | }, | 4354 | }, |
| 4365 | cx, | 4355 | cx, |
| 4366 | ); | 4356 | ); |
| 4367 | }) | 4357 | }, |
| 4368 | .render(), | 4358 | )), |
| 4369 | ) | 4359 | ) |
| 4370 | } else { | 4360 | } else { |
| 4371 | None | 4361 | None |
| @@ -4380,7 +4370,7 @@ impl Editor { · 8 unmodified lines | |||
| 4380 | line_height: Pixels, | 4370 | line_height: Pixels, |
| 4381 | gutter_margin: Pixels, | 4371 | gutter_margin: Pixels, |
| 4382 | cx: &mut ViewContext<Self>, | 4372 | cx: &mut ViewContext<Self>, |
| 4383 | ) -> Vec<Option<AnyElement<Self>>> { | 4373 | ) -> Vec<Option<IconButton>> { |
| 4384 | fold_data | 4374 | fold_data |
| 4385 | .iter() | 4375 | .iter() |
| 4386 | .enumerate() | 4376 | .enumerate() |
| @@ -4393,16 +4383,15 @@ impl Editor { · 6 unmodified lines | |||
| 4393 | FoldStatus::Foldable => ui::Icon::ChevronDown, | 4383 | FoldStatus::Foldable => ui::Icon::ChevronDown, |
| 4394 | }; | 4384 | }; |
| 4395 | IconButton::new(ix as usize, icon) | 4385 | IconButton::new(ix as usize, icon) |
| 4396 | .on_click(move |editor: &mut Editor, cx| match fold_status { | 4386 | .on_click(cx.listener(move |editor, e, cx| match fold_status { |
| 4397 | FoldStatus::Folded => { | 4387 | FoldStatus::Folded => { |
| 4398 | editor.unfold_at(&UnfoldAt { buffer_row }, cx); | 4388 | editor.unfold_at(&UnfoldAt { buffer_row }, cx); |
| 4399 | } | 4389 | } |
| 4400 | FoldStatus::Foldable => { | 4390 | FoldStatus::Foldable => { |
| 4401 | editor.fold_at(&FoldAt { buffer_row }, cx); | 4391 | editor.fold_at(&FoldAt { buffer_row }, cx); |
| 4402 | } | 4392 | } |
| 4403 | }) | 4393 | })) |
| 4404 | .color(ui::TextColor::Muted) | 4394 | .color(ui::Color::Muted) |
| 4405 | .render() | ||
| 4406 | }) | 4395 | }) |
| 4407 | }) | 4396 | }) |
| 4408 | .flatten() | 4397 | .flatten() |
| @@ -4421,12 +4410,14 @@ impl Editor { · 12 unmodified lines | |||
| 4421 | &self, | 4410 | &self, |
| 4422 | cursor_position: DisplayPoint, | 4411 | cursor_position: DisplayPoint, |
| 4423 | style: &EditorStyle, | 4412 | style: &EditorStyle, |
| 4413 | max_height: Pixels, | ||
| 4424 | cx: &mut ViewContext<Editor>, | 4414 | cx: &mut ViewContext<Editor>, |
| 4425 | ) -> Option<(DisplayPoint, AnyElement<Editor>)> { | 4415 | ) -> Option<(DisplayPoint, AnyElement)> { |
| 4426 | self.context_menu.read().as_ref().map(|menu| { | 4416 | self.context_menu.read().as_ref().map(|menu| { |
| 4427 | menu.render( | 4417 | menu.render( |
| 4428 | cursor_position, | 4418 | cursor_position, |
| 4429 | style, | 4419 | style, |
| 4420 | max_height, | ||
| 4430 | self.workspace.as_ref().map(|(w, _)| w.clone()), | 4421 | self.workspace.as_ref().map(|(w, _)| w.clone()), |
| 4431 | cx, | 4422 | cx, |
| 4432 | ) | 4423 | ) |
| @@ -7781,7 +7772,7 @@ impl Editor { · 3348 unmodified lines | |||
| 7781 | } | 7772 | } |
| 7782 | div() | 7773 | div() |
| 7783 | .pl(cx.anchor_x) | 7774 | .pl(cx.anchor_x) |
| 7784 | .child(rename_editor.render_with(EditorElement::new( | 7775 | .child(EditorElement::new( |
| 7785 | &rename_editor, | 7776 | &rename_editor, |
| 7786 | EditorStyle { | 7777 | EditorStyle { |
| 7787 | background: cx.theme().system().transparent, | 7778 | background: cx.theme().system().transparent, |
| @@ -7789,11 +7780,13 @@ impl Editor { · 1 unmodified line | |||
| 7789 | text: text_style, | 7780 | text: text_style, |
| 7790 | scrollbar_width: cx.editor_style.scrollbar_width, | 7781 | scrollbar_width: cx.editor_style.scrollbar_width, |
| 7791 | syntax: cx.editor_style.syntax.clone(), | 7782 | syntax: cx.editor_style.syntax.clone(), |
| 7792 | diagnostic_style: | 7783 | diagnostic_style: cx |
| 7793 | cx.editor_style.diagnostic_style.clone(), | 7784 | .editor_style |
| 7785 | .diagnostic_style | ||
| 7786 | .clone(), | ||
| 7794 | }, | 7787 | }, |
| 7795 | ))) | 7788 | )) |
| 7796 | .render() | 7789 | .into_any_element() |
| 7797 | } | 7790 | } |
| 7798 | }), | 7791 | }), |
| 7799 | disposition: BlockDisposition::Below, | 7792 | disposition: BlockDisposition::Below, |
| @@ -9207,6 +9200,26 @@ impl Editor { · 1407 unmodified lines | |||
| 9207 | cx.emit(EditorEvent::Blurred); | 9200 | cx.emit(EditorEvent::Blurred); |
| 9208 | cx.notify(); | 9201 | cx.notify(); |
| 9209 | } | 9202 | } |
| 9203 | |||
| 9204 | pub fn register_action<A: Action>( | ||
| 9205 | &mut self, | ||
| 9206 | listener: impl Fn(&A, &mut WindowContext) + 'static, | ||
| 9207 | ) -> &mut Self { | ||
| 9208 | let listener = Arc::new(listener); | ||
| 9209 | |||
| 9210 | self.editor_actions.push(Box::new(move |cx| { | ||
| 9211 | let view = cx.view().clone(); | ||
| 9212 | let cx = cx.window_context(); | ||
| 9213 | let listener = listener.clone(); | ||
| 9214 | cx.on_action(TypeId::of::<A>(), move |action, phase, cx| { | ||
| 9215 | let action = action.downcast_ref().unwrap(); | ||
| 9216 | if phase == DispatchPhase::Bubble { | ||
| 9217 | listener(action, cx) | ||
| 9218 | } | ||
| 9219 | }) | ||
| 9220 | })); | ||
| 9221 | self | ||
| 9222 | } | ||
| 9210 | } | 9223 | } |
| 9211 | 9224 | ||
| 9212 | pub trait CollaborationHub { | 9225 | pub trait CollaborationHub { |
| @@ -9396,7 +9409,9 @@ impl Render for Editor { · 183 unmodified lines | |||
| 9396 | font_weight: FontWeight::NORMAL, | 9409 | font_weight: FontWeight::NORMAL, |
| 9397 | font_style: FontStyle::Normal, | 9410 | font_style: FontStyle::Normal, |
| 9398 | line_height: relative(1.).into(), | 9411 | line_height: relative(1.).into(), |
| 9412 | background_color: None, | ||
| 9399 | underline: None, | 9413 | underline: None, |
| 9414 | white_space: WhiteSpace::Normal, | ||
| 9400 | }, | 9415 | }, |
| 9401 | 9416 | ||
| 9402 | EditorMode::AutoHeight { max_lines } => todo!(), | 9417 | EditorMode::AutoHeight { max_lines } => todo!(), |
| @@ -9409,7 +9424,9 @@ impl Render for Editor { · 6 unmodified lines | |||
| 9409 | font_weight: FontWeight::NORMAL, | 9424 | font_weight: FontWeight::NORMAL, |
| 9410 | font_style: FontStyle::Normal, | 9425 | font_style: FontStyle::Normal, |
| 9411 | line_height: relative(settings.buffer_line_height.value()), | 9426 | line_height: relative(settings.buffer_line_height.value()), |
| 9427 | background_color: None, | ||
| 9412 | underline: None, | 9428 | underline: None, |
| 9429 | white_space: WhiteSpace::Normal, | ||
| 9413 | }, | 9430 | }, |
| 9414 | }; | 9431 | }; |
| 9415 | 9432 | ||
| @@ -9996,11 +10013,11 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend · 580 unmodified lines | |||
| 9996 | .ml(cx.anchor_x) | 10013 | .ml(cx.anchor_x) |
| 9997 | })) | 10014 | })) |
| 9998 | .cursor_pointer() | 10015 | .cursor_pointer() |
| 9999 | .on_click(move |_, _, cx| { | 10016 | .on_click(cx.listener(move |_, _, cx| { |
| 10000 | cx.write_to_clipboard(ClipboardItem::new(message.clone())); | 10017 | cx.write_to_clipboard(ClipboardItem::new(message.clone())); |
| 10001 | }) | 10018 | })) |
| 10002 | .tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx)) | 10019 | .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)) |
| 10003 | .render() | 10020 | .into_any_element() |
| 10004 | }) | 10021 | }) |
| 10005 | } | 10022 | } |
| 10006 | 10023 | ||
| @@ -10047,120 +10064,75 @@ pub fn diagnostic_style( · 40 unmodified lines | |||
| 10047 | } | 10064 | } |
| 10048 | } | 10065 | } |
| 10049 | 10066 | ||
| 10050 | pub fn combine_syntax_and_fuzzy_match_highlights( | 10067 | pub fn text_runs_for_highlights( |
| 10051 | text: &str, | 10068 | text: &str, |
| 10052 | default_style: HighlightStyle, | 10069 | default_style: &TextStyle, |
| 10053 | syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>, | 10070 | highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>, |
| 10054 | match_indices: &[usize], | 10071 | ) -> Vec<TextRun> { |
| 10055 | ) -> Vec<(Range<usize>, HighlightStyle)> { | 10072 | let mut runs = Vec::new(); |
| 10056 | let mut result = Vec::new(); | 10073 | let mut ix = 0; |
| 10057 | let mut match_indices = match_indices.iter().copied().peekable(); | 10074 | for (range, highlight) in highlights { |
| 10058 | 10075 | if ix < range.start { | |
| 10059 | for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())]) | 10076 | runs.push(default_style.clone().to_run(range.start - ix)); |
| 10060 | { | 10077 | } |
| 10061 | syntax_highlight.font_weight = None; | 10078 | runs.push( |
| 10062 | 10079 | default_style | |
| 10063 | // Add highlights for any fuzzy match characters before the next | 10080 | .clone() |
| 10064 | // syntax highlight range. | 10081 | .highlight(highlight) |
| 10065 | while let Some(&match_index) = match_indices.peek() { | 10082 | .to_run(range.len()), |
| 10066 | if match_index >= range.start { | 10083 | ); |
| 10067 | break; | 10084 | ix = range.end; |
| 10068 | } | ||
| 10069 | match_indices.next(); | ||
| 10070 | let end_index = char_ix_after(match_index, text); | ||
| 10071 | let mut match_style = default_style; | ||
| 10072 | match_style.font_weight = Some(FontWeight::BOLD); | ||
| 10073 | result.push((match_index..end_index, match_style)); | ||
| 10074 | } | ||
| 10075 | |||
| 10076 | if range.start == usize::MAX { | ||
| 10077 | break; | ||
| 10078 | } | ||
| 10079 | |||
| 10080 | // Add highlights for any fuzzy match characters within the | ||
| 10081 | // syntax highlight range. | ||
| 10082 | let mut offset = range.start; | ||
| 10083 | while let Some(&match_index) = match_indices.peek() { | ||
| 10084 | if match_index >= range.end { | ||
| 10085 | break; | ||
| 10086 | } | ||
| 10087 | |||
| 10088 | match_indices.next(); | ||
| 10089 | if match_index > offset { | ||
| 10090 | result.push((offset..match_index, syntax_highlight)); | ||
| 10091 | } | ||
| 10092 | |||
| 10093 | let mut end_index = char_ix_after(match_index, text); | ||
| 10094 | while let Some(&next_match_index) = match_indices.peek() { | ||
| 10095 | if next_match_index == end_index && next_match_index < range.end { | ||
| 10096 | end_index = char_ix_after(next_match_index, text); | ||
| 10097 | match_indices.next(); | ||
| 10098 | } else { | ||
| 10099 | break; | ||
| 10100 | } | ||
| 10101 | } | ||
| 10102 | |||
| 10103 | let mut match_style = syntax_highlight; | ||
| 10104 | match_style.font_weight = Some(FontWeight::BOLD); | ||
| 10105 | result.push((match_index..end_index, match_style)); | ||
| 10106 | offset = end_index; | ||
| 10107 | } | ||
| 10108 | |||
| 10109 | if offset < range.end { | ||
| 10110 | result.push((offset..range.end, syntax_highlight)); | ||
| 10111 | } | ||
| 10112 | } | 10085 | } |
| 10113 | 10086 | if ix < text.len() { | |
| 10114 | fn char_ix_after(ix: usize, text: &str) -> usize { | 10087 | runs.push(default_style.to_run(text.len() - ix)); |
| 10115 | ix + text[ix..].chars().next().unwrap().len_utf8() | ||
| 10116 | } | 10088 | } |
| 10117 | 10089 | runs | |
| 10118 | result | ||
| 10119 | } | 10090 | } |
| 10120 | 10091 | ||
| 10121 | // pub fn styled_runs_for_code_label<'a>( | 10092 | pub fn styled_runs_for_code_label<'a>( |
| 10122 | // label: &'a CodeLabel, | 10093 | label: &'a CodeLabel, |
| 10123 | // syntax_theme: &'a theme::SyntaxTheme, | 10094 | syntax_theme: &'a theme::SyntaxTheme, |
| 10124 | // ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> { | 10095 | ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> { |
| 10125 | // let fade_out = HighlightStyle { | 10096 | let fade_out = HighlightStyle { |
| 10126 | // fade_out: Some(0.35), | 10097 | fade_out: Some(0.35), |
| 10127 | // ..Default::default() | 10098 | ..Default::default() |
| 10128 | // }; | 10099 | }; |
| 10100 | |||
| 10101 | let mut prev_end = label.filter_range.end; | ||
| 10102 | label | ||
| 10103 | .runs | ||
| 10104 | .iter() | ||
| 10105 | .enumerate() | ||
| 10106 | .flat_map(move |(ix, (range, highlight_id))| { | ||
| 10107 | let style = if let Some(style) = highlight_id.style(syntax_theme) { | ||
| 10108 | style | ||
| 10109 | } else { | ||
| 10110 | return Default::default(); | ||
| 10111 | }; | ||
| 10112 | let mut muted_style = style; | ||
| 10113 | muted_style.highlight(fade_out); | ||
| 10129 | 10114 | ||
| 10130 | // let mut prev_end = label.filter_range.end; | 10115 | let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new(); |
| 10131 | // label | 10116 | if range.start >= label.filter_range.end { |
| 10132 | // .runs | 10117 | if range.start > prev_end { |
| 10133 | // .iter() | 10118 | runs.push((prev_end..range.start, fade_out)); |
| 10134 | // .enumerate() | 10119 | } |
| 10135 | // .flat_map(move |(ix, (range, highlight_id))| { | 10120 | runs.push((range.clone(), muted_style)); |
| 10136 | // let style = if let Some(style) = highlight_id.style(syntax_theme) { | 10121 | } else if range.end <= label.filter_range.end { |
| 10137 | // style | 10122 | runs.push((range.clone(), style)); |
| 10138 | // } else { | 10123 | } else { |
| 10139 | // return Default::default(); | 10124 | runs.push((range.start..label.filter_range.end, style)); |
| 10140 | // }; | 10125 | runs.push((label.filter_range.end..range.end, muted_style)); |
| 10141 | // let mut muted_style = style; | 10126 | } |
| 10142 | // muted_style.highlight(fade_out); | 10127 | prev_end = cmp::max(prev_end, range.end); |
| 10143 | |||
| 10144 | // let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new(); | ||
| 10145 | // if range.start >= label.filter_range.end { | ||
| 10146 | // if range.start > prev_end { | ||
| 10147 | // runs.push((prev_end..range.start, fade_out)); | ||
| 10148 | // } | ||
| 10149 | // runs.push((range.clone(), muted_style)); | ||
| 10150 | // } else if range.end <= label.filter_range.end { | ||
| 10151 | // runs.push((range.clone(), style)); | ||
| 10152 | // } else { | ||
| 10153 | // runs.push((range.start..label.filter_range.end, style)); | ||
| 10154 | // runs.push((label.filter_range.end..range.end, muted_style)); | ||
| 10155 | // } | ||
| 10156 | // prev_end = cmp::max(prev_end, range.end); | ||
| 10157 | 10128 | ||
| 10158 | // if ix + 1 == label.runs.len() && label.text.len() > prev_end { | 10129 | if ix + 1 == label.runs.len() && label.text.len() > prev_end { |
| 10159 | // runs.push((prev_end..label.text.len(), fade_out)); | 10130 | runs.push((prev_end..label.text.len(), fade_out)); |
| 10160 | // } | 10131 | } |
| 10161 | 10132 | ||
| 10162 | // runs | 10133 | runs |
| 10163 | // }) | 10134 | }) |
| 10135 | } | ||
| 10164 | 10136 | ||
| 10165 | pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a { | 10137 | pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a { |
| 10166 | let mut index = 0; | 10138 | let mut index = 0; |
| @@ -3048,7 +3048,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { · 3047 unmodified lines | |||
| 3048 | position: snapshot.anchor_after(Point::new(2, 0)), | 3048 | position: snapshot.anchor_after(Point::new(2, 0)), |
| 3049 | disposition: BlockDisposition::Below, | 3049 | disposition: BlockDisposition::Below, |
| 3050 | height: 1, | 3050 | height: 1, |
| 3051 | render: Arc::new(|_| div().render()), | 3051 | render: Arc::new(|_| div().into_any()), |
| 3052 | }], | 3052 | }], |
| 3053 | Some(Autoscroll::fit()), | 3053 | Some(Autoscroll::fit()), |
| 3054 | cx, | 3054 | cx, |
| @@ -6740,75 +6740,6 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { · 3685 unmodified lines | |||
| 6740 | // ); | 6740 | // ); |
| 6741 | // } | 6741 | // } |
| 6742 | 6742 | ||
| 6743 | #[test] | ||
| 6744 | fn test_combine_syntax_and_fuzzy_match_highlights() { | ||
| 6745 | let string = "abcdefghijklmnop"; | ||
| 6746 | let syntax_ranges = [ | ||
| 6747 | ( | ||
| 6748 | 0..3, | ||
| 6749 | HighlightStyle { | ||
| 6750 | color: Some(Hsla::red()), | ||
| 6751 | ..Default::default() | ||
| 6752 | }, | ||
| 6753 | ), | ||
| 6754 | ( | ||
| 6755 | 4..8, | ||
| 6756 | HighlightStyle { | ||
| 6757 | color: Some(Hsla::green()), | ||
| 6758 | ..Default::default() | ||
| 6759 | }, | ||
| 6760 | ), | ||
| 6761 | ]; | ||
| 6762 | let match_indices = [4, 6, 7, 8]; | ||
| 6763 | assert_eq!( | ||
| 6764 | combine_syntax_and_fuzzy_match_highlights( | ||
| 6765 | string, | ||
| 6766 | Default::default(), | ||
| 6767 | syntax_ranges.into_iter(), | ||
| 6768 | &match_indices, | ||
| 6769 | ), | ||
| 6770 | &[ | ||
| 6771 | ( | ||
| 6772 | 0..3, | ||
| 6773 | HighlightStyle { | ||
| 6774 | color: Some(Hsla::red()), | ||
| 6775 | ..Default::default() | ||
| 6776 | }, | ||
| 6777 | ), | ||
| 6778 | ( | ||
| 6779 | 4..5, | ||
| 6780 | HighlightStyle { | ||
| 6781 | color: Some(Hsla::green()), | ||
| 6782 | font_weight: Some(gpui::FontWeight::BOLD), | ||
| 6783 | ..Default::default() | ||
| 6784 | }, | ||
| 6785 | ), | ||
| 6786 | ( | ||
| 6787 | 5..6, | ||
| 6788 | HighlightStyle { | ||
| 6789 | color: Some(Hsla::green()), | ||
| 6790 | ..Default::default() | ||
| 6791 | }, | ||
| 6792 | ), | ||
| 6793 | ( | ||
| 6794 | 6..8, | ||
| 6795 | HighlightStyle { | ||
| 6796 | color: Some(Hsla::green()), | ||
| 6797 | font_weight: Some(gpui::FontWeight::BOLD), | ||
| 6798 | ..Default::default() | ||
| 6799 | }, | ||
| 6800 | ), | ||
| 6801 | ( | ||
| 6802 | 8..9, | ||
| 6803 | HighlightStyle { | ||
| 6804 | font_weight: Some(gpui::FontWeight::BOLD), | ||
| 6805 | ..Default::default() | ||
| 6806 | }, | ||
| 6807 | ), | ||
| 6808 | ] | ||
| 6809 | ); | ||
| 6810 | } | ||
| 6811 | |||
| 6812 | #[gpui::test] | 6743 | #[gpui::test] |
| 6813 | async fn go_to_prev_overlapping_diagnostic( | 6744 | async fn go_to_prev_overlapping_diagnostic( |
| 6814 | executor: BackgroundExecutor, | 6745 | executor: BackgroundExecutor, |
| @@ -5,7 +5,9 @@ use crate::{ · 4 unmodified lines | |||
| 5 | }, | 5 | }, |
| 6 | editor_settings::ShowScrollbar, | 6 | editor_settings::ShowScrollbar, |
| 7 | git::{diff_hunk_to_display, DisplayDiffHunk}, | 7 | git::{diff_hunk_to_display, DisplayDiffHunk}, |
| 8 | hover_popover::hover_at, | 8 | hover_popover::{ |
| 9 | self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, | ||
| 10 | }, | ||
| 9 | link_go_to_definition::{ | 11 | link_go_to_definition::{ |
| 10 | go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link, | 12 | go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link, |
| 11 | update_inlay_link_and_hover_points, GoToDefinitionTrigger, | 13 | update_inlay_link_and_hover_points, GoToDefinitionTrigger, |
| @@ -19,11 +21,11 @@ use anyhow::Result; · 7 unmodified lines | |||
| 19 | use collections::{BTreeMap, HashMap}; | 21 | use collections::{BTreeMap, HashMap}; |
| 20 | use gpui::{ | 22 | use gpui::{ |
| 21 | div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, | 23 | div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, |
| 22 | BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element, | 24 | BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId, |
| 23 | ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, LineLayout, | 25 | ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, IntoElement, LineLayout, |
| 24 | MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, | 26 | MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce, |
| 25 | ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveComponent, Style, Styled, | 27 | ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, |
| 26 | TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine, | 28 | TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine, |
| 27 | }; | 29 | }; |
| 28 | use itertools::Itertools; | 30 | use itertools::Itertools; |
| 29 | use language::language_settings::ShowWhitespaceSetting; | 31 | use language::language_settings::ShowWhitespaceSetting; |
| @@ -112,18 +114,202 @@ impl SelectionLayout { · 82 unmodified lines | |||
| 112 | } | 114 | } |
| 113 | 115 | ||
| 114 | pub struct EditorElement { | 116 | pub struct EditorElement { |
| 115 | editor_id: EntityId, | 117 | editor: View<Editor>, |
| 116 | style: EditorStyle, | 118 | style: EditorStyle, |
| 117 | } | 119 | } |
| 118 | 120 | ||
| 119 | impl EditorElement { | 121 | impl EditorElement { |
| 120 | pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self { | 122 | pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self { |
| 121 | Self { | 123 | Self { |
| 122 | editor_id: editor.entity_id(), | 124 | editor: editor.clone(), |
| 123 | style, | 125 | style, |
| 124 | } | 126 | } |
| 125 | } | 127 | } |
| 126 | 128 | ||
| 129 | fn register_actions(&self, cx: &mut WindowContext) { | ||
| 130 | let view = &self.editor; | ||
| 131 | self.editor.update(cx, |editor, cx| { | ||
| 132 | for action in editor.editor_actions.iter() { | ||
| 133 | (action)(cx) | ||
| 134 | } | ||
| 135 | }); | ||
| 136 | register_action(view, cx, Editor::move_left); | ||
| 137 | register_action(view, cx, Editor::move_right); | ||
| 138 | register_action(view, cx, Editor::move_down); | ||
| 139 | register_action(view, cx, Editor::move_up); | ||
| 140 | // on_action(cx, Editor::new_file); todo!() | ||
| 141 | // on_action(cx, Editor::new_file_in_direction); todo!() | ||
| 142 | register_action(view, cx, Editor::cancel); | ||
| 143 | register_action(view, cx, Editor::newline); | ||
| 144 | register_action(view, cx, Editor::newline_above); | ||
| 145 | register_action(view, cx, Editor::newline_below); | ||
| 146 | register_action(view, cx, Editor::backspace); | ||
| 147 | register_action(view, cx, Editor::delete); | ||
| 148 | register_action(view, cx, Editor::tab); | ||
| 149 | register_action(view, cx, Editor::tab_prev); | ||
| 150 | register_action(view, cx, Editor::indent); | ||
| 151 | register_action(view, cx, Editor::outdent); | ||
| 152 | register_action(view, cx, Editor::delete_line); | ||
| 153 | register_action(view, cx, Editor::join_lines); | ||
| 154 | register_action(view, cx, Editor::sort_lines_case_sensitive); | ||
| 155 | register_action(view, cx, Editor::sort_lines_case_insensitive); | ||
| 156 | register_action(view, cx, Editor::reverse_lines); | ||
| 157 | register_action(view, cx, Editor::shuffle_lines); | ||
| 158 | register_action(view, cx, Editor::convert_to_upper_case); | ||
| 159 | register_action(view, cx, Editor::convert_to_lower_case); | ||
| 160 | register_action(view, cx, Editor::convert_to_title_case); | ||
| 161 | register_action(view, cx, Editor::convert_to_snake_case); | ||
| 162 | register_action(view, cx, Editor::convert_to_kebab_case); | ||
| 163 | register_action(view, cx, Editor::convert_to_upper_camel_case); | ||
| 164 | register_action(view, cx, Editor::convert_to_lower_camel_case); | ||
| 165 | register_action(view, cx, Editor::delete_to_previous_word_start); | ||
| 166 | register_action(view, cx, Editor::delete_to_previous_subword_start); | ||
| 167 | register_action(view, cx, Editor::delete_to_next_word_end); | ||
| 168 | register_action(view, cx, Editor::delete_to_next_subword_end); | ||
| 169 | register_action(view, cx, Editor::delete_to_beginning_of_line); | ||
| 170 | register_action(view, cx, Editor::delete_to_end_of_line); | ||
| 171 | register_action(view, cx, Editor::cut_to_end_of_line); | ||
| 172 | register_action(view, cx, Editor::duplicate_line); | ||
| 173 | register_action(view, cx, Editor::move_line_up); | ||
| 174 | register_action(view, cx, Editor::move_line_down); | ||
| 175 | register_action(view, cx, Editor::transpose); | ||
| 176 | register_action(view, cx, Editor::cut); | ||
| 177 | register_action(view, cx, Editor::copy); | ||
| 178 | register_action(view, cx, Editor::paste); | ||
| 179 | register_action(view, cx, Editor::undo); | ||
| 180 | register_action(view, cx, Editor::redo); | ||
| 181 | register_action(view, cx, Editor::move_page_up); | ||
| 182 | register_action(view, cx, Editor::move_page_down); | ||
| 183 | register_action(view, cx, Editor::next_screen); | ||
| 184 | register_action(view, cx, Editor::scroll_cursor_top); | ||
| 185 | register_action(view, cx, Editor::scroll_cursor_center); | ||
| 186 | register_action(view, cx, Editor::scroll_cursor_bottom); | ||
| 187 | register_action(view, cx, |editor, _: &LineDown, cx| { | ||
| 188 | editor.scroll_screen(&ScrollAmount::Line(1.), cx) | ||
| 189 | }); | ||
| 190 | register_action(view, cx, |editor, _: &LineUp, cx| { | ||
| 191 | editor.scroll_screen(&ScrollAmount::Line(-1.), cx) | ||
| 192 | }); | ||
| 193 | register_action(view, cx, |editor, _: &HalfPageDown, cx| { | ||
| 194 | editor.scroll_screen(&ScrollAmount::Page(0.5), cx) | ||
| 195 | }); | ||
| 196 | register_action(view, cx, |editor, _: &HalfPageUp, cx| { | ||
| 197 | editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) | ||
| 198 | }); | ||
| 199 | register_action(view, cx, |editor, _: &PageDown, cx| { | ||
| 200 | editor.scroll_screen(&ScrollAmount::Page(1.), cx) | ||
| 201 | }); | ||
| 202 | register_action(view, cx, |editor, _: &PageUp, cx| { | ||
| 203 | editor.scroll_screen(&ScrollAmount::Page(-1.), cx) | ||
| 204 | }); | ||
| 205 | register_action(view, cx, Editor::move_to_previous_word_start); | ||
| 206 | register_action(view, cx, Editor::move_to_previous_subword_start); | ||
| 207 | register_action(view, cx, Editor::move_to_next_word_end); | ||
| 208 | register_action(view, cx, Editor::move_to_next_subword_end); | ||
| 209 | register_action(view, cx, Editor::move_to_beginning_of_line); | ||
| 210 | register_action(view, cx, Editor::move_to_end_of_line); | ||
| 211 | register_action(view, cx, Editor::move_to_start_of_paragraph); | ||
| 212 | register_action(view, cx, Editor::move_to_end_of_paragraph); | ||
| 213 | register_action(view, cx, Editor::move_to_beginning); | ||
| 214 | register_action(view, cx, Editor::move_to_end); | ||
| 215 | register_action(view, cx, Editor::select_up); | ||
| 216 | register_action(view, cx, Editor::select_down); | ||
| 217 | register_action(view, cx, Editor::select_left); | ||
| 218 | register_action(view, cx, Editor::select_right); | ||
| 219 | register_action(view, cx, Editor::select_to_previous_word_start); | ||
| 220 | register_action(view, cx, Editor::select_to_previous_subword_start); | ||
| 221 | register_action(view, cx, Editor::select_to_next_word_end); | ||
| 222 | register_action(view, cx, Editor::select_to_next_subword_end); | ||
| 223 | register_action(view, cx, Editor::select_to_beginning_of_line); | ||
| 224 | register_action(view, cx, Editor::select_to_end_of_line); | ||
| 225 | register_action(view, cx, Editor::select_to_start_of_paragraph); | ||
| 226 | register_action(view, cx, Editor::select_to_end_of_paragraph); | ||
| 227 | register_action(view, cx, Editor::select_to_beginning); | ||
| 228 | register_action(view, cx, Editor::select_to_end); | ||
| 229 | register_action(view, cx, Editor::select_all); | ||
| 230 | register_action(view, cx, |editor, action, cx| { | ||
| 231 | editor.select_all_matches(action, cx).log_err(); | ||
| 232 | }); | ||
| 233 | register_action(view, cx, Editor::select_line); | ||
| 234 | register_action(view, cx, Editor::split_selection_into_lines); | ||
| 235 | register_action(view, cx, Editor::add_selection_above); | ||
| 236 | register_action(view, cx, Editor::add_selection_below); | ||
| 237 | register_action(view, cx, |editor, action, cx| { | ||
| 238 | editor.select_next(action, cx).log_err(); | ||
| 239 | }); | ||
| 240 | register_action(view, cx, |editor, action, cx| { | ||
| 241 | editor.select_previous(action, cx).log_err(); | ||
| 242 | }); | ||
| 243 | register_action(view, cx, Editor::toggle_comments); | ||
| 244 | register_action(view, cx, Editor::select_larger_syntax_node); | ||
| 245 | register_action(view, cx, Editor::select_smaller_syntax_node); | ||
| 246 | register_action(view, cx, Editor::move_to_enclosing_bracket); | ||
| 247 | register_action(view, cx, Editor::undo_selection); | ||
| 248 | register_action(view, cx, Editor::redo_selection); | ||
| 249 | register_action(view, cx, Editor::go_to_diagnostic); | ||
| 250 | register_action(view, cx, Editor::go_to_prev_diagnostic); | ||
| 251 | register_action(view, cx, Editor::go_to_hunk); | ||
| 252 | register_action(view, cx, Editor::go_to_prev_hunk); | ||
| 253 | register_action(view, cx, Editor::go_to_definition); | ||
| 254 | register_action(view, cx, Editor::go_to_definition_split); | ||
| 255 | register_action(view, cx, Editor::go_to_type_definition); | ||
| 256 | register_action(view, cx, Editor::go_to_type_definition_split); | ||
| 257 | register_action(view, cx, Editor::fold); | ||
| 258 | register_action(view, cx, Editor::fold_at); | ||
| 259 | register_action(view, cx, Editor::unfold_lines); | ||
| 260 | register_action(view, cx, Editor::unfold_at); | ||
| 261 | register_action(view, cx, Editor::fold_selected_ranges); | ||
| 262 | register_action(view, cx, Editor::show_completions); | ||
| 263 | register_action(view, cx, Editor::toggle_code_actions); | ||
| 264 | // on_action(cx, Editor::open_excerpts); todo!() | ||
| 265 | register_action(view, cx, Editor::toggle_soft_wrap); | ||
| 266 | register_action(view, cx, Editor::toggle_inlay_hints); | ||
| 267 | register_action(view, cx, hover_popover::hover); | ||
| 268 | register_action(view, cx, Editor::reveal_in_finder); | ||
| 269 | register_action(view, cx, Editor::copy_path); | ||
| 270 | register_action(view, cx, Editor::copy_relative_path); | ||
| 271 | register_action(view, cx, Editor::copy_highlight_json); | ||
| 272 | register_action(view, cx, |editor, action, cx| { | ||
| 273 | editor | ||
| 274 | .format(action, cx) | ||
| 275 | .map(|task| task.detach_and_log_err(cx)); | ||
| 276 | }); | ||
| 277 | register_action(view, cx, Editor::restart_language_server); | ||
| 278 | register_action(view, cx, Editor::show_character_palette); | ||
| 279 | register_action(view, cx, |editor, action, cx| { | ||
| 280 | editor | ||
| 281 | .confirm_completion(action, cx) | ||
| 282 | .map(|task| task.detach_and_log_err(cx)); | ||
| 283 | }); | ||
| 284 | register_action(view, cx, |editor, action, cx| { | ||
| 285 | editor | ||
| 286 | .confirm_code_action(action, cx) | ||
| 287 | .map(|task| task.detach_and_log_err(cx)); | ||
| 288 | }); | ||
| 289 | register_action(view, cx, |editor, action, cx| { | ||
| 290 | editor | ||
| 291 | .rename(action, cx) | ||
| 292 | .map(|task| task.detach_and_log_err(cx)); | ||
| 293 | }); | ||
| 294 | register_action(view, cx, |editor, action, cx| { | ||
| 295 | editor | ||
| 296 | .confirm_rename(action, cx) | ||
| 297 | .map(|task| task.detach_and_log_err(cx)); | ||
| 298 | }); | ||
| 299 | register_action(view, cx, |editor, action, cx| { | ||
| 300 | editor | ||
| 301 | .find_all_references(action, cx) | ||
| 302 | .map(|task| task.detach_and_log_err(cx)); | ||
| 303 | }); | ||
| 304 | register_action(view, cx, Editor::next_copilot_suggestion); | ||
| 305 | register_action(view, cx, Editor::previous_copilot_suggestion); | ||
| 306 | register_action(view, cx, Editor::copilot_suggest); | ||
| 307 | register_action(view, cx, Editor::context_menu_first); | ||
| 308 | register_action(view, cx, Editor::context_menu_prev); | ||
| 309 | register_action(view, cx, Editor::context_menu_next); | ||
| 310 | register_action(view, cx, Editor::context_menu_last); | ||
| 311 | } | ||
| 312 | |||
| 127 | fn mouse_down( | 313 | fn mouse_down( |
| 128 | editor: &mut Editor, | 314 | editor: &mut Editor, |
| 129 | event: &MouseDownEvent, | 315 | event: &MouseDownEvent, |
| @@ -349,7 +535,7 @@ impl EditorElement { · 219 unmodified lines | |||
| 349 | gutter_bounds: Bounds<Pixels>, | 535 | gutter_bounds: Bounds<Pixels>, |
| 350 | text_bounds: Bounds<Pixels>, | 536 | text_bounds: Bounds<Pixels>, |
| 351 | layout: &LayoutState, | 537 | layout: &LayoutState, |
| 352 | cx: &mut ViewContext<Editor>, | 538 | cx: &mut WindowContext, |
| 353 | ) { | 539 | ) { |
| 354 | let bounds = gutter_bounds.union(&text_bounds); | 540 | let bounds = gutter_bounds.union(&text_bounds); |
| 355 | let scroll_top = | 541 | let scroll_top = |
| @@ -459,8 +645,7 @@ impl EditorElement { · 103 unmodified lines | |||
| 459 | &mut self, | 645 | &mut self, |
| 460 | bounds: Bounds<Pixels>, | 646 | bounds: Bounds<Pixels>, |
| 461 | layout: &mut LayoutState, | 647 | layout: &mut LayoutState, |
| 462 | editor: &mut Editor, | 648 | cx: &mut WindowContext, |
| 463 | cx: &mut ViewContext<Editor>, | ||
| 464 | ) { | 649 | ) { |
| 465 | let line_height = layout.position_map.line_height; | 650 | let line_height = layout.position_map.line_height; |
| 466 | 651 | ||
| @@ -488,13 +673,14 @@ impl EditorElement { · 21 unmodified lines | |||
| 488 | } | 673 | } |
| 489 | } | 674 | } |
| 490 | 675 | ||
| 491 | for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() { | 676 | for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() { |
| 492 | if let Some(fold_indicator) = fold_indicator.as_mut() { | 677 | if let Some(mut fold_indicator) = fold_indicator { |
| 678 | let mut fold_indicator = fold_indicator.into_any_element(); | ||
| 493 | let available_space = size( | 679 | let available_space = size( |
| 494 | AvailableSpace::MinContent, | 680 | AvailableSpace::MinContent, |
| 495 | AvailableSpace::Definite(line_height * 0.55), | 681 | AvailableSpace::Definite(line_height * 0.55), |
| 496 | ); | 682 | ); |
| 497 | let fold_indicator_size = fold_indicator.measure(available_space, editor, cx); | 683 | let fold_indicator_size = fold_indicator.measure(available_space, cx); |
| 498 | 684 | ||
| 499 | let position = point( | 685 | let position = point( |
| 500 | bounds.size.width - layout.gutter_padding, | 686 | bounds.size.width - layout.gutter_padding, |
| @@ -505,32 +691,29 @@ impl EditorElement { · 4 unmodified lines | |||
| 505 | (line_height - fold_indicator_size.height) / 2., | 691 | (line_height - fold_indicator_size.height) / 2., |
| 506 | ); | 692 | ); |
| 507 | let origin = bounds.origin + position + centering_offset; | 693 | let origin = bounds.origin + position + centering_offset; |
| 508 | fold_indicator.draw(origin, available_space, editor, cx); | 694 | fold_indicator.draw(origin, available_space, cx); |
| 509 | } | 695 | } |
| 510 | } | 696 | } |
| 511 | 697 | ||
| 512 | if let Some(indicator) = layout.code_actions_indicator.as_mut() { | 698 | if let Some(indicator) = layout.code_actions_indicator.take() { |
| 699 | let mut button = indicator.button.into_any_element(); | ||
| 513 | let available_space = size( | 700 | let available_space = size( |
| 514 | AvailableSpace::MinContent, | 701 | AvailableSpace::MinContent, |
| 515 | AvailableSpace::Definite(line_height), | 702 | AvailableSpace::Definite(line_height), |
| 516 | ); | 703 | ); |
| 517 | let indicator_size = indicator.element.measure(available_space, editor, cx); | 704 | let indicator_size = button.measure(available_space, cx); |
| 705 | |||
| 518 | let mut x = Pixels::ZERO; | 706 | let mut x = Pixels::ZERO; |
| 519 | let mut y = indicator.row as f32 * line_height - scroll_top; | 707 | let mut y = indicator.row as f32 * line_height - scroll_top; |
| 520 | // Center indicator. | 708 | // Center indicator. |
| 521 | x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.; | 709 | x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.; |
| 522 | y += (line_height - indicator_size.height) / 2.; | 710 | y += (line_height - indicator_size.height) / 2.; |
| 523 | indicator | 711 | |
| 524 | .element | 712 | button.draw(bounds.origin + point(x, y), available_space, cx); |
| 525 | .draw(bounds.origin + point(x, y), available_space, editor, cx); | ||
| 526 | } | 713 | } |
| 527 | } | 714 | } |
| 528 | 715 | ||
| 529 | fn paint_diff_hunks( | 716 | fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) { |
| 530 | bounds: Bounds<Pixels>, | ||
| 531 | layout: &LayoutState, | ||
| 532 | cx: &mut ViewContext<Editor>, | ||
| 533 | ) { | ||
| 534 | // todo!() | 717 | // todo!() |
| 535 | // let diff_style = &theme::current(cx).editor.diff.clone(); | 718 | // let diff_style = &theme::current(cx).editor.diff.clone(); |
| 536 | // let line_height = layout.position_map.line_height; | 719 | // let line_height = layout.position_map.line_height; |
| @@ -618,14 +801,19 @@ impl EditorElement { · 81 unmodified lines | |||
| 618 | &mut self, | 801 | &mut self, |
| 619 | text_bounds: Bounds<Pixels>, | 802 | text_bounds: Bounds<Pixels>, |
| 620 | layout: &mut LayoutState, | 803 | layout: &mut LayoutState, |
| 621 | editor: &mut Editor, | 804 | cx: &mut WindowContext, |
| 622 | cx: &mut ViewContext<Editor>, | ||
| 623 | ) { | 805 | ) { |
| 624 | let scroll_position = layout.position_map.snapshot.scroll_position(); | 806 | let scroll_position = layout.position_map.snapshot.scroll_position(); |
| 625 | let start_row = layout.visible_display_row_range.start; | 807 | let start_row = layout.visible_display_row_range.start; |
| 626 | let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); | 808 | let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); |
| 627 | let line_end_overshoot = 0.15 * layout.position_map.line_height; | 809 | let line_end_overshoot = 0.15 * layout.position_map.line_height; |
| 628 | let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces; | 810 | let whitespace_setting = self |
| 811 | .editor | ||
| 812 | .read(cx) | ||
| 813 | .buffer | ||
| 814 | .read(cx) | ||
| 815 | .settings_at(0, cx) | ||
| 816 | .show_whitespaces; | ||
| 629 | 817 | ||
| 630 | cx.with_content_mask( | 818 | cx.with_content_mask( |
| 631 | Some(ContentMask { | 819 | Some(ContentMask { |
| @@ -674,20 +862,22 @@ impl EditorElement { · 42 unmodified lines | |||
| 674 | div() | 862 | div() |
| 675 | .id(fold.id) | 863 | .id(fold.id) |
| 676 | .size_full() | 864 | .size_full() |
| 677 | .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) | 865 | .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) |
| 678 | .on_click(move |editor: &mut Editor, _, cx| { | 866 | .on_click(cx.listener_for( |
| 679 | editor.unfold_ranges( | 867 | &self.editor, |
| 680 | [fold_range.start..fold_range.end], | 868 | move |editor: &mut Editor, _, cx| { |
| 681 | true, | 869 | editor.unfold_ranges( |
| 682 | false, | 870 | [fold_range.start..fold_range.end], |
| 683 | cx, | 871 | true, |
| 684 | ); | 872 | false, |
| 685 | cx.stop_propagation(); | 873 | cx, |
| 686 | }) | 874 | ); |
| 875 | cx.stop_propagation(); | ||
| 876 | }, | ||
| 877 | )) | ||
| 687 | .draw( | 878 | .draw( |
| 688 | fold_bounds.origin, | 879 | fold_bounds.origin, |
| 689 | fold_bounds.size, | 880 | fold_bounds.size, |
| 690 | editor, | ||
| 691 | cx, | 881 | cx, |
| 692 | |fold_element_state, cx| { | 882 | |fold_element_state, cx| { |
| 693 | if fold_element_state.is_active() { | 883 | if fold_element_state.is_active() { |
| @@ -748,7 +938,7 @@ impl EditorElement { · 54 unmodified lines | |||
| 748 | invisible_display_ranges.push(selection.range.clone()); | 938 | invisible_display_ranges.push(selection.range.clone()); |
| 749 | } | 939 | } |
| 750 | 940 | ||
| 751 | if !selection.is_local || editor.show_local_cursors(cx) { | 941 | if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) { |
| 752 | let cursor_position = selection.head; | 942 | let cursor_position = selection.head; |
| 753 | if layout | 943 | if layout |
| 754 | .visible_display_row_range | 944 | .visible_display_row_range |
| @@ -800,12 +990,14 @@ impl EditorElement { · 45 unmodified lines | |||
| 800 | * layout.position_map.line_height | 990 | * layout.position_map.line_height |
| 801 | - layout.position_map.scroll_position.y; | 991 | - layout.position_map.scroll_position.y; |
| 802 | if selection.is_newest { | 992 | if selection.is_newest { |
| 803 | editor.pixel_position_of_newest_cursor = Some(point( | 993 | self.editor.update(cx, |editor, _| { |
| 804 | text_bounds.origin.x + x + block_width / 2., | 994 | editor.pixel_position_of_newest_cursor = Some(point( |
| 805 | text_bounds.origin.y | 995 | text_bounds.origin.x + x + block_width / 2., |
| 806 | + y | 996 | text_bounds.origin.y |
| 807 | + layout.position_map.line_height / 2., | 997 | + y |
| 808 | )); | 998 | + layout.position_map.line_height / 2., |
| 999 | )) | ||
| 1000 | }); | ||
| 809 | } | 1001 | } |
| 810 | cursors.push(Cursor { | 1002 | cursors.push(Cursor { |
| 811 | color: selection_style.cursor, | 1003 | color: selection_style.cursor, |
| @@ -840,17 +1032,11 @@ impl EditorElement { · 28 unmodified lines | |||
| 840 | } | 1032 | } |
| 841 | }); | 1033 | }); |
| 842 | 1034 | ||
| 843 | if let Some((position, context_menu)) = layout.context_menu.as_mut() { | 1035 | cx.with_z_index(1, |cx| { |
| 844 | cx.with_z_index(1, |cx| { | 1036 | if let Some((position, mut context_menu)) = layout.context_menu.take() { |
| 845 | let line_height = self.style.text.line_height_in_pixels(cx.rem_size()); | 1037 | let available_space = |
| 846 | let available_space = size( | 1038 | size(AvailableSpace::MinContent, AvailableSpace::MinContent); |
| 847 | AvailableSpace::MinContent, | 1039 | let context_menu_size = context_menu.measure(available_space, cx); |
| 848 | AvailableSpace::Definite( | ||
| 849 | (12. * line_height) | ||
| 850 | .min((text_bounds.size.height - line_height) / 2.), | ||
| 851 | ), | ||
| 852 | ); | ||
| 853 | let context_menu_size = context_menu.measure(available_space, editor, cx); | ||
| 854 | 1040 | ||
| 855 | let cursor_row_layout = &layout.position_map.line_layouts | 1041 | let cursor_row_layout = &layout.position_map.line_layouts |
| 856 | [(position.row() - start_row) as usize] | 1042 | [(position.row() - start_row) as usize] |
| @@ -871,84 +1057,77 @@ impl EditorElement { · 14 unmodified lines | |||
| 871 | } | 1057 | } |
| 872 | 1058 | ||
| 873 | if list_origin.y + list_height > text_bounds.lower_right().y { | 1059 | if list_origin.y + list_height > text_bounds.lower_right().y { |
| 874 | list_origin.y -= layout.position_map.line_height - list_height; | 1060 | list_origin.y -= layout.position_map.line_height + list_height; |
| 875 | } | 1061 | } |
| 876 | 1062 | ||
| 877 | context_menu.draw(list_origin, available_space, editor, cx); | 1063 | cx.break_content_mask(|cx| { |
| 878 | }) | 1064 | context_menu.draw(list_origin, available_space, cx) |
| 879 | } | 1065 | }); |
| 1066 | } | ||
| 880 | 1067 | ||
| 881 | // if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() { | 1068 | if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { |
| 882 | // cx.scene().push_stacking_context(None, None); | 1069 | let available_space = |
| 883 | 1070 | size(AvailableSpace::MinContent, AvailableSpace::MinContent); | |
| 884 | // // This is safe because we check on layout whether the required row is available | 1071 | |
| 885 | // let hovered_row_layout = | 1072 | // This is safe because we check on layout whether the required row is available |
| 886 | // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; | 1073 | let hovered_row_layout = &layout.position_map.line_layouts |
| 887 | 1074 | [(position.row() - start_row) as usize] | |
| 888 | // // Minimum required size: Take the first popover, and add 1.5 times the minimum popover | 1075 | .line; |
| 889 | // // height. This is the size we will use to decide whether to render popovers above or below | 1076 | |
| 890 | // // the hovered line. | 1077 | // Minimum required size: Take the first popover, and add 1.5 times the minimum popover |
| 891 | // let first_size = hover_popovers[0].size(); | 1078 | // height. This is the size we will use to decide whether to render popovers above or below |
| 892 | // let height_to_reserve = first_size.y | 1079 | // the hovered line. |
| 893 | // + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height; | 1080 | let first_size = hover_popovers[0].measure(available_space, cx); |
| 894 | 1081 | let height_to_reserve = first_size.height | |
| 895 | // // Compute Hovered Point | 1082 | + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height; |
| 896 | // let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left; | 1083 | |
| 897 | // let y = position.row() as f32 * layout.position_map.line_height - scroll_top; | 1084 | // Compute Hovered Point |
| 898 | // let hovered_point = content_origin + point(x, y); | 1085 | let x = hovered_row_layout.x_for_index(position.column() as usize) |
| 899 | 1086 | - layout.position_map.scroll_position.x; | |
| 900 | // if hovered_point.y - height_to_reserve > 0.0 { | 1087 | let y = position.row() as f32 * layout.position_map.line_height |
| 901 | // // There is enough space above. Render popovers above the hovered point | 1088 | - layout.position_map.scroll_position.y; |
| 902 | // let mut current_y = hovered_point.y; | 1089 | let hovered_point = content_origin + point(x, y); |
| 903 | // for hover_popover in hover_popovers { | 1090 | |
| 904 | // let size = hover_popover.size(); | 1091 | if hovered_point.y - height_to_reserve > Pixels::ZERO { |
| 905 | // let mut popover_origin = point(hovered_point.x, current_y - size.y); | 1092 | // There is enough space above. Render popovers above the hovered point |
| 906 | 1093 | let mut current_y = hovered_point.y; | |
| 907 | // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x); | 1094 | for mut hover_popover in hover_popovers { |
| 908 | // if x_out_of_bounds < 0.0 { | 1095 | let size = hover_popover.measure(available_space, cx); |
| 909 | // popover_origin.set_x(popover_origin.x + x_out_of_bounds); | 1096 | let mut popover_origin = |
| 910 | // } | 1097 | point(hovered_point.x, current_y - size.height); |
| 911 | 1098 | ||
| 912 | // hover_popover.paint( | 1099 | let x_out_of_bounds = |
| 913 | // popover_origin, | 1100 | text_bounds.upper_right().x - (popover_origin.x + size.width); |
| 914 | // Bounds::<Pixels>::from_points( | 1101 | if x_out_of_bounds < Pixels::ZERO { |
| 915 | // gpui::Point::<Pixels>::zero(), | 1102 | popover_origin.x = popover_origin.x + x_out_of_bounds; |
| 916 | // point(f32::MAX, f32::MAX), | 1103 | } |
| 917 | // ), // Let content bleed outside of editor | 1104 | |
| 918 | // editor, | 1105 | cx.break_content_mask(|cx| { |
| 919 | // cx, | 1106 | hover_popover.draw(popover_origin, available_space, cx) |
| 920 | // ); | 1107 | }); |
| 921 | 1108 | ||
| 922 | // current_y = popover_origin.y - HOVER_POPOVER_GAP; | 1109 | current_y = popover_origin.y - HOVER_POPOVER_GAP; |
| 923 | // } | 1110 | } |
| 924 | // } else { | 1111 | } else { |
| 925 | // // There is not enough space above. Render popovers below the hovered point | 1112 | // There is not enough space above. Render popovers below the hovered point |
| 926 | // let mut current_y = hovered_point.y + layout.position_map.line_height; | 1113 | let mut current_y = hovered_point.y + layout.position_map.line_height; |
| 927 | // for hover_popover in hover_popovers { | 1114 | for mut hover_popover in hover_popovers { |
| 928 | // let size = hover_popover.size(); | 1115 | let size = hover_popover.measure(available_space, cx); |
| 929 | // let mut popover_origin = point(hovered_point.x, current_y); | 1116 | let mut popover_origin = point(hovered_point.x, current_y); |
| 930 | 1117 | ||
| 931 | // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x); | 1118 | let x_out_of_bounds = |
| 932 | // if x_out_of_bounds < 0.0 { | 1119 | text_bounds.upper_right().x - (popover_origin.x + size.width); |
| 933 | // popover_origin.set_x(popover_origin.x + x_out_of_bounds); | 1120 | if x_out_of_bounds < Pixels::ZERO { |
| 934 | // } | 1121 | popover_origin.x = popover_origin.x + x_out_of_bounds; |
| 935 | 1122 | } | |
| 936 | // hover_popover.paint( | 1123 | |
| 937 | // popover_origin, | 1124 | hover_popover.draw(popover_origin, available_space, cx); |
| 938 | // Bounds::<Pixels>::from_points( | 1125 | |
| 939 | // gpui::Point::<Pixels>::zero(), | 1126 | current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; |
| 940 | // point(f32::MAX, f32::MAX), | 1127 | } |
| 941 | // ), // Let content bleed outside of editor | 1128 | } |
| 942 | // editor, | 1129 | } |
| 943 | // cx, | 1130 | }) |
| 944 | // ); | ||
| 945 | |||
| 946 | // current_y = popover_origin.y + size.y + HOVER_POPOVER_GAP; | ||
| 947 | // } | ||
| 948 | // } | ||
| 949 | |||
| 950 | // cx.scene().pop_stacking_context(); | ||
| 951 | // } | ||
| 952 | }, | 1131 | }, |
| 953 | ) | 1132 | ) |
| 954 | } | 1133 | } |
| @@ -1165,7 +1344,7 @@ impl EditorElement { · 210 unmodified lines | |||
| 1165 | layout: &LayoutState, | 1344 | layout: &LayoutState, |
| 1166 | content_origin: gpui::Point<Pixels>, | 1345 | content_origin: gpui::Point<Pixels>, |
| 1167 | bounds: Bounds<Pixels>, | 1346 | bounds: Bounds<Pixels>, |
| 1168 | cx: &mut ViewContext<Editor>, | 1347 | cx: &mut WindowContext, |
| 1169 | ) { | 1348 | ) { |
| 1170 | let start_row = layout.visible_display_row_range.start; | 1349 | let start_row = layout.visible_display_row_range.start; |
| 1171 | let end_row = layout.visible_display_row_range.end; | 1350 | let end_row = layout.visible_display_row_range.end; |
| @@ -1217,14 +1396,13 @@ impl EditorElement { · 45 unmodified lines | |||
| 1217 | &mut self, | 1396 | &mut self, |
| 1218 | bounds: Bounds<Pixels>, | 1397 | bounds: Bounds<Pixels>, |
| 1219 | layout: &mut LayoutState, | 1398 | layout: &mut LayoutState, |
| 1220 | editor: &mut Editor, | 1399 | cx: &mut WindowContext, |
| 1221 | cx: &mut ViewContext<Editor>, | ||
| 1222 | ) { | 1400 | ) { |
| 1223 | let scroll_position = layout.position_map.snapshot.scroll_position(); | 1401 | let scroll_position = layout.position_map.snapshot.scroll_position(); |
| 1224 | let scroll_left = scroll_position.x * layout.position_map.em_width; | 1402 | let scroll_left = scroll_position.x * layout.position_map.em_width; |
| 1225 | let scroll_top = scroll_position.y * layout.position_map.line_height; | 1403 | let scroll_top = scroll_position.y * layout.position_map.line_height; |
| 1226 | 1404 | ||
| 1227 | for block in &mut layout.blocks { | 1405 | for block in layout.blocks.drain(..) { |
| 1228 | let mut origin = bounds.origin | 1406 | let mut origin = bounds.origin |
| 1229 | + point( | 1407 | + point( |
| 1230 | Pixels::ZERO, | 1408 | Pixels::ZERO, |
| @@ -1233,13 +1411,11 @@ impl EditorElement { · 2 unmodified lines | |||
| 1233 | if !matches!(block.style, BlockStyle::Sticky) { | 1411 | if !matches!(block.style, BlockStyle::Sticky) { |
| 1234 | origin += point(-scroll_left, Pixels::ZERO); | 1412 | origin += point(-scroll_left, Pixels::ZERO); |
| 1235 | } | 1413 | } |
| 1236 | block | 1414 | block.element.draw(origin, block.available_space, cx); |
| 1237 | .element | ||
| 1238 | .draw(origin, block.available_space, editor, cx); | ||
| 1239 | } | 1415 | } |
| 1240 | } | 1416 | } |
| 1241 | 1417 | ||
| 1242 | fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels { | 1418 | fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels { |
| 1243 | let style = &self.style; | 1419 | let style = &self.style; |
| 1244 | let font_size = style.text.font_size.to_pixels(cx.rem_size()); | 1420 | let font_size = style.text.font_size.to_pixels(cx.rem_size()); |
| 1245 | let layout = cx | 1421 | let layout = cx |
| @@ -1260,7 +1436,7 @@ impl EditorElement { · 14 unmodified lines | |||
| 1260 | layout.width | 1436 | layout.width |
| 1261 | } | 1437 | } |
| 1262 | 1438 | ||
| 1263 | fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> Pixels { | 1439 | fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels { |
| 1264 | let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; | 1440 | let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; |
| 1265 | self.column_pixels(digit_count, cx) | 1441 | self.column_pixels(digit_count, cx) |
| 1266 | } | 1442 | } |
| @@ -1415,7 +1591,7 @@ impl EditorElement { · 148 unmodified lines | |||
| 1415 | } | 1591 | } |
| 1416 | 1592 | ||
| 1417 | fn layout_lines( | 1593 | fn layout_lines( |
| 1418 | &mut self, | 1594 | &self, |
| 1419 | rows: Range<u32>, | 1595 | rows: Range<u32>, |
| 1420 | line_number_layouts: &[Option<ShapedLine>], | 1596 | line_number_layouts: &[Option<ShapedLine>], |
| 1421 | snapshot: &EditorSnapshot, | 1597 | snapshot: &EditorSnapshot, |
| @@ -1471,483 +1647,457 @@ impl EditorElement { · 49 unmodified lines | |||
| 1471 | 1647 | ||
| 1472 | fn compute_layout( | 1648 | fn compute_layout( |
| 1473 | &mut self, | 1649 | &mut self, |
| 1474 | editor: &mut Editor, | ||
| 1475 | cx: &mut ViewContext<'_, Editor>, | ||
| 1476 | mut bounds: Bounds<Pixels>, | 1650 | mut bounds: Bounds<Pixels>, |
| 1651 | cx: &mut WindowContext, | ||
| 1477 | ) -> LayoutState { | 1652 | ) -> LayoutState { |
| 1478 | // let mut size = constraint.max; | 1653 | self.editor.update(cx, |editor, cx| { |
| 1479 | // if size.x.is_infinite() { | 1654 | // let mut size = constraint.max; |
| 1480 | // unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); | 1655 | // if size.x.is_infinite() { |
| 1481 | // } | 1656 | // unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); |
| 1482 | 1657 | // } | |
| 1483 | let snapshot = editor.snapshot(cx); | 1658 | |
| 1484 | let style = self.style.clone(); | 1659 | let snapshot = editor.snapshot(cx); |
| 1660 | let style = self.style.clone(); | ||
| 1661 | |||
| 1662 | let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); | ||
| 1663 | let font_size = style.text.font_size.to_pixels(cx.rem_size()); | ||
| 1664 | let line_height = style.text.line_height_in_pixels(cx.rem_size()); | ||
| 1665 | let em_width = cx | ||
| 1666 | .text_system() | ||
| 1667 | .typographic_bounds(font_id, font_size, 'm') | ||
| 1668 | .unwrap() | ||
| 1669 | .size | ||
| 1670 | .width; | ||
| 1671 | let em_advance = cx | ||
| 1672 | .text_system() | ||
| 1673 | .advance(font_id, font_size, 'm') | ||
| 1674 | .unwrap() | ||
| 1675 | .width; | ||
| 1676 | |||
| 1677 | let gutter_padding; | ||
| 1678 | let gutter_width; | ||
| 1679 | let gutter_margin; | ||
| 1680 | if snapshot.show_gutter { | ||
| 1681 | let descent = cx.text_system().descent(font_id, font_size).unwrap(); | ||
| 1682 | |||
| 1683 | let gutter_padding_factor = 3.5; | ||
| 1684 | gutter_padding = (em_width * gutter_padding_factor).round(); | ||
| 1685 | gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; | ||
| 1686 | gutter_margin = -descent; | ||
| 1687 | } else { | ||
| 1688 | gutter_padding = Pixels::ZERO; | ||
| 1689 | gutter_width = Pixels::ZERO; | ||
| 1690 | gutter_margin = Pixels::ZERO; | ||
| 1691 | }; | ||
| 1485 | 1692 | ||
| 1486 | let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); | 1693 | editor.gutter_width = gutter_width; |
| 1487 | let font_size = style.text.font_size.to_pixels(cx.rem_size()); | 1694 | let text_width = bounds.size.width - gutter_width; |
| 1488 | let line_height = style.text.line_height_in_pixels(cx.rem_size()); | 1695 | let overscroll = size(em_width, px(0.)); |
| 1489 | let em_width = cx | 1696 | let snapshot = { |
| 1490 | .text_system() | 1697 | editor.set_visible_line_count((bounds.size.height / line_height).into(), cx); |
| 1491 | .typographic_bounds(font_id, font_size, 'm') | 1698 | |
| 1492 | .unwrap() | 1699 | let editor_width = text_width - gutter_margin - overscroll.width - em_width; |
| 1493 | .size | 1700 | let wrap_width = match editor.soft_wrap_mode(cx) { |
| 1494 | .width; | 1701 | SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, |
| 1495 | let em_advance = cx | 1702 | SoftWrap::EditorWidth => editor_width, |
| 1496 | .text_system() | 1703 | SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), |
| 1497 | .advance(font_id, font_size, 'm') | 1704 | }; |
| 1498 | .unwrap() | ||
| 1499 | .width; | ||
| 1500 | |||
| 1501 | let gutter_padding; | ||
| 1502 | let gutter_width; | ||
| 1503 | let gutter_margin; | ||
| 1504 | if snapshot.show_gutter { | ||
| 1505 | let descent = cx.text_system().descent(font_id, font_size).unwrap(); | ||
| 1506 | |||
| 1507 | let gutter_padding_factor = 3.5; | ||
| 1508 | gutter_padding = (em_width * gutter_padding_factor).round(); | ||
| 1509 | gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; | ||
| 1510 | gutter_margin = -descent; | ||
| 1511 | } else { | ||
| 1512 | gutter_padding = Pixels::ZERO; | ||
| 1513 | gutter_width = Pixels::ZERO; | ||
| 1514 | gutter_margin = Pixels::ZERO; | ||
| 1515 | }; | ||
| 1516 | 1705 | ||
| 1517 | editor.gutter_width = gutter_width; | 1706 | if editor.set_wrap_width(Some(wrap_width), cx) { |
| 1518 | let text_width = bounds.size.width - gutter_width; | 1707 | editor.snapshot(cx) |
| 1519 | let overscroll = size(em_width, px(0.)); | 1708 | } else { |
| 1520 | let snapshot = { | 1709 | snapshot |
| 1521 | editor.set_visible_line_count((bounds.size.height / line_height).into(), cx); | 1710 | } |
| 1522 | |||
| 1523 | let editor_width = text_width - gutter_margin - overscroll.width - em_width; | ||
| 1524 | let wrap_width = match editor.soft_wrap_mode(cx) { | ||
| 1525 | SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance, | ||
| 1526 | SoftWrap::EditorWidth => editor_width, | ||
| 1527 | SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance), | ||
| 1528 | }; | 1711 | }; |
| 1529 | 1712 | ||
| 1530 | if editor.set_wrap_width(Some(wrap_width), cx) { | 1713 | let wrap_guides = editor |
| 1531 | editor.snapshot(cx) | 1714 | .wrap_guides(cx) |
| 1715 | .iter() | ||
| 1716 | .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) | ||
| 1717 | .collect::<SmallVec<[_; 2]>>(); | ||
| 1718 | |||
| 1719 | let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height; | ||
| 1720 | // todo!("this should happen during layout") | ||
| 1721 | let editor_mode = snapshot.mode; | ||
| 1722 | if let EditorMode::AutoHeight { max_lines } = editor_mode { | ||
| 1723 | todo!() | ||
| 1724 | // size.set_y( | ||
| 1725 | // scroll_height | ||
| 1726 | // .min(constraint.max_along(Axis::Vertical)) | ||
| 1727 | // .max(constraint.min_along(Axis::Vertical)) | ||
| 1728 | // .max(line_height) | ||
| 1729 | // .min(line_height * max_lines as f32), | ||
| 1730 | // ) | ||
| 1731 | } else if let EditorMode::SingleLine = editor_mode { | ||
| 1732 | bounds.size.height = line_height.min(bounds.size.height); | ||
| 1733 | } | ||
| 1734 | // todo!() | ||
| 1735 | // else if size.y.is_infinite() { | ||
| 1736 | // // size.set_y(scroll_height); | ||
| 1737 | // } | ||
| 1738 | // | ||
| 1739 | let gutter_size = size(gutter_width, bounds.size.height); | ||
| 1740 | let text_size = size(text_width, bounds.size.height); | ||
| 1741 | |||
| 1742 | let autoscroll_horizontally = | ||
| 1743 | editor.autoscroll_vertically(bounds.size.height, line_height, cx); | ||
| 1744 | let mut snapshot = editor.snapshot(cx); | ||
| 1745 | |||
| 1746 | let scroll_position = snapshot.scroll_position(); | ||
| 1747 | // The scroll position is a fractional point, the whole number of which represents | ||
| 1748 | // the top of the window in terms of display rows. | ||
| 1749 | let start_row = scroll_position.y as u32; | ||
| 1750 | let height_in_lines = f32::from(bounds.size.height / line_height); | ||
| 1751 | let max_row = snapshot.max_point().row(); | ||
| 1752 | |||
| 1753 | // Add 1 to ensure selections bleed off screen | ||
| 1754 | let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row); | ||
| 1755 | |||
| 1756 | let start_anchor = if start_row == 0 { | ||
| 1757 | Anchor::min() | ||
| 1532 | } else { | 1758 | } else { |
| 1533 | snapshot | 1759 | snapshot |
| 1534 | } | 1760 | .buffer_snapshot |
| 1535 | }; | 1761 | .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left)) |
| 1762 | }; | ||
| 1763 | let end_anchor = if end_row > max_row { | ||
| 1764 | Anchor::max() | ||
| 1765 | } else { | ||
| 1766 | snapshot | ||
| 1767 | .buffer_snapshot | ||
| 1768 | .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right)) | ||
| 1769 | }; | ||
| 1536 | 1770 | ||
| 1537 | let wrap_guides = editor | 1771 | let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new(); |
| 1538 | .wrap_guides(cx) | 1772 | let mut active_rows = BTreeMap::new(); |
| 1539 | .iter() | 1773 | let is_singleton = editor.is_singleton(cx); |
| 1540 | .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) | ||
| 1541 | .collect::<SmallVec<[_; 2]>>(); | ||
| 1542 | |||
| 1543 | let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height; | ||
| 1544 | // todo!("this should happen during layout") | ||
| 1545 | let editor_mode = snapshot.mode; | ||
| 1546 | if let EditorMode::AutoHeight { max_lines } = editor_mode { | ||
| 1547 | todo!() | ||
| 1548 | // size.set_y( | ||
| 1549 | // scroll_height | ||
| 1550 | // .min(constraint.max_along(Axis::Vertical)) | ||
| 1551 | // .max(constraint.min_along(Axis::Vertical)) | ||
| 1552 | // .max(line_height) | ||
| 1553 | // .min(line_height * max_lines as f32), | ||
| 1554 | // ) | ||
| 1555 | } else if let EditorMode::SingleLine = editor_mode { | ||
| 1556 | bounds.size.height = line_height.min(bounds.size.height); | ||
| 1557 | } | ||
| 1558 | // todo!() | ||
| 1559 | // else if size.y.is_infinite() { | ||
| 1560 | // // size.set_y(scroll_height); | ||
| 1561 | // } | ||
| 1562 | // | ||
| 1563 | let gutter_size = size(gutter_width, bounds.size.height); | ||
| 1564 | let text_size = size(text_width, bounds.size.height); | ||
| 1565 | |||
| 1566 | let autoscroll_horizontally = | ||
| 1567 | editor.autoscroll_vertically(bounds.size.height, line_height, cx); | ||
| 1568 | let mut snapshot = editor.snapshot(cx); | ||
| 1569 | |||
| 1570 | let scroll_position = snapshot.scroll_position(); | ||
| 1571 | // The scroll position is a fractional point, the whole number of which represents | ||
| 1572 | // the top of the window in terms of display rows. | ||
| 1573 | let start_row = scroll_position.y as u32; | ||
| 1574 | let height_in_lines = f32::from(bounds.size.height / line_height); | ||
| 1575 | let max_row = snapshot.max_point().row(); | ||
| 1576 | |||
| 1577 | // Add 1 to ensure selections bleed off screen | ||
| 1578 | let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row); | ||
| 1579 | |||
| 1580 | let start_anchor = if start_row == 0 { | ||
| 1581 | Anchor::min() | ||
| 1582 | } else { | ||
| 1583 | snapshot | ||
| 1584 | .buffer_snapshot | ||
| 1585 | .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left)) | ||
| 1586 | }; | ||
| 1587 | let end_anchor = if end_row > max_row { | ||
| 1588 | Anchor::max() | ||
| 1589 | } else { | ||
| 1590 | snapshot | ||
| 1591 | .buffer_snapshot | ||
| 1592 | .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right)) | ||
| 1593 | }; | ||
| 1594 | 1774 | ||
| 1595 | let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new(); | 1775 | let highlighted_rows = editor.highlighted_rows(); |
| 1596 | let mut active_rows = BTreeMap::new(); | 1776 | let highlighted_ranges = editor.background_highlights_in_range( |
| 1597 | let is_singleton = editor.is_singleton(cx); | 1777 | start_anchor..end_anchor, |
| 1778 | &snapshot.display_snapshot, | ||
| 1779 | cx.theme().colors(), | ||
| 1780 | ); | ||
| 1598 | 1781 | ||
| 1599 | let highlighted_rows = editor.highlighted_rows(); | 1782 | let mut newest_selection_head = None; |
| 1600 | let highlighted_ranges = editor.background_highlights_in_range( | 1783 | |
| 1601 | start_anchor..end_anchor, | 1784 | if editor.show_local_selections { |
| 1602 | &snapshot.display_snapshot, | 1785 | let mut local_selections: Vec<Selection<Point>> = editor |
| 1603 | cx.theme().colors(), | 1786 | .selections |
| 1604 | ); | 1787 | .disjoint_in_range(start_anchor..end_anchor, cx); |
| 1788 | local_selections.extend(editor.selections.pending(cx)); | ||
| 1789 | let mut layouts = Vec::new(); | ||
| 1790 | let newest = editor.selections.newest(cx); | ||
| 1791 | for selection in local_selections.drain(..) { | ||
| 1792 | let is_empty = selection.start == selection.end; | ||
| 1793 | let is_newest = selection == newest; | ||
| 1794 | |||
| 1795 | let layout = SelectionLayout::new( | ||
| 1796 | selection, | ||
| 1797 | editor.selections.line_mode, | ||
| 1798 | editor.cursor_shape, | ||
| 1799 | &snapshot.display_snapshot, | ||
| 1800 | is_newest, | ||
| 1801 | true, | ||
| 1802 | ); | ||
| 1803 | if is_newest { | ||
| 1804 | newest_selection_head = Some(layout.head); | ||
| 1805 | } | ||
| 1605 | 1806 | ||
| 1606 | let mut newest_selection_head = None; | 1807 | for row in cmp::max(layout.active_rows.start, start_row) |
| 1607 | 1808 | ..=cmp::min(layout.active_rows.end, end_row) | |
| 1608 | if editor.show_local_selections { | 1809 | { |
| 1609 | let mut local_selections: Vec<Selection<Point>> = editor | 1810 | let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty); |
| 1610 | .selections | 1811 | *contains_non_empty_selection |= !is_empty; |
| 1611 | .disjoint_in_range(start_anchor..end_anchor, cx); | 1812 | } |
| 1612 | local_selections.extend(editor.selections.pending(cx)); | 1813 | layouts.push(layout); |
| 1613 | let mut layouts = Vec::new(); | ||
| 1614 | let newest = editor.selections.newest(cx); | ||
| 1615 | for selection in local_selections.drain(..) { | ||
| 1616 | let is_empty = selection.start == selection.end; | ||
| 1617 | let is_newest = selection == newest; | ||
| 1618 | |||
| 1619 | let layout = SelectionLayout::new( | ||
| 1620 | selection, | ||
| 1621 | editor.selections.line_mode, | ||
| 1622 | editor.cursor_shape, | ||
| 1623 | &snapshot.display_snapshot, | ||
| 1624 | is_newest, | ||
| 1625 | true, | ||
| 1626 | ); | ||
| 1627 | if is_newest { | ||
| 1628 | newest_selection_head = Some(layout.head); | ||
| 1629 | } | 1814 | } |
| 1630 | 1815 | ||
| 1631 | for row in cmp::max(layout.active_rows.start, start_row) | 1816 | selections.push((style.local_player, layouts)); |
| 1632 | ..=cmp::min(layout.active_rows.end, end_row) | ||
| 1633 | { | ||
| 1634 | let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty); | ||
| 1635 | *contains_non_empty_selection |= !is_empty; | ||
| 1636 | } | ||
| 1637 | layouts.push(layout); | ||
| 1638 | } | 1817 | } |
| 1639 | 1818 | ||
| 1640 | selections.push((style.local_player, layouts)); | 1819 | if let Some(collaboration_hub) = &editor.collaboration_hub { |
| 1641 | } | 1820 | // When following someone, render the local selections in their color. |
| 1642 | 1821 | if let Some(leader_id) = editor.leader_peer_id { | |
| 1643 | if let Some(collaboration_hub) = &editor.collaboration_hub { | 1822 | if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) { |
| 1644 | // When following someone, render the local selections in their color. | 1823 | if let Some(participant_index) = collaboration_hub |
| 1645 | if let Some(leader_id) = editor.leader_peer_id { | 1824 | .user_participant_indices(cx) |
| 1646 | if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) { | 1825 | .get(&collaborator.user_id) |
| 1647 | if let Some(participant_index) = collaboration_hub | 1826 | { |
| 1648 | .user_participant_indices(cx) | 1827 | if let Some((local_selection_style, _)) = selections.first_mut() { |
| 1649 | .get(&collaborator.user_id) | 1828 | *local_selection_style = cx |
| 1650 | { | 1829 | .theme() |
| 1651 | if let Some((local_selection_style, _)) = selections.first_mut() { | 1830 | .players() |
| 1652 | *local_selection_style = cx | 1831 | .color_for_participant(participant_index.0); |
| 1653 | .theme() | 1832 | } |
| 1654 | .players() | ||
| 1655 | .color_for_participant(participant_index.0); | ||
| 1656 | } | 1833 | } |
| 1657 | } | 1834 | } |
| 1658 | } | 1835 | } |
| 1659 | } | ||
| 1660 | 1836 | ||
| 1661 | let mut remote_selections = HashMap::default(); | 1837 | let mut remote_selections = HashMap::default(); |
| 1662 | for selection in snapshot.remote_selections_in_range( | 1838 | for selection in snapshot.remote_selections_in_range( |
| 1663 | &(start_anchor..end_anchor), | 1839 | &(start_anchor..end_anchor), |
| 1664 | collaboration_hub.as_ref(), | 1840 | collaboration_hub.as_ref(), |
| 1665 | cx, | 1841 | cx, |
| 1666 | ) { | 1842 | ) { |
| 1667 | let selection_style = if let Some(participant_index) = selection.participant_index { | 1843 | let selection_style = if let Some(participant_index) = selection.participant_index { |
| 1668 | cx.theme() | 1844 | cx.theme() |
| 1669 | .players() | 1845 | .players() |
| 1670 | .color_for_participant(participant_index.0) | 1846 | .color_for_participant(participant_index.0) |
| 1671 | } else { | 1847 | } else { |
| 1672 | cx.theme().players().absent() | 1848 | cx.theme().players().absent() |
| 1673 | }; | 1849 | }; |
| 1674 | 1850 | ||
| 1675 | // Don't re-render the leader's selections, since the local selections | 1851 | // Don't re-render the leader's selections, since the local selections |
| 1676 | // match theirs. | 1852 | // match theirs. |
| 1677 | if Some(selection.peer_id) == editor.leader_peer_id { | 1853 | if Some(selection.peer_id) == editor.leader_peer_id { |
| 1678 | continue; | 1854 | continue; |
| 1855 | } | ||
| 1856 | |||
| 1857 | remote_selections | ||
| 1858 | .entry(selection.replica_id) | ||
| 1859 | .or_insert((selection_style, Vec::new())) | ||
| 1860 | .1 | ||
| 1861 | .push(SelectionLayout::new( | ||
| 1862 | selection.selection, | ||
| 1863 | selection.line_mode, | ||
| 1864 | selection.cursor_shape, | ||
| 1865 | &snapshot.display_snapshot, | ||
| 1866 | false, | ||
| 1867 | false, | ||
| 1868 | )); | ||
| 1679 | } | 1869 | } |
| 1680 | 1870 | ||
| 1681 | remote_selections | 1871 | selections.extend(remote_selections.into_values()); |
| 1682 | .entry(selection.replica_id) | ||
| 1683 | .or_insert((selection_style, Vec::new())) | ||
| 1684 | .1 | ||
| 1685 | .push(SelectionLayout::new( | ||
| 1686 | selection.selection, | ||
| 1687 | selection.line_mode, | ||
| 1688 | selection.cursor_shape, | ||
| 1689 | &snapshot.display_snapshot, | ||
| 1690 | false, | ||
| 1691 | false, | ||
| 1692 | )); | ||
| 1693 | } | 1872 | } |
| 1694 | 1873 | ||
| 1695 | selections.extend(remote_selections.into_values()); | 1874 | let scrollbar_settings = EditorSettings::get_global(cx).scrollbar; |
| 1696 | } | 1875 | let show_scrollbars = match scrollbar_settings.show { |
| 1697 | 1876 | ShowScrollbar::Auto => { | |
| 1698 | let scrollbar_settings = EditorSettings::get_global(cx).scrollbar; | 1877 | // Git |
| 1699 | let show_scrollbars = match scrollbar_settings.show { | 1878 | (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) |
| 1700 | ShowScrollbar::Auto => { | 1879 | || |
| 1701 | // Git | 1880 | // Selections |
| 1702 | (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) | 1881 | (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty()) |
| 1703 | || | 1882 | // Scrollmanager |
| 1704 | // Selections | 1883 | || editor.scroll_manager.scrollbars_visible() |
| 1705 | (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty()) | 1884 | } |
| 1706 | // Scrollmanager | 1885 | ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(), |
| 1707 | || editor.scroll_manager.scrollbars_visible() | 1886 | ShowScrollbar::Always => true, |
| 1708 | } | 1887 | ShowScrollbar::Never => false, |
| 1709 | ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(), | 1888 | }; |
| 1710 | ShowScrollbar::Always => true, | ||
| 1711 | ShowScrollbar::Never => false, | ||
| 1712 | }; | ||
| 1713 | 1889 | ||
| 1714 | let head_for_relative = newest_selection_head.unwrap_or_else(|| { | 1890 | let head_for_relative = newest_selection_head.unwrap_or_else(|| { |
| 1715 | let newest = editor.selections.newest::<Point>(cx); | 1891 | let newest = editor.selections.newest::<Point>(cx); |
| 1716 | SelectionLayout::new( | 1892 | SelectionLayout::new( |
| 1717 | newest, | 1893 | newest, |
| 1718 | editor.selections.line_mode, | 1894 | editor.selections.line_mode, |
| 1719 | editor.cursor_shape, | 1895 | editor.cursor_shape, |
| 1720 | &snapshot.display_snapshot, | 1896 | &snapshot.display_snapshot, |
| 1721 | true, | 1897 | true, |
| 1722 | true, | 1898 | true, |
| 1723 | ) | 1899 | ) |
| 1724 | .head | 1900 | .head |
| 1725 | }); | 1901 | }); |
| 1726 | 1902 | ||
| 1727 | let (line_numbers, fold_statuses) = self.shape_line_numbers( | 1903 | let (line_numbers, fold_statuses) = self.shape_line_numbers( |
| 1728 | start_row..end_row, | 1904 | start_row..end_row, |
| 1729 | &active_rows, | 1905 | &active_rows, |
| 1730 | head_for_relative, | 1906 | head_for_relative, |
| 1731 | is_singleton, | 1907 | is_singleton, |
| 1732 | &snapshot, | 1908 | &snapshot, |
| 1733 | cx, | 1909 | cx, |
| 1734 | ); | 1910 | ); |
| 1735 | 1911 | ||
| 1736 | let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); | 1912 | let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); |
| 1737 | 1913 | ||
| 1738 | let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines); | 1914 | let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines); |
| 1739 | 1915 | ||
| 1740 | let mut max_visible_line_width = Pixels::ZERO; | 1916 | let mut max_visible_line_width = Pixels::ZERO; |
| 1741 | let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx); | 1917 | let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx); |
| 1742 | for line_with_invisibles in &line_layouts { | 1918 | for line_with_invisibles in &line_layouts { |
| 1743 | if line_with_invisibles.line.width > max_visible_line_width { | 1919 | if line_with_invisibles.line.width > max_visible_line_width { |
| 1744 | max_visible_line_width = line_with_invisibles.line.width; | 1920 | max_visible_line_width = line_with_invisibles.line.width; |
| 1921 | } | ||
| 1745 | } | 1922 | } |
| 1746 | } | ||
| 1747 | 1923 | ||
| 1748 | let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx) | 1924 | let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx) |
| 1749 | .unwrap() | 1925 | .unwrap() |
| 1750 | .width; | 1926 | .width; |
| 1751 | let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; | 1927 | let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; |
| 1752 | 1928 | ||
| 1753 | let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| { | 1929 | let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| { |
| 1754 | self.layout_blocks( | 1930 | self.layout_blocks( |
| 1755 | start_row..end_row, | 1931 | start_row..end_row, |
| 1756 | &snapshot, | 1932 | &snapshot, |
| 1757 | bounds.size.width, | 1933 | bounds.size.width, |
| 1758 | scroll_width, | 1934 | scroll_width, |
| 1759 | gutter_padding, | 1935 | gutter_padding, |
| 1760 | gutter_width, | 1936 | gutter_width, |
| 1761 | em_width, | 1937 | em_width, |
| 1762 | gutter_width + gutter_margin, | 1938 | gutter_width + gutter_margin, |
| 1763 | line_height, | 1939 | line_height, |
| 1764 | &style, | 1940 | &style, |
| 1765 | &line_layouts, | 1941 | &line_layouts, |
| 1766 | editor, | 1942 | editor, |
| 1767 | cx, | 1943 | cx, |
| 1768 | ) | 1944 | ) |
| 1769 | }); | 1945 | }); |
| 1770 | |||
| 1771 | let scroll_max = point( | ||
| 1772 | f32::from((scroll_width - text_size.width) / em_width).max(0.0), | ||
| 1773 | max_row as f32, | ||
| 1774 | ); | ||
| 1775 | 1946 | ||
| 1776 | let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x); | 1947 | let scroll_max = point( |
| 1948 | f32::from((scroll_width - text_size.width) / em_width).max(0.0), | ||
| 1949 | max_row as f32, | ||
| 1950 | ); | ||
| 1777 | 1951 | ||
| 1778 | let autoscrolled = if autoscroll_horizontally { | 1952 | let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x); |
| 1779 | editor.autoscroll_horizontally( | 1953 | |
| 1780 | start_row, | 1954 | let autoscrolled = if autoscroll_horizontally { |
| 1781 | text_size.width, | 1955 | editor.autoscroll_horizontally( |
| 1782 | scroll_width, | 1956 | start_row, |
| 1783 | em_width, | 1957 | text_size.width, |
| 1784 | &line_layouts, | 1958 | scroll_width, |
| 1785 | cx, | 1959 | em_width, |
| 1786 | ) | 1960 | &line_layouts, |
| 1787 | } else { | 1961 | cx, |
| 1788 | false | 1962 | ) |
| 1789 | }; | 1963 | } else { |
| 1964 | false | ||
| 1965 | }; | ||
| 1790 | 1966 | ||
| 1791 | if clamped || autoscrolled { | 1967 | if clamped || autoscrolled { |
| 1792 | snapshot = editor.snapshot(cx); | 1968 | snapshot = editor.snapshot(cx); |
| 1793 | } | 1969 | } |
| 1794 | 1970 | ||
| 1795 | let mut context_menu = None; | 1971 | let mut context_menu = None; |
| 1796 | let mut code_actions_indicator = None; | 1972 | let mut code_actions_indicator = None; |
| 1797 | if let Some(newest_selection_head) = newest_selection_head { | 1973 | if let Some(newest_selection_head) = newest_selection_head { |
| 1798 | if (start_row..end_row).contains(&newest_selection_head.row()) { | 1974 | if (start_row..end_row).contains(&newest_selection_head.row()) { |
| 1799 | if editor.context_menu_visible() { | 1975 | if editor.context_menu_visible() { |
| 1800 | context_menu = | 1976 | let max_height = (12. * line_height).min((bounds.size.height - line_height) / 2.); |
| 1801 | editor.render_context_menu(newest_selection_head, &self.style, cx); | 1977 | context_menu = |
| 1802 | } | 1978 | editor.render_context_menu(newest_selection_head, &self.style, max_height, cx); |
| 1979 | } | ||
| 1803 | 1980 | ||
| 1804 | let active = matches!( | 1981 | let active = matches!( |
| 1805 | editor.context_menu.read().as_ref(), | 1982 | editor.context_menu.read().as_ref(), |
| 1806 | Some(crate::ContextMenu::CodeActions(_)) | 1983 | Some(crate::ContextMenu::CodeActions(_)) |
| 1807 | ); | 1984 | ); |
| 1808 | 1985 | ||
| 1809 | code_actions_indicator = editor | 1986 | code_actions_indicator = editor |
| 1810 | .render_code_actions_indicator(&style, active, cx) | 1987 | .render_code_actions_indicator(&style, active, cx) |
| 1811 | .map(|element| CodeActionsIndicator { | 1988 | .map(|element| CodeActionsIndicator { |
| 1812 | row: newest_selection_head.row(), | 1989 | row: newest_selection_head.row(), |
| 1813 | element, | 1990 | button: element, |
| 1814 | }); | 1991 | }); |
| 1992 | } | ||
| 1815 | } | 1993 | } |
| 1816 | } | ||
| 1817 | 1994 | ||
| 1818 | let visible_rows = start_row..start_row + line_layouts.len() as u32; | 1995 | let visible_rows = start_row..start_row + line_layouts.len() as u32; |
| 1819 | // todo!("hover") | 1996 | let max_size = size( |
| 1820 | // let mut hover = editor.hover_state.render( | 1997 | (120. * em_width) // Default size |
| 1821 | // &snapshot, | 1998 | .min(bounds.size.width / 2.) // Shrink to half of the editor width |
| 1822 | // &style, | 1999 | .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters |
| 1823 | // visible_rows, | 2000 | (16. * line_height) // Default size |
| 1824 | // editor.workspace.as_ref().map(|(w, _)| w.clone()), | 2001 | .min(bounds.size.height / 2.) // Shrink to half of the editor height |
| 1825 | // cx, | 2002 | .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines |
| 1826 | // ); | 2003 | ); |
| 1827 | // let mode = editor.mode; | 2004 | |
| 1828 | 2005 | let mut hover = editor.hover_state.render( | |
| 1829 | let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| { | 2006 | &snapshot, |
| 1830 | editor.render_fold_indicators( | ||
| 1831 | fold_statuses, | ||
| 1832 | &style, | 2007 | &style, |
| 1833 | editor.gutter_hovered, | 2008 | visible_rows, |
| 1834 | line_height, | 2009 | max_size, |
| 1835 | gutter_margin, | 2010 | editor.workspace.as_ref().map(|(w, _)| w.clone()), |
| 1836 | cx, | 2011 | cx, |
| 1837 | ) | 2012 | ); |
| 1838 | }); | ||
| 1839 | |||
| 1840 | // todo!("context_menu") | ||
| 1841 | // if let Some((_, context_menu)) = context_menu.as_mut() { | ||
| 1842 | // context_menu.layout( | ||
| 1843 | // SizeConstraint { | ||
| 1844 | // min: gpui::Point::<Pixels>::zero(), | ||
| 1845 | // max: point( | ||
| 1846 | // cx.window_size().x * 0.7, | ||
| 1847 | // (12. * line_height).min((size.y - line_height) / 2.), | ||
| 1848 | // ), | ||
| 1849 | // }, | ||
| 1850 | // editor, | ||
| 1851 | // cx, | ||
| 1852 | // ); | ||
| 1853 | // } | ||
| 1854 | |||
| 1855 | // todo!("hover popovers") | ||
| 1856 | // if let Some((_, hover_popovers)) = hover.as_mut() { | ||
| 1857 | // for hover_popover in hover_popovers.iter_mut() { | ||
| 1858 | // hover_popover.layout( | ||
| 1859 | // SizeConstraint { | ||
| 1860 | // min: gpui::Point::<Pixels>::zero(), | ||
| 1861 | // max: point( | ||
| 1862 | // (120. * em_width) // Default size | ||
| 1863 | // .min(size.x / 2.) // Shrink to half of the editor width | ||
| 1864 | // .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters | ||
| 1865 | // (16. * line_height) // Default size | ||
| 1866 | // .min(size.y / 2.) // Shrink to half of the editor height | ||
| 1867 | // .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines | ||
| 1868 | // ), | ||
| 1869 | // }, | ||
| 1870 | // editor, | ||
| 1871 | // cx, | ||
| 1872 | // ); | ||
| 1873 | // } | ||
| 1874 | // } | ||
| 1875 | 2013 | ||
| 1876 | let invisible_symbol_font_size = font_size / 2.; | 2014 | let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| { |
| 1877 | let tab_invisible = cx | 2015 | editor.render_fold_indicators( |
| 1878 | .text_system() | 2016 | fold_statuses, |
| 1879 | .shape_line( | 2017 | &style, |
| 1880 | "→".into(), | 2018 | editor.gutter_hovered, |
| 1881 | invisible_symbol_font_size, | 2019 | line_height, |
| 1882 | &[TextRun { | 2020 | gutter_margin, |
| 1883 | len: "→".len(), | 2021 | cx, |
| 1884 | font: self.style.text.font(), | 2022 | ) |
| 1885 | color: cx.theme().colors().editor_invisible, | 2023 | }); |
| 1886 | background_color: None, | ||
| 1887 | underline: None, | ||
| 1888 | }], | ||
| 1889 | ) | ||
| 1890 | .unwrap(); | ||
| 1891 | let space_invisible = cx | ||
| 1892 | .text_system() | ||
| 1893 | .shape_line( | ||
| 1894 | "•".into(), | ||
| 1895 | invisible_symbol_font_size, | ||
| 1896 | &[TextRun { | ||
| 1897 | len: "•".len(), | ||
| 1898 | font: self.style.text.font(), | ||
| 1899 | color: cx.theme().colors().editor_invisible, | ||
| 1900 | background_color: None, | ||
| 1901 | underline: None, | ||
| 1902 | }], | ||
| 1903 | ) | ||
| 1904 | .unwrap(); | ||
| 1905 | 2024 | ||
| 1906 | LayoutState { | 2025 | let invisible_symbol_font_size = font_size / 2.; |
| 1907 | mode: editor_mode, | 2026 | let tab_invisible = cx |
| 1908 | position_map: Arc::new(PositionMap { | 2027 | .text_system() |
| 1909 | size: bounds.size, | 2028 | .shape_line( |
| 1910 | scroll_position: point( | 2029 | "→".into(), |
| 1911 | scroll_position.x * em_width, | 2030 | invisible_symbol_font_size, |
| 1912 | scroll_position.y * line_height, | 2031 | &[TextRun { |
| 1913 | ), | 2032 | len: "→".len(), |
| 1914 | scroll_max, | 2033 | font: self.style.text.font(), |
| 1915 | line_layouts, | 2034 | color: cx.theme().colors().editor_invisible, |
| 1916 | line_height, | 2035 | background_color: None, |
| 1917 | em_width, | 2036 | underline: None, |
| 1918 | em_advance, | 2037 | }], |
| 1919 | snapshot, | 2038 | ) |
| 1920 | }), | 2039 | .unwrap(); |
| 1921 | visible_anchor_range: start_anchor..end_anchor, | 2040 | let space_invisible = cx |
| 1922 | visible_display_row_range: start_row..end_row, | 2041 | .text_system() |
| 1923 | wrap_guides, | 2042 | .shape_line( |
| 1924 | gutter_size, | 2043 | "•".into(), |
| 1925 | gutter_padding, | 2044 | invisible_symbol_font_size, |
| 1926 | text_size, | 2045 | &[TextRun { |
| 1927 | scrollbar_row_range, | 2046 | len: "•".len(), |
| 1928 | show_scrollbars, | 2047 | font: self.style.text.font(), |
| 1929 | is_singleton, | 2048 | color: cx.theme().colors().editor_invisible, |
| 1930 | max_row, | 2049 | background_color: None, |
| 1931 | gutter_margin, | 2050 | underline: None, |
| 1932 | active_rows, | 2051 | }], |
| 1933 | highlighted_rows, | 2052 | ) |
| 1934 | highlighted_ranges, | 2053 | .unwrap(); |
| 1935 | line_numbers, | 2054 | |
| 1936 | display_hunks, | 2055 | LayoutState { |
| 1937 | blocks, | 2056 | mode: editor_mode, |
| 1938 | selections, | 2057 | position_map: Arc::new(PositionMap { |
| 1939 | context_menu, | 2058 | size: bounds.size, |
| 1940 | code_actions_indicator, | 2059 | scroll_position: point( |
| 1941 | fold_indicators, | 2060 | scroll_position.x * em_width, |
| 1942 | tab_invisible, | 2061 | scroll_position.y * line_height, |
| 1943 | space_invisible, | 2062 | ), |
| 1944 | // hover_popovers: hover, | 2063 | scroll_max, |
| 1945 | } | 2064 | line_layouts, |
| 2065 | line_height, | ||
| 2066 | em_width, | ||
| 2067 | em_advance, | ||
| 2068 | snapshot, | ||
| 2069 | }), | ||
| 2070 | visible_anchor_range: start_anchor..end_anchor, | ||
| 2071 | visible_display_row_range: start_row..end_row, | ||
| 2072 | wrap_guides, | ||
| 2073 | gutter_size, | ||
| 2074 | gutter_padding, | ||
| 2075 | text_size, | ||
| 2076 | scrollbar_row_range, | ||
| 2077 | show_scrollbars, | ||
| 2078 | is_singleton, | ||
| 2079 | max_row, | ||
| 2080 | gutter_margin, | ||
| 2081 | active_rows, | ||
| 2082 | highlighted_rows, | ||
| 2083 | highlighted_ranges, | ||
| 2084 | line_numbers, | ||
| 2085 | display_hunks, | ||
| 2086 | blocks, | ||
| 2087 | selections, | ||
| 2088 | context_menu, | ||
| 2089 | code_actions_indicator, | ||
| 2090 | fold_indicators, | ||
| 2091 | tab_invisible, | ||
| 2092 | space_invisible, | ||
| 2093 | hover_popovers: hover, | ||
| 2094 | } | ||
| 2095 | }) | ||
| 1946 | } | 2096 | } |
| 1947 | 2097 | ||
| 1948 | #[allow(clippy::too_many_arguments)] | 2098 | #[allow(clippy::too_many_arguments)] |
| 1949 | fn layout_blocks( | 2099 | fn layout_blocks( |
| 1950 | &mut self, | 2100 | &self, |
| 1951 | rows: Range<u32>, | 2101 | rows: Range<u32>, |
| 1952 | snapshot: &EditorSnapshot, | 2102 | snapshot: &EditorSnapshot, |
| 1953 | editor_width: Pixels, | 2103 | editor_width: Pixels, |
| @@ -2028,12 +2178,10 @@ impl EditorElement { · 74 unmodified lines | |||
| 2028 | let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); | 2178 | let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); |
| 2029 | 2179 | ||
| 2030 | IconButton::new(block_id, ui::Icon::ArrowUpRight) | 2180 | IconButton::new(block_id, ui::Icon::ArrowUpRight) |
| 2031 | .on_click(move |editor: &mut Editor, cx| { | 2181 | .on_click(cx.listener_for(&self.editor, move |editor, e, cx| { |
| 2032 | editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); | 2182 | editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); |
| 2033 | }) | 2183 | })) |
| 2034 | .tooltip(move |_, cx| { | 2184 | .tooltip(|cx| Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx)) |
| 2035 | Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx) | ||
| 2036 | }) | ||
| 2037 | }); | 2185 | }); |
| 2038 | 2186 | ||
| 2039 | let element = if *starts_new_buffer { | 2187 | let element = if *starts_new_buffer { |
| @@ -2043,15 +2191,20 @@ impl EditorElement { · 3 unmodified lines | |||
| 2043 | // Can't use .and_then() because `.file_name()` and `.parent()` return references :( | 2191 | // Can't use .and_then() because `.file_name()` and `.parent()` return references :( |
| 2044 | if let Some(path) = path { | 2192 | if let Some(path) = path { |
| 2045 | filename = path.file_name().map(|f| f.to_string_lossy().to_string()); | 2193 | filename = path.file_name().map(|f| f.to_string_lossy().to_string()); |
| 2046 | parent_path = | 2194 | parent_path = path |
| 2047 | path.parent().map(|p| p.to_string_lossy().to_string() + "/"); | 2195 | .parent() |
| 2196 | .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/")); | ||
| 2048 | } | 2197 | } |
| 2049 | 2198 | ||
| 2050 | h_stack() | 2199 | h_stack() |
| 2051 | .id("path header block") | 2200 | .id("path header block") |
| 2052 | .size_full() | 2201 | .size_full() |
| 2053 | .bg(gpui::red()) | 2202 | .bg(gpui::red()) |
| 2054 | .child(filename.unwrap_or_else(|| "untitled".to_string())) | 2203 | .child( |
| 2204 | filename | ||
| 2205 | .map(SharedString::from) | ||
| 2206 | .unwrap_or_else(|| "untitled".into()), | ||
| 2207 | ) | ||
| 2055 | .children(parent_path) | 2208 | .children(parent_path) |
| 2056 | .children(jump_icon) // .p_x(gutter_padding) | 2209 | .children(jump_icon) // .p_x(gutter_padding) |
| 2057 | } else { | 2210 | } else { |
| @@ -2063,11 +2216,11 @@ impl EditorElement { · 5 unmodified lines | |||
| 2063 | .child("⋯") | 2216 | .child("⋯") |
| 2064 | .children(jump_icon) // .p_x(gutter_padding) | 2217 | .children(jump_icon) // .p_x(gutter_padding) |
| 2065 | }; | 2218 | }; |
| 2066 | element.render() | 2219 | element.into_any() |
| 2067 | } | 2220 | } |
| 2068 | }; | 2221 | }; |
| 2069 | 2222 | ||
| 2070 | let size = element.measure(available_space, editor, cx); | 2223 | let size = element.measure(available_space, cx); |
| 2071 | (element, size) | 2224 | (element, size) |
| 2072 | }; | 2225 | }; |
| 2073 | 2226 | ||
| @@ -2126,47 +2279,61 @@ impl EditorElement { · 52 unmodified lines | |||
| 2126 | gutter_bounds: Bounds<Pixels>, | 2279 | gutter_bounds: Bounds<Pixels>, |
| 2127 | text_bounds: Bounds<Pixels>, | 2280 | text_bounds: Bounds<Pixels>, |
| 2128 | layout: &LayoutState, | 2281 | layout: &LayoutState, |
| 2129 | cx: &mut ViewContext<Editor>, | 2282 | cx: &mut WindowContext, |
| 2130 | ) { | 2283 | ) { |
| 2131 | let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); | 2284 | let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); |
| 2132 | 2285 | ||
| 2133 | cx.on_mouse_event({ | 2286 | cx.on_mouse_event({ |
| 2134 | let position_map = layout.position_map.clone(); | 2287 | let position_map = layout.position_map.clone(); |
| 2135 | move |editor, event: &ScrollWheelEvent, phase, cx| { | 2288 | let editor = self.editor.clone(); |
| 2289 | |||
| 2290 | move |event: &ScrollWheelEvent, phase, cx| { | ||
| 2136 | if phase != DispatchPhase::Bubble { | 2291 | if phase != DispatchPhase::Bubble { |
| 2137 | return; | 2292 | return; |
| 2138 | } | 2293 | } |
| 2139 | 2294 | ||
| 2140 | if Self::scroll(editor, event, &position_map, bounds, cx) { | 2295 | let should_cancel = editor.update(cx, |editor, cx| { |
| 2296 | Self::scroll(editor, event, &position_map, bounds, cx) | ||
| 2297 | }); | ||
| 2298 | if should_cancel { | ||
| 2141 | cx.stop_propagation(); | 2299 | cx.stop_propagation(); |
| 2142 | } | 2300 | } |
| 2143 | } | 2301 | } |
| 2144 | }); | 2302 | }); |
| 2303 | |||
| 2145 | cx.on_mouse_event({ | 2304 | cx.on_mouse_event({ |
| 2146 | let position_map = layout.position_map.clone(); | 2305 | let position_map = layout.position_map.clone(); |
| 2147 | move |editor, event: &MouseDownEvent, phase, cx| { | 2306 | let editor = self.editor.clone(); |
| 2307 | |||
| 2308 | move |event: &MouseDownEvent, phase, cx| { | ||
| 2148 | if phase != DispatchPhase::Bubble { | 2309 | if phase != DispatchPhase::Bubble { |
| 2149 | return; | 2310 | return; |
| 2150 | } | 2311 | } |
| 2151 | 2312 | ||
| 2152 | if Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) { | 2313 | let should_cancel = editor.update(cx, |editor, cx| { |
| 2314 | Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) | ||
| 2315 | }); | ||
| 2316 | |||
| 2317 | if should_cancel { | ||
| 2153 | cx.stop_propagation() | 2318 | cx.stop_propagation() |
| 2154 | } | 2319 | } |
| 2155 | } | 2320 | } |
| 2156 | }); | 2321 | }); |
| 2322 | |||
| 2157 | cx.on_mouse_event({ | 2323 | cx.on_mouse_event({ |
| 2158 | let position_map = layout.position_map.clone(); | 2324 | let position_map = layout.position_map.clone(); |
| 2159 | move |editor, event: &MouseUpEvent, phase, cx| { | 2325 | let editor = self.editor.clone(); |
| 2160 | if phase != DispatchPhase::Bubble { | 2326 | move |event: &MouseUpEvent, phase, cx| { |
| 2161 | return; | 2327 | let should_cancel = editor.update(cx, |editor, cx| { |
| 2162 | } | 2328 | Self::mouse_up(editor, event, &position_map, text_bounds, cx) |
| 2329 | }); | ||
| 2163 | 2330 | ||
| 2164 | if Self::mouse_up(editor, event, &position_map, text_bounds, cx) { | 2331 | if should_cancel { |
| 2165 | cx.stop_propagation() | 2332 | cx.stop_propagation() |
| 2166 | } | 2333 | } |
| 2167 | } | 2334 | } |
| 2168 | }); | 2335 | }); |
| 2169 | // todo!() | 2336 | //todo!() |
| 2170 | // on_down(MouseButton::Right, { | 2337 | // on_down(MouseButton::Right, { |
| 2171 | // let position_map = layout.position_map.clone(); | 2338 | // let position_map = layout.position_map.clone(); |
| 2172 | // move |event, editor, cx| { | 2339 | // move |event, editor, cx| { |
| @@ -2183,12 +2350,17 @@ impl EditorElement { · 10 unmodified lines | |||
| 2183 | // }); | 2350 | // }); |
| 2184 | cx.on_mouse_event({ | 2351 | cx.on_mouse_event({ |
| 2185 | let position_map = layout.position_map.clone(); | 2352 | let position_map = layout.position_map.clone(); |
| 2186 | move |editor, event: &MouseMoveEvent, phase, cx| { | 2353 | let editor = self.editor.clone(); |
| 2354 | move |event: &MouseMoveEvent, phase, cx| { | ||
| 2187 | if phase != DispatchPhase::Bubble { | 2355 | if phase != DispatchPhase::Bubble { |
| 2188 | return; | 2356 | return; |
| 2189 | } | 2357 | } |
| 2190 | 2358 | ||
| 2191 | if Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) { | 2359 | let stop_propogating = editor.update(cx, |editor, cx| { |
| 2360 | Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) | ||
| 2361 | }); | ||
| 2362 | |||
| 2363 | if stop_propogating { | ||
| 2192 | cx.stop_propagation() | 2364 | cx.stop_propagation() |
| 2193 | } | 2365 | } |
| 2194 | } | 2366 | } |
| @@ -2267,7 +2439,7 @@ impl LineWithInvisibles { · 72 unmodified lines | |||
| 2267 | len: line_chunk.len(), | 2439 | len: line_chunk.len(), |
| 2268 | font: text_style.font(), | 2440 | font: text_style.font(), |
| 2269 | color: text_style.color, | 2441 | color: text_style.color, |
| 2270 | background_color: None, | 2442 | background_color: text_style.background_color, |
| 2271 | underline: text_style.underline, | 2443 | underline: text_style.underline, |
| 2272 | }); | 2444 | }); |
| 2273 | 2445 | ||
| @@ -2317,7 +2489,7 @@ impl LineWithInvisibles { · 43 unmodified lines | |||
| 2317 | content_origin: gpui::Point<Pixels>, | 2489 | content_origin: gpui::Point<Pixels>, |
| 2318 | whitespace_setting: ShowWhitespaceSetting, | 2490 | whitespace_setting: ShowWhitespaceSetting, |
| 2319 | selection_ranges: &[Range<DisplayPoint>], | 2491 | selection_ranges: &[Range<DisplayPoint>], |
| 2320 | cx: &mut ViewContext<Editor>, | 2492 | cx: &mut WindowContext, |
| 2321 | ) { | 2493 | ) { |
| 2322 | let line_height = layout.position_map.line_height; | 2494 | let line_height = layout.position_map.line_height; |
| 2323 | let line_y = line_height * row as f32 - layout.position_map.scroll_position.y; | 2495 | let line_y = line_height * row as f32 - layout.position_map.scroll_position.y; |
| @@ -2349,7 +2521,7 @@ impl LineWithInvisibles { · 25 unmodified lines | |||
| 2349 | row: u32, | 2521 | row: u32, |
| 2350 | line_height: Pixels, | 2522 | line_height: Pixels, |
| 2351 | whitespace_setting: ShowWhitespaceSetting, | 2523 | whitespace_setting: ShowWhitespaceSetting, |
| 2352 | cx: &mut ViewContext<Editor>, | 2524 | cx: &mut WindowContext, |
| 2353 | ) { | 2525 | ) { |
| 2354 | let allowed_invisibles_regions = match whitespace_setting { | 2526 | let allowed_invisibles_regions = match whitespace_setting { |
| 2355 | ShowWhitespaceSetting::None => return, | 2527 | ShowWhitespaceSetting::None => return, |
| @@ -2392,41 +2564,42 @@ enum Invisible { · 36 unmodified lines | |||
| 2392 | Whitespace { line_offset: usize }, | 2564 | Whitespace { line_offset: usize }, |
| 2393 | } | 2565 | } |
| 2394 | 2566 | ||
| 2395 | impl Element<Editor> for EditorElement { | 2567 | impl Element for EditorElement { |
| 2396 | type ElementState = (); | 2568 | type State = (); |
| 2397 | |||
| 2398 | fn element_id(&self) -> Option<gpui::ElementId> { | ||
| 2399 | Some(self.editor_id.into()) | ||
| 2400 | } | ||
| 2401 | 2569 | ||
| 2402 | fn layout( | 2570 | fn layout( |
| 2403 | &mut self, | 2571 | &mut self, |
| 2404 | editor: &mut Editor, | 2572 | element_state: Option<Self::State>, |
| 2405 | element_state: Option<Self::ElementState>, | 2573 | cx: &mut gpui::WindowContext, |
| 2406 | cx: &mut gpui::ViewContext<Editor>, | 2574 | ) -> (gpui::LayoutId, Self::State) { |
| 2407 | ) -> (gpui::LayoutId, Self::ElementState) { | 2575 | self.editor.update(cx, |editor, cx| { |
| 2408 | editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this. | 2576 | editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this. |
| 2409 | 2577 | ||
| 2410 | let rem_size = cx.rem_size(); | 2578 | let rem_size = cx.rem_size(); |
| 2411 | let mut style = Style::default(); | 2579 | let mut style = Style::default(); |
| 2412 | style.size.width = relative(1.).into(); | 2580 | style.size.width = relative(1.).into(); |
| 2413 | style.size.height = match editor.mode { | 2581 | style.size.height = match editor.mode { |
| 2414 | EditorMode::SingleLine => self.style.text.line_height_in_pixels(cx.rem_size()).into(), | 2582 | EditorMode::SingleLine => { |
| 2415 | EditorMode::AutoHeight { .. } => todo!(), | 2583 | self.style.text.line_height_in_pixels(cx.rem_size()).into() |
| 2416 | EditorMode::Full => relative(1.).into(), | 2584 | } |
| 2417 | }; | 2585 | EditorMode::AutoHeight { .. } => todo!(), |
| 2418 | let layout_id = cx.request_layout(&style, None); | 2586 | EditorMode::Full => relative(1.).into(), |
| 2419 | (layout_id, ()) | 2587 | }; |
| 2588 | let layout_id = cx.request_layout(&style, None); | ||
| 2589 | |||
| 2590 | (layout_id, ()) | ||
| 2591 | }) | ||
| 2420 | } | 2592 | } |
| 2421 | 2593 | ||
| 2422 | fn paint( | 2594 | fn paint( |
| 2423 | &mut self, | 2595 | mut self, |
| 2424 | bounds: Bounds<gpui::Pixels>, | 2596 | bounds: Bounds<gpui::Pixels>, |
| 2425 | editor: &mut Editor, | 2597 | element_state: &mut Self::State, |
| 2426 | element_state: &mut Self::ElementState, | 2598 | cx: &mut gpui::WindowContext, |
| 2427 | cx: &mut gpui::ViewContext<Editor>, | ||
| 2428 | ) { | 2599 | ) { |
| 2429 | let mut layout = self.compute_layout(editor, cx, bounds); | 2600 | let editor = self.editor.clone(); |
| 2601 | |||
| 2602 | let mut layout = self.compute_layout(bounds, cx); | ||
| 2430 | let gutter_bounds = Bounds { | 2603 | let gutter_bounds = Bounds { |
| 2431 | origin: bounds.origin, | 2604 | origin: bounds.origin, |
| 2432 | size: layout.gutter_size, | 2605 | size: layout.gutter_size, |
| @@ -2436,43 +2609,46 @@ impl Element<Editor> for EditorElement { · 3 unmodified lines | |||
| 2436 | size: layout.text_size, | 2609 | size: layout.text_size, |
| 2437 | }; | 2610 | }; |
| 2438 | 2611 | ||
| 2439 | let dispatch_context = editor.dispatch_context(cx); | 2612 | let focus_handle = editor.focus_handle(cx); |
| 2440 | cx.with_key_dispatch( | 2613 | let dispatch_context = self.editor.read(cx).dispatch_context(cx); |
| 2441 | dispatch_context, | 2614 | cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| { |
| 2442 | Some(editor.focus_handle.clone()), | 2615 | self.register_actions(cx); |
| 2443 | |_, cx| { | 2616 | |
| 2444 | register_actions(cx); | 2617 | // We call with_z_index to establish a new stacking context. |
| 2445 | 2618 | cx.with_z_index(0, |cx| { | |
| 2446 | // We call with_z_index to establish a new stacking context. | 2619 | cx.with_content_mask(Some(ContentMask { bounds }), |cx| { |
| 2447 | cx.with_z_index(0, |cx| { | 2620 | // Paint mouse listeners first, so any elements we paint on top of the editor |
| 2448 | cx.with_content_mask(Some(ContentMask { bounds }), |cx| { | 2621 | // take precedence. |
| 2449 | // Paint mouse listeners first, so any elements we paint on top of the editor | 2622 | self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); |
| 2450 | // take precedence. | 2623 | let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); |
| 2451 | self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); | 2624 | cx.handle_input(&focus_handle, input_handler); |
| 2452 | let input_handler = ElementInputHandler::new(bounds, cx); | 2625 | |
| 2453 | cx.handle_input(&editor.focus_handle, input_handler); | 2626 | self.paint_background(gutter_bounds, text_bounds, &layout, cx); |
| 2454 | 2627 | if layout.gutter_size.width > Pixels::ZERO { | |
| 2455 | self.paint_background(gutter_bounds, text_bounds, &layout, cx); | 2628 | self.paint_gutter(gutter_bounds, &mut layout, cx); |
| 2456 | if layout.gutter_size.width > Pixels::ZERO { | 2629 | } |
| 2457 | self.paint_gutter(gutter_bounds, &mut layout, editor, cx); | 2630 | self.paint_text(text_bounds, &mut layout, cx); |
| 2458 | } | ||
| 2459 | self.paint_text(text_bounds, &mut layout, editor, cx); | ||
| 2460 | 2631 | ||
| 2461 | if !layout.blocks.is_empty() { | 2632 | if !layout.blocks.is_empty() { |
| 2462 | cx.with_element_id(Some("editor_blocks"), |cx| { | 2633 | cx.with_element_id(Some("editor_blocks"), |cx| { |
| 2463 | self.paint_blocks(bounds, &mut layout, editor, cx); | 2634 | self.paint_blocks(bounds, &mut layout, cx); |
| 2464 | }) | 2635 | }) |
| 2465 | } | 2636 | } |
| 2466 | }); | ||
| 2467 | }); | 2637 | }); |
| 2468 | }, | 2638 | }); |
| 2469 | ) | 2639 | }) |
| 2470 | } | 2640 | } |
| 2471 | } | 2641 | } |
| 2472 | 2642 | ||
| 2473 | impl Component<Editor> for EditorElement { | 2643 | impl IntoElement for EditorElement { |
| 2474 | fn render(self) -> AnyElement<Editor> { | 2644 | type Element = Self; |
| 2475 | AnyElement::new(self) | 2645 | |
| 2646 | fn element_id(&self) -> Option<gpui::ElementId> { | ||
| 2647 | self.editor.element_id() | ||
| 2648 | } | ||
| 2649 | |||
| 2650 | fn into_element(self) -> Self::Element { | ||
| 2651 | self | ||
| 2476 | } | 2652 | } |
| 2477 | } | 2653 | } |
| 2478 | 2654 | ||
| @@ -3097,17 +3273,17 @@ pub struct LayoutState { · 618 unmodified lines | |||
| 3097 | show_scrollbars: bool, | 3273 | show_scrollbars: bool, |
| 3098 | is_singleton: bool, | 3274 | is_singleton: bool, |
| 3099 | max_row: u32, | 3275 | max_row: u32, |
| 3100 | context_menu: Option<(DisplayPoint, AnyElement<Editor>)>, | 3276 | context_menu: Option<(DisplayPoint, AnyElement)>, |
| 3101 | code_actions_indicator: Option<CodeActionsIndicator>, | 3277 | code_actions_indicator: Option<CodeActionsIndicator>, |
| 3102 | // hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>, | 3278 | hover_popovers: Option<(DisplayPoint, Vec<AnyElement>)>, |
| 3103 | fold_indicators: Vec<Option<AnyElement<Editor>>>, | 3279 | fold_indicators: Vec<Option<IconButton>>, |
| 3104 | tab_invisible: ShapedLine, | 3280 | tab_invisible: ShapedLine, |
| 3105 | space_invisible: ShapedLine, | 3281 | space_invisible: ShapedLine, |
| 3106 | } | 3282 | } |
| 3107 | 3283 | ||
| 3108 | struct CodeActionsIndicator { | 3284 | struct CodeActionsIndicator { |
| 3109 | row: u32, | 3285 | row: u32, |
| 3110 | element: AnyElement<Editor>, | 3286 | button: IconButton, |
| 3111 | } | 3287 | } |
| 3112 | 3288 | ||
| 3113 | struct PositionMap { | 3289 | struct PositionMap { |
| @@ -3192,7 +3368,7 @@ impl PositionMap { · 78 unmodified lines | |||
| 3192 | 3368 | ||
| 3193 | struct BlockLayout { | 3369 | struct BlockLayout { |
| 3194 | row: u32, | 3370 | row: u32, |
| 3195 | element: AnyElement<Editor>, | 3371 | element: AnyElement, |
| 3196 | available_space: Size<AvailableSpace>, | 3372 | available_space: Size<AvailableSpace>, |
| 3197 | style: BlockStyle, | 3373 | style: BlockStyle, |
| 3198 | } | 3374 | } |
| @@ -3897,187 +4073,18 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { · 698 unmodified lines | |||
| 3897 | // } | 4073 | // } |
| 3898 | // } | 4074 | // } |
| 3899 | 4075 | ||
| 3900 | fn register_actions(cx: &mut ViewContext<Editor>) { | 4076 | pub fn register_action<T: Action>( |
| 3901 | register_action(cx, Editor::move_left); | 4077 | view: &View<Editor>, |
| 3902 | register_action(cx, Editor::move_right); | 4078 | cx: &mut WindowContext, |
| 3903 | register_action(cx, Editor::move_down); | ||
| 3904 | register_action(cx, Editor::move_up); | ||
| 3905 | // on_action(cx, Editor::new_file); todo!() | ||
| 3906 | // on_action(cx, Editor::new_file_in_direction); todo!() | ||
| 3907 | register_action(cx, Editor::cancel); | ||
| 3908 | register_action(cx, Editor::newline); | ||
| 3909 | register_action(cx, Editor::newline_above); | ||
| 3910 | register_action(cx, Editor::newline_below); | ||
| 3911 | register_action(cx, Editor::backspace); | ||
| 3912 | register_action(cx, Editor::delete); | ||
| 3913 | register_action(cx, Editor::tab); | ||
| 3914 | register_action(cx, Editor::tab_prev); | ||
| 3915 | register_action(cx, Editor::indent); | ||
| 3916 | register_action(cx, Editor::outdent); | ||
| 3917 | register_action(cx, Editor::delete_line); | ||
| 3918 | register_action(cx, Editor::join_lines); | ||
| 3919 | register_action(cx, Editor::sort_lines_case_sensitive); | ||
| 3920 | register_action(cx, Editor::sort_lines_case_insensitive); | ||
| 3921 | register_action(cx, Editor::reverse_lines); | ||
| 3922 | register_action(cx, Editor::shuffle_lines); | ||
| 3923 | register_action(cx, Editor::convert_to_upper_case); | ||
| 3924 | register_action(cx, Editor::convert_to_lower_case); | ||
| 3925 | register_action(cx, Editor::convert_to_title_case); | ||
| 3926 | register_action(cx, Editor::convert_to_snake_case); | ||
| 3927 | register_action(cx, Editor::convert_to_kebab_case); | ||
| 3928 | register_action(cx, Editor::convert_to_upper_camel_case); | ||
| 3929 | register_action(cx, Editor::convert_to_lower_camel_case); | ||
| 3930 | register_action(cx, Editor::delete_to_previous_word_start); | ||
| 3931 | register_action(cx, Editor::delete_to_previous_subword_start); | ||
| 3932 | register_action(cx, Editor::delete_to_next_word_end); | ||
| 3933 | register_action(cx, Editor::delete_to_next_subword_end); | ||
| 3934 | register_action(cx, Editor::delete_to_beginning_of_line); | ||
| 3935 | register_action(cx, Editor::delete_to_end_of_line); | ||
| 3936 | register_action(cx, Editor::cut_to_end_of_line); | ||
| 3937 | register_action(cx, Editor::duplicate_line); | ||
| 3938 | register_action(cx, Editor::move_line_up); | ||
| 3939 | register_action(cx, Editor::move_line_down); | ||
| 3940 | register_action(cx, Editor::transpose); | ||
| 3941 | register_action(cx, Editor::cut); | ||
| 3942 | register_action(cx, Editor::copy); | ||
| 3943 | register_action(cx, Editor::paste); | ||
| 3944 | register_action(cx, Editor::undo); | ||
| 3945 | register_action(cx, Editor::redo); | ||
| 3946 | register_action(cx, Editor::move_page_up); | ||
| 3947 | register_action(cx, Editor::move_page_down); | ||
| 3948 | register_action(cx, Editor::next_screen); | ||
| 3949 | register_action(cx, Editor::scroll_cursor_top); | ||
| 3950 | register_action(cx, Editor::scroll_cursor_center); | ||
| 3951 | register_action(cx, Editor::scroll_cursor_bottom); | ||
| 3952 | register_action(cx, |editor, _: &LineDown, cx| { | ||
| 3953 | editor.scroll_screen(&ScrollAmount::Line(1.), cx) | ||
| 3954 | }); | ||
| 3955 | register_action(cx, |editor, _: &LineUp, cx| { | ||
| 3956 | editor.scroll_screen(&ScrollAmount::Line(-1.), cx) | ||
| 3957 | }); | ||
| 3958 | register_action(cx, |editor, _: &HalfPageDown, cx| { | ||
| 3959 | editor.scroll_screen(&ScrollAmount::Page(0.5), cx) | ||
| 3960 | }); | ||
| 3961 | register_action(cx, |editor, _: &HalfPageUp, cx| { | ||
| 3962 | editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) | ||
| 3963 | }); | ||
| 3964 | register_action(cx, |editor, _: &PageDown, cx| { | ||
| 3965 | editor.scroll_screen(&ScrollAmount::Page(1.), cx) | ||
| 3966 | }); | ||
| 3967 | register_action(cx, |editor, _: &PageUp, cx| { | ||
| 3968 | editor.scroll_screen(&ScrollAmount::Page(-1.), cx) | ||
| 3969 | }); | ||
| 3970 | register_action(cx, Editor::move_to_previous_word_start); | ||
| 3971 | register_action(cx, Editor::move_to_previous_subword_start); | ||
| 3972 | register_action(cx, Editor::move_to_next_word_end); | ||
| 3973 | register_action(cx, Editor::move_to_next_subword_end); | ||
| 3974 | register_action(cx, Editor::move_to_beginning_of_line); | ||
| 3975 | register_action(cx, Editor::move_to_end_of_line); | ||
| 3976 | register_action(cx, Editor::move_to_start_of_paragraph); | ||
| 3977 | register_action(cx, Editor::move_to_end_of_paragraph); | ||
| 3978 | register_action(cx, Editor::move_to_beginning); | ||
| 3979 | register_action(cx, Editor::move_to_end); | ||
| 3980 | register_action(cx, Editor::select_up); | ||
| 3981 | register_action(cx, Editor::select_down); | ||
| 3982 | register_action(cx, Editor::select_left); | ||
| 3983 | register_action(cx, Editor::select_right); | ||
| 3984 | register_action(cx, Editor::select_to_previous_word_start); | ||
| 3985 | register_action(cx, Editor::select_to_previous_subword_start); | ||
| 3986 | register_action(cx, Editor::select_to_next_word_end); | ||
| 3987 | register_action(cx, Editor::select_to_next_subword_end); | ||
| 3988 | register_action(cx, Editor::select_to_beginning_of_line); | ||
| 3989 | register_action(cx, Editor::select_to_end_of_line); | ||
| 3990 | register_action(cx, Editor::select_to_start_of_paragraph); | ||
| 3991 | register_action(cx, Editor::select_to_end_of_paragraph); | ||
| 3992 | register_action(cx, Editor::select_to_beginning); | ||
| 3993 | register_action(cx, Editor::select_to_end); | ||
| 3994 | register_action(cx, Editor::select_all); | ||
| 3995 | register_action(cx, |editor, action, cx| { | ||
| 3996 | editor.select_all_matches(action, cx).log_err(); | ||
| 3997 | }); | ||
| 3998 | register_action(cx, Editor::select_line); | ||
| 3999 | register_action(cx, Editor::split_selection_into_lines); | ||
| 4000 | register_action(cx, Editor::add_selection_above); | ||
| 4001 | register_action(cx, Editor::add_selection_below); | ||
| 4002 | register_action(cx, |editor, action, cx| { | ||
| 4003 | editor.select_next(action, cx).log_err(); | ||
| 4004 | }); | ||
| 4005 | register_action(cx, |editor, action, cx| { | ||
| 4006 | editor.select_previous(action, cx).log_err(); | ||
| 4007 | }); | ||
| 4008 | register_action(cx, Editor::toggle_comments); | ||
| 4009 | register_action(cx, Editor::select_larger_syntax_node); | ||
| 4010 | register_action(cx, Editor::select_smaller_syntax_node); | ||
| 4011 | register_action(cx, Editor::move_to_enclosing_bracket); | ||
| 4012 | register_action(cx, Editor::undo_selection); | ||
| 4013 | register_action(cx, Editor::redo_selection); | ||
| 4014 | register_action(cx, Editor::go_to_diagnostic); | ||
| 4015 | register_action(cx, Editor::go_to_prev_diagnostic); | ||
| 4016 | register_action(cx, Editor::go_to_hunk); | ||
| 4017 | register_action(cx, Editor::go_to_prev_hunk); | ||
| 4018 | register_action(cx, Editor::go_to_definition); | ||
| 4019 | register_action(cx, Editor::go_to_definition_split); | ||
| 4020 | register_action(cx, Editor::go_to_type_definition); | ||
| 4021 | register_action(cx, Editor::go_to_type_definition_split); | ||
| 4022 | register_action(cx, Editor::fold); | ||
| 4023 | register_action(cx, Editor::fold_at); | ||
| 4024 | register_action(cx, Editor::unfold_lines); | ||
| 4025 | register_action(cx, Editor::unfold_at); | ||
| 4026 | register_action(cx, Editor::fold_selected_ranges); | ||
| 4027 | register_action(cx, Editor::show_completions); | ||
| 4028 | register_action(cx, Editor::toggle_code_actions); | ||
| 4029 | // on_action(cx, Editor::open_excerpts); todo!() | ||
| 4030 | register_action(cx, Editor::toggle_soft_wrap); | ||
| 4031 | register_action(cx, Editor::toggle_inlay_hints); | ||
| 4032 | register_action(cx, Editor::reveal_in_finder); | ||
| 4033 | register_action(cx, Editor::copy_path); | ||
| 4034 | register_action(cx, Editor::copy_relative_path); | ||
| 4035 | register_action(cx, Editor::copy_highlight_json); | ||
| 4036 | register_action(cx, |editor, action, cx| { | ||
| 4037 | editor | ||
| 4038 | .format(action, cx) | ||
| 4039 | .map(|task| task.detach_and_log_err(cx)); | ||
| 4040 | }); | ||
| 4041 | register_action(cx, Editor::restart_language_server); | ||
| 4042 | register_action(cx, Editor::show_character_palette); | ||
| 4043 | // on_action(cx, Editor::confirm_completion); todo!() | ||
| 4044 | register_action(cx, |editor, action, cx| { | ||
| 4045 | editor | ||
| 4046 | .confirm_code_action(action, cx) | ||
| 4047 | .map(|task| task.detach_and_log_err(cx)); | ||
| 4048 | }); | ||
| 4049 | register_action(cx, |editor, action, cx| { | ||
| 4050 | editor | ||
| 4051 | .rename(action, cx) | ||
| 4052 | .map(|task| task.detach_and_log_err(cx)); | ||
| 4053 | }); | ||
| 4054 | register_action(cx, |editor, action, cx| { | ||
| 4055 | editor | ||
| 4056 | .confirm_rename(action, cx) | ||
| 4057 | .map(|task| task.detach_and_log_err(cx)); | ||
| 4058 | }); | ||
| 4059 | register_action(cx, |editor, action, cx| { | ||
| 4060 | editor | ||
| 4061 | .find_all_references(action, cx) | ||
| 4062 | .map(|task| task.detach_and_log_err(cx)); | ||
| 4063 | }); | ||
| 4064 | register_action(cx, Editor::next_copilot_suggestion); | ||
| 4065 | register_action(cx, Editor::previous_copilot_suggestion); | ||
| 4066 | register_action(cx, Editor::copilot_suggest); | ||
| 4067 | register_action(cx, Editor::context_menu_first); | ||
| 4068 | register_action(cx, Editor::context_menu_prev); | ||
| 4069 | register_action(cx, Editor::context_menu_next); | ||
| 4070 | register_action(cx, Editor::context_menu_last); | ||
| 4071 | } | ||
| 4072 | |||
| 4073 | fn register_action<T: Action>( | ||
| 4074 | cx: &mut ViewContext<Editor>, | ||
| 4075 | listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static, | 4079 | listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static, |
| 4076 | ) { | 4080 | ) { |
| 4077 | cx.on_action(TypeId::of::<T>(), move |editor, action, phase, cx| { | 4081 | let view = view.clone(); |
| 4082 | cx.on_action(TypeId::of::<T>(), move |action, phase, cx| { | ||
| 4078 | let action = action.downcast_ref().unwrap(); | 4083 | let action = action.downcast_ref().unwrap(); |
| 4079 | if phase == DispatchPhase::Bubble { | 4084 | if phase == DispatchPhase::Bubble { |
| 4080 | listener(editor, action, cx); | 4085 | view.update(cx, |editor, cx| { |
| 4086 | listener(editor, action, cx); | ||
| 4087 | }) | ||
| 4081 | } | 4088 | } |
| 4082 | }) | 4089 | }) |
| 4083 | } | 4090 | } |
| @@ -1,15 +1,21 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | display_map::InlayOffset, | 2 | display_map::{InlayOffset, ToDisplayPoint}, |
| 3 | link_go_to_definition::{InlayHighlight, RangeInEditor}, | 3 | link_go_to_definition::{InlayHighlight, RangeInEditor}, |
| 4 | Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, | 4 | Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, |
| 5 | ExcerptId, RangeToAnchorExt, | 5 | ExcerptId, RangeToAnchorExt, |
| 6 | }; | 6 | }; |
| 7 | use futures::FutureExt; | 7 | use futures::FutureExt; |
| 8 | use gpui::{AnyElement, AppContext, Model, Task, ViewContext, WeakView}; | 8 | use gpui::{ |
| 9 | actions, div, px, AnyElement, AppContext, CursorStyle, InteractiveElement, IntoElement, Model, | ||
| 10 | MouseButton, ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, | ||
| 11 | Task, ViewContext, WeakView, | ||
| 12 | }; | ||
| 9 | use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; | 13 | use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; |
| 14 | use lsp::DiagnosticSeverity; | ||
| 10 | use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; | 15 | use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; |
| 11 | use settings::Settings; | 16 | use settings::Settings; |
| 12 | use std::{ops::Range, sync::Arc, time::Duration}; | 17 | use std::{ops::Range, sync::Arc, time::Duration}; |
| 18 | use ui::Tooltip; | ||
| 13 | use util::TryFutureExt; | 19 | use util::TryFutureExt; |
| 14 | use workspace::Workspace; | 20 | use workspace::Workspace; |
| 15 | 21 | ||
| @@ -17,22 +23,17 @@ pub const HOVER_DELAY_MILLIS: u64 = 350; · 1 unmodified line | |||
| 17 | pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; | 23 | pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; |
| 18 | 24 | ||
| 19 | pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; | 25 | pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; |
| 20 | pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.; | 26 | pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.); |
| 21 | pub const HOVER_POPOVER_GAP: f32 = 10.; | 27 | pub const HOVER_POPOVER_GAP: Pixels = px(10.); |
| 22 | 28 | ||
| 23 | // actions!(editor, [Hover]); | 29 | actions!(Hover); |
| 24 | 30 | ||
| 25 | pub fn init(cx: &mut AppContext) { | 31 | /// Bindable action which uses the most recent selection head to trigger a hover |
| 26 | // cx.add_action(hover); | 32 | pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) { |
| 33 | let head = editor.selections.newest_display(cx).head(); | ||
| 34 | show_hover(editor, head, true, cx); | ||
| 27 | } | 35 | } |
| 28 | 36 | ||
| 29 | // todo!() | ||
| 30 | // /// Bindable action which uses the most recent selection head to trigger a hover | ||
| 31 | // pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) { | ||
| 32 | // let head = editor.selections.newest_display(cx).head(); | ||
| 33 | // show_hover(editor, head, true, cx); | ||
| 34 | // } | ||
| 35 | |||
| 36 | /// The internal hover action dispatches between `show_hover` or `hide_hover` | 37 | /// The internal hover action dispatches between `show_hover` or `hide_hover` |
| 37 | /// depending on whether a point to hover over is provided. | 38 | /// depending on whether a point to hover over is provided. |
| 38 | pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) { | 39 | pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) { |
| @@ -74,64 +75,63 @@ pub fn find_hovered_hint_part( · 35 unmodified lines | |||
| 74 | } | 75 | } |
| 75 | 76 | ||
| 76 | pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) { | 77 | pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) { |
| 77 | todo!() | 78 | if EditorSettings::get_global(cx).hover_popover_enabled { |
| 78 | // if EditorSettings::get_global(cx).hover_popover_enabled { | 79 | if editor.pending_rename.is_some() { |
| 79 | // if editor.pending_rename.is_some() { | 80 | return; |
| 80 | // return; | 81 | } |
| 81 | // } | 82 | |
| 82 | 83 | let Some(project) = editor.project.clone() else { | |
| 83 | // let Some(project) = editor.project.clone() else { | 84 | return; |
| 84 | // return; | 85 | }; |
| 85 | // }; | 86 | |
| 86 | 87 | if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { | |
| 87 | // if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { | 88 | if let RangeInEditor::Inlay(range) = symbol_range { |
| 88 | // if let RangeInEditor::Inlay(range) = symbol_range { | 89 | if range == &inlay_hover.range { |
| 89 | // if range == &inlay_hover.range { | 90 | // Hover triggered from same location as last time. Don't show again. |
| 90 | // // Hover triggered from same location as last time. Don't show again. | 91 | return; |
| 91 | // return; | 92 | } |
| 92 | // } | 93 | } |
| 93 | // } | 94 | hide_hover(editor, cx); |
| 94 | // hide_hover(editor, cx); | 95 | } |
| 95 | // } | 96 | |
| 96 | 97 | let task = cx.spawn(|this, mut cx| { | |
| 97 | // let task = cx.spawn(|this, mut cx| { | 98 | async move { |
| 98 | // async move { | 99 | cx.background_executor() |
| 99 | // cx.background_executor() | 100 | .timer(Duration::from_millis(HOVER_DELAY_MILLIS)) |
| 100 | // .timer(Duration::from_millis(HOVER_DELAY_MILLIS)) | 101 | .await; |
| 101 | // .await; | 102 | this.update(&mut cx, |this, _| { |
| 102 | // this.update(&mut cx, |this, _| { | 103 | this.hover_state.diagnostic_popover = None; |
| 103 | // this.hover_state.diagnostic_popover = None; | 104 | })?; |
| 104 | // })?; | 105 | |
| 105 | 106 | let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?; | |
| 106 | // let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?; | 107 | let blocks = vec![inlay_hover.tooltip]; |
| 107 | // let blocks = vec![inlay_hover.tooltip]; | 108 | let parsed_content = parse_blocks(&blocks, &language_registry, None).await; |
| 108 | // let parsed_content = parse_blocks(&blocks, &language_registry, None).await; | 109 | |
| 109 | 110 | let hover_popover = InfoPopover { | |
| 110 | // let hover_popover = InfoPopover { | 111 | project: project.clone(), |
| 111 | // project: project.clone(), | 112 | symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), |
| 112 | // symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), | 113 | blocks, |
| 113 | // blocks, | 114 | parsed_content, |
| 114 | // parsed_content, | 115 | }; |
| 115 | // }; | 116 | |
| 116 | 117 | this.update(&mut cx, |this, cx| { | |
| 117 | // this.update(&mut cx, |this, cx| { | 118 | // Highlight the selected symbol using a background highlight |
| 118 | // // Highlight the selected symbol using a background highlight | 119 | this.highlight_inlay_background::<HoverState>( |
| 119 | // this.highlight_inlay_background::<HoverState>( | 120 | vec![inlay_hover.range], |
| 120 | // vec![inlay_hover.range], | 121 | |theme| theme.element_hover, // todo!("use a proper background here") |
| 121 | // |theme| theme.editor.hover_popover.highlight, | 122 | cx, |
| 122 | // cx, | 123 | ); |
| 123 | // ); | 124 | this.hover_state.info_popover = Some(hover_popover); |
| 124 | // this.hover_state.info_popover = Some(hover_popover); | 125 | cx.notify(); |
| 125 | // cx.notify(); | 126 | })?; |
| 126 | // })?; | 127 | |
| 127 | 128 | anyhow::Ok(()) | |
| 128 | // anyhow::Ok(()) | 129 | } |
| 129 | // } | 130 | .log_err() |
| 130 | // .log_err() | 131 | }); |
| 131 | // }); | 132 | |
| 132 | 133 | editor.hover_state.info_task = Some(task); | |
| 133 | // editor.hover_state.info_task = Some(task); | 134 | } |
| 134 | // } | ||
| 135 | } | 135 | } |
| 136 | 136 | ||
| 137 | /// Hides the type information popup. | 137 | /// Hides the type information popup. |
| @@ -420,43 +420,42 @@ impl HoverState { · 282 unmodified lines | |||
| 420 | snapshot: &EditorSnapshot, | 420 | snapshot: &EditorSnapshot, |
| 421 | style: &EditorStyle, | 421 | style: &EditorStyle, |
| 422 | visible_rows: Range<u32>, | 422 | visible_rows: Range<u32>, |
| 423 | max_size: Size<Pixels>, | ||
| 423 | workspace: Option<WeakView<Workspace>>, | 424 | workspace: Option<WeakView<Workspace>>, |
| 424 | cx: &mut ViewContext<Editor>, | 425 | cx: &mut ViewContext<Editor>, |
| 425 | ) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> { | 426 | ) -> Option<(DisplayPoint, Vec<AnyElement>)> { |
| 426 | todo!("old version below") | 427 | // If there is a diagnostic, position the popovers based on that. |
| 428 | // Otherwise use the start of the hover range | ||
| 429 | let anchor = self | ||
| 430 | .diagnostic_popover | ||
| 431 | .as_ref() | ||
| 432 | .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start) | ||
| 433 | .or_else(|| { | ||
| 434 | self.info_popover | ||
| 435 | .as_ref() | ||
| 436 | .map(|info_popover| match &info_popover.symbol_range { | ||
| 437 | RangeInEditor::Text(range) => &range.start, | ||
| 438 | RangeInEditor::Inlay(range) => &range.inlay_position, | ||
| 439 | }) | ||
| 440 | })?; | ||
| 441 | let point = anchor.to_display_point(&snapshot.display_snapshot); | ||
| 442 | |||
| 443 | // Don't render if the relevant point isn't on screen | ||
| 444 | if !self.visible() || !visible_rows.contains(&point.row()) { | ||
| 445 | return None; | ||
| 446 | } | ||
| 447 | |||
| 448 | let mut elements = Vec::new(); | ||
| 449 | |||
| 450 | if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() { | ||
| 451 | elements.push(diagnostic_popover.render(style, max_size, cx)); | ||
| 452 | } | ||
| 453 | if let Some(info_popover) = self.info_popover.as_mut() { | ||
| 454 | elements.push(info_popover.render(style, max_size, workspace, cx)); | ||
| 455 | } | ||
| 456 | |||
| 457 | Some((point, elements)) | ||
| 427 | } | 458 | } |
| 428 | // // If there is a diagnostic, position the popovers based on that. | ||
| 429 | // // Otherwise use the start of the hover range | ||
| 430 | // let anchor = self | ||
| 431 | // .diagnostic_popover | ||
| 432 | // .as_ref() | ||
| 433 | // .map(|diagnostic_popover| &diagnostic_popover.local_diagnostic.range.start) | ||
| 434 | // .or_else(|| { | ||
| 435 | // self.info_popover | ||
| 436 | // .as_ref() | ||
| 437 | // .map(|info_popover| match &info_popover.symbol_range { | ||
| 438 | // RangeInEditor::Text(range) => &range.start, | ||
| 439 | // RangeInEditor::Inlay(range) => &range.inlay_position, | ||
| 440 | // }) | ||
| 441 | // })?; | ||
| 442 | // let point = anchor.to_display_point(&snapshot.display_snapshot); | ||
| 443 | |||
| 444 | // // Don't render if the relevant point isn't on screen | ||
| 445 | // if !self.visible() || !visible_rows.contains(&point.row()) { | ||
| 446 | // return None; | ||
| 447 | // } | ||
| 448 | |||
| 449 | // let mut elements = Vec::new(); | ||
| 450 | |||
| 451 | // if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() { | ||
| 452 | // elements.push(diagnostic_popover.render(style, cx)); | ||
| 453 | // } | ||
| 454 | // if let Some(info_popover) = self.info_popover.as_mut() { | ||
| 455 | // elements.push(info_popover.render(style, workspace, cx)); | ||
| 456 | // } | ||
| 457 | |||
| 458 | // Some((point, elements)) | ||
| 459 | // } | ||
| 460 | } | 459 | } |
| 461 | 460 | ||
| 462 | #[derive(Debug, Clone)] | 461 | #[derive(Debug, Clone)] |
| @@ -467,35 +466,36 @@ pub struct InfoPopover { · 4 unmodified lines | |||
| 467 | parsed_content: ParsedMarkdown, | 466 | parsed_content: ParsedMarkdown, |
| 468 | } | 467 | } |
| 469 | 468 | ||
| 470 | // impl InfoPopover { | 469 | impl InfoPopover { |
| 471 | // pub fn render( | 470 | pub fn render( |
| 472 | // &mut self, | 471 | &mut self, |
| 473 | // style: &EditorStyle, | 472 | style: &EditorStyle, |
| 474 | // workspace: Option<WeakView<Workspace>>, | 473 | max_size: Size<Pixels>, |
| 475 | // cx: &mut ViewContext<Editor>, | 474 | workspace: Option<WeakView<Workspace>>, |
| 476 | // ) -> AnyElement<Editor> { | 475 | cx: &mut ViewContext<Editor>, |
| 477 | // MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| { | 476 | ) -> AnyElement { |
| 478 | // Flex::column() | 477 | div() |
| 479 | // .scrollable::<HoverBlock>(0, None, cx) | 478 | .id("info_popover") |
| 480 | // .with_child(crate::render_parsed_markdown::<HoverBlock>( | 479 | .overflow_y_scroll() |
| 481 | // &self.parsed_content, | 480 | .bg(gpui::red()) |
| 482 | // style, | 481 | .max_w(max_size.width) |
| 483 | // workspace, | 482 | .max_h(max_size.height) |
| 484 | // cx, | 483 | // Prevent a mouse move on the popover from being propagated to the editor, |
| 485 | // )) | 484 | // because that would dismiss the popover. |
| 486 | // .contained() | 485 | .on_mouse_move(|_, cx| cx.stop_propagation()) |
| 487 | // .with_style(style.hover_popover.container) | 486 | // Prevent a mouse down on the popover from being propagated to the editor, |
| 488 | // }) | 487 | // because that would move the cursor. |
| 489 | // .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath. | 488 | .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) |
| 490 | // .with_cursor_style(CursorStyle::Arrow) | 489 | .child(crate::render_parsed_markdown( |
| 491 | // .with_padding(Padding { | 490 | "content", |
| 492 | // bottom: HOVER_POPOVER_GAP, | 491 | &self.parsed_content, |
| 493 | // top: HOVER_POPOVER_GAP, | 492 | style, |
| 494 | // ..Default::default() | 493 | workspace, |
| 495 | // }) | 494 | cx, |
| 496 | // .into_any() | 495 | )) |
| 497 | // } | 496 | .into_any_element() |
| 498 | // } | 497 | } |
| 498 | } | ||
| 499 | 499 | ||
| 500 | #[derive(Debug, Clone)] | 500 | #[derive(Debug, Clone)] |
| 501 | pub struct DiagnosticPopover { | 501 | pub struct DiagnosticPopover { |
| @@ -504,57 +504,40 @@ pub struct DiagnosticPopover { · 2 unmodified lines | |||
| 504 | } | 504 | } |
| 505 | 505 | ||
| 506 | impl DiagnosticPopover { | 506 | impl DiagnosticPopover { |
| 507 | pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> { | 507 | pub fn render( |
| 508 | todo!() | 508 | &self, |
| 509 | // enum PrimaryDiagnostic {} | 509 | style: &EditorStyle, |
| 510 | 510 | max_size: Size<Pixels>, | |
| 511 | // let mut text_style = style.hover_popover.prose.clone(); | 511 | cx: &mut ViewContext<Editor>, |
| 512 | // text_style.font_size = style.text.font_size; | 512 | ) -> AnyElement { |
| 513 | // let diagnostic_source_style = style.hover_popover.diagnostic_source_highlight.clone(); | 513 | let text = match &self.local_diagnostic.diagnostic.source { |
| 514 | 514 | Some(source) => format!("{source}: {}", self.local_diagnostic.diagnostic.message), | |
| 515 | // let text = match &self.local_diagnostic.diagnostic.source { | 515 | None => self.local_diagnostic.diagnostic.message.clone(), |
| 516 | // Some(source) => Text::new( | 516 | }; |
| 517 | // format!("{source}: {}", self.local_diagnostic.diagnostic.message), | 517 | |
| 518 | // text_style, | 518 | let container_bg = crate::diagnostic_style( |
| 519 | // ) | 519 | self.local_diagnostic.diagnostic.severity, |
| 520 | // .with_highlights(vec![(0..source.len(), diagnostic_source_style)]), | 520 | true, |
| 521 | 521 | &style.diagnostic_style, | |
| 522 | // None => Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style), | 522 | ); |
| 523 | // }; | 523 | |
| 524 | 524 | div() | |
| 525 | // let container_style = match self.local_diagnostic.diagnostic.severity { | 525 | .id("diagnostic") |
| 526 | // DiagnosticSeverity::HINT => style.hover_popover.info_container, | 526 | .overflow_y_scroll() |
| 527 | // DiagnosticSeverity::INFORMATION => style.hover_popover.info_container, | 527 | .bg(container_bg) |
| 528 | // DiagnosticSeverity::WARNING => style.hover_popover.warning_container, | 528 | .max_w(max_size.width) |
| 529 | // DiagnosticSeverity::ERROR => style.hover_popover.error_container, | 529 | .max_h(max_size.height) |
| 530 | // _ => style.hover_popover.container, | 530 | .cursor(CursorStyle::PointingHand) |
| 531 | // }; | 531 | .tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx)) |
| 532 | 532 | // Prevent a mouse move on the popover from being propagated to the editor, | |
| 533 | // let tooltip_style = theme::current(cx).tooltip.clone(); | 533 | // because that would dismiss the popover. |
| 534 | 534 | .on_mouse_move(|_, cx| cx.stop_propagation()) | |
| 535 | // MouseEventHandler::new::<DiagnosticPopover, _>(0, cx, |_, _| { | 535 | // Prevent a mouse down on the popover from being propagated to the editor, |
| 536 | // text.with_soft_wrap(true) | 536 | // because that would move the cursor. |
| 537 | // .contained() | 537 | .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation()) |
| 538 | // .with_style(container_style) | 538 | .on_click(cx.listener(|editor, _, cx| editor.go_to_diagnostic(&Default::default(), cx))) |
| 539 | // }) | 539 | .child(SharedString::from(text)) |
| 540 | // .with_padding(Padding { | 540 | .into_any_element() |
| 541 | // top: HOVER_POPOVER_GAP, | ||
| 542 | // bottom: HOVER_POPOVER_GAP, | ||
| 543 | // ..Default::default() | ||
| 544 | // }) | ||
| 545 | // .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath. | ||
| 546 | // .on_click(MouseButton::Left, |_, this, cx| { | ||
| 547 | // this.go_to_diagnostic(&Default::default(), cx) | ||
| 548 | // }) | ||
| 549 | // .with_cursor_style(CursorStyle::PointingHand) | ||
| 550 | // .with_tooltip::<PrimaryDiagnostic>( | ||
| 551 | // 0, | ||
| 552 | // "Go To Diagnostic".to_string(), | ||
| 553 | // Some(Box::new(crate::GoToDiagnostic)), | ||
| 554 | // tooltip_style, | ||
| 555 | // cx, | ||
| 556 | // ) | ||
| 557 | // .into_any() | ||
| 558 | } | 541 | } |
| 559 | 542 | ||
| 560 | pub fn activation_info(&self) -> (usize, Anchor) { | 543 | pub fn activation_info(&self) -> (usize, Anchor) { |
| @@ -567,763 +550,763 @@ impl DiagnosticPopover { · 6 unmodified lines | |||
| 567 | } | 550 | } |
| 568 | } | 551 | } |
| 569 | 552 | ||
| 570 | // #[cfg(test)] | 553 | #[cfg(test)] |
| 571 | // mod tests { | 554 | mod tests { |
| 572 | // use super::*; | 555 | use super::*; |
| 573 | // use crate::{ | 556 | use crate::{ |
| 574 | // editor_tests::init_test, | 557 | editor_tests::init_test, |
| 575 | // element::PointForPosition, | 558 | element::PointForPosition, |
| 576 | // inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, | 559 | inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, |
| 577 | // link_go_to_definition::update_inlay_link_and_hover_points, | 560 | link_go_to_definition::update_inlay_link_and_hover_points, |
| 578 | // test::editor_lsp_test_context::EditorLspTestContext, | 561 | test::editor_lsp_test_context::EditorLspTestContext, |
| 579 | // InlayId, | 562 | InlayId, |
| 580 | // }; | 563 | }; |
| 581 | // use collections::BTreeSet; | 564 | use collections::BTreeSet; |
| 582 | // use gpui::fonts::{HighlightStyle, Underline, Weight}; | 565 | use gpui::{FontWeight, HighlightStyle, UnderlineStyle}; |
| 583 | // use indoc::indoc; | 566 | use indoc::indoc; |
| 584 | // use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; | 567 | use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; |
| 585 | // use lsp::LanguageServerId; | 568 | use lsp::LanguageServerId; |
| 586 | // use project::{HoverBlock, HoverBlockKind}; | 569 | use project::{HoverBlock, HoverBlockKind}; |
| 587 | // use smol::stream::StreamExt; | 570 | use smol::stream::StreamExt; |
| 588 | // use unindent::Unindent; | 571 | use unindent::Unindent; |
| 589 | // use util::test::marked_text_ranges; | 572 | use util::test::marked_text_ranges; |
| 590 | 573 | ||
| 591 | // #[gpui::test] | 574 | #[gpui::test] |
| 592 | // async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) { | 575 | async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) { |
| 593 | // init_test(cx, |_| {}); | 576 | init_test(cx, |_| {}); |
| 594 | 577 | ||
| 595 | // let mut cx = EditorLspTestContext::new_rust( | 578 | let mut cx = EditorLspTestContext::new_rust( |
| 596 | // lsp::ServerCapabilities { | 579 | lsp::ServerCapabilities { |
| 597 | // hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), | 580 | hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), |
| 598 | // ..Default::default() | 581 | ..Default::default() |
| 599 | // }, | 582 | }, |
| 600 | // cx, | 583 | cx, |
| 601 | // ) | 584 | ) |
| 602 | // .await; | 585 | .await; |
| 603 | 586 | ||
| 604 | // // Basic hover delays and then pops without moving the mouse | 587 | // Basic hover delays and then pops without moving the mouse |
| 605 | // cx.set_state(indoc! {" | 588 | cx.set_state(indoc! {" |
| 606 | // fn ˇtest() { println!(); } | 589 | fn ˇtest() { println!(); } |
| 607 | // "}); | 590 | "}); |
| 608 | // let hover_point = cx.display_point(indoc! {" | 591 | let hover_point = cx.display_point(indoc! {" |
| 609 | // fn test() { printˇln!(); } | 592 | fn test() { printˇln!(); } |
| 610 | // "}); | 593 | "}); |
| 611 | 594 | ||
| 612 | // cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); | 595 | cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); |
| 613 | // assert!(!cx.editor(|editor, _| editor.hover_state.visible())); | 596 | assert!(!cx.editor(|editor, _| editor.hover_state.visible())); |
| 614 | 597 | ||
| 615 | // // After delay, hover should be visible. | 598 | // After delay, hover should be visible. |
| 616 | // let symbol_range = cx.lsp_range(indoc! {" | 599 | let symbol_range = cx.lsp_range(indoc! {" |
| 617 | // fn test() { «println!»(); } | 600 | fn test() { «println!»(); } |
| 618 | // "}); | 601 | "}); |
| 619 | // let mut requests = | 602 | let mut requests = |
| 620 | // cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { | 603 | cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { |
| 621 | // Ok(Some(lsp::Hover { | 604 | Ok(Some(lsp::Hover { |
| 622 | // contents: lsp::HoverContents::Markup(lsp::MarkupContent { | 605 | contents: lsp::HoverContents::Markup(lsp::MarkupContent { |
| 623 | // kind: lsp::MarkupKind::Markdown, | 606 | kind: lsp::MarkupKind::Markdown, |
| 624 | // value: "some basic docs".to_string(), | 607 | value: "some basic docs".to_string(), |
| 625 | // }), | 608 | }), |
| 626 | // range: Some(symbol_range), | 609 | range: Some(symbol_range), |
| 627 | // })) | 610 | })) |
| 628 | // }); | 611 | }); |
| 629 | // cx.foreground() | 612 | cx.background_executor |
| 630 | // .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); | 613 | .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); |
| 631 | // requests.next().await; | 614 | requests.next().await; |
| 632 | 615 | ||
| 633 | // cx.editor(|editor, _| { | 616 | cx.editor(|editor, _| { |
| 634 | // assert!(editor.hover_state.visible()); | 617 | assert!(editor.hover_state.visible()); |
| 635 | // assert_eq!( | 618 | assert_eq!( |
| 636 | // editor.hover_state.info_popover.clone().unwrap().blocks, | 619 | editor.hover_state.info_popover.clone().unwrap().blocks, |
| 637 | // vec![HoverBlock { | 620 | vec![HoverBlock { |
| 638 | // text: "some basic docs".to_string(), | 621 | text: "some basic docs".to_string(), |
| 639 | // kind: HoverBlockKind::Markdown, | 622 | kind: HoverBlockKind::Markdown, |
| 640 | // },] | 623 | },] |
| 641 | // ) | 624 | ) |
| 642 | // }); | 625 | }); |
| 643 | 626 | ||
| 644 | // // Mouse moved with no hover response dismisses | 627 | // Mouse moved with no hover response dismisses |
| 645 | // let hover_point = cx.display_point(indoc! {" | 628 | let hover_point = cx.display_point(indoc! {" |
| 646 | // fn teˇst() { println!(); } | 629 | fn teˇst() { println!(); } |
| 647 | // "}); | 630 | "}); |
| 648 | // let mut request = cx | 631 | let mut request = cx |
| 649 | // .lsp | 632 | .lsp |
| 650 | // .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) }); | 633 | .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) }); |
| 651 | // cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); | 634 | cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); |
| 652 | // cx.foreground() | 635 | cx.background_executor |
| 653 | // .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); | 636 | .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); |
| 654 | // request.next().await; | 637 | request.next().await; |
| 655 | // cx.editor(|editor, _| { | 638 | cx.editor(|editor, _| { |
| 656 | // assert!(!editor.hover_state.visible()); | 639 | assert!(!editor.hover_state.visible()); |
| 657 | // }); | 640 | }); |
| 658 | // } | 641 | } |
| 659 | 642 | ||
| 660 | // #[gpui::test] | 643 | #[gpui::test] |
| 661 | // async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) { | 644 | async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) { |
| 662 | // init_test(cx, |_| {}); | 645 | init_test(cx, |_| {}); |
| 663 | 646 | ||
| 664 | // let mut cx = EditorLspTestContext::new_rust( | 647 | let mut cx = EditorLspTestContext::new_rust( |
| 665 | // lsp::ServerCapabilities { | 648 | lsp::ServerCapabilities { |
| 666 | // hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), | 649 | hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), |
| 667 | // ..Default::default() | 650 | ..Default::default() |
| 668 | // }, | 651 | }, |
| 669 | // cx, | 652 | cx, |
| 670 | // ) | 653 | ) |
| 671 | // .await; | 654 | .await; |
| 672 | 655 | ||
| 673 | // // Hover with keyboard has no delay | 656 | // Hover with keyboard has no delay |
| 674 | // cx.set_state(indoc! {" | 657 | cx.set_state(indoc! {" |
| 675 | // fˇn test() { println!(); } | 658 | fˇn test() { println!(); } |
| 676 | // "}); | 659 | "}); |
| 677 | // cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); | 660 | cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); |
| 678 | // let symbol_range = cx.lsp_range(indoc! {" | 661 | let symbol_range = cx.lsp_range(indoc! {" |
| 679 | // «fn» test() { println!(); } | 662 | «fn» test() { println!(); } |
| 680 | // "}); | 663 | "}); |
| 681 | // cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { | 664 | cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { |
| 682 | // Ok(Some(lsp::Hover { | 665 | Ok(Some(lsp::Hover { |
| 683 | // contents: lsp::HoverContents::Markup(lsp::MarkupContent { | 666 | contents: lsp::HoverContents::Markup(lsp::MarkupContent { |
| 684 | // kind: lsp::MarkupKind::Markdown, | 667 | kind: lsp::MarkupKind::Markdown, |
| 685 | // value: "some other basic docs".to_string(), | 668 | value: "some other basic docs".to_string(), |
| 686 | // }), | 669 | }), |
| 687 | // range: Some(symbol_range), | 670 | range: Some(symbol_range), |
| 688 | // })) | 671 | })) |
| 689 | // }) | 672 | }) |
| 690 | // .next() | 673 | .next() |
| 691 | // .await; | 674 | .await; |
| 692 | 675 | ||
| 693 | // cx.condition(|editor, _| editor.hover_state.visible()).await; | 676 | cx.condition(|editor, _| editor.hover_state.visible()).await; |
| 694 | // cx.editor(|editor, _| { | 677 | cx.editor(|editor, _| { |
| 695 | // assert_eq!( | 678 | assert_eq!( |
| 696 | // editor.hover_state.info_popover.clone().unwrap().blocks, | 679 | editor.hover_state.info_popover.clone().unwrap().blocks, |
| 697 | // vec![HoverBlock { | 680 | vec![HoverBlock { |
| 698 | // text: "some other basic docs".to_string(), | 681 | text: "some other basic docs".to_string(), |
| 699 | // kind: HoverBlockKind::Markdown, | 682 | kind: HoverBlockKind::Markdown, |
| 700 | // }] | 683 | }] |
| 701 | // ) | 684 | ) |
| 702 | // }); | 685 | }); |
| 703 | // } | 686 | } |
| 704 | 687 | ||
| 705 | // #[gpui::test] | 688 | #[gpui::test] |
| 706 | // async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) { | 689 | async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) { |
| 707 | // init_test(cx, |_| {}); | 690 | init_test(cx, |_| {}); |
| 708 | 691 | ||
| 709 | // let mut cx = EditorLspTestContext::new_rust( | 692 | let mut cx = EditorLspTestContext::new_rust( |
| 710 | // lsp::ServerCapabilities { | 693 | lsp::ServerCapabilities { |
| 711 | // hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), | 694 | hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), |
| 712 | // ..Default::default() | 695 | ..Default::default() |
| 713 | // }, | 696 | }, |
| 714 | // cx, | 697 | cx, |
| 715 | // ) | 698 | ) |
| 716 | // .await; | 699 | .await; |
| 717 | 700 | ||
| 718 | // // Hover with keyboard has no delay | 701 | // Hover with keyboard has no delay |
| 719 | // cx.set_state(indoc! {" | 702 | cx.set_state(indoc! {" |
| 720 | // fˇn test() { println!(); } | 703 | fˇn test() { println!(); } |
| 721 | // "}); | 704 | "}); |
| 722 | // cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); | 705 | cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); |
| 723 | // let symbol_range = cx.lsp_range(indoc! {" | 706 | let symbol_range = cx.lsp_range(indoc! {" |
| 724 | // «fn» test() { println!(); } | 707 | «fn» test() { println!(); } |
| 725 | // "}); | 708 | "}); |
| 726 | // cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { | 709 | cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { |
| 727 | // Ok(Some(lsp::Hover { | 710 | Ok(Some(lsp::Hover { |
| 728 | // contents: lsp::HoverContents::Array(vec![ | 711 | contents: lsp::HoverContents::Array(vec![ |
| 729 | // lsp::MarkedString::String("regular text for hover to show".to_string()), | 712 | lsp::MarkedString::String("regular text for hover to show".to_string()), |
| 730 | // lsp::MarkedString::String("".to_string()), | 713 | lsp::MarkedString::String("".to_string()), |
| 731 | // lsp::MarkedString::LanguageString(lsp::LanguageString { | 714 | lsp::MarkedString::LanguageString(lsp::LanguageString { |
| 732 | // language: "Rust".to_string(), | 715 | language: "Rust".to_string(), |
| 733 | // value: "".to_string(), | 716 | value: "".to_string(), |
| 734 | // }), | 717 | }), |
| 735 | // ]), | 718 | ]), |
| 736 | // range: Some(symbol_range), | 719 | range: Some(symbol_range), |
| 737 | // })) | 720 | })) |
| 738 | // }) | 721 | }) |
| 739 | // .next() | 722 | .next() |
| 740 | // .await; | 723 | .await; |
| 741 | 724 | ||
| 742 | // cx.condition(|editor, _| editor.hover_state.visible()).await; | 725 | cx.condition(|editor, _| editor.hover_state.visible()).await; |
| 743 | // cx.editor(|editor, _| { | 726 | cx.editor(|editor, _| { |
| 744 | // assert_eq!( | 727 | assert_eq!( |
| 745 | // editor.hover_state.info_popover.clone().unwrap().blocks, | 728 | editor.hover_state.info_popover.clone().unwrap().blocks, |
| 746 | // vec![HoverBlock { | 729 | vec![HoverBlock { |
| 747 | // text: "regular text for hover to show".to_string(), | 730 | text: "regular text for hover to show".to_string(), |
| 748 | // kind: HoverBlockKind::Markdown, | 731 | kind: HoverBlockKind::Markdown, |
| 749 | // }], | 732 | }], |
| 750 | // "No empty string hovers should be shown" | 733 | "No empty string hovers should be shown" |
| 751 | // ); | 734 | ); |
| 752 | // }); | 735 | }); |
| 753 | // } | 736 | } |
| 754 | 737 | ||
| 755 | // #[gpui::test] | 738 | #[gpui::test] |
| 756 | // async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) { | 739 | async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) { |
| 757 | // init_test(cx, |_| {}); | 740 | init_test(cx, |_| {}); |
| 758 | 741 | ||
| 759 | // let mut cx = EditorLspTestContext::new_rust( | 742 | let mut cx = EditorLspTestContext::new_rust( |
| 760 | // lsp::ServerCapabilities { | 743 | lsp::ServerCapabilities { |
| 761 | // hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), | 744 | hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), |
| 762 | // ..Default::default() | 745 | ..Default::default() |
| 763 | // }, | 746 | }, |
| 764 | // cx, | 747 | cx, |
| 765 | // ) | 748 | ) |
| 766 | // .await; | 749 | .await; |
| 767 | 750 | ||
| 768 | // // Hover with keyboard has no delay | 751 | // Hover with keyboard has no delay |
| 769 | // cx.set_state(indoc! {" | 752 | cx.set_state(indoc! {" |
| 770 | // fˇn test() { println!(); } | 753 | fˇn test() { println!(); } |
| 771 | // "}); | 754 | "}); |
| 772 | // cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); | 755 | cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); |
| 773 | // let symbol_range = cx.lsp_range(indoc! {" | 756 | let symbol_range = cx.lsp_range(indoc! {" |
| 774 | // «fn» test() { println!(); } | 757 | «fn» test() { println!(); } |
| 775 | // "}); | 758 | "}); |
| 776 | 759 | ||
| 777 | // let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n"; | 760 | let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n"; |
| 778 | // let markdown_string = format!("\n```rust\n{code_str}```"); | 761 | let markdown_string = format!("\n```rust\n{code_str}```"); |
| 779 | 762 | ||
| 780 | // let closure_markdown_string = markdown_string.clone(); | 763 | let closure_markdown_string = markdown_string.clone(); |
| 781 | // cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| { | 764 | cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| { |
| 782 | // let future_markdown_string = closure_markdown_string.clone(); | 765 | let future_markdown_string = closure_markdown_string.clone(); |
| 783 | // async move { | 766 | async move { |
| 784 | // Ok(Some(lsp::Hover { | 767 | Ok(Some(lsp::Hover { |
| 785 | // contents: lsp::HoverContents::Markup(lsp::MarkupContent { | 768 | contents: lsp::HoverContents::Markup(lsp::MarkupContent { |
| 786 | // kind: lsp::MarkupKind::Markdown, | 769 | kind: lsp::MarkupKind::Markdown, |
| 787 | // value: future_markdown_string, | 770 | value: future_markdown_string, |
| 788 | // }), | 771 | }), |
| 789 | // range: Some(symbol_range), | 772 | range: Some(symbol_range), |
| 790 | // })) | 773 | })) |
| 791 | // } | 774 | } |
| 792 | // }) | 775 | }) |
| 793 | // .next() | 776 | .next() |
| 794 | // .await; | 777 | .await; |
| 795 | 778 | ||
| 796 | // cx.condition(|editor, _| editor.hover_state.visible()).await; | 779 | cx.condition(|editor, _| editor.hover_state.visible()).await; |
| 797 | // cx.editor(|editor, _| { | 780 | cx.editor(|editor, _| { |
| 798 | // let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; | 781 | let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; |
| 799 | // assert_eq!( | 782 | assert_eq!( |
| 800 | // blocks, | 783 | blocks, |
| 801 | // vec![HoverBlock { | 784 | vec![HoverBlock { |
| 802 | // text: markdown_string, | 785 | text: markdown_string, |
| 803 | // kind: HoverBlockKind::Markdown, | 786 | kind: HoverBlockKind::Markdown, |
| 804 | // }], | 787 | }], |
| 805 | // ); | 788 | ); |
| 806 | 789 | ||
| 807 | // let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); | 790 | let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); |
| 808 | // assert_eq!( | 791 | assert_eq!( |
| 809 | // rendered.text, | 792 | rendered.text, |
| 810 | // code_str.trim(), | 793 | code_str.trim(), |
| 811 | // "Should not have extra line breaks at end of rendered hover" | 794 | "Should not have extra line breaks at end of rendered hover" |
| 812 | // ); | 795 | ); |
| 813 | // }); | 796 | }); |
| 814 | // } | 797 | } |
| 815 | 798 | ||
| 816 | // #[gpui::test] | 799 | #[gpui::test] |
| 817 | // async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) { | 800 | async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) { |
| 818 | // init_test(cx, |_| {}); | 801 | init_test(cx, |_| {}); |
| 819 | 802 | ||
| 820 | // let mut cx = EditorLspTestContext::new_rust( | 803 | let mut cx = EditorLspTestContext::new_rust( |
| 821 | // lsp::ServerCapabilities { | 804 | lsp::ServerCapabilities { |
| 822 | // hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), | 805 | hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), |
| 823 | // ..Default::default() | 806 | ..Default::default() |
| 824 | // }, | 807 | }, |
| 825 | // cx, | 808 | cx, |
| 826 | // ) | 809 | ) |
| 827 | // .await; | 810 | .await; |
| 828 | 811 | ||
| 829 | // // Hover with just diagnostic, pops DiagnosticPopover immediately and then | 812 | // Hover with just diagnostic, pops DiagnosticPopover immediately and then |
| 830 | // // info popover once request completes | 813 | // info popover once request completes |
| 831 | // cx.set_state(indoc! {" | 814 | cx.set_state(indoc! {" |
| 832 | // fn teˇst() { println!(); } | 815 | fn teˇst() { println!(); } |
| 833 | // "}); | 816 | "}); |
| 834 | 817 | ||
| 835 | // // Send diagnostic to client | 818 | // Send diagnostic to client |
| 836 | // let range = cx.text_anchor_range(indoc! {" | 819 | let range = cx.text_anchor_range(indoc! {" |
| 837 | // fn «test»() { println!(); } | 820 | fn «test»() { println!(); } |
| 838 | // "}); | 821 | "}); |
| 839 | // cx.update_buffer(|buffer, cx| { | 822 | cx.update_buffer(|buffer, cx| { |
| 840 | // let snapshot = buffer.text_snapshot(); | 823 | let snapshot = buffer.text_snapshot(); |
| 841 | // let set = DiagnosticSet::from_sorted_entries( | 824 | let set = DiagnosticSet::from_sorted_entries( |
| 842 | // vec![DiagnosticEntry { | 825 | vec![DiagnosticEntry { |
| 843 | // range, | 826 | range, |
| 844 | // diagnostic: Diagnostic { | 827 | diagnostic: Diagnostic { |
| 845 | // message: "A test diagnostic message.".to_string(), | 828 | message: "A test diagnostic message.".to_string(), |
| 846 | // ..Default::default() | 829 | ..Default::default() |
| 847 | // }, | 830 | }, |
| 848 | // }], | 831 | }], |
| 849 | // &snapshot, | 832 | &snapshot, |
| 850 | // ); | 833 | ); |
| 851 | // buffer.update_diagnostics(LanguageServerId(0), set, cx); | 834 | buffer.update_diagnostics(LanguageServerId(0), set, cx); |
| 852 | // }); | 835 | }); |
| 853 | 836 | ||
| 854 | // // Hover pops diagnostic immediately | 837 | // Hover pops diagnostic immediately |
| 855 | // cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); | 838 | cx.update_editor(|editor, cx| hover(editor, &Hover, cx)); |
| 856 | // cx.foreground().run_until_parked(); | 839 | cx.background_executor.run_until_parked(); |
| 857 | 840 | ||
| 858 | // cx.editor(|Editor { hover_state, .. }, _| { | 841 | cx.editor(|Editor { hover_state, .. }, _| { |
| 859 | // assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none()) | 842 | assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none()) |
| 860 | // }); | 843 | }); |
| 861 | 844 | ||
| 862 | // // Info Popover shows after request responded to | 845 | // Info Popover shows after request responded to |
| 863 | // let range = cx.lsp_range(indoc! {" | 846 | let range = cx.lsp_range(indoc! {" |
| 864 | // fn «test»() { println!(); } | 847 | fn «test»() { println!(); } |
| 865 | // "}); | 848 | "}); |
| 866 | // cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { | 849 | cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move { |
| 867 | // Ok(Some(lsp::Hover { | 850 | Ok(Some(lsp::Hover { |
| 868 | // contents: lsp::HoverContents::Markup(lsp::MarkupContent { | 851 | contents: lsp::HoverContents::Markup(lsp::MarkupContent { |
| 869 | // kind: lsp::MarkupKind::Markdown, | 852 | kind: lsp::MarkupKind::Markdown, |
| 870 | // value: "some new docs".to_string(), | 853 | value: "some new docs".to_string(), |
| 871 | // }), | 854 | }), |
| 872 | // range: Some(range), | 855 | range: Some(range), |
| 873 | // })) | 856 | })) |
| 874 | // }); | 857 | }); |
| 875 | // cx.foreground() | 858 | cx.background_executor |
| 876 | // .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); | 859 | .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); |
| 877 | 860 | ||
| 878 | // cx.foreground().run_until_parked(); | 861 | cx.background_executor.run_until_parked(); |
| 879 | // cx.editor(|Editor { hover_state, .. }, _| { | 862 | cx.editor(|Editor { hover_state, .. }, _| { |
| 880 | // hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some() | 863 | hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some() |
| 881 | // }); | 864 | }); |
| 882 | // } | 865 | } |
| 883 | 866 | ||
| 884 | // #[gpui::test] | 867 | #[gpui::test] |
| 885 | // fn test_render_blocks(cx: &mut gpui::TestAppContext) { | 868 | fn test_render_blocks(cx: &mut gpui::TestAppContext) { |
| 886 | // init_test(cx, |_| {}); | 869 | init_test(cx, |_| {}); |
| 887 | 870 | ||
| 888 | // cx.add_window(|cx| { | 871 | let editor = cx.add_window(|cx| Editor::single_line(cx)); |
| 889 | // let editor = Editor::single_line(None, cx); | 872 | editor |
| 890 | // let style = editor.style(cx); | 873 | .update(cx, |editor, cx| { |
| 891 | 874 | let style = editor.style.clone().unwrap(); | |
| 892 | // struct Row { | 875 | |
| 893 | // blocks: Vec<HoverBlock>, | 876 | struct Row { |
| 894 | // expected_marked_text: String, | 877 | blocks: Vec<HoverBlock>, |
| 895 | // expected_styles: Vec<HighlightStyle>, | 878 | expected_marked_text: String, |
| 896 | // } | 879 | expected_styles: Vec<HighlightStyle>, |
| 897 | 880 | } | |
| 898 | // let rows = &[ | 881 | |
| 899 | // // Strong emphasis | 882 | let rows = &[ |
| 900 | // Row { | 883 | // Strong emphasis |
| 901 | // blocks: vec![HoverBlock { | 884 | Row { |
| 902 | // text: "one **two** three".to_string(), | 885 | blocks: vec![HoverBlock { |
| 903 | // kind: HoverBlockKind::Markdown, | 886 | text: "one **two** three".to_string(), |
| 904 | // }], | 887 | kind: HoverBlockKind::Markdown, |
| 905 | // expected_marked_text: "one «two» three".to_string(), | 888 | }], |
| 906 | // expected_styles: vec![HighlightStyle { | 889 | expected_marked_text: "one «two» three".to_string(), |
| 907 | // weight: Some(Weight::BOLD), | 890 | expected_styles: vec![HighlightStyle { |
| 908 | // ..Default::default() | 891 | font_weight: Some(FontWeight::BOLD), |
| 909 | // }], | 892 | ..Default::default() |
| 910 | // }, | 893 | }], |
| 911 | // // Links | 894 | }, |
| 912 | // Row { | 895 | // Links |
| 913 | // blocks: vec three".to_string(), | 897 | blocks: vec three".to_string(), |
| 916 | // }], | 899 | kind: HoverBlockKind::Markdown, |
| 917 | // expected_marked_text: "one «two» three".to_string(), | 900 | }], |
| 918 | // expected_styles: vec![HighlightStyle { | 901 | expected_marked_text: "one «two» three".to_string(), |
| 919 | // underline: Some(Underline { | 902 | expected_styles: vec![HighlightStyle { |
| 920 | // thickness: 1.0.into(), | 903 | underline: Some(UnderlineStyle { |
| 921 | // ..Default::default() | 904 | thickness: 1.0.into(), |
| 922 | // }), | 905 | ..Default::default() |
| 923 | // ..Default::default() | 906 | }), |
| 924 | // }], | 907 | ..Default::default() |
| 925 | // }, | 908 | }], |
| 926 | // // Lists | 909 | }, |
| 927 | // Row { | 910 | // Lists |
| 928 | // blocks: vec | 918 | * two |
| 936 | // - d" | 919 | - [c](https://the-url) |
| 937 | // .unindent(), | 920 | - d" |
| 938 | // kind: HoverBlockKind::Markdown, | 921 | .unindent(), |
| 939 | // }], | 922 | kind: HoverBlockKind::Markdown, |
| 940 | // expected_marked_text: " | 923 | }], |
| 941 | // lists: | 924 | expected_marked_text: " |
| 942 | // - one | 925 | lists: |
| 943 | // - a | 926 | - one |
| 944 | // - b | 927 | - a |
| 945 | // - two | 928 | - b |
| 946 | // - «c» | 929 | - two |
| 947 | // - d" | 930 | - «c» |
| 948 | // .unindent(), | 931 | - d" |
| 949 | // expected_styles: vec![HighlightStyle { | 932 | .unindent(), |
| 950 | // underline: Some(Underline { | 933 | expected_styles: vec![HighlightStyle { |
| 951 | // thickness: 1.0.into(), | 934 | underline: Some(UnderlineStyle { |
| 952 | // ..Default::default() | 935 | thickness: 1.0.into(), |
| 953 | // }), | 936 | ..Default::default() |
| 954 | // ..Default::default() | 937 | }), |
| 955 | // }], | 938 | ..Default::default() |
| 956 | // }, | 939 | }], |
| 957 | // // Multi-paragraph list items | 940 | }, |
| 958 | // Row { | 941 | // Multi-paragraph list items |
| 959 | // blocks: vec![HoverBlock { | 942 | Row { |
| 960 | // text: " | 943 | blocks: vec![HoverBlock { |
| 961 | // * one two | 944 | text: " |
| 962 | // three | 945 | * one two |
| 963 | 946 | three | |
| 964 | // * four five | 947 | |
| 965 | // * six seven | 948 | * four five |
| 966 | // eight | 949 | * six seven |
| 967 | 950 | eight | |
| 968 | // nine | 951 | |
| 969 | // * ten | 952 | nine |
| 970 | // * six" | 953 | * ten |
| 971 | // .unindent(), | 954 | * six" |
| 972 | // kind: HoverBlockKind::Markdown, | 955 | .unindent(), |
| 973 | // }], | 956 | kind: HoverBlockKind::Markdown, |
| 974 | // expected_marked_text: " | 957 | }], |
| 975 | // - one two three | 958 | expected_marked_text: " |
| 976 | // - four five | 959 | - one two three |
| 977 | // - six seven eight | 960 | - four five |
| 978 | 961 | - six seven eight | |
| 979 | // nine | 962 | |
| 980 | // - ten | 963 | nine |
| 981 | // - six" | 964 | - ten |
| 982 | // .unindent(), | 965 | - six" |
| 983 | // expected_styles: vec![HighlightStyle { | 966 | .unindent(), |
| 984 | // underline: Some(Underline { | 967 | expected_styles: vec![HighlightStyle { |
| 985 | // thickness: 1.0.into(), | 968 | underline: Some(UnderlineStyle { |
| 986 | // ..Default::default() | 969 | thickness: 1.0.into(), |
| 987 | // }), | 970 | ..Default::default() |
| 988 | // ..Default::default() | 971 | }), |
| 989 | // }], | 972 | ..Default::default() |
| 990 | // }, | 973 | }], |
| 991 | // ]; | 974 | }, |
| 992 | 975 | ]; | |
| 993 | // for Row { | 976 | |
| 994 | // blocks, | 977 | for Row { |
| 995 | // expected_marked_text, | 978 | blocks, |
| 996 | // expected_styles, | 979 | expected_marked_text, |
| 997 | // } in &rows[0..] | 980 | expected_styles, |
| 998 | // { | 981 | } in &rows[0..] |
| 999 | // let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); | 982 | { |
| 1000 | 983 | let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); | |
| 1001 | // let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); | 984 | |
| 1002 | // let expected_highlights = ranges | 985 | let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); |
| 1003 | // .into_iter() | 986 | let expected_highlights = ranges |
| 1004 | // .zip(expected_styles.iter().cloned()) | 987 | .into_iter() |
| 1005 | // .collect::<Vec<_>>(); | 988 | .zip(expected_styles.iter().cloned()) |
| 1006 | // assert_eq!( | 989 | .collect::<Vec<_>>(); |
| 1007 | // rendered.text, expected_text, | 990 | assert_eq!( |
| 1008 | // "wrong text for input {blocks:?}" | 991 | rendered.text, expected_text, |
| 1009 | // ); | 992 | "wrong text for input {blocks:?}" |
| 1010 | 993 | ); | |
| 1011 | // let rendered_highlights: Vec<_> = rendered | 994 | |
| 1012 | // .highlights | 995 | let rendered_highlights: Vec<_> = rendered |
| 1013 | // .iter() | 996 | .highlights |
| 1014 | // .filter_map(|(range, highlight)| { | 997 | .iter() |
| 1015 | // let highlight = highlight.to_highlight_style(&style.syntax)?; | 998 | .filter_map(|(range, highlight)| { |
| 1016 | // Some((range.clone(), highlight)) | 999 | let highlight = highlight.to_highlight_style(&style.syntax)?; |
| 1017 | // }) | 1000 | Some((range.clone(), highlight)) |
| 1018 | // .collect(); | 1001 | }) |
| 1019 | 1002 | .collect(); | |
| 1020 | // assert_eq!( | 1003 | |
| 1021 | // rendered_highlights, expected_highlights, | 1004 | assert_eq!( |
| 1022 | // "wrong highlights for input {blocks:?}" | 1005 | rendered_highlights, expected_highlights, |
| 1023 | // ); | 1006 | "wrong highlights for input {blocks:?}" |
| 1024 | // } | 1007 | ); |
| 1025 | 1008 | } | |
| 1026 | // editor | 1009 | }) |
| 1027 | // }); | 1010 | .unwrap(); |
| 1028 | // } | 1011 | } |
| 1029 | 1012 | ||
| 1030 | // #[gpui::test] | 1013 | #[gpui::test] |
| 1031 | // async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) { | 1014 | async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) { |
| 1032 | // init_test(cx, |settings| { | 1015 | init_test(cx, |settings| { |
| 1033 | // settings.defaults.inlay_hints = Some(InlayHintSettings { | 1016 | settings.defaults.inlay_hints = Some(InlayHintSettings { |
| 1034 | // enabled: true, | 1017 | enabled: true, |
| 1035 | // show_type_hints: true, | 1018 | show_type_hints: true, |
| 1036 | // show_parameter_hints: true, | 1019 | show_parameter_hints: true, |
| 1037 | // show_other_hints: true, | 1020 | show_other_hints: true, |
| 1038 | // }) | 1021 | }) |
| 1039 | // }); | 1022 | }); |
| 1040 | 1023 | ||
| 1041 | // let mut cx = EditorLspTestContext::new_rust( | 1024 | let mut cx = EditorLspTestContext::new_rust( |
| 1042 | // lsp::ServerCapabilities { | 1025 | lsp::ServerCapabilities { |
| 1043 | // inlay_hint_provider: Some(lsp::OneOf::Right( | 1026 | inlay_hint_provider: Some(lsp::OneOf::Right( |
| 1044 | // lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions { | 1027 | lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions { |
| 1045 | // resolve_provider: Some(true), | 1028 | resolve_provider: Some(true), |
| 1046 | // ..Default::default() | 1029 | ..Default::default() |
| 1047 | // }), | 1030 | }), |
| 1048 | // )), | 1031 | )), |
| 1049 | // ..Default::default() | 1032 | ..Default::default() |
| 1050 | // }, | 1033 | }, |
| 1051 | // cx, | 1034 | cx, |
| 1052 | // ) | 1035 | ) |
| 1053 | // .await; | 1036 | .await; |
| 1054 | 1037 | ||
| 1055 | // cx.set_state(indoc! {" | 1038 | cx.set_state(indoc! {" |
| 1056 | // struct TestStruct; | 1039 | struct TestStruct; |
| 1057 | 1040 | ||
| 1058 | // // ================== | 1041 | // ================== |
| 1059 | 1042 | ||
| 1060 | // struct TestNewType<T>(T); | 1043 | struct TestNewType<T>(T); |
| 1061 | 1044 | ||
| 1062 | // fn main() { | 1045 | fn main() { |
| 1063 | // let variableˇ = TestNewType(TestStruct); | 1046 | let variableˇ = TestNewType(TestStruct); |
| 1064 | // } | 1047 | } |
| 1065 | // "}); | 1048 | "}); |
| 1066 | 1049 | ||
| 1067 | // let hint_start_offset = cx.ranges(indoc! {" | 1050 | let hint_start_offset = cx.ranges(indoc! {" |
| 1068 | // struct TestStruct; | 1051 | struct TestStruct; |
| 1069 | 1052 | ||
| 1070 | // // ================== | 1053 | // ================== |
| 1071 | 1054 | ||
| 1072 | // struct TestNewType<T>(T); | 1055 | struct TestNewType<T>(T); |
| 1073 | 1056 | ||
| 1074 | // fn main() { | 1057 | fn main() { |
| 1075 | // let variableˇ = TestNewType(TestStruct); | 1058 | let variableˇ = TestNewType(TestStruct); |
| 1076 | // } | 1059 | } |
| 1077 | // "})[0] | 1060 | "})[0] |
| 1078 | // .start; | 1061 | .start; |
| 1079 | // let hint_position = cx.to_lsp(hint_start_offset); | 1062 | let hint_position = cx.to_lsp(hint_start_offset); |
| 1080 | // let new_type_target_range = cx.lsp_range(indoc! {" | 1063 | let new_type_target_range = cx.lsp_range(indoc! {" |
| 1081 | // struct TestStruct; | 1064 | struct TestStruct; |
| 1082 | 1065 | ||
| 1083 | // // ================== | 1066 | // ================== |
| 1084 | 1067 | ||
| 1085 | // struct «TestNewType»<T>(T); | 1068 | struct «TestNewType»<T>(T); |
| 1086 | 1069 | ||
| 1087 | // fn main() { | 1070 | fn main() { |
| 1088 | // let variable = TestNewType(TestStruct); | 1071 | let variable = TestNewType(TestStruct); |
| 1089 | // } | 1072 | } |
| 1090 | // "}); | 1073 | "}); |
| 1091 | // let struct_target_range = cx.lsp_range(indoc! {" | 1074 | let struct_target_range = cx.lsp_range(indoc! {" |
| 1092 | // struct «TestStruct»; | 1075 | struct «TestStruct»; |
| 1093 | 1076 | ||
| 1094 | // // ================== | 1077 | // ================== |
| 1095 | 1078 | ||
| 1096 | // struct TestNewType<T>(T); | 1079 | struct TestNewType<T>(T); |
| 1097 | 1080 | ||
| 1098 | // fn main() { | 1081 | fn main() { |
| 1099 | // let variable = TestNewType(TestStruct); | 1082 | let variable = TestNewType(TestStruct); |
| 1100 | // } | 1083 | } |
| 1101 | // "}); | 1084 | "}); |
| 1102 | 1085 | ||
| 1103 | // let uri = cx.buffer_lsp_url.clone(); | 1086 | let uri = cx.buffer_lsp_url.clone(); |
| 1104 | // let new_type_label = "TestNewType"; | 1087 | let new_type_label = "TestNewType"; |
| 1105 | // let struct_label = "TestStruct"; | 1088 | let struct_label = "TestStruct"; |
| 1106 | // let entire_hint_label = ": TestNewType<TestStruct>"; | 1089 | let entire_hint_label = ": TestNewType<TestStruct>"; |
| 1107 | // let closure_uri = uri.clone(); | 1090 | let closure_uri = uri.clone(); |
| 1108 | // cx.lsp | 1091 | cx.lsp |
| 1109 | // .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| { | 1092 | .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| { |
| 1110 | // let task_uri = closure_uri.clone(); | 1093 | let task_uri = closure_uri.clone(); |
| 1111 | // async move { | 1094 | async move { |
| 1112 | // assert_eq!(params.text_document.uri, task_uri); | 1095 | assert_eq!(params.text_document.uri, task_uri); |
| 1113 | // Ok(Some(vec![lsp::InlayHint { | 1096 | Ok(Some(vec![lsp::InlayHint { |
| 1114 | // position: hint_position, | 1097 | position: hint_position, |
| 1115 | // label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { | 1098 | label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart { |
| 1116 | // value: entire_hint_label.to_string(), | 1099 | value: entire_hint_label.to_string(), |
| 1117 | // ..Default::default() | 1100 | ..Default::default() |
| 1118 | // }]), | 1101 | }]), |
| 1119 | // kind: Some(lsp::InlayHintKind::TYPE), | 1102 | kind: Some(lsp::InlayHintKind::TYPE), |
| 1120 | // text_edits: None, | 1103 | text_edits: None, |
| 1121 | // tooltip: None, | 1104 | tooltip: None, |
| 1122 | // padding_left: Some(false), | 1105 | padding_left: Some(false), |
| 1123 | // padding_right: Some(false), | 1106 | padding_right: Some(false), |
| 1124 | // data: None, | 1107 | data: None, |
| 1125 | // }])) | 1108 | }])) |
| 1126 | // } | 1109 | } |
| 1127 | // }) | 1110 | }) |
| 1128 | // .next() | 1111 | .next() |
| 1129 | // .await; | 1112 | .await; |
| 1130 | // cx.foreground().run_until_parked(); | 1113 | cx.background_executor.run_until_parked(); |
| 1131 | // cx.update_editor(|editor, cx| { | 1114 | cx.update_editor(|editor, cx| { |
| 1132 | // let expected_layers = vec![entire_hint_label.to_string()]; | 1115 | let expected_layers = vec![entire_hint_label.to_string()]; |
| 1133 | // assert_eq!(expected_layers, cached_hint_labels(editor)); | 1116 | assert_eq!(expected_layers, cached_hint_labels(editor)); |
| 1134 | // assert_eq!(expected_layers, visible_hint_labels(editor, cx)); | 1117 | assert_eq!(expected_layers, visible_hint_labels(editor, cx)); |
| 1135 | // }); | 1118 | }); |
| 1136 | 1119 | ||
| 1137 | // let inlay_range = cx | 1120 | let inlay_range = cx |
| 1138 | // .ranges(indoc! {" | 1121 | .ranges(indoc! {" |
| 1139 | // struct TestStruct; | 1122 | struct TestStruct; |
| 1140 | 1123 | ||
| 1141 | // // ================== | 1124 | // ================== |
| 1142 | 1125 | ||
| 1143 | // struct TestNewType<T>(T); | 1126 | struct TestNewType<T>(T); |
| 1144 | 1127 | ||
| 1145 | // fn main() { | 1128 | fn main() { |
| 1146 | // let variable« »= TestNewType(TestStruct); | 1129 | let variable« »= TestNewType(TestStruct); |
| 1147 | // } | 1130 | } |
| 1148 | // "}) | 1131 | "}) |
| 1149 | // .get(0) | 1132 | .get(0) |
| 1150 | // .cloned() | 1133 | .cloned() |
| 1151 | // .unwrap(); | 1134 | .unwrap(); |
| 1152 | // let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| { | 1135 | let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| { |
| 1153 | // let snapshot = editor.snapshot(cx); | 1136 | let snapshot = editor.snapshot(cx); |
| 1154 | // let previous_valid = inlay_range.start.to_display_point(&snapshot); | 1137 | let previous_valid = inlay_range.start.to_display_point(&snapshot); |
| 1155 | // let next_valid = inlay_range.end.to_display_point(&snapshot); | 1138 | let next_valid = inlay_range.end.to_display_point(&snapshot); |
| 1156 | // assert_eq!(previous_valid.row(), next_valid.row()); | 1139 | assert_eq!(previous_valid.row(), next_valid.row()); |
| 1157 | // assert!(previous_valid.column() < next_valid.column()); | 1140 | assert!(previous_valid.column() < next_valid.column()); |
| 1158 | // let exact_unclipped = DisplayPoint::new( | 1141 | let exact_unclipped = DisplayPoint::new( |
| 1159 | // previous_valid.row(), | 1142 | previous_valid.row(), |
| 1160 | // previous_valid.column() | 1143 | previous_valid.column() |
| 1161 | // + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2) | 1144 | + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2) |
| 1162 | // as u32, | 1145 | as u32, |
| 1163 | // ); | 1146 | ); |
| 1164 | // PointForPosition { | 1147 | PointForPosition { |
| 1165 | // previous_valid, | 1148 | previous_valid, |
| 1166 | // next_valid, | 1149 | next_valid, |
| 1167 | // exact_unclipped, | 1150 | exact_unclipped, |
| 1168 | // column_overshoot_after_line_end: 0, | 1151 | column_overshoot_after_line_end: 0, |
| 1169 | // } | 1152 | } |
| 1170 | // }); | 1153 | }); |
| 1171 | // cx.update_editor(|editor, cx| { | 1154 | cx.update_editor(|editor, cx| { |
| 1172 | // update_inlay_link_and_hover_points( | 1155 | update_inlay_link_and_hover_points( |
| 1173 | // &editor.snapshot(cx), | 1156 | &editor.snapshot(cx), |
| 1174 | // new_type_hint_part_hover_position, | 1157 | new_type_hint_part_hover_position, |
| 1175 | // editor, | 1158 | editor, |
| 1176 | // true, | 1159 | true, |
| 1177 | // false, | 1160 | false, |
| 1178 | // cx, | 1161 | cx, |
| 1179 | // ); | 1162 | ); |
| 1180 | // }); | 1163 | }); |
| 1181 | 1164 | ||
| 1182 | // let resolve_closure_uri = uri.clone(); | 1165 | let resolve_closure_uri = uri.clone(); |
| 1183 | // cx.lsp | 1166 | cx.lsp |
| 1184 | // .handle_request::<lsp::request::InlayHintResolveRequest, _, _>( | 1167 | .handle_request::<lsp::request::InlayHintResolveRequest, _, _>( |
| 1185 | // move |mut hint_to_resolve, _| { | 1168 | move |mut hint_to_resolve, _| { |
| 1186 | // let mut resolved_hint_positions = BTreeSet::new(); | 1169 | let mut resolved_hint_positions = BTreeSet::new(); |
| 1187 | // let task_uri = resolve_closure_uri.clone(); | 1170 | let task_uri = resolve_closure_uri.clone(); |
| 1188 | // async move { | 1171 | async move { |
| 1189 | // let inserted = resolved_hint_positions.insert(hint_to_resolve.position); | 1172 | let inserted = resolved_hint_positions.insert(hint_to_resolve.position); |
| 1190 | // assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice"); | 1173 | assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice"); |
| 1191 | 1174 | ||
| 1192 | // // `: TestNewType<TestStruct>` | 1175 | // `: TestNewType<TestStruct>` |
| 1193 | // hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![ | 1176 | hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![ |
| 1194 | // lsp::InlayHintLabelPart { | 1177 | lsp::InlayHintLabelPart { |
| 1195 | // value: ": ".to_string(), | 1178 | value: ": ".to_string(), |
| 1196 | // ..Default::default() | 1179 | ..Default::default() |
| 1197 | // }, | 1180 | }, |
| 1198 | // lsp::InlayHintLabelPart { | 1181 | lsp::InlayHintLabelPart { |
| 1199 | // value: new_type_label.to_string(), | 1182 | value: new_type_label.to_string(), |
| 1200 | // location: Some(lsp::Location { | 1183 | location: Some(lsp::Location { |
| 1201 | // uri: task_uri.clone(), | 1184 | uri: task_uri.clone(), |
| 1202 | // range: new_type_target_range, | 1185 | range: new_type_target_range, |
| 1203 | // }), | 1186 | }), |
| 1204 | // tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!( | 1187 | tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!( |
| 1205 | // "A tooltip for `{new_type_label}`" | 1188 | "A tooltip for `{new_type_label}`" |
| 1206 | // ))), | 1189 | ))), |
| 1207 | // ..Default::default() | 1190 | ..Default::default() |
| 1208 | // }, | 1191 | }, |
| 1209 | // lsp::InlayHintLabelPart { | 1192 | lsp::InlayHintLabelPart { |
| 1210 | // value: "<".to_string(), | 1193 | value: "<".to_string(), |
| 1211 | // ..Default::default() | 1194 | ..Default::default() |
| 1212 | // }, | 1195 | }, |
| 1213 | // lsp::InlayHintLabelPart { | 1196 | lsp::InlayHintLabelPart { |
| 1214 | // value: struct_label.to_string(), | 1197 | value: struct_label.to_string(), |
| 1215 | // location: Some(lsp::Location { | 1198 | location: Some(lsp::Location { |
| 1216 | // uri: task_uri, | 1199 | uri: task_uri, |
| 1217 | // range: struct_target_range, | 1200 | range: struct_target_range, |
| 1218 | // }), | 1201 | }), |
| 1219 | // tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent( | 1202 | tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent( |
| 1220 | // lsp::MarkupContent { | 1203 | lsp::MarkupContent { |
| 1221 | // kind: lsp::MarkupKind::Markdown, | 1204 | kind: lsp::MarkupKind::Markdown, |
| 1222 | // value: format!("A tooltip for `{struct_label}`"), | 1205 | value: format!("A tooltip for `{struct_label}`"), |
| 1223 | // }, | 1206 | }, |
| 1224 | // )), | 1207 | )), |
| 1225 | // ..Default::default() | 1208 | ..Default::default() |
| 1226 | // }, | 1209 | }, |
| 1227 | // lsp::InlayHintLabelPart { | 1210 | lsp::InlayHintLabelPart { |
| 1228 | // value: ">".to_string(), | 1211 | value: ">".to_string(), |
| 1229 | // ..Default::default() | 1212 | ..Default::default() |
| 1230 | // }, | 1213 | }, |
| 1231 | // ]); | 1214 | ]); |
| 1232 | 1215 | ||
| 1233 | // Ok(hint_to_resolve) | 1216 | Ok(hint_to_resolve) |
| 1234 | // } | 1217 | } |
| 1235 | // }, | 1218 | }, |
| 1236 | // ) | 1219 | ) |
| 1237 | // .next() | 1220 | .next() |
| 1238 | // .await; | 1221 | .await; |
| 1239 | // cx.foreground().run_until_parked(); | 1222 | cx.background_executor.run_until_parked(); |
| 1240 | 1223 | ||
| 1241 | // cx.update_editor(|editor, cx| { | 1224 | cx.update_editor(|editor, cx| { |
| 1242 | // update_inlay_link_and_hover_points( | 1225 | update_inlay_link_and_hover_points( |
| 1243 | // &editor.snapshot(cx), | 1226 | &editor.snapshot(cx), |
| 1244 | // new_type_hint_part_hover_position, | 1227 | new_type_hint_part_hover_position, |
| 1245 | // editor, | 1228 | editor, |
| 1246 | // true, | 1229 | true, |
| 1247 | // false, | 1230 | false, |
| 1248 | // cx, | 1231 | cx, |
| 1249 | // ); | 1232 | ); |
| 1250 | // }); | 1233 | }); |
| 1251 | // cx.foreground() | 1234 | cx.background_executor |
| 1252 | // .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); | 1235 | .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); |
| 1253 | // cx.foreground().run_until_parked(); | 1236 | cx.background_executor.run_until_parked(); |
| 1254 | // cx.update_editor(|editor, cx| { | 1237 | cx.update_editor(|editor, cx| { |
| 1255 | // let hover_state = &editor.hover_state; | 1238 | let hover_state = &editor.hover_state; |
| 1256 | // assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); | 1239 | assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); |
| 1257 | // let popover = hover_state.info_popover.as_ref().unwrap(); | 1240 | let popover = hover_state.info_popover.as_ref().unwrap(); |
| 1258 | // let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); | 1241 | let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); |
| 1259 | // assert_eq!( | 1242 | assert_eq!( |
| 1260 | // popover.symbol_range, | 1243 | popover.symbol_range, |
| 1261 | // RangeInEditor::Inlay(InlayHighlight { | 1244 | RangeInEditor::Inlay(InlayHighlight { |
| 1262 | // inlay: InlayId::Hint(0), | 1245 | inlay: InlayId::Hint(0), |
| 1263 | // inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), | 1246 | inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), |
| 1264 | // range: ": ".len()..": ".len() + new_type_label.len(), | 1247 | range: ": ".len()..": ".len() + new_type_label.len(), |
| 1265 | // }), | 1248 | }), |
| 1266 | // "Popover range should match the new type label part" | 1249 | "Popover range should match the new type label part" |
| 1267 | // ); | 1250 | ); |
| 1268 | // assert_eq!( | 1251 | assert_eq!( |
| 1269 | // popover.parsed_content.text, | 1252 | popover.parsed_content.text, |
| 1270 | // format!("A tooltip for `{new_type_label}`"), | 1253 | format!("A tooltip for `{new_type_label}`"), |
| 1271 | // "Rendered text should not anyhow alter backticks" | 1254 | "Rendered text should not anyhow alter backticks" |
| 1272 | // ); | 1255 | ); |
| 1273 | // }); | 1256 | }); |
| 1274 | 1257 | ||
| 1275 | // let struct_hint_part_hover_position = cx.update_editor(|editor, cx| { | 1258 | let struct_hint_part_hover_position = cx.update_editor(|editor, cx| { |
| 1276 | // let snapshot = editor.snapshot(cx); | 1259 | let snapshot = editor.snapshot(cx); |
| 1277 | // let previous_valid = inlay_range.start.to_display_point(&snapshot); | 1260 | let previous_valid = inlay_range.start.to_display_point(&snapshot); |
| 1278 | // let next_valid = inlay_range.end.to_display_point(&snapshot); | 1261 | let next_valid = inlay_range.end.to_display_point(&snapshot); |
| 1279 | // assert_eq!(previous_valid.row(), next_valid.row()); | 1262 | assert_eq!(previous_valid.row(), next_valid.row()); |
| 1280 | // assert!(previous_valid.column() < next_valid.column()); | 1263 | assert!(previous_valid.column() < next_valid.column()); |
| 1281 | // let exact_unclipped = DisplayPoint::new( | 1264 | let exact_unclipped = DisplayPoint::new( |
| 1282 | // previous_valid.row(), | 1265 | previous_valid.row(), |
| 1283 | // previous_valid.column() | 1266 | previous_valid.column() |
| 1284 | // + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2) | 1267 | + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2) |
| 1285 | // as u32, | 1268 | as u32, |
| 1286 | // ); | 1269 | ); |
| 1287 | // PointForPosition { | 1270 | PointForPosition { |
| 1288 | // previous_valid, | 1271 | previous_valid, |
| 1289 | // next_valid, | 1272 | next_valid, |
| 1290 | // exact_unclipped, | 1273 | exact_unclipped, |
| 1291 | // column_overshoot_after_line_end: 0, | 1274 | column_overshoot_after_line_end: 0, |
| 1292 | // } | 1275 | } |
| 1293 | // }); | 1276 | }); |
| 1294 | // cx.update_editor(|editor, cx| { | 1277 | cx.update_editor(|editor, cx| { |
| 1295 | // update_inlay_link_and_hover_points( | 1278 | update_inlay_link_and_hover_points( |
| 1296 | // &editor.snapshot(cx), | 1279 | &editor.snapshot(cx), |
| 1297 | // struct_hint_part_hover_position, | 1280 | struct_hint_part_hover_position, |
| 1298 | // editor, | 1281 | editor, |
| 1299 | // true, | 1282 | true, |
| 1300 | // false, | 1283 | false, |
| 1301 | // cx, | 1284 | cx, |
| 1302 | // ); | 1285 | ); |
| 1303 | // }); | 1286 | }); |
| 1304 | // cx.foreground() | 1287 | cx.background_executor |
| 1305 | // .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); | 1288 | .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); |
| 1306 | // cx.foreground().run_until_parked(); | 1289 | cx.background_executor.run_until_parked(); |
| 1307 | // cx.update_editor(|editor, cx| { | 1290 | cx.update_editor(|editor, cx| { |
| 1308 | // let hover_state = &editor.hover_state; | 1291 | let hover_state = &editor.hover_state; |
| 1309 | // assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); | 1292 | assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); |
| 1310 | // let popover = hover_state.info_popover.as_ref().unwrap(); | 1293 | let popover = hover_state.info_popover.as_ref().unwrap(); |
| 1311 | // let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); | 1294 | let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); |
| 1312 | // assert_eq!( | 1295 | assert_eq!( |
| 1313 | // popover.symbol_range, | 1296 | popover.symbol_range, |
| 1314 | // RangeInEditor::Inlay(InlayHighlight { | 1297 | RangeInEditor::Inlay(InlayHighlight { |
| 1315 | // inlay: InlayId::Hint(0), | 1298 | inlay: InlayId::Hint(0), |
| 1316 | // inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), | 1299 | inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), |
| 1317 | // range: ": ".len() + new_type_label.len() + "<".len() | 1300 | range: ": ".len() + new_type_label.len() + "<".len() |
| 1318 | // ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), | 1301 | ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), |
| 1319 | // }), | 1302 | }), |
| 1320 | // "Popover range should match the struct label part" | 1303 | "Popover range should match the struct label part" |
| 1321 | // ); | 1304 | ); |
| 1322 | // assert_eq!( | 1305 | assert_eq!( |
| 1323 | // popover.parsed_content.text, | 1306 | popover.parsed_content.text, |
| 1324 | // format!("A tooltip for {struct_label}"), | 1307 | format!("A tooltip for {struct_label}"), |
| 1325 | // "Rendered markdown element should remove backticks from text" | 1308 | "Rendered markdown element should remove backticks from text" |
| 1326 | // ); | 1309 | ); |
| 1327 | // }); | 1310 | }); |
| 1328 | // } | 1311 | } |
| 1329 | // } | 1312 | } |
| @@ -861,7 +861,7 @@ async fn fetch_and_update_hints( · 860 unmodified lines | |||
| 861 | let inlay_hints_fetch_task = editor | 861 | let inlay_hints_fetch_task = editor |
| 862 | .update(&mut cx, |editor, cx| { | 862 | .update(&mut cx, |editor, cx| { |
| 863 | if got_throttled { | 863 | if got_throttled { |
| 864 | let query_not_around_visible_range = match editor.excerpt_visible_offsets(None, cx).remove(&query.excerpt_id) { | 864 | let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) { |
| 865 | Some((_, _, current_visible_range)) => { | 865 | Some((_, _, current_visible_range)) => { |
| 866 | let visible_offset_length = current_visible_range.len(); | 866 | let visible_offset_length = current_visible_range.len(); |
| 867 | let double_visible_range = current_visible_range | 867 | let double_visible_range = current_visible_range |
| @@ -2201,7 +2201,9 @@ pub mod tests { · 1333 unmodified lines | |||
| 2201 | cx: &mut gpui::TestAppContext, | 2201 | cx: &mut gpui::TestAppContext, |
| 2202 | ) -> Range<Point> { | 2202 | ) -> Range<Point> { |
| 2203 | let ranges = editor | 2203 | let ranges = editor |
| 2204 | .update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx)) | 2204 | .update(cx, |editor, cx| { |
| 2205 | editor.excerpts_for_inlay_hints_query(None, cx) | ||
| 2206 | }) | ||
| 2205 | .unwrap(); | 2207 | .unwrap(); |
| 2206 | assert_eq!( | 2208 | assert_eq!( |
| 2207 | ranges.len(), | 2209 | ranges.len(), |
| @@ -9,8 +9,8 @@ use collections::HashSet; · 8 unmodified lines | |||
| 9 | use futures::future::try_join_all; | 9 | use futures::future::try_join_all; |
| 10 | use gpui::{ | 10 | use gpui::{ |
| 11 | div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter, | 11 | div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter, |
| 12 | FocusHandle, Model, ParentComponent, Pixels, SharedString, Styled, Subscription, Task, View, | 12 | FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View, |
| 13 | ViewContext, VisualContext, WeakView, | 13 | ViewContext, VisualContext, WeakView, WindowContext, |
| 14 | }; | 14 | }; |
| 15 | use language::{ | 15 | use language::{ |
| 16 | proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt, | 16 | proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt, |
| @@ -30,7 +30,7 @@ use std::{ · 13 unmodified lines | |||
| 30 | }; | 30 | }; |
| 31 | use text::Selection; | 31 | use text::Selection; |
| 32 | use theme::{ActiveTheme, Theme}; | 32 | use theme::{ActiveTheme, Theme}; |
| 33 | use ui::{Label, TextColor}; | 33 | use ui::{Color, Label}; |
| 34 | use util::{paths::PathExt, ResultExt, TryFutureExt}; | 34 | use util::{paths::PathExt, ResultExt, TryFutureExt}; |
| 35 | use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle}; | 35 | use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle}; |
| 36 | use workspace::{ | 36 | use workspace::{ |
| @@ -584,7 +584,7 @@ impl Item for Editor { · 547 unmodified lines | |||
| 584 | Some(path.to_string_lossy().to_string().into()) | 584 | Some(path.to_string_lossy().to_string().into()) |
| 585 | } | 585 | } |
| 586 | 586 | ||
| 587 | fn tab_content<T: 'static>(&self, detail: Option<usize>, cx: &AppContext) -> AnyElement<T> { | 587 | fn tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement { |
| 588 | let theme = cx.theme(); | 588 | let theme = cx.theme(); |
| 589 | 589 | ||
| 590 | AnyElement::new( | 590 | AnyElement::new( |
| @@ -604,7 +604,7 @@ impl Item for Editor { · 13 unmodified lines | |||
| 604 | &description, | 604 | &description, |
| 605 | MAX_TAB_TITLE_LEN, | 605 | MAX_TAB_TITLE_LEN, |
| 606 | )) | 606 | )) |
| 607 | .color(TextColor::Muted), | 607 | .color(Color::Muted), |
| 608 | ), | 608 | ), |
| 609 | ) | 609 | ) |
| 610 | })), | 610 | })), |
| @@ -761,7 +761,7 @@ impl Item for Editor { · 150 unmodified lines | |||
| 761 | } | 761 | } |
| 762 | 762 | ||
| 763 | fn breadcrumb_location(&self) -> ToolbarItemLocation { | 763 | fn breadcrumb_location(&self) -> ToolbarItemLocation { |
| 764 | ToolbarItemLocation::PrimaryLeft { flex: None } | 764 | ToolbarItemLocation::PrimaryLeft |
| 765 | } | 765 | } |
| 766 | 766 | ||
| 767 | fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> { | 767 | fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> { |
| @@ -907,17 +907,15 @@ impl SearchableItem for Editor { · 139 unmodified lines | |||
| 907 | type Match = Range<Anchor>; | 907 | type Match = Range<Anchor>; |
| 908 | 908 | ||
| 909 | fn clear_matches(&mut self, cx: &mut ViewContext<Self>) { | 909 | fn clear_matches(&mut self, cx: &mut ViewContext<Self>) { |
| 910 | todo!() | 910 | self.clear_background_highlights::<BufferSearchHighlights>(cx); |
| 911 | // self.clear_background_highlights::<BufferSearchHighlights>(cx); | ||
| 912 | } | 911 | } |
| 913 | 912 | ||
| 914 | fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) { | 913 | fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) { |
| 915 | todo!() | 914 | self.highlight_background::<BufferSearchHighlights>( |
| 916 | // self.highlight_background::<BufferSearchHighlights>( | 915 | matches, |
| 917 | // matches, | 916 | |theme| theme.title_bar_background, // todo: update theme |
| 918 | // |theme| theme.search.match_background, | 917 | cx, |
| 919 | // cx, | 918 | ); |
| 920 | // ); | ||
| 921 | } | 919 | } |
| 922 | 920 | ||
| 923 | fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String { | 921 | fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String { |
| @@ -952,22 +950,20 @@ impl SearchableItem for Editor { · 28 unmodified lines | |||
| 952 | matches: Vec<Range<Anchor>>, | 950 | matches: Vec<Range<Anchor>>, |
| 953 | cx: &mut ViewContext<Self>, | 951 | cx: &mut ViewContext<Self>, |
| 954 | ) { | 952 | ) { |
| 955 | todo!() | 953 | self.unfold_ranges([matches[index].clone()], false, true, cx); |
| 956 | // self.unfold_ranges([matches[index].clone()], false, true, cx); | 954 | let range = self.range_for_match(&matches[index]); |
| 957 | // let range = self.range_for_match(&matches[index]); | 955 | self.change_selections(Some(Autoscroll::fit()), cx, |s| { |
| 958 | // self.change_selections(Some(Autoscroll::fit()), cx, |s| { | 956 | s.select_ranges([range]); |
| 959 | // s.select_ranges([range]); | 957 | }) |
| 960 | // }) | ||
| 961 | } | 958 | } |
| 962 | 959 | ||
| 963 | fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) { | 960 | fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) { |
| 964 | todo!() | 961 | self.unfold_ranges(matches.clone(), false, false, cx); |
| 965 | // self.unfold_ranges(matches.clone(), false, false, cx); | 962 | let mut ranges = Vec::new(); |
| 966 | // let mut ranges = Vec::new(); | 963 | for m in &matches { |
| 967 | // for m in &matches { | 964 | ranges.push(self.range_for_match(&m)) |
| 968 | // ranges.push(self.range_for_match(&m)) | 965 | } |
| 969 | // } | 966 | self.change_selections(None, cx, |s| s.select_ranges(ranges)); |
| 970 | // self.change_selections(None, cx, |s| s.select_ranges(ranges)); | ||
| 971 | } | 967 | } |
| 972 | fn replace( | 968 | fn replace( |
| 973 | &mut self, | 969 | &mut self, |
| @@ -2,9 +2,8 @@ use collections::HashMap; · 1 unmodified line | |||
| 2 | use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; | 2 | use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; |
| 3 | use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; | 3 | use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; |
| 4 | use gpui::{ | 4 | use gpui::{ |
| 5 | actions, div, AppContext, Component, Div, EventEmitter, FocusHandle, FocusableView, | 5 | actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model, |
| 6 | InteractiveComponent, Manager, Model, ParentComponent, Render, Styled, Task, View, ViewContext, | 6 | ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView, |
| 7 | VisualContext, WeakView, | ||
| 8 | }; | 7 | }; |
| 9 | use picker::{Picker, PickerDelegate}; | 8 | use picker::{Picker, PickerDelegate}; |
| 10 | use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; | 9 | use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; |
| @@ -16,8 +15,7 @@ use std::{ · 5 unmodified lines | |||
| 16 | }, | 15 | }, |
| 17 | }; | 16 | }; |
| 18 | use text::Point; | 17 | use text::Point; |
| 19 | use theme::ActiveTheme; | 18 | use ui::{v_stack, HighlightedLabel, ListItem}; |
| 20 | use ui::{v_stack, HighlightedLabel, StyledExt}; | ||
| 21 | use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; | 19 | use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; |
| 22 | use workspace::Workspace; | 20 | use workspace::Workspace; |
| 23 | 21 | ||
| @@ -111,14 +109,14 @@ impl FileFinder { · 87 unmodified lines | |||
| 111 | } | 109 | } |
| 112 | } | 110 | } |
| 113 | 111 | ||
| 114 | impl EventEmitter<Manager> for FileFinder {} | 112 | impl EventEmitter<DismissEvent> for FileFinder {} |
| 115 | impl FocusableView for FileFinder { | 113 | impl FocusableView for FileFinder { |
| 116 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle { | 114 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle { |
| 117 | self.picker.focus_handle(cx) | 115 | self.picker.focus_handle(cx) |
| 118 | } | 116 | } |
| 119 | } | 117 | } |
| 120 | impl Render for FileFinder { | 118 | impl Render for FileFinder { |
| 121 | type Element = Div<Self>; | 119 | type Element = Div; |
| 122 | 120 | ||
| 123 | fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { | 121 | fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { |
| 124 | v_stack().w_96().child(self.picker.clone()) | 122 | v_stack().w_96().child(self.picker.clone()) |
| @@ -530,7 +528,7 @@ impl FileFinderDelegate { · 405 unmodified lines | |||
| 530 | } | 528 | } |
| 531 | 529 | ||
| 532 | impl PickerDelegate for FileFinderDelegate { | 530 | impl PickerDelegate for FileFinderDelegate { |
| 533 | type ListItem = Div<Picker<Self>>; | 531 | type ListItem = ListItem; |
| 534 | 532 | ||
| 535 | fn placeholder_text(&self) -> Arc<str> { | 533 | fn placeholder_text(&self) -> Arc<str> { |
| 536 | "Search project files...".into() | 534 | "Search project files...".into() |
| @@ -690,7 +688,7 @@ impl PickerDelegate for FileFinderDelegate { · 153 unmodified lines | |||
| 690 | } | 688 | } |
| 691 | } | 689 | } |
| 692 | finder | 690 | finder |
| 693 | .update(&mut cx, |_, cx| cx.emit(Manager::Dismiss)) | 691 | .update(&mut cx, |_, cx| cx.emit(DismissEvent::Dismiss)) |
| 694 | .ok()?; | 692 | .ok()?; |
| 695 | 693 | ||
| 696 | Some(()) | 694 | Some(()) |
| @@ -702,7 +700,7 @@ impl PickerDelegate for FileFinderDelegate { · 5 unmodified lines | |||
| 702 | 700 | ||
| 703 | fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) { | 701 | fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) { |
| 704 | self.file_finder | 702 | self.file_finder |
| 705 | .update(cx, |_, cx| cx.emit(Manager::Dismiss)) | 703 | .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) |
| 706 | .log_err(); | 704 | .log_err(); |
| 707 | } | 705 | } |
| 708 | 706 | ||
| @@ -711,30 +709,22 @@ impl PickerDelegate for FileFinderDelegate { · 2 unmodified lines | |||
| 711 | ix: usize, | 709 | ix: usize, |
| 712 | selected: bool, | 710 | selected: bool, |
| 713 | cx: &mut ViewContext<Picker<Self>>, | 711 | cx: &mut ViewContext<Picker<Self>>, |
| 714 | ) -> Self::ListItem { | 712 | ) -> Option<Self::ListItem> { |
| 715 | let path_match = self | 713 | let path_match = self |
| 716 | .matches | 714 | .matches |
| 717 | .get(ix) | 715 | .get(ix) |
| 718 | .expect("Invalid matches state: no element for index {ix}"); | 716 | .expect("Invalid matches state: no element for index {ix}"); |
| 719 | let theme = cx.theme(); | ||
| 720 | let colors = theme.colors(); | ||
| 721 | 717 | ||
| 722 | let (file_name, file_name_positions, full_path, full_path_positions) = | 718 | let (file_name, file_name_positions, full_path, full_path_positions) = |
| 723 | self.labels_for_match(path_match, cx, ix); | 719 | self.labels_for_match(path_match, cx, ix); |
| 724 | 720 | ||
| 725 | div() | 721 | Some( |
| 726 | .px_1() | 722 | ListItem::new(ix).selected(selected).child( |
| 727 | .text_color(colors.text) | ||
| 728 | .text_ui() | ||
| 729 | .bg(colors.ghost_element_background) | ||
| 730 | .rounded_md() | ||
| 731 | .when(selected, |this| this.bg(colors.ghost_element_selected)) | ||
| 732 | .hover(|this| this.bg(colors.ghost_element_hover)) | ||
| 733 | .child( | ||
| 734 | v_stack() | 723 | v_stack() |
| 735 | .child(HighlightedLabel::new(file_name, file_name_positions)) | 724 | .child(HighlightedLabel::new(file_name, file_name_positions)) |
| 736 | .child(HighlightedLabel::new(full_path, full_path_positions)), | 725 | .child(HighlightedLabel::new(full_path, full_path_positions)), |
| 737 | ) | 726 | ), |
| 727 | ) | ||
| 738 | } | 728 | } |
| 739 | } | 729 | } |
| 740 | 730 | ||
| @@ -6,6 +6,8 @@ use gpui::BackgroundExecutor; · 5 unmodified lines | |||
| 6 | use std::{ | 6 | use std::{ |
| 7 | borrow::Cow, | 7 | borrow::Cow, |
| 8 | cmp::{self, Ordering}, | 8 | cmp::{self, Ordering}, |
| 9 | iter, | ||
| 10 | ops::Range, | ||
| 9 | sync::atomic::AtomicBool, | 11 | sync::atomic::AtomicBool, |
| 10 | }; | 12 | }; |
| 11 | 13 | ||
| @@ -54,6 +56,32 @@ pub struct StringMatch { · 42 unmodified lines | |||
| 54 | pub string: String, | 56 | pub string: String, |
| 55 | } | 57 | } |
| 56 | 58 | ||
| 59 | impl StringMatch { | ||
| 60 | pub fn ranges<'a>(&'a self) -> impl 'a + Iterator<Item = Range<usize>> { | ||
| 61 | let mut positions = self.positions.iter().peekable(); | ||
| 62 | iter::from_fn(move || { | ||
| 63 | while let Some(start) = positions.next().copied() { | ||
| 64 | let mut end = start + self.char_len_at_index(start); | ||
| 65 | while let Some(next_start) = positions.peek() { | ||
| 66 | if end == **next_start { | ||
| 67 | end += self.char_len_at_index(end); | ||
| 68 | positions.next(); | ||
| 69 | } else { | ||
| 70 | break; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | return Some(start..end); | ||
| 75 | } | ||
| 76 | None | ||
| 77 | }) | ||
| 78 | } | ||
| 79 | |||
| 80 | fn char_len_at_index(&self, ix: usize) -> usize { | ||
| 81 | self.string[ix..].chars().next().unwrap().len_utf8() | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 57 | impl PartialEq for StringMatch { | 85 | impl PartialEq for StringMatch { |
| 58 | fn eq(&self, other: &Self) -> bool { | 86 | fn eq(&self, other: &Self) -> bool { |
| 59 | self.cmp(other).is_eq() | 87 | self.cmp(other).is_eq() |
| @@ -1,14 +1,13 @@ | |||
| 1 | use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor}; | 1 | use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor}; |
| 2 | use gpui::{ | 2 | use gpui::{ |
| 3 | actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager, | 3 | actions, div, prelude::*, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, |
| 4 | ParentComponent, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, | 4 | FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, |
| 5 | WindowContext, | 5 | WindowContext, |
| 6 | }; | 6 | }; |
| 7 | use text::{Bias, Point}; | 7 | use text::{Bias, Point}; |
| 8 | use theme::ActiveTheme; | 8 | use theme::ActiveTheme; |
| 9 | use ui::{h_stack, v_stack, Label, StyledExt, TextColor}; | 9 | use ui::{h_stack, v_stack, Color, Label, StyledExt}; |
| 10 | use util::paths::FILE_ROW_COLUMN_DELIMITER; | 10 | use util::paths::FILE_ROW_COLUMN_DELIMITER; |
| 11 | use workspace::Workspace; | ||
| 12 | 11 | ||
| 13 | actions!(Toggle); | 12 | actions!(Toggle); |
| 14 | 13 | ||
| @@ -26,22 +25,24 @@ pub struct GoToLine { · 11 unmodified lines | |||
| 26 | 25 | ||
| 27 | impl FocusableView for GoToLine { | 26 | impl FocusableView for GoToLine { |
| 28 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle { | 27 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle { |
| 29 | self.active_editor.focus_handle(cx) | 28 | self.line_editor.focus_handle(cx) |
| 30 | } | 29 | } |
| 31 | } | 30 | } |
| 32 | impl EventEmitter<Manager> for GoToLine {} | 31 | impl EventEmitter<DismissEvent> for GoToLine {} |
| 33 | 32 | ||
| 34 | impl GoToLine { | 33 | impl GoToLine { |
| 35 | fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) { | 34 | fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) { |
| 36 | workspace.register_action(|workspace, _: &Toggle, cx| { | 35 | let handle = cx.view().downgrade(); |
| 37 | let Some(editor) = workspace | 36 | editor.register_action(move |_: &Toggle, cx| { |
| 38 | .active_item(cx) | 37 | let Some(editor) = handle.upgrade() else { |
| 39 | .and_then(|active_item| active_item.downcast::<Editor>()) | ||
| 40 | else { | ||
| 41 | return; | 38 | return; |
| 42 | }; | 39 | }; |
| 43 | 40 | let Some(workspace) = editor.read(cx).workspace() else { | |
| 44 | workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx)); | 41 | return; |
| 42 | }; | ||
| 43 | workspace.update(cx, |workspace, cx| { | ||
| 44 | workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx)); | ||
| 45 | }) | ||
| 45 | }); | 46 | }); |
| 46 | } | 47 | } |
| 47 | 48 | ||
| @@ -89,7 +90,7 @@ impl GoToLine { · 41 unmodified lines | |||
| 89 | ) { | 90 | ) { |
| 90 | match event { | 91 | match event { |
| 91 | // todo!() this isn't working... | 92 | // todo!() this isn't working... |
| 92 | editor::EditorEvent::Blurred => cx.emit(Manager::Dismiss), | 93 | editor::EditorEvent::Blurred => cx.emit(DismissEvent::Dismiss), |
| 93 | editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx), | 94 | editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx), |
| 94 | _ => {} | 95 | _ => {} |
| 95 | } | 96 | } |
| @@ -124,7 +125,7 @@ impl GoToLine { · 28 unmodified lines | |||
| 124 | } | 125 | } |
| 125 | 126 | ||
| 126 | fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { | 127 | fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { |
| 127 | cx.emit(Manager::Dismiss); | 128 | cx.emit(DismissEvent::Dismiss); |
| 128 | } | 129 | } |
| 129 | 130 | ||
| 130 | fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) { | 131 | fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) { |
| @@ -141,19 +142,19 @@ impl GoToLine { · 10 unmodified lines | |||
| 141 | self.prev_scroll_position.take(); | 142 | self.prev_scroll_position.take(); |
| 142 | } | 143 | } |
| 143 | 144 | ||
| 144 | cx.emit(Manager::Dismiss); | 145 | cx.emit(DismissEvent::Dismiss); |
| 145 | } | 146 | } |
| 146 | } | 147 | } |
| 147 | 148 | ||
| 148 | impl Render for GoToLine { | 149 | impl Render for GoToLine { |
| 149 | type Element = Div<Self>; | 150 | type Element = Div; |
| 150 | 151 | ||
| 151 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | 152 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 152 | div() | 153 | div() |
| 153 | .elevation_2(cx) | 154 | .elevation_2(cx) |
| 154 | .key_context("GoToLine") | 155 | .key_context("GoToLine") |
| 155 | .on_action(Self::cancel) | 156 | .on_action(cx.listener(Self::cancel)) |
| 156 | .on_action(Self::confirm) | 157 | .on_action(cx.listener(Self::confirm)) |
| 157 | .w_96() | 158 | .w_96() |
| 158 | .child( | 159 | .child( |
| 159 | v_stack() | 160 | v_stack() |
| @@ -177,7 +178,7 @@ impl Render for GoToLine { · 17 unmodified lines | |||
| 177 | .justify_between() | 178 | .justify_between() |
| 178 | .px_2() | 179 | .px_2() |
| 179 | .py_1() | 180 | .py_1() |
| 180 | .child(Label::new(self.current_text.clone()).color(TextColor::Muted)), | 181 | .child(Label::new(self.current_text.clone()).color(Color::Muted)), |
| 181 | ), | 182 | ), |
| 182 | ) | 183 | ) |
| 183 | } | 184 | } |
| @@ -10,11 +10,12 @@ pub use entity_map::*; · 9 unmodified lines | |||
| 10 | pub use model_context::*; | 10 | pub use model_context::*; |
| 11 | use refineable::Refineable; | 11 | use refineable::Refineable; |
| 12 | use smallvec::SmallVec; | 12 | use smallvec::SmallVec; |
| 13 | use smol::future::FutureExt; | ||
| 13 | #[cfg(any(test, feature = "test-support"))] | 14 | #[cfg(any(test, feature = "test-support"))] |
| 14 | pub use test_context::*; | 15 | pub use test_context::*; |
| 15 | 16 | ||
| 16 | use crate::{ | 17 | use crate::{ |
| 17 | current_platform, image_cache::ImageCache, Action, ActionRegistry, AnyBox, AnyView, | 18 | current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView, |
| 18 | AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, | 19 | AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, |
| 19 | DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, | 20 | DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, |
| 20 | ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform, | 21 | ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform, |
| @@ -28,7 +29,7 @@ use futures::{channel::oneshot, future::LocalBoxFuture, Future}; · 7 unmodified lines | |||
| 28 | use parking_lot::Mutex; | 29 | use parking_lot::Mutex; |
| 29 | use slotmap::SlotMap; | 30 | use slotmap::SlotMap; |
| 30 | use std::{ | 31 | use std::{ |
| 31 | any::{type_name, Any, TypeId}, | 32 | any::{type_name, TypeId}, |
| 32 | cell::{Ref, RefCell, RefMut}, | 33 | cell::{Ref, RefCell, RefMut}, |
| 33 | marker::PhantomData, | 34 | marker::PhantomData, |
| 34 | mem, | 35 | mem, |
| @@ -194,7 +195,7 @@ pub struct AppContext { · 159 unmodified lines | |||
| 194 | asset_source: Arc<dyn AssetSource>, | 195 | asset_source: Arc<dyn AssetSource>, |
| 195 | pub(crate) image_cache: ImageCache, | 196 | pub(crate) image_cache: ImageCache, |
| 196 | pub(crate) text_style_stack: Vec<TextStyleRefinement>, | 197 | pub(crate) text_style_stack: Vec<TextStyleRefinement>, |
| 197 | pub(crate) globals_by_type: HashMap<TypeId, AnyBox>, | 198 | pub(crate) globals_by_type: HashMap<TypeId, Box<dyn Any>>, |
| 198 | pub(crate) entities: EntityMap, | 199 | pub(crate) entities: EntityMap, |
| 199 | pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>, | 200 | pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>, |
| 200 | pub(crate) windows: SlotMap<WindowId, Option<Window>>, | 201 | pub(crate) windows: SlotMap<WindowId, Option<Window>>, |
| @@ -424,7 +425,7 @@ impl AppContext { · 223 unmodified lines | |||
| 424 | /// Opens a new window with the given option and the root view returned by the given function. | 425 | /// Opens a new window with the given option and the root view returned by the given function. |
| 425 | /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific | 426 | /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific |
| 426 | /// functionality. | 427 | /// functionality. |
| 427 | pub fn open_window<V: Render>( | 428 | pub fn open_window<V: 'static + Render>( |
| 428 | &mut self, | 429 | &mut self, |
| 429 | options: crate::WindowOptions, | 430 | options: crate::WindowOptions, |
| 430 | build_root_view: impl FnOnce(&mut WindowContext) -> View<V>, | 431 | build_root_view: impl FnOnce(&mut WindowContext) -> View<V>, |
| @@ -579,7 +580,7 @@ impl AppContext { · 148 unmodified lines | |||
| 579 | .windows | 580 | .windows |
| 580 | .iter() | 581 | .iter() |
| 581 | .filter_map(|(_, window)| { | 582 | .filter_map(|(_, window)| { |
| 582 | let window = window.as_ref().unwrap(); | 583 | let window = window.as_ref()?; |
| 583 | if window.dirty { | 584 | if window.dirty { |
| 584 | Some(window.handle.clone()) | 585 | Some(window.handle.clone()) |
| 585 | } else { | 586 | } else { |
| @@ -983,6 +984,22 @@ impl AppContext { · 397 unmodified lines | |||
| 983 | pub fn all_action_names(&self) -> &[SharedString] { | 984 | pub fn all_action_names(&self) -> &[SharedString] { |
| 984 | self.actions.all_action_names() | 985 | self.actions.all_action_names() |
| 985 | } | 986 | } |
| 987 | |||
| 988 | pub fn on_app_quit<Fut>( | ||
| 989 | &mut self, | ||
| 990 | mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static, | ||
| 991 | ) -> Subscription | ||
| 992 | where | ||
| 993 | Fut: 'static + Future<Output = ()>, | ||
| 994 | { | ||
| 995 | self.quit_observers.insert( | ||
| 996 | (), | ||
| 997 | Box::new(move |cx| { | ||
| 998 | let future = on_quit(cx); | ||
| 999 | async move { future.await }.boxed_local() | ||
| 1000 | }), | ||
| 1001 | ) | ||
| 1002 | } | ||
| 986 | } | 1003 | } |
| 987 | 1004 | ||
| 988 | impl Context for AppContext { | 1005 | impl Context for AppContext { |
| @@ -1032,7 +1049,9 @@ impl Context for AppContext { · 43 unmodified lines | |||
| 1032 | let root_view = window.root_view.clone().unwrap(); | 1049 | let root_view = window.root_view.clone().unwrap(); |
| 1033 | let result = update(root_view, &mut WindowContext::new(cx, &mut window)); | 1050 | let result = update(root_view, &mut WindowContext::new(cx, &mut window)); |
| 1034 | 1051 | ||
| 1035 | if !window.removed { | 1052 | if window.removed { |
| 1053 | cx.windows.remove(handle.id); | ||
| 1054 | } else { | ||
| 1036 | cx.windows | 1055 | cx.windows |
| 1037 | .get_mut(handle.id) | 1056 | .get_mut(handle.id) |
| 1038 | .ok_or_else(|| anyhow!("window not found"))? | 1057 | .ok_or_else(|| anyhow!("window not found"))? |
| @@ -1104,12 +1123,12 @@ pub(crate) enum Effect { · 65 unmodified lines | |||
| 1104 | 1123 | ||
| 1105 | /// Wraps a global variable value during `update_global` while the value has been moved to the stack. | 1124 | /// Wraps a global variable value during `update_global` while the value has been moved to the stack. |
| 1106 | pub(crate) struct GlobalLease<G: 'static> { | 1125 | pub(crate) struct GlobalLease<G: 'static> { |
| 1107 | global: AnyBox, | 1126 | global: Box<dyn Any>, |
| 1108 | global_type: PhantomData<G>, | 1127 | global_type: PhantomData<G>, |
| 1109 | } | 1128 | } |
| 1110 | 1129 | ||
| 1111 | impl<G: 'static> GlobalLease<G> { | 1130 | impl<G: 'static> GlobalLease<G> { |
| 1112 | fn new(global: AnyBox) -> Self { | 1131 | fn new(global: Box<dyn Any>) -> Self { |
| 1113 | GlobalLease { | 1132 | GlobalLease { |
| 1114 | global, | 1133 | global, |
| 1115 | global_type: PhantomData, | 1134 | global_type: PhantomData, |
| @@ -1,7 +1,7 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, FocusableView, | 2 | AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, DismissEvent, |
| 3 | ForegroundExecutor, Manager, Model, ModelContext, Render, Result, Task, View, ViewContext, | 3 | FocusableView, ForegroundExecutor, Model, ModelContext, Render, Result, Task, View, |
| 4 | VisualContext, WindowContext, WindowHandle, | 4 | ViewContext, VisualContext, WindowContext, WindowHandle, |
| 5 | }; | 5 | }; |
| 6 | use anyhow::{anyhow, Context as _}; | 6 | use anyhow::{anyhow, Context as _}; |
| 7 | use derive_more::{Deref, DerefMut}; | 7 | use derive_more::{Deref, DerefMut}; |
| @@ -115,7 +115,7 @@ impl AsyncAppContext { · 107 unmodified lines | |||
| 115 | build_root_view: impl FnOnce(&mut WindowContext) -> View<V>, | 115 | build_root_view: impl FnOnce(&mut WindowContext) -> View<V>, |
| 116 | ) -> Result<WindowHandle<V>> | 116 | ) -> Result<WindowHandle<V>> |
| 117 | where | 117 | where |
| 118 | V: Render, | 118 | V: 'static + Render, |
| 119 | { | 119 | { |
| 120 | let app = self | 120 | let app = self |
| 121 | .app | 121 | .app |
| @@ -306,7 +306,7 @@ impl VisualContext for AsyncWindowContext { · 184 unmodified lines | |||
| 306 | build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, | 306 | build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, |
| 307 | ) -> Self::Result<View<V>> | 307 | ) -> Self::Result<View<V>> |
| 308 | where | 308 | where |
| 309 | V: Render, | 309 | V: 'static + Render, |
| 310 | { | 310 | { |
| 311 | self.window | 311 | self.window |
| 312 | .update(self, |_, cx| cx.replace_root_view(build_view)) | 312 | .update(self, |_, cx| cx.replace_root_view(build_view)) |
| @@ -326,7 +326,7 @@ impl VisualContext for AsyncWindowContext { · 13 unmodified lines | |||
| 326 | V: crate::ManagedView, | 326 | V: crate::ManagedView, |
| 327 | { | 327 | { |
| 328 | self.window.update(self, |_, cx| { | 328 | self.window.update(self, |_, cx| { |
| 329 | view.update(cx, |_, cx| cx.emit(Manager::Dismiss)) | 329 | view.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss)) |
| 330 | }) | 330 | }) |
| 331 | } | 331 | } |
| 332 | } | 332 | } |
| @@ -1,10 +1,10 @@ | |||
| 1 | use crate::{private::Sealed, AnyBox, AppContext, Context, Entity, ModelContext}; | 1 | use crate::{private::Sealed, AppContext, Context, Entity, ModelContext}; |
| 2 | use anyhow::{anyhow, Result}; | 2 | use anyhow::{anyhow, Result}; |
| 3 | use derive_more::{Deref, DerefMut}; | 3 | use derive_more::{Deref, DerefMut}; |
| 4 | use parking_lot::{RwLock, RwLockUpgradableReadGuard}; | 4 | use parking_lot::{RwLock, RwLockUpgradableReadGuard}; |
| 5 | use slotmap::{SecondaryMap, SlotMap}; | 5 | use slotmap::{SecondaryMap, SlotMap}; |
| 6 | use std::{ | 6 | use std::{ |
| 7 | any::{type_name, TypeId}, | 7 | any::{type_name, Any, TypeId}, |
| 8 | fmt::{self, Display}, | 8 | fmt::{self, Display}, |
| 9 | hash::{Hash, Hasher}, | 9 | hash::{Hash, Hasher}, |
| 10 | marker::PhantomData, | 10 | marker::PhantomData, |
| @@ -31,7 +31,7 @@ impl Display for EntityId { · 20 unmodified lines | |||
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | pub(crate) struct EntityMap { | 33 | pub(crate) struct EntityMap { |
| 34 | entities: SecondaryMap<EntityId, AnyBox>, | 34 | entities: SecondaryMap<EntityId, Box<dyn Any>>, |
| 35 | ref_counts: Arc<RwLock<EntityRefCounts>>, | 35 | ref_counts: Arc<RwLock<EntityRefCounts>>, |
| 36 | } | 36 | } |
| 37 | 37 | ||
| @@ -102,7 +102,7 @@ impl EntityMap { · 64 unmodified lines | |||
| 102 | ); | 102 | ); |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | pub fn take_dropped(&mut self) -> Vec<(EntityId, AnyBox)> { | 105 | pub fn take_dropped(&mut self) -> Vec<(EntityId, Box<dyn Any>)> { |
| 106 | let mut ref_counts = self.ref_counts.write(); | 106 | let mut ref_counts = self.ref_counts.write(); |
| 107 | let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids); | 107 | let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids); |
| 108 | 108 | ||
| @@ -122,7 +122,7 @@ impl EntityMap { · 13 unmodified lines | |||
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | pub struct Lease<'a, T> { | 124 | pub struct Lease<'a, T> { |
| 125 | entity: Option<AnyBox>, | 125 | entity: Option<Box<dyn Any>>, |
| 126 | pub model: &'a Model<T>, | 126 | pub model: &'a Model<T>, |
| 127 | entity_type: PhantomData<T>, | 127 | entity_type: PhantomData<T>, |
| 128 | } | 128 | } |
| @@ -1,8 +1,9 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, | 2 | div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, |
| 3 | BackgroundExecutor, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, | 3 | BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent, |
| 4 | Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, TestWindow, | 4 | KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, |
| 5 | View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions, | 5 | TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle, |
| 6 | WindowOptions, | ||
| 6 | }; | 7 | }; |
| 7 | use anyhow::{anyhow, bail}; | 8 | use anyhow::{anyhow, bail}; |
| 8 | use futures::{Stream, StreamExt}; | 9 | use futures::{Stream, StreamExt}; |
| @@ -126,7 +127,7 @@ impl TestAppContext { · 117 unmodified lines | |||
| 126 | pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V> | 127 | pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V> |
| 127 | where | 128 | where |
| 128 | F: FnOnce(&mut ViewContext<V>) -> V, | 129 | F: FnOnce(&mut ViewContext<V>) -> V, |
| 129 | V: Render, | 130 | V: 'static + Render, |
| 130 | { | 131 | { |
| 131 | let mut cx = self.app.borrow_mut(); | 132 | let mut cx = self.app.borrow_mut(); |
| 132 | cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)) | 133 | cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)) |
| @@ -143,7 +144,7 @@ impl TestAppContext { · 10 unmodified lines | |||
| 143 | pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext) | 144 | pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext) |
| 144 | where | 145 | where |
| 145 | F: FnOnce(&mut ViewContext<V>) -> V, | 146 | F: FnOnce(&mut ViewContext<V>) -> V, |
| 146 | V: Render, | 147 | V: 'static + Render, |
| 147 | { | 148 | { |
| 148 | let mut cx = self.app.borrow_mut(); | 149 | let mut cx = self.app.borrow_mut(); |
| 149 | let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)); | 150 | let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)); |
| @@ -296,21 +297,19 @@ impl TestAppContext { · 146 unmodified lines | |||
| 296 | .unwrap() | 297 | .unwrap() |
| 297 | } | 298 | } |
| 298 | 299 | ||
| 299 | pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> { | 300 | pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> { |
| 300 | let (tx, rx) = futures::channel::mpsc::unbounded(); | 301 | let (tx, rx) = futures::channel::mpsc::unbounded(); |
| 301 | 302 | self.update(|cx| { | |
| 302 | entity.update(self, move |_, cx: &mut ModelContext<T>| { | ||
| 303 | cx.observe(entity, { | 303 | cx.observe(entity, { |
| 304 | let tx = tx.clone(); | 304 | let tx = tx.clone(); |
| 305 | move |_, _, _| { | 305 | move |_, _| { |
| 306 | let _ = tx.unbounded_send(()); | 306 | let _ = tx.unbounded_send(()); |
| 307 | } | 307 | } |
| 308 | }) | 308 | }) |
| 309 | .detach(); | 309 | .detach(); |
| 310 | 310 | cx.observe_release(entity, move |_, _| tx.close_channel()) | |
| 311 | cx.on_release(move |_, _| tx.close_channel()).detach(); | 311 | .detach() |
| 312 | }); | 312 | }); |
| 313 | |||
| 314 | rx | 313 | rx |
| 315 | } | 314 | } |
| 316 | 315 | ||
| @@ -591,7 +590,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { · 274 unmodified lines | |||
| 591 | build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, | 590 | build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, |
| 592 | ) -> Self::Result<View<V>> | 591 | ) -> Self::Result<View<V>> |
| 593 | where | 592 | where |
| 594 | V: Render, | 593 | V: 'static + Render, |
| 595 | { | 594 | { |
| 596 | self.window | 595 | self.window |
| 597 | .update(self.cx, |_, cx| cx.replace_root_view(build_view)) | 596 | .update(self.cx, |_, cx| cx.replace_root_view(build_view)) |
| @@ -612,7 +611,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { · 14 unmodified lines | |||
| 612 | { | 611 | { |
| 613 | self.window | 612 | self.window |
| 614 | .update(self.cx, |_, cx| { | 613 | .update(self.cx, |_, cx| { |
| 615 | view.update(cx, |_, cx| cx.emit(crate::Manager::Dismiss)) | 614 | view.update(cx, |_, cx| cx.emit(crate::DismissEvent::Dismiss)) |
| 616 | }) | 615 | }) |
| 617 | .unwrap() | 616 | .unwrap() |
| 618 | } | 617 | } |
| @@ -631,7 +630,7 @@ impl AnyWindowHandle { · 12 unmodified lines | |||
| 631 | pub struct EmptyView {} | 630 | pub struct EmptyView {} |
| 632 | 631 | ||
| 633 | impl Render for EmptyView { | 632 | impl Render for EmptyView { |
| 634 | type Element = Div<Self>; | 633 | type Element = Div; |
| 635 | 634 | ||
| 636 | fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element { | 635 | fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element { |
| 637 | div() | 636 | div() |
| @@ -1,236 +1,328 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext, | 2 | AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext, |
| 3 | WindowContext, | ||
| 3 | }; | 4 | }; |
| 4 | use derive_more::{Deref, DerefMut}; | 5 | use derive_more::{Deref, DerefMut}; |
| 5 | pub(crate) use smallvec::SmallVec; | 6 | pub(crate) use smallvec::SmallVec; |
| 6 | use std::{any::Any, fmt::Debug, mem}; | 7 | use std::{any::Any, fmt::Debug}; |
| 7 | 8 | ||
| 8 | pub trait Element<V: 'static> { | 9 | pub trait Render: 'static + Sized { |
| 9 | type ElementState: 'static; | 10 | type Element: Element + 'static; |
| 11 | |||
| 12 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element; | ||
| 13 | } | ||
| 14 | |||
| 15 | pub trait IntoElement: Sized { | ||
| 16 | type Element: Element + 'static; | ||
| 10 | 17 | ||
| 11 | fn element_id(&self) -> Option<ElementId>; | 18 | fn element_id(&self) -> Option<ElementId>; |
| 12 | 19 | ||
| 13 | fn layout( | 20 | fn into_element(self) -> Self::Element; |
| 14 | &mut self, | ||
| 15 | view_state: &mut V, | ||
| 16 | element_state: Option<Self::ElementState>, | ||
| 17 | cx: &mut ViewContext<V>, | ||
| 18 | ) -> (LayoutId, Self::ElementState); | ||
| 19 | 21 | ||
| 20 | fn paint( | 22 | fn into_any_element(self) -> AnyElement { |
| 21 | &mut self, | 23 | self.into_element().into_any() |
| 22 | bounds: Bounds<Pixels>, | 24 | } |
| 23 | view_state: &mut V, | ||
| 24 | element_state: &mut Self::ElementState, | ||
| 25 | cx: &mut ViewContext<V>, | ||
| 26 | ); | ||
| 27 | 25 | ||
| 28 | fn draw<T, R>( | 26 | fn draw<T, R>( |
| 29 | self, | 27 | self, |
| 30 | origin: Point<Pixels>, | 28 | origin: Point<Pixels>, |
| 31 | available_space: Size<T>, | 29 | available_space: Size<T>, |
| 32 | view_state: &mut V, | 30 | cx: &mut WindowContext, |
| 33 | cx: &mut ViewContext<V>, | 31 | f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R, |
| 34 | f: impl FnOnce(&Self::ElementState, &mut ViewContext<V>) -> R, | ||
| 35 | ) -> R | 32 | ) -> R |
| 36 | where | 33 | where |
| 37 | Self: Sized, | ||
| 38 | T: Clone + Default + Debug + Into<AvailableSpace>, | 34 | T: Clone + Default + Debug + Into<AvailableSpace>, |
| 39 | { | 35 | { |
| 40 | let mut element = RenderedElement { | 36 | let element = self.into_element(); |
| 41 | element: self, | 37 | let element_id = element.element_id(); |
| 42 | phase: ElementRenderPhase::Start, | 38 | let element = DrawableElement { |
| 39 | element: Some(element), | ||
| 40 | phase: ElementDrawPhase::Start, | ||
| 43 | }; | 41 | }; |
| 44 | element.draw(origin, available_space.map(Into::into), view_state, cx); | 42 | |
| 45 | if let ElementRenderPhase::Painted { frame_state } = &element.phase { | 43 | let frame_state = |
| 46 | if let Some(frame_state) = frame_state.as_ref() { | 44 | DrawableElement::draw(element, origin, available_space.map(Into::into), cx); |
| 47 | f(&frame_state, cx) | 45 | |
| 46 | if let Some(mut frame_state) = frame_state { | ||
| 47 | f(&mut frame_state, cx) | ||
| 48 | } else { | ||
| 49 | cx.with_element_state(element_id.unwrap(), |element_state, cx| { | ||
| 50 | let mut element_state = element_state.unwrap(); | ||
| 51 | let result = f(&mut element_state, cx); | ||
| 52 | (result, element_state) | ||
| 53 | }) | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | fn map<U>(self, f: impl FnOnce(Self) -> U) -> U | ||
| 58 | where | ||
| 59 | Self: Sized, | ||
| 60 | U: IntoElement, | ||
| 61 | { | ||
| 62 | f(self) | ||
| 63 | } | ||
| 64 | |||
| 65 | fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self | ||
| 66 | where | ||
| 67 | Self: Sized, | ||
| 68 | { | ||
| 69 | self.map(|this| if condition { then(this) } else { this }) | ||
| 70 | } | ||
| 71 | |||
| 72 | fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self | ||
| 73 | where | ||
| 74 | Self: Sized, | ||
| 75 | { | ||
| 76 | self.map(|this| { | ||
| 77 | if let Some(value) = option { | ||
| 78 | then(this, value) | ||
| 48 | } else { | 79 | } else { |
| 49 | let element_id = element | 80 | this |
| 50 | .element | ||
| 51 | .element_id() | ||
| 52 | .expect("we either have some frame_state or some element_id"); | ||
| 53 | cx.with_element_state(element_id, |element_state, cx| { | ||
| 54 | let element_state = element_state.unwrap(); | ||
| 55 | let result = f(&element_state, cx); | ||
| 56 | (result, element_state) | ||
| 57 | }) | ||
| 58 | } | 81 | } |
| 59 | } else { | 82 | }) |
| 60 | unreachable!() | 83 | } |
| 84 | } | ||
| 85 | |||
| 86 | pub trait Element: 'static + IntoElement { | ||
| 87 | type State: 'static; | ||
| 88 | |||
| 89 | fn layout( | ||
| 90 | &mut self, | ||
| 91 | state: Option<Self::State>, | ||
| 92 | cx: &mut WindowContext, | ||
| 93 | ) -> (LayoutId, Self::State); | ||
| 94 | |||
| 95 | fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext); | ||
| 96 | |||
| 97 | fn into_any(self) -> AnyElement { | ||
| 98 | AnyElement::new(self) | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | pub trait RenderOnce: 'static { | ||
| 103 | type Rendered: IntoElement; | ||
| 104 | |||
| 105 | fn render(self, cx: &mut WindowContext) -> Self::Rendered; | ||
| 106 | } | ||
| 107 | |||
| 108 | pub struct Component<C> { | ||
| 109 | component: Option<C>, | ||
| 110 | } | ||
| 111 | |||
| 112 | pub struct CompositeElementState<C: RenderOnce> { | ||
| 113 | rendered_element: Option<<C::Rendered as IntoElement>::Element>, | ||
| 114 | rendered_element_state: <<C::Rendered as IntoElement>::Element as Element>::State, | ||
| 115 | } | ||
| 116 | |||
| 117 | impl<C> Component<C> { | ||
| 118 | pub fn new(component: C) -> Self { | ||
| 119 | Component { | ||
| 120 | component: Some(component), | ||
| 61 | } | 121 | } |
| 62 | } | 122 | } |
| 63 | } | 123 | } |
| 64 | 124 | ||
| 125 | impl<C: RenderOnce> Element for Component<C> { | ||
| 126 | type State = CompositeElementState<C>; | ||
| 127 | |||
| 128 | fn layout( | ||
| 129 | &mut self, | ||
| 130 | state: Option<Self::State>, | ||
| 131 | cx: &mut WindowContext, | ||
| 132 | ) -> (LayoutId, Self::State) { | ||
| 133 | let mut element = self.component.take().unwrap().render(cx).into_element(); | ||
| 134 | let (layout_id, state) = element.layout(state.map(|s| s.rendered_element_state), cx); | ||
| 135 | let state = CompositeElementState { | ||
| 136 | rendered_element: Some(element), | ||
| 137 | rendered_element_state: state, | ||
| 138 | }; | ||
| 139 | (layout_id, state) | ||
| 140 | } | ||
| 141 | |||
| 142 | fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) { | ||
| 143 | state | ||
| 144 | .rendered_element | ||
| 145 | .take() | ||
| 146 | .unwrap() | ||
| 147 | .paint(bounds, &mut state.rendered_element_state, cx); | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | impl<C: RenderOnce> IntoElement for Component<C> { | ||
| 152 | type Element = Self; | ||
| 153 | |||
| 154 | fn element_id(&self) -> Option<ElementId> { | ||
| 155 | None | ||
| 156 | } | ||
| 157 | |||
| 158 | fn into_element(self) -> Self::Element { | ||
| 159 | self | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 65 | #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)] | 163 | #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)] |
| 66 | pub struct GlobalElementId(SmallVec<[ElementId; 32]>); | 164 | pub struct GlobalElementId(SmallVec<[ElementId; 32]>); |
| 67 | 165 | ||
| 68 | pub trait ParentComponent<V: 'static> { | 166 | pub trait ParentElement { |
| 69 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>; | 167 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>; |
| 70 | 168 | ||
| 71 | fn child(mut self, child: impl Component<V>) -> Self | 169 | fn child(mut self, child: impl IntoElement) -> Self |
| 72 | where | 170 | where |
| 73 | Self: Sized, | 171 | Self: Sized, |
| 74 | { | 172 | { |
| 75 | self.children_mut().push(child.render()); | 173 | self.children_mut().push(child.into_element().into_any()); |
| 76 | self | 174 | self |
| 77 | } | 175 | } |
| 78 | 176 | ||
| 79 | fn children(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self | 177 | fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self |
| 80 | where | 178 | where |
| 81 | Self: Sized, | 179 | Self: Sized, |
| 82 | { | 180 | { |
| 83 | self.children_mut() | 181 | self.children_mut() |
| 84 | .extend(iter.into_iter().map(|item| item.render())); | 182 | .extend(children.into_iter().map(|child| child.into_any_element())); |
| 85 | self | 183 | self |
| 86 | } | 184 | } |
| 87 | } | 185 | } |
| 88 | 186 | ||
| 89 | trait ElementObject<V> { | 187 | trait ElementObject { |
| 90 | fn element_id(&self) -> Option<ElementId>; | 188 | fn element_id(&self) -> Option<ElementId>; |
| 91 | fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId; | 189 | |
| 92 | fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>); | 190 | fn layout(&mut self, cx: &mut WindowContext) -> LayoutId; |
| 191 | |||
| 192 | fn paint(&mut self, cx: &mut WindowContext); | ||
| 193 | |||
| 93 | fn measure( | 194 | fn measure( |
| 94 | &mut self, | 195 | &mut self, |
| 95 | available_space: Size<AvailableSpace>, | 196 | available_space: Size<AvailableSpace>, |
| 96 | view_state: &mut V, | 197 | cx: &mut WindowContext, |
| 97 | cx: &mut ViewContext<V>, | ||
| 98 | ) -> Size<Pixels>; | 198 | ) -> Size<Pixels>; |
| 199 | |||
| 99 | fn draw( | 200 | fn draw( |
| 100 | &mut self, | 201 | &mut self, |
| 101 | origin: Point<Pixels>, | 202 | origin: Point<Pixels>, |
| 102 | available_space: Size<AvailableSpace>, | 203 | available_space: Size<AvailableSpace>, |
| 103 | view_state: &mut V, | 204 | cx: &mut WindowContext, |
| 104 | cx: &mut ViewContext<V>, | ||
| 105 | ); | 205 | ); |
| 106 | } | 206 | } |
| 107 | 207 | ||
| 108 | struct RenderedElement<V: 'static, E: Element<V>> { | 208 | pub struct DrawableElement<E: Element> { |
| 109 | element: E, | 209 | element: Option<E>, |
| 110 | phase: ElementRenderPhase<E::ElementState>, | 210 | phase: ElementDrawPhase<E::State>, |
| 111 | } | 211 | } |
| 112 | 212 | ||
| 113 | #[derive(Default)] | 213 | #[derive(Default)] |
| 114 | enum ElementRenderPhase<V> { | 214 | enum ElementDrawPhase<S> { |
| 115 | #[default] | 215 | #[default] |
| 116 | Start, | 216 | Start, |
| 117 | LayoutRequested { | 217 | LayoutRequested { |
| 118 | layout_id: LayoutId, | 218 | layout_id: LayoutId, |
| 119 | frame_state: Option<V>, | 219 | frame_state: Option<S>, |
| 120 | }, | 220 | }, |
| 121 | LayoutComputed { | 221 | LayoutComputed { |
| 122 | layout_id: LayoutId, | 222 | layout_id: LayoutId, |
| 123 | available_space: Size<AvailableSpace>, | 223 | available_space: Size<AvailableSpace>, |
| 124 | frame_state: Option<V>, | 224 | frame_state: Option<S>, |
| 125 | }, | ||
| 126 | Painted { | ||
| 127 | frame_state: Option<V>, | ||
| 128 | }, | 225 | }, |
| 129 | } | 226 | } |
| 130 | 227 | ||
| 131 | /// Internal struct that wraps an element to store Layout and ElementState after the element is rendered. | 228 | /// A wrapper around an implementer of [Element] that allows it to be drawn in a window. |
| 132 | /// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for | 229 | impl<E: Element> DrawableElement<E> { |
| 133 | /// improved usability. | ||
| 134 | impl<V, E: Element<V>> RenderedElement<V, E> { | ||
| 135 | fn new(element: E) -> Self { | 230 | fn new(element: E) -> Self { |
| 136 | RenderedElement { | 231 | DrawableElement { |
| 137 | element, | 232 | element: Some(element), |
| 138 | phase: ElementRenderPhase::Start, | 233 | phase: ElementDrawPhase::Start, |
| 139 | } | 234 | } |
| 140 | } | 235 | } |
| 141 | } | ||
| 142 | 236 | ||
| 143 | impl<V, E> ElementObject<V> for RenderedElement<V, E> | ||
| 144 | where | ||
| 145 | E: Element<V>, | ||
| 146 | E::ElementState: 'static, | ||
| 147 | { | ||
| 148 | fn element_id(&self) -> Option<ElementId> { | 237 | fn element_id(&self) -> Option<ElementId> { |
| 149 | self.element.element_id() | 238 | self.element.as_ref()?.element_id() |
| 150 | } | 239 | } |
| 151 | 240 | ||
| 152 | fn layout(&mut self, state: &mut V, cx: &mut ViewContext<V>) -> LayoutId { | 241 | fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { |
| 153 | let (layout_id, frame_state) = match mem::take(&mut self.phase) { | 242 | let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id() |
| 154 | ElementRenderPhase::Start => { | 243 | { |
| 155 | if let Some(id) = self.element.element_id() { | 244 | let layout_id = cx.with_element_state(id, |element_state, cx| { |
| 156 | let layout_id = cx.with_element_state(id, |element_state, cx| { | 245 | self.element.as_mut().unwrap().layout(element_state, cx) |
| 157 | self.element.layout(state, element_state, cx) | 246 | }); |
| 158 | }); | 247 | (layout_id, None) |
| 159 | (layout_id, None) | 248 | } else { |
| 160 | } else { | 249 | let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(None, cx); |
| 161 | let (layout_id, frame_state) = self.element.layout(state, None, cx); | 250 | (layout_id, Some(frame_state)) |
| 162 | (layout_id, Some(frame_state)) | ||
| 163 | } | ||
| 164 | } | ||
| 165 | ElementRenderPhase::LayoutRequested { .. } | ||
| 166 | | ElementRenderPhase::LayoutComputed { .. } | ||
| 167 | | ElementRenderPhase::Painted { .. } => { | ||
| 168 | panic!("element rendered twice") | ||
| 169 | } | ||
| 170 | }; | 251 | }; |
| 171 | 252 | ||
| 172 | self.phase = ElementRenderPhase::LayoutRequested { | 253 | self.phase = ElementDrawPhase::LayoutRequested { |
| 173 | layout_id, | 254 | layout_id, |
| 174 | frame_state, | 255 | frame_state, |
| 175 | }; | 256 | }; |
| 176 | layout_id | 257 | layout_id |
| 177 | } | 258 | } |
| 178 | 259 | ||
| 179 | fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) { | 260 | fn paint(mut self, cx: &mut WindowContext) -> Option<E::State> { |
| 180 | self.phase = match mem::take(&mut self.phase) { | 261 | match self.phase { |
| 181 | ElementRenderPhase::LayoutRequested { | 262 | ElementDrawPhase::LayoutRequested { |
| 182 | layout_id, | 263 | layout_id, |
| 183 | mut frame_state, | 264 | frame_state, |
| 184 | } | 265 | } |
| 185 | | ElementRenderPhase::LayoutComputed { | 266 | | ElementDrawPhase::LayoutComputed { |
| 186 | layout_id, | 267 | layout_id, |
| 187 | mut frame_state, | 268 | frame_state, |
| 188 | .. | 269 | .. |
| 189 | } => { | 270 | } => { |
| 190 | let bounds = cx.layout_bounds(layout_id); | 271 | let bounds = cx.layout_bounds(layout_id); |
| 191 | if let Some(id) = self.element.element_id() { | 272 | |
| 192 | cx.with_element_state(id, |element_state, cx| { | 273 | if let Some(mut frame_state) = frame_state { |
| 274 | self.element | ||
| 275 | .take() | ||
| 276 | .unwrap() | ||
| 277 | .paint(bounds, &mut frame_state, cx); | ||
| 278 | Some(frame_state) | ||
| 279 | } else { | ||
| 280 | let element_id = self | ||
| 281 | .element | ||
| 282 | .as_ref() | ||
| 283 | .unwrap() | ||
| 284 | .element_id() | ||
| 285 | .expect("if we don't have frame state, we should have element state"); | ||
| 286 | cx.with_element_state(element_id, |element_state, cx| { | ||
| 193 | let mut element_state = element_state.unwrap(); | 287 | let mut element_state = element_state.unwrap(); |
| 194 | self.element | 288 | self.element |
| 195 | .paint(bounds, view_state, &mut element_state, cx); | 289 | .take() |
| 290 | .unwrap() | ||
| 291 | .paint(bounds, &mut element_state, cx); | ||
| 196 | ((), element_state) | 292 | ((), element_state) |
| 197 | }); | 293 | }); |
| 198 | } else { | 294 | None |
| 199 | self.element | ||
| 200 | .paint(bounds, view_state, frame_state.as_mut().unwrap(), cx); | ||
| 201 | } | 295 | } |
| 202 | ElementRenderPhase::Painted { frame_state } | ||
| 203 | } | 296 | } |
| 204 | 297 | ||
| 205 | _ => panic!("must call layout before paint"), | 298 | _ => panic!("must call layout before paint"), |
| 206 | }; | 299 | } |
| 207 | } | 300 | } |
| 208 | 301 | ||
| 209 | fn measure( | 302 | fn measure( |
| 210 | &mut self, | 303 | &mut self, |
| 211 | available_space: Size<AvailableSpace>, | 304 | available_space: Size<AvailableSpace>, |
| 212 | view_state: &mut V, | 305 | cx: &mut WindowContext, |
| 213 | cx: &mut ViewContext<V>, | ||
| 214 | ) -> Size<Pixels> { | 306 | ) -> Size<Pixels> { |
| 215 | if matches!(&self.phase, ElementRenderPhase::Start) { | 307 | if matches!(&self.phase, ElementDrawPhase::Start) { |
| 216 | self.layout(view_state, cx); | 308 | self.layout(cx); |
| 217 | } | 309 | } |
| 218 | 310 | ||
| 219 | let layout_id = match &mut self.phase { | 311 | let layout_id = match &mut self.phase { |
| 220 | ElementRenderPhase::LayoutRequested { | 312 | ElementDrawPhase::LayoutRequested { |
| 221 | layout_id, | 313 | layout_id, |
| 222 | frame_state, | 314 | frame_state, |
| 223 | } => { | 315 | } => { |
| 224 | cx.compute_layout(*layout_id, available_space); | 316 | cx.compute_layout(*layout_id, available_space); |
| 225 | let layout_id = *layout_id; | 317 | let layout_id = *layout_id; |
| 226 | self.phase = ElementRenderPhase::LayoutComputed { | 318 | self.phase = ElementDrawPhase::LayoutComputed { |
| 227 | layout_id, | 319 | layout_id, |
| 228 | available_space, | 320 | available_space, |
| 229 | frame_state: frame_state.take(), | 321 | frame_state: frame_state.take(), |
| 230 | }; | 322 | }; |
| 231 | layout_id | 323 | layout_id |
| 232 | } | 324 | } |
| 233 | ElementRenderPhase::LayoutComputed { | 325 | ElementDrawPhase::LayoutComputed { |
| 234 | layout_id, | 326 | layout_id, |
| 235 | available_space: prev_available_space, | 327 | available_space: prev_available_space, |
| 236 | .. | 328 | .. |
| @@ -248,154 +340,203 @@ where · 11 unmodified lines | |||
| 248 | } | 340 | } |
| 249 | 341 | ||
| 250 | fn draw( | 342 | fn draw( |
| 251 | &mut self, | 343 | mut self, |
| 252 | origin: Point<Pixels>, | 344 | origin: Point<Pixels>, |
| 253 | available_space: Size<AvailableSpace>, | 345 | available_space: Size<AvailableSpace>, |
| 254 | view_state: &mut V, | 346 | cx: &mut WindowContext, |
| 255 | cx: &mut ViewContext<V>, | 347 | ) -> Option<E::State> { |
| 256 | ) { | 348 | self.measure(available_space, cx); |
| 257 | self.measure(available_space, view_state, cx); | 349 | cx.with_absolute_element_offset(origin, |cx| self.paint(cx)) |
| 258 | cx.with_absolute_element_offset(origin, |cx| self.paint(view_state, cx)) | ||
| 259 | } | 350 | } |
| 260 | } | 351 | } |
| 261 | 352 | ||
| 262 | pub struct AnyElement<V>(Box<dyn ElementObject<V>>); | 353 | // impl<V: 'static, E: Element> Element for DrawableElement<V, E> { |
| 354 | // type State = <E::Element as Element>::State; | ||
| 263 | 355 | ||
| 264 | impl<V> AnyElement<V> { | 356 | // fn layout( |
| 265 | pub fn new<E>(element: E) -> Self | 357 | // &mut self, |
| 266 | where | 358 | // element_state: Option<Self::State>, |
| 267 | V: 'static, | 359 | // cx: &mut WindowContext, |
| 268 | E: 'static + Element<V>, | 360 | // ) -> (LayoutId, Self::State) { |
| 269 | E::ElementState: Any, | ||
| 270 | { | ||
| 271 | AnyElement(Box::new(RenderedElement::new(element))) | ||
| 272 | } | ||
| 273 | 361 | ||
| 274 | pub fn element_id(&self) -> Option<ElementId> { | 362 | // } |
| 275 | self.0.element_id() | 363 | |
| 364 | // fn paint( | ||
| 365 | // self, | ||
| 366 | // bounds: Bounds<Pixels>, | ||
| 367 | // element_state: &mut Self::State, | ||
| 368 | // cx: &mut WindowContext, | ||
| 369 | // ) { | ||
| 370 | // todo!() | ||
| 371 | // } | ||
| 372 | // } | ||
| 373 | |||
| 374 | // impl<V: 'static, E: 'static + Element> RenderOnce for DrawableElement<V, E> { | ||
| 375 | // type Element = Self; | ||
| 376 | |||
| 377 | // fn element_id(&self) -> Option<ElementId> { | ||
| 378 | // self.element.as_ref()?.element_id() | ||
| 379 | // } | ||
| 380 | |||
| 381 | // fn render_once(self) -> Self::Element { | ||
| 382 | // self | ||
| 383 | // } | ||
| 384 | // } | ||
| 385 | |||
| 386 | impl<E> ElementObject for Option<DrawableElement<E>> | ||
| 387 | where | ||
| 388 | E: Element, | ||
| 389 | E::State: 'static, | ||
| 390 | { | ||
| 391 | fn element_id(&self) -> Option<ElementId> { | ||
| 392 | self.as_ref().unwrap().element_id() | ||
| 276 | } | 393 | } |
| 277 | 394 | ||
| 278 | pub fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId { | 395 | fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { |
| 279 | self.0.layout(view_state, cx) | 396 | DrawableElement::layout(self.as_mut().unwrap(), cx) |
| 280 | } | 397 | } |
| 281 | 398 | ||
| 282 | pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) { | 399 | fn paint(&mut self, cx: &mut WindowContext) { |
| 283 | self.0.paint(view_state, cx) | 400 | DrawableElement::paint(self.take().unwrap(), cx); |
| 284 | } | 401 | } |
| 285 | 402 | ||
| 286 | /// Initializes this element and performs layout within the given available space to determine its size. | 403 | fn measure( |
| 287 | pub fn measure( | ||
| 288 | &mut self, | 404 | &mut self, |
| 289 | available_space: Size<AvailableSpace>, | 405 | available_space: Size<AvailableSpace>, |
| 290 | view_state: &mut V, | 406 | cx: &mut WindowContext, |
| 291 | cx: &mut ViewContext<V>, | ||
| 292 | ) -> Size<Pixels> { | 407 | ) -> Size<Pixels> { |
| 293 | self.0.measure(available_space, view_state, cx) | 408 | DrawableElement::measure(self.as_mut().unwrap(), available_space, cx) |
| 294 | } | 409 | } |
| 295 | 410 | ||
| 296 | /// Initializes this element and performs layout in the available space, then paints it at the given origin. | 411 | fn draw( |
| 297 | pub fn draw( | ||
| 298 | &mut self, | 412 | &mut self, |
| 299 | origin: Point<Pixels>, | 413 | origin: Point<Pixels>, |
| 300 | available_space: Size<AvailableSpace>, | 414 | available_space: Size<AvailableSpace>, |
| 301 | view_state: &mut V, | 415 | cx: &mut WindowContext, |
| 302 | cx: &mut ViewContext<V>, | ||
| 303 | ) { | 416 | ) { |
| 304 | self.0.draw(origin, available_space, view_state, cx) | 417 | DrawableElement::draw(self.take().unwrap(), origin, available_space, cx); |
| 305 | } | 418 | } |
| 306 | } | 419 | } |
| 307 | 420 | ||
| 308 | pub trait Component<V> { | 421 | pub struct AnyElement(Box<dyn ElementObject>); |
| 309 | fn render(self) -> AnyElement<V>; | ||
| 310 | 422 | ||
| 311 | fn map<U>(self, f: impl FnOnce(Self) -> U) -> U | 423 | impl AnyElement { |
| 424 | pub fn new<E>(element: E) -> Self | ||
| 312 | where | 425 | where |
| 313 | Self: Sized, | 426 | E: 'static + Element, |
| 314 | U: Component<V>, | 427 | E::State: Any, |
| 315 | { | 428 | { |
| 316 | f(self) | 429 | AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>) |
| 317 | } | 430 | } |
| 318 | 431 | ||
| 319 | fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self | 432 | pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { |
| 320 | where | 433 | self.0.layout(cx) |
| 321 | Self: Sized, | ||
| 322 | { | ||
| 323 | self.map(|this| if condition { then(this) } else { this }) | ||
| 324 | } | 434 | } |
| 325 | 435 | ||
| 326 | fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self | 436 | pub fn paint(mut self, cx: &mut WindowContext) { |
| 327 | where | 437 | self.0.paint(cx) |
| 328 | Self: Sized, | ||
| 329 | { | ||
| 330 | self.map(|this| { | ||
| 331 | if let Some(value) = option { | ||
| 332 | then(this, value) | ||
| 333 | } else { | ||
| 334 | this | ||
| 335 | } | ||
| 336 | }) | ||
| 337 | } | 438 | } |
| 338 | } | ||
| 339 | 439 | ||
| 340 | impl<V> Component<V> for AnyElement<V> { | 440 | /// Initializes this element and performs layout within the given available space to determine its size. |
| 341 | fn render(self) -> AnyElement<V> { | 441 | pub fn measure( |
| 342 | self | 442 | &mut self, |
| 443 | available_space: Size<AvailableSpace>, | ||
| 444 | cx: &mut WindowContext, | ||
| 445 | ) -> Size<Pixels> { | ||
| 446 | self.0.measure(available_space, cx) | ||
| 343 | } | 447 | } |
| 344 | } | ||
| 345 | 448 | ||
| 346 | impl<V, E, F> Element<V> for Option<F> | 449 | /// Initializes this element and performs layout in the available space, then paints it at the given origin. |
| 347 | where | 450 | pub fn draw( |
| 348 | V: 'static, | 451 | mut self, |
| 349 | E: 'static + Component<V>, | 452 | origin: Point<Pixels>, |
| 350 | F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, | 453 | available_space: Size<AvailableSpace>, |
| 351 | { | 454 | cx: &mut WindowContext, |
| 352 | type ElementState = AnyElement<V>; | 455 | ) { |
| 456 | self.0.draw(origin, available_space, cx) | ||
| 457 | } | ||
| 353 | 458 | ||
| 354 | fn element_id(&self) -> Option<ElementId> { | 459 | /// Converts this `AnyElement` into a trait object that can be stored and manipulated. |
| 355 | None | 460 | pub fn into_any(self) -> AnyElement { |
| 461 | AnyElement::new(self) | ||
| 356 | } | 462 | } |
| 357 | 463 | ||
| 464 | pub fn inner_id(&self) -> Option<ElementId> { | ||
| 465 | self.0.element_id() | ||
| 466 | } | ||
| 467 | } | ||
| 468 | |||
| 469 | impl Element for AnyElement { | ||
| 470 | type State = (); | ||
| 471 | |||
| 358 | fn layout( | 472 | fn layout( |
| 359 | &mut self, | 473 | &mut self, |
| 360 | view_state: &mut V, | 474 | _: Option<Self::State>, |
| 361 | _: Option<Self::ElementState>, | 475 | cx: &mut WindowContext, |
| 362 | cx: &mut ViewContext<V>, | 476 | ) -> (LayoutId, Self::State) { |
| 363 | ) -> (LayoutId, Self::ElementState) { | 477 | let layout_id = self.layout(cx); |
| 364 | let render = self.take().unwrap(); | 478 | (layout_id, ()) |
| 365 | let mut rendered_element = (render)(view_state, cx).render(); | ||
| 366 | let layout_id = rendered_element.layout(view_state, cx); | ||
| 367 | (layout_id, rendered_element) | ||
| 368 | } | 479 | } |
| 369 | 480 | ||
| 370 | fn paint( | 481 | fn paint(self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) { |
| 371 | &mut self, | 482 | self.paint(cx); |
| 372 | _bounds: Bounds<Pixels>, | ||
| 373 | view_state: &mut V, | ||
| 374 | rendered_element: &mut Self::ElementState, | ||
| 375 | cx: &mut ViewContext<V>, | ||
| 376 | ) { | ||
| 377 | rendered_element.paint(view_state, cx) | ||
| 378 | } | 483 | } |
| 379 | } | 484 | } |
| 380 | 485 | ||
| 381 | impl<V, E, F> Component<V> for Option<F> | 486 | impl IntoElement for AnyElement { |
| 382 | where | 487 | type Element = Self; |
| 383 | V: 'static, | 488 | |
| 384 | E: 'static + Component<V>, | 489 | fn element_id(&self) -> Option<ElementId> { |
| 385 | F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, | 490 | None |
| 386 | { | ||
| 387 | fn render(self) -> AnyElement<V> { | ||
| 388 | AnyElement::new(self) | ||
| 389 | } | 491 | } |
| 390 | } | ||
| 391 | 492 | ||
| 392 | impl<V, E, F> Component<V> for F | 493 | fn into_element(self) -> Self::Element { |
| 393 | where | 494 | self |
| 394 | V: 'static, | ||
| 395 | E: 'static + Component<V>, | ||
| 396 | F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, | ||
| 397 | { | ||
| 398 | fn render(self) -> AnyElement<V> { | ||
| 399 | AnyElement::new(Some(self)) | ||
| 400 | } | 495 | } |
| 401 | } | 496 | } |
| 497 | |||
| 498 | // impl<V, E, F> Element for Option<F> | ||
| 499 | // where | ||
| 500 | // V: 'static, | ||
| 501 | // E: Element, | ||
| 502 | // F: FnOnce(&mut V, &mut WindowContext<'_, V>) -> E + 'static, | ||
| 503 | // { | ||
| 504 | // type State = Option<AnyElement>; | ||
| 505 | |||
| 506 | // fn element_id(&self) -> Option<ElementId> { | ||
| 507 | // None | ||
| 508 | // } | ||
| 509 | |||
| 510 | // fn layout( | ||
| 511 | // &mut self, | ||
| 512 | // _: Option<Self::State>, | ||
| 513 | // cx: &mut WindowContext, | ||
| 514 | // ) -> (LayoutId, Self::State) { | ||
| 515 | // let render = self.take().unwrap(); | ||
| 516 | // let mut element = (render)(view_state, cx).into_any(); | ||
| 517 | // let layout_id = element.layout(view_state, cx); | ||
| 518 | // (layout_id, Some(element)) | ||
| 519 | // } | ||
| 520 | |||
| 521 | // fn paint( | ||
| 522 | // self, | ||
| 523 | // _bounds: Bounds<Pixels>, | ||
| 524 | // rendered_element: &mut Self::State, | ||
| 525 | // cx: &mut WindowContext, | ||
| 526 | // ) { | ||
| 527 | // rendered_element.take().unwrap().paint(view_state, cx); | ||
| 528 | // } | ||
| 529 | // } | ||
| 530 | |||
| 531 | // impl<V, E, F> RenderOnce for Option<F> | ||
| 532 | // where | ||
| 533 | // V: 'static, | ||
| 534 | // E: Element, | ||
| 535 | // F: FnOnce(&mut V, &mut WindowContext) -> E + 'static, | ||
| 536 | // { | ||
| 537 | // type Element = Self; | ||
| 538 | |||
| 539 | // fn render(self) -> Self::Element { | ||
| 540 | // self | ||
| 541 | // } | ||
| 542 | // } | ||
| @@ -1,9 +1,9 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, | 2 | point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, |
| 3 | BorrowWindow, Bounds, ClickEvent, Component, DispatchPhase, Element, ElementId, FocusEvent, | 3 | BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, |
| 4 | FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, | 4 | IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, |
| 5 | MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, Point, Render, ScrollWheelEvent, | 5 | MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, |
| 6 | SharedString, Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility, | 6 | SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, |
| 7 | }; | 7 | }; |
| 8 | use collections::HashMap; | 8 | use collections::HashMap; |
| 9 | use refineable::Refineable; | 9 | use refineable::Refineable; |
| @@ -12,7 +12,6 @@ use std::{ · 2 unmodified lines | |||
| 12 | any::{Any, TypeId}, | 12 | any::{Any, TypeId}, |
| 13 | cell::RefCell, | 13 | cell::RefCell, |
| 14 | fmt::Debug, | 14 | fmt::Debug, |
| 15 | marker::PhantomData, | ||
| 16 | mem, | 15 | mem, |
| 17 | rc::Rc, | 16 | rc::Rc, |
| 18 | time::Duration, | 17 | time::Duration, |
| @@ -28,30 +27,24 @@ pub struct GroupStyle { · 9 unmodified lines | |||
| 28 | pub style: StyleRefinement, | 27 | pub style: StyleRefinement, |
| 29 | } | 28 | } |
| 30 | 29 | ||
| 31 | pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { | 30 | pub trait InteractiveElement: Sized + Element { |
| 32 | fn interactivity(&mut self) -> &mut Interactivity<V>; | 31 | fn interactivity(&mut self) -> &mut Interactivity; |
| 33 | 32 | ||
| 34 | fn group(mut self, group: impl Into<SharedString>) -> Self { | 33 | fn group(mut self, group: impl Into<SharedString>) -> Self { |
| 35 | self.interactivity().group = Some(group.into()); | 34 | self.interactivity().group = Some(group.into()); |
| 36 | self | 35 | self |
| 37 | } | 36 | } |
| 38 | 37 | ||
| 39 | fn id(mut self, id: impl Into<ElementId>) -> Stateful<V, Self> { | 38 | fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> { |
| 40 | self.interactivity().element_id = Some(id.into()); | 39 | self.interactivity().element_id = Some(id.into()); |
| 41 | 40 | ||
| 42 | Stateful { | 41 | Stateful { element: self } |
| 43 | element: self, | ||
| 44 | view_type: PhantomData, | ||
| 45 | } | ||
| 46 | } | 42 | } |
| 47 | 43 | ||
| 48 | fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<V, Self> { | 44 | fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> { |
| 49 | self.interactivity().focusable = true; | 45 | self.interactivity().focusable = true; |
| 50 | self.interactivity().tracked_focus_handle = Some(focus_handle.clone()); | 46 | self.interactivity().tracked_focus_handle = Some(focus_handle.clone()); |
| 51 | Focusable { | 47 | Focusable { element: self } |
| 52 | element: self, | ||
| 53 | view_type: PhantomData, | ||
| 54 | } | ||
| 55 | } | 48 | } |
| 56 | 49 | ||
| 57 | fn key_context<C, E>(mut self, key_context: C) -> Self | 50 | fn key_context<C, E>(mut self, key_context: C) -> Self |
| @@ -85,15 +78,15 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 27 unmodified lines | |||
| 85 | fn on_mouse_down( | 78 | fn on_mouse_down( |
| 86 | mut self, | 79 | mut self, |
| 87 | button: MouseButton, | 80 | button: MouseButton, |
| 88 | handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static, | 81 | listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, |
| 89 | ) -> Self { | 82 | ) -> Self { |
| 90 | self.interactivity().mouse_down_listeners.push(Box::new( | 83 | self.interactivity().mouse_down_listeners.push(Box::new( |
| 91 | move |view, event, bounds, phase, cx| { | 84 | move |event, bounds, phase, cx| { |
| 92 | if phase == DispatchPhase::Bubble | 85 | if phase == DispatchPhase::Bubble |
| 93 | && event.button == button | 86 | && event.button == button |
| 94 | && bounds.contains_point(&event.position) | 87 | && bounds.contains_point(&event.position) |
| 95 | { | 88 | { |
| 96 | handler(view, event, cx) | 89 | (listener)(event, cx) |
| 97 | } | 90 | } |
| 98 | }, | 91 | }, |
| 99 | )); | 92 | )); |
| @@ -102,12 +95,12 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines | |||
| 102 | 95 | ||
| 103 | fn on_any_mouse_down( | 96 | fn on_any_mouse_down( |
| 104 | mut self, | 97 | mut self, |
| 105 | handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static, | 98 | listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, |
| 106 | ) -> Self { | 99 | ) -> Self { |
| 107 | self.interactivity().mouse_down_listeners.push(Box::new( | 100 | self.interactivity().mouse_down_listeners.push(Box::new( |
| 108 | move |view, event, bounds, phase, cx| { | 101 | move |event, bounds, phase, cx| { |
| 109 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { | 102 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { |
| 110 | handler(view, event, cx) | 103 | (listener)(event, cx) |
| 111 | } | 104 | } |
| 112 | }, | 105 | }, |
| 113 | )); | 106 | )); |
| @@ -117,43 +110,43 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 3 unmodified lines | |||
| 117 | fn on_mouse_up( | 110 | fn on_mouse_up( |
| 118 | mut self, | 111 | mut self, |
| 119 | button: MouseButton, | 112 | button: MouseButton, |
| 120 | handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static, | 113 | listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, |
| 121 | ) -> Self { | 114 | ) -> Self { |
| 122 | self.interactivity().mouse_up_listeners.push(Box::new( | 115 | self.interactivity() |
| 123 | move |view, event, bounds, phase, cx| { | 116 | .mouse_up_listeners |
| 117 | .push(Box::new(move |event, bounds, phase, cx| { | ||
| 124 | if phase == DispatchPhase::Bubble | 118 | if phase == DispatchPhase::Bubble |
| 125 | && event.button == button | 119 | && event.button == button |
| 126 | && bounds.contains_point(&event.position) | 120 | && bounds.contains_point(&event.position) |
| 127 | { | 121 | { |
| 128 | handler(view, event, cx) | 122 | (listener)(event, cx) |
| 129 | } | 123 | } |
| 130 | }, | 124 | })); |
| 131 | )); | ||
| 132 | self | 125 | self |
| 133 | } | 126 | } |
| 134 | 127 | ||
| 135 | fn on_any_mouse_up( | 128 | fn on_any_mouse_up( |
| 136 | mut self, | 129 | mut self, |
| 137 | handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static, | 130 | listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, |
| 138 | ) -> Self { | 131 | ) -> Self { |
| 139 | self.interactivity().mouse_up_listeners.push(Box::new( | 132 | self.interactivity() |
| 140 | move |view, event, bounds, phase, cx| { | 133 | .mouse_up_listeners |
| 134 | .push(Box::new(move |event, bounds, phase, cx| { | ||
| 141 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { | 135 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { |
| 142 | handler(view, event, cx) | 136 | (listener)(event, cx) |
| 143 | } | 137 | } |
| 144 | }, | 138 | })); |
| 145 | )); | ||
| 146 | self | 139 | self |
| 147 | } | 140 | } |
| 148 | 141 | ||
| 149 | fn on_mouse_down_out( | 142 | fn on_mouse_down_out( |
| 150 | mut self, | 143 | mut self, |
| 151 | handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static, | 144 | listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, |
| 152 | ) -> Self { | 145 | ) -> Self { |
| 153 | self.interactivity().mouse_down_listeners.push(Box::new( | 146 | self.interactivity().mouse_down_listeners.push(Box::new( |
| 154 | move |view, event, bounds, phase, cx| { | 147 | move |event, bounds, phase, cx| { |
| 155 | if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) { | 148 | if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) { |
| 156 | handler(view, event, cx) | 149 | (listener)(event, cx) |
| 157 | } | 150 | } |
| 158 | }, | 151 | }, |
| 159 | )); | 152 | )); |
| @@ -163,29 +156,29 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 3 unmodified lines | |||
| 163 | fn on_mouse_up_out( | 156 | fn on_mouse_up_out( |
| 164 | mut self, | 157 | mut self, |
| 165 | button: MouseButton, | 158 | button: MouseButton, |
| 166 | handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static, | 159 | listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, |
| 167 | ) -> Self { | 160 | ) -> Self { |
| 168 | self.interactivity().mouse_up_listeners.push(Box::new( | 161 | self.interactivity() |
| 169 | move |view, event, bounds, phase, cx| { | 162 | .mouse_up_listeners |
| 163 | .push(Box::new(move |event, bounds, phase, cx| { | ||
| 170 | if phase == DispatchPhase::Capture | 164 | if phase == DispatchPhase::Capture |
| 171 | && event.button == button | 165 | && event.button == button |
| 172 | && !bounds.contains_point(&event.position) | 166 | && !bounds.contains_point(&event.position) |
| 173 | { | 167 | { |
| 174 | handler(view, event, cx); | 168 | (listener)(event, cx); |
| 175 | } | 169 | } |
| 176 | }, | 170 | })); |
| 177 | )); | ||
| 178 | self | 171 | self |
| 179 | } | 172 | } |
| 180 | 173 | ||
| 181 | fn on_mouse_move( | 174 | fn on_mouse_move( |
| 182 | mut self, | 175 | mut self, |
| 183 | handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static, | 176 | listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, |
| 184 | ) -> Self { | 177 | ) -> Self { |
| 185 | self.interactivity().mouse_move_listeners.push(Box::new( | 178 | self.interactivity().mouse_move_listeners.push(Box::new( |
| 186 | move |view, event, bounds, phase, cx| { | 179 | move |event, bounds, phase, cx| { |
| 187 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { | 180 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { |
| 188 | handler(view, event, cx); | 181 | (listener)(event, cx); |
| 189 | } | 182 | } |
| 190 | }, | 183 | }, |
| 191 | )); | 184 | )); |
| @@ -194,29 +187,29 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines | |||
| 194 | 187 | ||
| 195 | fn on_scroll_wheel( | 188 | fn on_scroll_wheel( |
| 196 | mut self, | 189 | mut self, |
| 197 | handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static, | 190 | listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static, |
| 198 | ) -> Self { | 191 | ) -> Self { |
| 199 | self.interactivity().scroll_wheel_listeners.push(Box::new( | 192 | self.interactivity().scroll_wheel_listeners.push(Box::new( |
| 200 | move |view, event, bounds, phase, cx| { | 193 | move |event, bounds, phase, cx| { |
| 201 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { | 194 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { |
| 202 | handler(view, event, cx); | 195 | (listener)(event, cx); |
| 203 | } | 196 | } |
| 204 | }, | 197 | }, |
| 205 | )); | 198 | )); |
| 206 | self | 199 | self |
| 207 | } | 200 | } |
| 208 | 201 | ||
| 209 | /// Capture the given action, fires during the capture phase | 202 | /// Capture the given action, before normal action dispatch can fire |
| 210 | fn capture_action<A: Action>( | 203 | fn capture_action<A: Action>( |
| 211 | mut self, | 204 | mut self, |
| 212 | listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static, | 205 | listener: impl Fn(&A, &mut WindowContext) + 'static, |
| 213 | ) -> Self { | 206 | ) -> Self { |
| 214 | self.interactivity().action_listeners.push(( | 207 | self.interactivity().action_listeners.push(( |
| 215 | TypeId::of::<A>(), | 208 | TypeId::of::<A>(), |
| 216 | Box::new(move |view, action, phase, cx| { | 209 | Box::new(move |action, phase, cx| { |
| 217 | let action = action.downcast_ref().unwrap(); | 210 | let action = action.downcast_ref().unwrap(); |
| 218 | if phase == DispatchPhase::Capture { | 211 | if phase == DispatchPhase::Capture { |
| 219 | listener(view, action, cx) | 212 | (listener)(action, cx) |
| 220 | } | 213 | } |
| 221 | }), | 214 | }), |
| 222 | )); | 215 | )); |
| @@ -224,10 +217,7 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 1 unmodified line | |||
| 224 | } | 217 | } |
| 225 | 218 | ||
| 226 | /// Add a listener for the given action, fires during the bubble event phase | 219 | /// Add a listener for the given action, fires during the bubble event phase |
| 227 | fn on_action<A: Action>( | 220 | fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self { |
| 228 | mut self, | ||
| 229 | listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static, | ||
| 230 | ) -> Self { | ||
| 231 | // NOTE: this debug assert has the side-effect of working around | 221 | // NOTE: this debug assert has the side-effect of working around |
| 232 | // a bug where a crate consisting only of action definitions does | 222 | // a bug where a crate consisting only of action definitions does |
| 233 | // not register the actions in debug builds: | 223 | // not register the actions in debug builds: |
| @@ -244,10 +234,10 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 10 unmodified lines | |||
| 244 | // ); | 234 | // ); |
| 245 | self.interactivity().action_listeners.push(( | 235 | self.interactivity().action_listeners.push(( |
| 246 | TypeId::of::<A>(), | 236 | TypeId::of::<A>(), |
| 247 | Box::new(move |view, action, phase, cx| { | 237 | Box::new(move |action, phase, cx| { |
| 248 | let action = action.downcast_ref().unwrap(); | 238 | let action = action.downcast_ref().unwrap(); |
| 249 | if phase == DispatchPhase::Bubble { | 239 | if phase == DispatchPhase::Bubble { |
| 250 | listener(view, action, cx) | 240 | (listener)(action, cx) |
| 251 | } | 241 | } |
| 252 | }), | 242 | }), |
| 253 | )); | 243 | )); |
| @@ -256,24 +246,53 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines | |||
| 256 | 246 | ||
| 257 | fn on_key_down( | 247 | fn on_key_down( |
| 258 | mut self, | 248 | mut self, |
| 259 | listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static, | 249 | listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static, |
| 260 | ) -> Self { | 250 | ) -> Self { |
| 261 | self.interactivity() | 251 | self.interactivity() |
| 262 | .key_down_listeners | 252 | .key_down_listeners |
| 263 | .push(Box::new(move |view, event, phase, cx| { | 253 | .push(Box::new(move |event, phase, cx| { |
| 264 | listener(view, event, phase, cx) | 254 | if phase == DispatchPhase::Bubble { |
| 255 | (listener)(event, cx) | ||
| 256 | } | ||
| 265 | })); | 257 | })); |
| 266 | self | 258 | self |
| 267 | } | 259 | } |
| 268 | 260 | ||
| 269 | fn on_key_up( | 261 | fn capture_key_down( |
| 270 | mut self, | 262 | mut self, |
| 271 | listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static, | 263 | listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static, |
| 272 | ) -> Self { | 264 | ) -> Self { |
| 265 | self.interactivity() | ||
| 266 | .key_down_listeners | ||
| 267 | .push(Box::new(move |event, phase, cx| { | ||
| 268 | if phase == DispatchPhase::Capture { | ||
| 269 | listener(event, cx) | ||
| 270 | } | ||
| 271 | })); | ||
| 272 | self | ||
| 273 | } | ||
| 274 | |||
| 275 | fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self { | ||
| 273 | self.interactivity() | 276 | self.interactivity() |
| 274 | .key_up_listeners | 277 | .key_up_listeners |
| 275 | .push(Box::new(move |view, event, phase, cx| { | 278 | .push(Box::new(move |event, phase, cx| { |
| 276 | listener(view, event, phase, cx) | 279 | if phase == DispatchPhase::Bubble { |
| 280 | listener(event, cx) | ||
| 281 | } | ||
| 282 | })); | ||
| 283 | self | ||
| 284 | } | ||
| 285 | |||
| 286 | fn capture_key_up( | ||
| 287 | mut self, | ||
| 288 | listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static, | ||
| 289 | ) -> Self { | ||
| 290 | self.interactivity() | ||
| 291 | .key_up_listeners | ||
| 292 | .push(Box::new(move |event, phase, cx| { | ||
| 293 | if phase == DispatchPhase::Capture { | ||
| 294 | listener(event, cx) | ||
| 295 | } | ||
| 277 | })); | 296 | })); |
| 278 | self | 297 | self |
| 279 | } | 298 | } |
| @@ -302,25 +321,22 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 22 unmodified lines | |||
| 302 | 321 | ||
| 303 | fn on_drop<W: 'static>( | 322 | fn on_drop<W: 'static>( |
| 304 | mut self, | 323 | mut self, |
| 305 | listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static, | 324 | listener: impl Fn(&View<W>, &mut WindowContext) + 'static, |
| 306 | ) -> Self { | 325 | ) -> Self { |
| 307 | self.interactivity().drop_listeners.push(( | 326 | self.interactivity().drop_listeners.push(( |
| 308 | TypeId::of::<W>(), | 327 | TypeId::of::<W>(), |
| 309 | Box::new(move |view, dragged_view, cx| { | 328 | Box::new(move |dragged_view, cx| { |
| 310 | listener(view, dragged_view.downcast().unwrap(), cx); | 329 | listener(&dragged_view.downcast().unwrap(), cx); |
| 311 | }), | 330 | }), |
| 312 | )); | 331 | )); |
| 313 | self | 332 | self |
| 314 | } | 333 | } |
| 315 | } | 334 | } |
| 316 | 335 | ||
| 317 | pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveComponent<V> { | 336 | pub trait StatefulInteractiveElement: InteractiveElement { |
| 318 | fn focusable(mut self) -> Focusable<V, Self> { | 337 | fn focusable(mut self) -> Focusable<Self> { |
| 319 | self.interactivity().focusable = true; | 338 | self.interactivity().focusable = true; |
| 320 | Focusable { | 339 | Focusable { element: self } |
| 321 | element: self, | ||
| 322 | view_type: PhantomData, | ||
| 323 | } | ||
| 324 | } | 340 | } |
| 325 | 341 | ||
| 326 | fn overflow_scroll(mut self) -> Self { | 342 | fn overflow_scroll(mut self) -> Self { |
| @@ -362,23 +378,17 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 35 unmodified lines | |||
| 362 | self | 378 | self |
| 363 | } | 379 | } |
| 364 | 380 | ||
| 365 | fn on_click( | 381 | fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self |
| 366 | mut self, | ||
| 367 | listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static, | ||
| 368 | ) -> Self | ||
| 369 | where | 382 | where |
| 370 | Self: Sized, | 383 | Self: Sized, |
| 371 | { | 384 | { |
| 372 | self.interactivity() | 385 | self.interactivity() |
| 373 | .click_listeners | 386 | .click_listeners |
| 374 | .push(Box::new(move |view, event, cx| listener(view, event, cx))); | 387 | .push(Box::new(move |event, cx| listener(event, cx))); |
| 375 | self | 388 | self |
| 376 | } | 389 | } |
| 377 | 390 | ||
| 378 | fn on_drag<W>( | 391 | fn on_drag<W>(mut self, listener: impl Fn(&mut WindowContext) -> View<W> + 'static) -> Self |
| 379 | mut self, | ||
| 380 | listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static, | ||
| 381 | ) -> Self | ||
| 382 | where | 392 | where |
| 383 | Self: Sized, | 393 | Self: Sized, |
| 384 | W: 'static + Render, | 394 | W: 'static + Render, |
| @@ -387,15 +397,14 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 2 unmodified lines | |||
| 387 | self.interactivity().drag_listener.is_none(), | 397 | self.interactivity().drag_listener.is_none(), |
| 388 | "calling on_drag more than once on the same element is not supported" | 398 | "calling on_drag more than once on the same element is not supported" |
| 389 | ); | 399 | ); |
| 390 | self.interactivity().drag_listener = | 400 | self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag { |
| 391 | Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag { | 401 | view: listener(cx).into(), |
| 392 | view: listener(view_state, cx).into(), | 402 | cursor_offset, |
| 393 | cursor_offset, | 403 | })); |
| 394 | })); | ||
| 395 | self | 404 | self |
| 396 | } | 405 | } |
| 397 | 406 | ||
| 398 | fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self | 407 | fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self |
| 399 | where | 408 | where |
| 400 | Self: Sized, | 409 | Self: Sized, |
| 401 | { | 410 | { |
| @@ -407,10 +416,7 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 5 unmodified lines | |||
| 407 | self | 416 | self |
| 408 | } | 417 | } |
| 409 | 418 | ||
| 410 | fn tooltip( | 419 | fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self |
| 411 | mut self, | ||
| 412 | build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static, | ||
| 413 | ) -> Self | ||
| 414 | where | 420 | where |
| 415 | Self: Sized, | 421 | Self: Sized, |
| 416 | { | 422 | { |
| @@ -418,14 +424,13 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 1 unmodified line | |||
| 418 | self.interactivity().tooltip_builder.is_none(), | 424 | self.interactivity().tooltip_builder.is_none(), |
| 419 | "calling tooltip more than once on the same element is not supported" | 425 | "calling tooltip more than once on the same element is not supported" |
| 420 | ); | 426 | ); |
| 421 | self.interactivity().tooltip_builder = | 427 | self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip)); |
| 422 | Some(Rc::new(move |view_state, cx| build_tooltip(view_state, cx))); | ||
| 423 | 428 | ||
| 424 | self | 429 | self |
| 425 | } | 430 | } |
| 426 | } | 431 | } |
| 427 | 432 | ||
| 428 | pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { | 433 | pub trait FocusableElement: InteractiveElement { |
| 429 | fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self | 434 | fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self |
| 430 | where | 435 | where |
| 431 | Self: Sized, | 436 | Self: Sized, |
| @@ -442,49 +447,41 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 10 unmodified lines | |||
| 442 | self | 447 | self |
| 443 | } | 448 | } |
| 444 | 449 | ||
| 445 | fn on_focus( | 450 | fn on_focus(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self |
| 446 | mut self, | ||
| 447 | listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static, | ||
| 448 | ) -> Self | ||
| 449 | where | 451 | where |
| 450 | Self: Sized, | 452 | Self: Sized, |
| 451 | { | 453 | { |
| 452 | self.interactivity().focus_listeners.push(Box::new( | 454 | self.interactivity() |
| 453 | move |view, focus_handle, event, cx| { | 455 | .focus_listeners |
| 456 | .push(Box::new(move |focus_handle, event, cx| { | ||
| 454 | if event.focused.as_ref() == Some(focus_handle) { | 457 | if event.focused.as_ref() == Some(focus_handle) { |
| 455 | listener(view, event, cx) | 458 | listener(event, cx) |
| 456 | } | 459 | } |
| 457 | }, | 460 | })); |
| 458 | )); | ||
| 459 | self | 461 | self |
| 460 | } | 462 | } |
| 461 | 463 | ||
| 462 | fn on_blur( | 464 | fn on_blur(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self |
| 463 | mut self, | ||
| 464 | listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static, | ||
| 465 | ) -> Self | ||
| 466 | where | 465 | where |
| 467 | Self: Sized, | 466 | Self: Sized, |
| 468 | { | 467 | { |
| 469 | self.interactivity().focus_listeners.push(Box::new( | 468 | self.interactivity() |
| 470 | move |view, focus_handle, event, cx| { | 469 | .focus_listeners |
| 470 | .push(Box::new(move |focus_handle, event, cx| { | ||
| 471 | if event.blurred.as_ref() == Some(focus_handle) { | 471 | if event.blurred.as_ref() == Some(focus_handle) { |
| 472 | listener(view, event, cx) | 472 | listener(event, cx) |
| 473 | } | 473 | } |
| 474 | }, | 474 | })); |
| 475 | )); | ||
| 476 | self | 475 | self |
| 477 | } | 476 | } |
| 478 | 477 | ||
| 479 | fn on_focus_in( | 478 | fn on_focus_in(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self |
| 480 | mut self, | ||
| 481 | listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static, | ||
| 482 | ) -> Self | ||
| 483 | where | 479 | where |
| 484 | Self: Sized, | 480 | Self: Sized, |
| 485 | { | 481 | { |
| 486 | self.interactivity().focus_listeners.push(Box::new( | 482 | self.interactivity() |
| 487 | move |view, focus_handle, event, cx| { | 483 | .focus_listeners |
| 484 | .push(Box::new(move |focus_handle, event, cx| { | ||
| 488 | let descendant_blurred = event | 485 | let descendant_blurred = event |
| 489 | .blurred | 486 | .blurred |
| 490 | .as_ref() | 487 | .as_ref() |
| @@ -495,22 +492,19 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 4 unmodified lines | |||
| 495 | .map_or(false, |focused| focus_handle.contains(focused, cx)); | 492 | .map_or(false, |focused| focus_handle.contains(focused, cx)); |
| 496 | 493 | ||
| 497 | if !descendant_blurred && descendant_focused { | 494 | if !descendant_blurred && descendant_focused { |
| 498 | listener(view, event, cx) | 495 | listener(event, cx) |
| 499 | } | 496 | } |
| 500 | }, | 497 | })); |
| 501 | )); | ||
| 502 | self | 498 | self |
| 503 | } | 499 | } |
| 504 | 500 | ||
| 505 | fn on_focus_out( | 501 | fn on_focus_out(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self |
| 506 | mut self, | ||
| 507 | listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static, | ||
| 508 | ) -> Self | ||
| 509 | where | 502 | where |
| 510 | Self: Sized, | 503 | Self: Sized, |
| 511 | { | 504 | { |
| 512 | self.interactivity().focus_listeners.push(Box::new( | 505 | self.interactivity() |
| 513 | move |view, focus_handle, event, cx| { | 506 | .focus_listeners |
| 507 | .push(Box::new(move |focus_handle, event, cx| { | ||
| 514 | let descendant_blurred = event | 508 | let descendant_blurred = event |
| 515 | .blurred | 509 | .blurred |
| 516 | .as_ref() | 510 | .as_ref() |
| @@ -520,98 +514,80 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 3 unmodified lines | |||
| 520 | .as_ref() | 514 | .as_ref() |
| 521 | .map_or(false, |focused| focus_handle.contains(focused, cx)); | 515 | .map_or(false, |focused| focus_handle.contains(focused, cx)); |
| 522 | if descendant_blurred && !descendant_focused { | 516 | if descendant_blurred && !descendant_focused { |
| 523 | listener(view, event, cx) | 517 | listener(event, cx) |
| 524 | } | 518 | } |
| 525 | }, | 519 | })); |
| 526 | )); | ||
| 527 | self | 520 | self |
| 528 | } | 521 | } |
| 529 | } | 522 | } |
| 530 | 523 | ||
| 531 | pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>; | 524 | pub type FocusListeners = SmallVec<[FocusListener; 2]>; |
| 532 | |||
| 533 | pub type FocusListener<V> = | ||
| 534 | Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>; | ||
| 535 | 525 | ||
| 536 | pub type MouseDownListener<V> = Box< | 526 | pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>; |
| 537 | dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static, | ||
| 538 | >; | ||
| 539 | pub type MouseUpListener<V> = Box< | ||
| 540 | dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static, | ||
| 541 | >; | ||
| 542 | 527 | ||
| 543 | pub type MouseMoveListener<V> = Box< | 528 | pub type MouseDownListener = |
| 544 | dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static, | 529 | Box<dyn Fn(&MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>; |
| 545 | >; | 530 | pub type MouseUpListener = |
| 531 | Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>; | ||
| 546 | 532 | ||
| 547 | pub type ScrollWheelListener<V> = Box< | 533 | pub type MouseMoveListener = |
| 548 | dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) | 534 | Box<dyn Fn(&MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>; |
| 549 | + 'static, | ||
| 550 | >; | ||
| 551 | 535 | ||
| 552 | pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>; | 536 | pub type ScrollWheelListener = |
| 537 | Box<dyn Fn(&ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>; | ||
| 553 | 538 | ||
| 554 | pub type DragListener<V> = | 539 | pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>; |
| 555 | Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>; | ||
| 556 | 540 | ||
| 557 | type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static; | 541 | pub type DragListener = Box<dyn Fn(Point<Pixels>, &mut WindowContext) -> AnyDrag + 'static>; |
| 558 | 542 | ||
| 559 | pub type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>; | 543 | type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static; |
| 560 | 544 | ||
| 561 | pub type TooltipBuilder<V> = Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>; | 545 | pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>; |
| 562 | 546 | ||
| 563 | pub type KeyDownListener<V> = | 547 | pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>; |
| 564 | Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>; | ||
| 565 | 548 | ||
| 566 | pub type KeyUpListener<V> = | 549 | pub type KeyUpListener = Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>; |
| 567 | Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>; | ||
| 568 | 550 | ||
| 569 | pub type ActionListener<V> = | 551 | pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>; |
| 570 | Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>; | ||
| 571 | 552 | ||
| 572 | pub fn div<V: 'static>() -> Div<V> { | 553 | pub fn div() -> Div { |
| 573 | Div { | 554 | Div { |
| 574 | interactivity: Interactivity::default(), | 555 | interactivity: Interactivity::default(), |
| 575 | children: SmallVec::default(), | 556 | children: SmallVec::default(), |
| 576 | } | 557 | } |
| 577 | } | 558 | } |
| 578 | 559 | ||
| 579 | pub struct Div<V> { | 560 | pub struct Div { |
| 580 | interactivity: Interactivity<V>, | 561 | interactivity: Interactivity, |
| 581 | children: SmallVec<[AnyElement<V>; 2]>, | 562 | children: SmallVec<[AnyElement; 2]>, |
| 582 | } | 563 | } |
| 583 | 564 | ||
| 584 | impl<V> Styled for Div<V> { | 565 | impl Styled for Div { |
| 585 | fn style(&mut self) -> &mut StyleRefinement { | 566 | fn style(&mut self) -> &mut StyleRefinement { |
| 586 | &mut self.interactivity.base_style | 567 | &mut self.interactivity.base_style |
| 587 | } | 568 | } |
| 588 | } | 569 | } |
| 589 | 570 | ||
| 590 | impl<V: 'static> InteractiveComponent<V> for Div<V> { | 571 | impl InteractiveElement for Div { |
| 591 | fn interactivity(&mut self) -> &mut Interactivity<V> { | 572 | fn interactivity(&mut self) -> &mut Interactivity { |
| 592 | &mut self.interactivity | 573 | &mut self.interactivity |
| 593 | } | 574 | } |
| 594 | } | 575 | } |
| 595 | 576 | ||
| 596 | impl<V: 'static> ParentComponent<V> for Div<V> { | 577 | impl ParentElement for Div { |
| 597 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { | 578 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { |
| 598 | &mut self.children | 579 | &mut self.children |
| 599 | } | 580 | } |
| 600 | } | 581 | } |
| 601 | 582 | ||
| 602 | impl<V: 'static> Element<V> for Div<V> { | 583 | impl Element for Div { |
| 603 | type ElementState = DivState; | 584 | type State = DivState; |
| 604 | |||
| 605 | fn element_id(&self) -> Option<ElementId> { | ||
| 606 | self.interactivity.element_id.clone() | ||
| 607 | } | ||
| 608 | 585 | ||
| 609 | fn layout( | 586 | fn layout( |
| 610 | &mut self, | 587 | &mut self, |
| 611 | view_state: &mut V, | 588 | element_state: Option<Self::State>, |
| 612 | element_state: Option<Self::ElementState>, | 589 | cx: &mut WindowContext, |
| 613 | cx: &mut ViewContext<V>, | 590 | ) -> (LayoutId, Self::State) { |
| 614 | ) -> (LayoutId, Self::ElementState) { | ||
| 615 | let mut child_layout_ids = SmallVec::new(); | 591 | let mut child_layout_ids = SmallVec::new(); |
| 616 | let mut interactivity = mem::take(&mut self.interactivity); | 592 | let mut interactivity = mem::take(&mut self.interactivity); |
| 617 | let (layout_id, interactive_state) = interactivity.layout( | 593 | let (layout_id, interactive_state) = interactivity.layout( |
| @@ -622,7 +598,7 @@ impl<V: 'static> Element<V> for Div<V> { · 4 unmodified lines | |||
| 622 | child_layout_ids = self | 598 | child_layout_ids = self |
| 623 | .children | 599 | .children |
| 624 | .iter_mut() | 600 | .iter_mut() |
| 625 | .map(|child| child.layout(view_state, cx)) | 601 | .map(|child| child.layout(cx)) |
| 626 | .collect::<SmallVec<_>>(); | 602 | .collect::<SmallVec<_>>(); |
| 627 | cx.request_layout(&style, child_layout_ids.iter().copied()) | 603 | cx.request_layout(&style, child_layout_ids.iter().copied()) |
| 628 | }) | 604 | }) |
| @@ -639,11 +615,10 @@ impl<V: 'static> Element<V> for Div<V> { · 10 unmodified lines | |||
| 639 | } | 615 | } |
| 640 | 616 | ||
| 641 | fn paint( | 617 | fn paint( |
| 642 | &mut self, | 618 | self, |
| 643 | bounds: Bounds<Pixels>, | 619 | bounds: Bounds<Pixels>, |
| 644 | view_state: &mut V, | 620 | element_state: &mut Self::State, |
| 645 | element_state: &mut Self::ElementState, | 621 | cx: &mut WindowContext, |
| 646 | cx: &mut ViewContext<V>, | ||
| 647 | ) { | 622 | ) { |
| 648 | let mut child_min = point(Pixels::MAX, Pixels::MAX); | 623 | let mut child_min = point(Pixels::MAX, Pixels::MAX); |
| 649 | let mut child_max = Point::default(); | 624 | let mut child_max = Point::default(); |
| @@ -658,8 +633,7 @@ impl<V: 'static> Element<V> for Div<V> { · 8 unmodified lines | |||
| 658 | (child_max - child_min).into() | 633 | (child_max - child_min).into() |
| 659 | }; | 634 | }; |
| 660 | 635 | ||
| 661 | let mut interactivity = mem::take(&mut self.interactivity); | 636 | self.interactivity.paint( |
| 662 | interactivity.paint( | ||
| 663 | bounds, | 637 | bounds, |
| 664 | content_size, | 638 | content_size, |
| 665 | &mut element_state.interactive_state, | 639 | &mut element_state.interactive_state, |
| @@ -679,8 +653,8 @@ impl<V: 'static> Element<V> for Div<V> { · 13 unmodified lines | |||
| 679 | cx.with_text_style(style.text_style().cloned(), |cx| { | 653 | cx.with_text_style(style.text_style().cloned(), |cx| { |
| 680 | cx.with_content_mask(style.overflow_mask(bounds), |cx| { | 654 | cx.with_content_mask(style.overflow_mask(bounds), |cx| { |
| 681 | cx.with_element_offset(scroll_offset, |cx| { | 655 | cx.with_element_offset(scroll_offset, |cx| { |
| 682 | for child in &mut self.children { | 656 | for child in self.children { |
| 683 | child.paint(view_state, cx); | 657 | child.paint(cx); |
| 684 | } | 658 | } |
| 685 | }) | 659 | }) |
| 686 | }) | 660 | }) |
| @@ -689,13 +663,18 @@ impl<V: 'static> Element<V> for Div<V> { · 2 unmodified lines | |||
| 689 | }) | 663 | }) |
| 690 | }, | 664 | }, |
| 691 | ); | 665 | ); |
| 692 | self.interactivity = interactivity; | ||
| 693 | } | 666 | } |
| 694 | } | 667 | } |
| 695 | 668 | ||
| 696 | impl<V: 'static> Component<V> for Div<V> { | 669 | impl IntoElement for Div { |
| 697 | fn render(self) -> AnyElement<V> { | 670 | type Element = Self; |
| 698 | AnyElement::new(self) | 671 | |
| 672 | fn element_id(&self) -> Option<ElementId> { | ||
| 673 | self.interactivity.element_id.clone() | ||
| 674 | } | ||
| 675 | |||
| 676 | fn into_element(self) -> Self::Element { | ||
| 677 | self | ||
| 699 | } | 678 | } |
| 700 | } | 679 | } |
| 701 | 680 | ||
| @@ -710,12 +689,12 @@ impl DivState { · 8 unmodified lines | |||
| 710 | } | 689 | } |
| 711 | } | 690 | } |
| 712 | 691 | ||
| 713 | pub struct Interactivity<V> { | 692 | pub struct Interactivity { |
| 714 | pub element_id: Option<ElementId>, | 693 | pub element_id: Option<ElementId>, |
| 715 | pub key_context: KeyContext, | 694 | pub key_context: KeyContext, |
| 716 | pub focusable: bool, | 695 | pub focusable: bool, |
| 717 | pub tracked_focus_handle: Option<FocusHandle>, | 696 | pub tracked_focus_handle: Option<FocusHandle>, |
| 718 | pub focus_listeners: FocusListeners<V>, | 697 | pub focus_listeners: FocusListeners, |
| 719 | pub group: Option<SharedString>, | 698 | pub group: Option<SharedString>, |
| 720 | pub base_style: StyleRefinement, | 699 | pub base_style: StyleRefinement, |
| 721 | pub focus_style: StyleRefinement, | 700 | pub focus_style: StyleRefinement, |
| @@ -726,29 +705,26 @@ pub struct Interactivity<V> { · 4 unmodified lines | |||
| 726 | pub group_active_style: Option<GroupStyle>, | 705 | pub group_active_style: Option<GroupStyle>, |
| 727 | pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, | 706 | pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, |
| 728 | pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, | 707 | pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, |
| 729 | pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>, | 708 | pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, |
| 730 | pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>, | 709 | pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, |
| 731 | pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>, | 710 | pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, |
| 732 | pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>, | 711 | pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>, |
| 733 | pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>, | 712 | pub key_down_listeners: SmallVec<[KeyDownListener; 2]>, |
| 734 | pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>, | 713 | pub key_up_listeners: SmallVec<[KeyUpListener; 2]>, |
| 735 | pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>, | 714 | pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, |
| 736 | pub drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>, | 715 | pub drop_listeners: SmallVec<[(TypeId, Box<DropListener>); 2]>, |
| 737 | pub click_listeners: SmallVec<[ClickListener<V>; 2]>, | 716 | pub click_listeners: SmallVec<[ClickListener; 2]>, |
| 738 | pub drag_listener: Option<DragListener<V>>, | 717 | pub drag_listener: Option<DragListener>, |
| 739 | pub hover_listener: Option<HoverListener<V>>, | 718 | pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>, |
| 740 | pub tooltip_builder: Option<TooltipBuilder<V>>, | 719 | pub tooltip_builder: Option<TooltipBuilder>, |
| 741 | } | 720 | } |
| 742 | 721 | ||
| 743 | impl<V> Interactivity<V> | 722 | impl Interactivity { |
| 744 | where | ||
| 745 | V: 'static, | ||
| 746 | { | ||
| 747 | pub fn layout( | 723 | pub fn layout( |
| 748 | &mut self, | 724 | &mut self, |
| 749 | element_state: Option<InteractiveElementState>, | 725 | element_state: Option<InteractiveElementState>, |
| 750 | cx: &mut ViewContext<V>, | 726 | cx: &mut WindowContext, |
| 751 | f: impl FnOnce(Style, &mut ViewContext<V>) -> LayoutId, | 727 | f: impl FnOnce(Style, &mut WindowContext) -> LayoutId, |
| 752 | ) -> (LayoutId, InteractiveElementState) { | 728 | ) -> (LayoutId, InteractiveElementState) { |
| 753 | let mut element_state = element_state.unwrap_or_default(); | 729 | let mut element_state = element_state.unwrap_or_default(); |
| 754 | 730 | ||
| @@ -770,12 +746,12 @@ where · 15 unmodified lines | |||
| 770 | } | 746 | } |
| 771 | 747 | ||
| 772 | pub fn paint( | 748 | pub fn paint( |
| 773 | &mut self, | 749 | mut self, |
| 774 | bounds: Bounds<Pixels>, | 750 | bounds: Bounds<Pixels>, |
| 775 | content_size: Size<Pixels>, | 751 | content_size: Size<Pixels>, |
| 776 | element_state: &mut InteractiveElementState, | 752 | element_state: &mut InteractiveElementState, |
| 777 | cx: &mut ViewContext<V>, | 753 | cx: &mut WindowContext, |
| 778 | f: impl FnOnce(Style, Point<Pixels>, &mut ViewContext<V>), | 754 | f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext), |
| 779 | ) { | 755 | ) { |
| 780 | let style = self.compute_style(Some(bounds), element_state, cx); | 756 | let style = self.compute_style(Some(bounds), element_state, cx); |
| 781 | 757 | ||
| @@ -787,26 +763,26 @@ where · 5 unmodified lines | |||
| 787 | } | 763 | } |
| 788 | 764 | ||
| 789 | for listener in self.mouse_down_listeners.drain(..) { | 765 | for listener in self.mouse_down_listeners.drain(..) { |
| 790 | cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| { | 766 | cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { |
| 791 | listener(state, event, &bounds, phase, cx); | 767 | listener(event, &bounds, phase, cx); |
| 792 | }) | 768 | }) |
| 793 | } | 769 | } |
| 794 | 770 | ||
| 795 | for listener in self.mouse_up_listeners.drain(..) { | 771 | for listener in self.mouse_up_listeners.drain(..) { |
| 796 | cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| { | 772 | cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { |
| 797 | listener(state, event, &bounds, phase, cx); | 773 | listener(event, &bounds, phase, cx); |
| 798 | }) | 774 | }) |
| 799 | } | 775 | } |
| 800 | 776 | ||
| 801 | for listener in self.mouse_move_listeners.drain(..) { | 777 | for listener in self.mouse_move_listeners.drain(..) { |
| 802 | cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| { | 778 | cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { |
| 803 | listener(state, event, &bounds, phase, cx); | 779 | listener(event, &bounds, phase, cx); |
| 804 | }) | 780 | }) |
| 805 | } | 781 | } |
| 806 | 782 | ||
| 807 | for listener in self.scroll_wheel_listeners.drain(..) { | 783 | for listener in self.scroll_wheel_listeners.drain(..) { |
| 808 | cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| { | 784 | cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { |
| 809 | listener(state, event, &bounds, phase, cx); | 785 | listener(event, &bounds, phase, cx); |
| 810 | }) | 786 | }) |
| 811 | } | 787 | } |
| 812 | 788 | ||
| @@ -817,7 +793,7 @@ where · 4 unmodified lines | |||
| 817 | 793 | ||
| 818 | if let Some(group_bounds) = hover_group_bounds { | 794 | if let Some(group_bounds) = hover_group_bounds { |
| 819 | let hovered = group_bounds.contains_point(&cx.mouse_position()); | 795 | let hovered = group_bounds.contains_point(&cx.mouse_position()); |
| 820 | cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { | 796 | cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { |
| 821 | if phase == DispatchPhase::Capture { | 797 | if phase == DispatchPhase::Capture { |
| 822 | if group_bounds.contains_point(&event.position) != hovered { | 798 | if group_bounds.contains_point(&event.position) != hovered { |
| 823 | cx.notify(); | 799 | cx.notify(); |
| @@ -830,7 +806,7 @@ where · 6 unmodified lines | |||
| 830 | || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty()) | 806 | || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty()) |
| 831 | { | 807 | { |
| 832 | let hovered = bounds.contains_point(&cx.mouse_position()); | 808 | let hovered = bounds.contains_point(&cx.mouse_position()); |
| 833 | cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { | 809 | cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { |
| 834 | if phase == DispatchPhase::Capture { | 810 | if phase == DispatchPhase::Capture { |
| 835 | if bounds.contains_point(&event.position) != hovered { | 811 | if bounds.contains_point(&event.position) != hovered { |
| 836 | cx.notify(); | 812 | cx.notify(); |
| @@ -841,7 +817,7 @@ where · 4 unmodified lines | |||
| 841 | 817 | ||
| 842 | if cx.active_drag.is_some() { | 818 | if cx.active_drag.is_some() { |
| 843 | let drop_listeners = mem::take(&mut self.drop_listeners); | 819 | let drop_listeners = mem::take(&mut self.drop_listeners); |
| 844 | cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| { | 820 | cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { |
| 845 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { | 821 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { |
| 846 | if let Some(drag_state_type) = | 822 | if let Some(drag_state_type) = |
| 847 | cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) | 823 | cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) |
| @@ -852,7 +828,7 @@ where · 4 unmodified lines | |||
| 852 | .active_drag | 828 | .active_drag |
| 853 | .take() | 829 | .take() |
| 854 | .expect("checked for type drag state type above"); | 830 | .expect("checked for type drag state type above"); |
| 855 | listener(view, drag.view.clone(), cx); | 831 | listener(drag.view.clone(), cx); |
| 856 | cx.notify(); | 832 | cx.notify(); |
| 857 | cx.stop_propagation(); | 833 | cx.stop_propagation(); |
| 858 | } | 834 | } |
| @@ -872,7 +848,7 @@ where · 13 unmodified lines | |||
| 872 | if let Some(drag_listener) = drag_listener { | 848 | if let Some(drag_listener) = drag_listener { |
| 873 | let active_state = element_state.clicked_state.clone(); | 849 | let active_state = element_state.clicked_state.clone(); |
| 874 | 850 | ||
| 875 | cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { | 851 | cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { |
| 876 | if cx.active_drag.is_some() { | 852 | if cx.active_drag.is_some() { |
| 877 | if phase == DispatchPhase::Capture { | 853 | if phase == DispatchPhase::Capture { |
| 878 | cx.notify(); | 854 | cx.notify(); |
| @@ -883,7 +859,7 @@ where · 4 unmodified lines | |||
| 883 | { | 859 | { |
| 884 | *active_state.borrow_mut() = ElementClickedState::default(); | 860 | *active_state.borrow_mut() = ElementClickedState::default(); |
| 885 | let cursor_offset = event.position - bounds.origin; | 861 | let cursor_offset = event.position - bounds.origin; |
| 886 | let drag = drag_listener(view_state, cursor_offset, cx); | 862 | let drag = drag_listener(cursor_offset, cx); |
| 887 | cx.active_drag = Some(drag); | 863 | cx.active_drag = Some(drag); |
| 888 | cx.notify(); | 864 | cx.notify(); |
| 889 | cx.stop_propagation(); | 865 | cx.stop_propagation(); |
| @@ -891,21 +867,21 @@ where · 1 unmodified line | |||
| 891 | }); | 867 | }); |
| 892 | } | 868 | } |
| 893 | 869 | ||
| 894 | cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| { | 870 | cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { |
| 895 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { | 871 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { |
| 896 | let mouse_click = ClickEvent { | 872 | let mouse_click = ClickEvent { |
| 897 | down: mouse_down.clone(), | 873 | down: mouse_down.clone(), |
| 898 | up: event.clone(), | 874 | up: event.clone(), |
| 899 | }; | 875 | }; |
| 900 | for listener in &click_listeners { | 876 | for listener in &click_listeners { |
| 901 | listener(view_state, &mouse_click, cx); | 877 | listener(&mouse_click, cx); |
| 902 | } | 878 | } |
| 903 | } | 879 | } |
| 904 | *pending_mouse_down.borrow_mut() = None; | 880 | *pending_mouse_down.borrow_mut() = None; |
| 905 | cx.notify(); | 881 | cx.notify(); |
| 906 | }); | 882 | }); |
| 907 | } else { | 883 | } else { |
| 908 | cx.on_mouse_event(move |_view_state, event: &MouseDownEvent, phase, cx| { | 884 | cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { |
| 909 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { | 885 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { |
| 910 | *pending_mouse_down.borrow_mut() = Some(event.clone()); | 886 | *pending_mouse_down.borrow_mut() = Some(event.clone()); |
| 911 | cx.notify(); | 887 | cx.notify(); |
| @@ -918,7 +894,7 @@ where · 6 unmodified lines | |||
| 918 | let was_hovered = element_state.hover_state.clone(); | 894 | let was_hovered = element_state.hover_state.clone(); |
| 919 | let has_mouse_down = element_state.pending_mouse_down.clone(); | 895 | let has_mouse_down = element_state.pending_mouse_down.clone(); |
| 920 | 896 | ||
| 921 | cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { | 897 | cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { |
| 922 | if phase != DispatchPhase::Bubble { | 898 | if phase != DispatchPhase::Bubble { |
| 923 | return; | 899 | return; |
| 924 | } | 900 | } |
| @@ -930,7 +906,7 @@ where · 5 unmodified lines | |||
| 930 | *was_hovered = is_hovered; | 906 | *was_hovered = is_hovered; |
| 931 | drop(was_hovered); | 907 | drop(was_hovered); |
| 932 | 908 | ||
| 933 | hover_listener(view_state, is_hovered, cx); | 909 | hover_listener(&is_hovered, cx); |
| 934 | } | 910 | } |
| 935 | }); | 911 | }); |
| 936 | } | 912 | } |
| @@ -939,7 +915,7 @@ where · 2 unmodified lines | |||
| 939 | let active_tooltip = element_state.active_tooltip.clone(); | 915 | let active_tooltip = element_state.active_tooltip.clone(); |
| 940 | let pending_mouse_down = element_state.pending_mouse_down.clone(); | 916 | let pending_mouse_down = element_state.pending_mouse_down.clone(); |
| 941 | 917 | ||
| 942 | cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { | 918 | cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { |
| 943 | if phase != DispatchPhase::Bubble { | 919 | if phase != DispatchPhase::Bubble { |
| 944 | return; | 920 | return; |
| 945 | } | 921 | } |
| @@ -956,12 +932,12 @@ where · 10 unmodified lines | |||
| 956 | let active_tooltip = active_tooltip.clone(); | 932 | let active_tooltip = active_tooltip.clone(); |
| 957 | let tooltip_builder = tooltip_builder.clone(); | 933 | let tooltip_builder = tooltip_builder.clone(); |
| 958 | 934 | ||
| 959 | move |view, mut cx| async move { | 935 | move |mut cx| async move { |
| 960 | cx.background_executor().timer(TOOLTIP_DELAY).await; | 936 | cx.background_executor().timer(TOOLTIP_DELAY).await; |
| 961 | view.update(&mut cx, move |view_state, cx| { | 937 | cx.update(|_, cx| { |
| 962 | active_tooltip.borrow_mut().replace(ActiveTooltip { | 938 | active_tooltip.borrow_mut().replace(ActiveTooltip { |
| 963 | tooltip: Some(AnyTooltip { | 939 | tooltip: Some(AnyTooltip { |
| 964 | view: tooltip_builder(view_state, cx), | 940 | view: tooltip_builder(cx), |
| 965 | cursor_offset: cx.mouse_position(), | 941 | cursor_offset: cx.mouse_position(), |
| 966 | }), | 942 | }), |
| 967 | _task: None, | 943 | _task: None, |
| @@ -979,7 +955,7 @@ where · 11 unmodified lines | |||
| 979 | }); | 955 | }); |
| 980 | 956 | ||
| 981 | let active_tooltip = element_state.active_tooltip.clone(); | 957 | let active_tooltip = element_state.active_tooltip.clone(); |
| 982 | cx.on_mouse_event(move |_, _: &MouseDownEvent, _, _| { | 958 | cx.on_mouse_event(move |_: &MouseDownEvent, _, _| { |
| 983 | active_tooltip.borrow_mut().take(); | 959 | active_tooltip.borrow_mut().take(); |
| 984 | }); | 960 | }); |
| 985 | 961 | ||
| @@ -992,7 +968,7 @@ where · 6 unmodified lines | |||
| 992 | 968 | ||
| 993 | let active_state = element_state.clicked_state.clone(); | 969 | let active_state = element_state.clicked_state.clone(); |
| 994 | if !active_state.borrow().is_clicked() { | 970 | if !active_state.borrow().is_clicked() { |
| 995 | cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| { | 971 | cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { |
| 996 | if phase == DispatchPhase::Capture { | 972 | if phase == DispatchPhase::Capture { |
| 997 | *active_state.borrow_mut() = ElementClickedState::default(); | 973 | *active_state.borrow_mut() = ElementClickedState::default(); |
| 998 | cx.notify(); | 974 | cx.notify(); |
| @@ -1003,7 +979,7 @@ where · 4 unmodified lines | |||
| 1003 | .group_active_style | 979 | .group_active_style |
| 1004 | .as_ref() | 980 | .as_ref() |
| 1005 | .and_then(|group_active| GroupBounds::get(&group_active.group, cx)); | 981 | .and_then(|group_active| GroupBounds::get(&group_active.group, cx)); |
| 1006 | cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| { | 982 | cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| { |
| 1007 | if phase == DispatchPhase::Bubble { | 983 | if phase == DispatchPhase::Bubble { |
| 1008 | let group = active_group_bounds | 984 | let group = active_group_bounds |
| 1009 | .map_or(false, |bounds| bounds.contains_point(&down.position)); | 985 | .map_or(false, |bounds| bounds.contains_point(&down.position)); |
| @@ -1025,7 +1001,7 @@ where · 15 unmodified lines | |||
| 1025 | let line_height = cx.line_height(); | 1001 | let line_height = cx.line_height(); |
| 1026 | let scroll_max = (content_size - bounds.size).max(&Size::default()); | 1002 | let scroll_max = (content_size - bounds.size).max(&Size::default()); |
| 1027 | 1003 | ||
| 1028 | cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| { | 1004 | cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { |
| 1029 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { | 1005 | if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { |
| 1030 | let mut scroll_offset = scroll_offset.borrow_mut(); | 1006 | let mut scroll_offset = scroll_offset.borrow_mut(); |
| 1031 | let old_scroll_offset = *scroll_offset; | 1007 | let old_scroll_offset = *scroll_offset; |
| @@ -1063,27 +1039,25 @@ where · 31 unmodified lines | |||
| 1063 | element_state.focus_handle.clone(), | 1039 | element_state.focus_handle.clone(), |
| 1064 | |_, cx| { | 1040 | |_, cx| { |
| 1065 | for listener in self.key_down_listeners.drain(..) { | 1041 | for listener in self.key_down_listeners.drain(..) { |
| 1066 | cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| { | 1042 | cx.on_key_event(move |event: &KeyDownEvent, phase, cx| { |
| 1067 | listener(state, event, phase, cx); | 1043 | listener(event, phase, cx); |
| 1068 | }) | 1044 | }) |
| 1069 | } | 1045 | } |
| 1070 | 1046 | ||
| 1071 | for listener in self.key_up_listeners.drain(..) { | 1047 | for listener in self.key_up_listeners.drain(..) { |
| 1072 | cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| { | 1048 | cx.on_key_event(move |event: &KeyUpEvent, phase, cx| { |
| 1073 | listener(state, event, phase, cx); | 1049 | listener(event, phase, cx); |
| 1074 | }) | 1050 | }) |
| 1075 | } | 1051 | } |
| 1076 | 1052 | ||
| 1077 | for (action_type, listener) in self.action_listeners.drain(..) { | 1053 | for (action_type, listener) in self.action_listeners { |
| 1078 | cx.on_action(action_type, listener) | 1054 | cx.on_action(action_type, listener) |
| 1079 | } | 1055 | } |
| 1080 | 1056 | ||
| 1081 | if let Some(focus_handle) = element_state.focus_handle.as_ref() { | 1057 | if let Some(focus_handle) = element_state.focus_handle.as_ref() { |
| 1082 | for listener in self.focus_listeners.drain(..) { | 1058 | for listener in self.focus_listeners { |
| 1083 | let focus_handle = focus_handle.clone(); | 1059 | let focus_handle = focus_handle.clone(); |
| 1084 | cx.on_focus_changed(move |view, event, cx| { | 1060 | cx.on_focus_changed(move |event, cx| listener(&focus_handle, event, cx)); |
| 1085 | listener(view, &focus_handle, event, cx) | ||
| 1086 | }); | ||
| 1087 | } | 1061 | } |
| 1088 | } | 1062 | } |
| 1089 | 1063 | ||
| @@ -1100,7 +1074,7 @@ where · 10 unmodified lines | |||
| 1100 | &self, | 1074 | &self, |
| 1101 | bounds: Option<Bounds<Pixels>>, | 1075 | bounds: Option<Bounds<Pixels>>, |
| 1102 | element_state: &mut InteractiveElementState, | 1076 | element_state: &mut InteractiveElementState, |
| 1103 | cx: &mut ViewContext<V>, | 1077 | cx: &mut WindowContext, |
| 1104 | ) -> Style { | 1078 | ) -> Style { |
| 1105 | let mut style = Style::default(); | 1079 | let mut style = Style::default(); |
| 1106 | style.refine(&self.base_style); | 1080 | style.refine(&self.base_style); |
| @@ -1171,7 +1145,7 @@ where · 64 unmodified lines | |||
| 1171 | } | 1145 | } |
| 1172 | } | 1146 | } |
| 1173 | 1147 | ||
| 1174 | impl<V: 'static> Default for Interactivity<V> { | 1148 | impl Default for Interactivity { |
| 1175 | fn default() -> Self { | 1149 | fn default() -> Self { |
| 1176 | Self { | 1150 | Self { |
| 1177 | element_id: None, | 1151 | element_id: None, |
| @@ -1259,31 +1233,25 @@ impl GroupBounds { · 81 unmodified lines | |||
| 1259 | } | 1233 | } |
| 1260 | } | 1234 | } |
| 1261 | 1235 | ||
| 1262 | pub struct Focusable<V, E> { | 1236 | pub struct Focusable<E> { |
| 1263 | element: E, | 1237 | element: E, |
| 1264 | view_type: PhantomData<V>, | ||
| 1265 | } | 1238 | } |
| 1266 | 1239 | ||
| 1267 | impl<V: 'static, E: InteractiveComponent<V>> FocusableComponent<V> for Focusable<V, E> {} | 1240 | impl<E: InteractiveElement> FocusableElement for Focusable<E> {} |
| 1268 | 1241 | ||
| 1269 | impl<V, E> InteractiveComponent<V> for Focusable<V, E> | 1242 | impl<E> InteractiveElement for Focusable<E> |
| 1270 | where | 1243 | where |
| 1271 | V: 'static, | 1244 | E: InteractiveElement, |
| 1272 | E: InteractiveComponent<V>, | ||
| 1273 | { | 1245 | { |
| 1274 | fn interactivity(&mut self) -> &mut Interactivity<V> { | 1246 | fn interactivity(&mut self) -> &mut Interactivity { |
| 1275 | self.element.interactivity() | 1247 | self.element.interactivity() |
| 1276 | } | 1248 | } |
| 1277 | } | 1249 | } |
| 1278 | 1250 | ||
| 1279 | impl<V: 'static, E: StatefulInteractiveComponent<V, E>> StatefulInteractiveComponent<V, E> | 1251 | impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {} |
| 1280 | for Focusable<V, E> | ||
| 1281 | { | ||
| 1282 | } | ||
| 1283 | 1252 | ||
| 1284 | impl<V, E> Styled for Focusable<V, E> | 1253 | impl<E> Styled for Focusable<E> |
| 1285 | where | 1254 | where |
| 1286 | V: 'static, | ||
| 1287 | E: Styled, | 1255 | E: Styled, |
| 1288 | { | 1256 | { |
| 1289 | fn style(&mut self) -> &mut StyleRefinement { | 1257 | fn style(&mut self) -> &mut StyleRefinement { |
| @@ -1291,65 +1259,55 @@ where · 1 unmodified line | |||
| 1291 | } | 1259 | } |
| 1292 | } | 1260 | } |
| 1293 | 1261 | ||
| 1294 | impl<V, E> Element<V> for Focusable<V, E> | 1262 | impl<E> Element for Focusable<E> |
| 1295 | where | 1263 | where |
| 1296 | V: 'static, | 1264 | E: Element, |
| 1297 | E: Element<V>, | ||
| 1298 | { | 1265 | { |
| 1299 | type ElementState = E::ElementState; | 1266 | type State = E::State; |
| 1300 | |||
| 1301 | fn element_id(&self) -> Option<ElementId> { | ||
| 1302 | self.element.element_id() | ||
| 1303 | } | ||
| 1304 | 1267 | ||
| 1305 | fn layout( | 1268 | fn layout( |
| 1306 | &mut self, | 1269 | &mut self, |
| 1307 | view_state: &mut V, | 1270 | state: Option<Self::State>, |
| 1308 | element_state: Option<Self::ElementState>, | 1271 | cx: &mut WindowContext, |
| 1309 | cx: &mut ViewContext<V>, | 1272 | ) -> (LayoutId, Self::State) { |
| 1310 | ) -> (LayoutId, Self::ElementState) { | 1273 | self.element.layout(state, cx) |
| 1311 | self.element.layout(view_state, element_state, cx) | ||
| 1312 | } | 1274 | } |
| 1313 | 1275 | ||
| 1314 | fn paint( | 1276 | fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) { |
| 1315 | &mut self, | 1277 | self.element.paint(bounds, state, cx) |
| 1316 | bounds: Bounds<Pixels>, | ||
| 1317 | view_state: &mut V, | ||
| 1318 | element_state: &mut Self::ElementState, | ||
| 1319 | cx: &mut ViewContext<V>, | ||
| 1320 | ) { | ||
| 1321 | self.element.paint(bounds, view_state, element_state, cx); | ||
| 1322 | } | 1278 | } |
| 1323 | } | 1279 | } |
| 1324 | 1280 | ||
| 1325 | impl<V, E> Component<V> for Focusable<V, E> | 1281 | impl<E> IntoElement for Focusable<E> |
| 1326 | where | 1282 | where |
| 1327 | V: 'static, | 1283 | E: Element, |
| 1328 | E: 'static + Element<V>, | ||
| 1329 | { | 1284 | { |
| 1330 | fn render(self) -> AnyElement<V> { | 1285 | type Element = E; |
| 1331 | AnyElement::new(self) | 1286 | |
| 1287 | fn element_id(&self) -> Option<ElementId> { | ||
| 1288 | self.element.element_id() | ||
| 1289 | } | ||
| 1290 | |||
| 1291 | fn into_element(self) -> Self::Element { | ||
| 1292 | self.element | ||
| 1332 | } | 1293 | } |
| 1333 | } | 1294 | } |
| 1334 | 1295 | ||
| 1335 | impl<V, E> ParentComponent<V> for Focusable<V, E> | 1296 | impl<E> ParentElement for Focusable<E> |
| 1336 | where | 1297 | where |
| 1337 | V: 'static, | 1298 | E: ParentElement, |
| 1338 | E: ParentComponent<V>, | ||
| 1339 | { | 1299 | { |
| 1340 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { | 1300 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { |
| 1341 | self.element.children_mut() | 1301 | self.element.children_mut() |
| 1342 | } | 1302 | } |
| 1343 | } | 1303 | } |
| 1344 | 1304 | ||
| 1345 | pub struct Stateful<V, E> { | 1305 | pub struct Stateful<E> { |
| 1346 | element: E, | 1306 | element: E, |
| 1347 | view_type: PhantomData<V>, | ||
| 1348 | } | 1307 | } |
| 1349 | 1308 | ||
| 1350 | impl<V, E> Styled for Stateful<V, E> | 1309 | impl<E> Styled for Stateful<E> |
| 1351 | where | 1310 | where |
| 1352 | V: 'static, | ||
| 1353 | E: Styled, | 1311 | E: Styled, |
| 1354 | { | 1312 | { |
| 1355 | fn style(&mut self) -> &mut StyleRefinement { | 1313 | fn style(&mut self) -> &mut StyleRefinement { |
| @@ -1357,73 +1315,63 @@ where · 1 unmodified line | |||
| 1357 | } | 1315 | } |
| 1358 | } | 1316 | } |
| 1359 | 1317 | ||
| 1360 | impl<V, E> StatefulInteractiveComponent<V, E> for Stateful<V, E> | 1318 | impl<E> StatefulInteractiveElement for Stateful<E> |
| 1361 | where | 1319 | where |
| 1362 | V: 'static, | 1320 | E: Element, |
| 1363 | E: Element<V>, | 1321 | Self: InteractiveElement, |
| 1364 | Self: InteractiveComponent<V>, | ||
| 1365 | { | 1322 | { |
| 1366 | } | 1323 | } |
| 1367 | 1324 | ||
| 1368 | impl<V, E> InteractiveComponent<V> for Stateful<V, E> | 1325 | impl<E> InteractiveElement for Stateful<E> |
| 1369 | where | 1326 | where |
| 1370 | V: 'static, | 1327 | E: InteractiveElement, |
| 1371 | E: InteractiveComponent<V>, | ||
| 1372 | { | 1328 | { |
| 1373 | fn interactivity(&mut self) -> &mut Interactivity<V> { | 1329 | fn interactivity(&mut self) -> &mut Interactivity { |
| 1374 | self.element.interactivity() | 1330 | self.element.interactivity() |
| 1375 | } | 1331 | } |
| 1376 | } | 1332 | } |
| 1377 | 1333 | ||
| 1378 | impl<V: 'static, E: FocusableComponent<V>> FocusableComponent<V> for Stateful<V, E> {} | 1334 | impl<E: FocusableElement> FocusableElement for Stateful<E> {} |
| 1379 | 1335 | ||
| 1380 | impl<V, E> Element<V> for Stateful<V, E> | 1336 | impl<E> Element for Stateful<E> |
| 1381 | where | 1337 | where |
| 1382 | V: 'static, | 1338 | E: Element, |
| 1383 | E: Element<V>, | ||
| 1384 | { | 1339 | { |
| 1385 | type ElementState = E::ElementState; | 1340 | type State = E::State; |
| 1386 | |||
| 1387 | fn element_id(&self) -> Option<ElementId> { | ||
| 1388 | self.element.element_id() | ||
| 1389 | } | ||
| 1390 | 1341 | ||
| 1391 | fn layout( | 1342 | fn layout( |
| 1392 | &mut self, | 1343 | &mut self, |
| 1393 | view_state: &mut V, | 1344 | state: Option<Self::State>, |
| 1394 | element_state: Option<Self::ElementState>, | 1345 | cx: &mut WindowContext, |
| 1395 | cx: &mut ViewContext<V>, | 1346 | ) -> (LayoutId, Self::State) { |
| 1396 | ) -> (LayoutId, Self::ElementState) { | 1347 | self.element.layout(state, cx) |
| 1397 | self.element.layout(view_state, element_state, cx) | ||
| 1398 | } | 1348 | } |
| 1399 | 1349 | ||
| 1400 | fn paint( | 1350 | fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) { |
| 1401 | &mut self, | 1351 | self.element.paint(bounds, state, cx) |
| 1402 | bounds: Bounds<Pixels>, | ||
| 1403 | view_state: &mut V, | ||
| 1404 | element_state: &mut Self::ElementState, | ||
| 1405 | cx: &mut ViewContext<V>, | ||
| 1406 | ) { | ||
| 1407 | self.element.paint(bounds, view_state, element_state, cx) | ||
| 1408 | } | 1352 | } |
| 1409 | } | 1353 | } |
| 1410 | 1354 | ||
| 1411 | impl<V, E> Component<V> for Stateful<V, E> | 1355 | impl<E> IntoElement for Stateful<E> |
| 1412 | where | 1356 | where |
| 1413 | V: 'static, | 1357 | E: Element, |
| 1414 | E: 'static + Element<V>, | ||
| 1415 | { | 1358 | { |
| 1416 | fn render(self) -> AnyElement<V> { | 1359 | type Element = Self; |
| 1417 | AnyElement::new(self) | 1360 | |
| 1361 | fn element_id(&self) -> Option<ElementId> { | ||
| 1362 | self.element.element_id() | ||
| 1363 | } | ||
| 1364 | |||
| 1365 | fn into_element(self) -> Self::Element { | ||
| 1366 | self | ||
| 1418 | } | 1367 | } |
| 1419 | } | 1368 | } |
| 1420 | 1369 | ||
| 1421 | impl<V, E> ParentComponent<V> for Stateful<V, E> | 1370 | impl<E> ParentElement for Stateful<E> |
| 1422 | where | 1371 | where |
| 1423 | V: 'static, | 1372 | E: ParentElement, |
| 1424 | E: ParentComponent<V>, | ||
| 1425 | { | 1373 | { |
| 1426 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { | 1374 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { |
| 1427 | self.element.children_mut() | 1375 | self.element.children_mut() |
| 1428 | } | 1376 | } |
| 1429 | } | 1377 | } |
| @@ -1,70 +1,83 @@ | |||
| 1 | use std::sync::Arc; | ||
| 2 | |||
| 1 | use crate::{ | 3 | use crate::{ |
| 2 | AnyElement, BorrowWindow, Bounds, Component, Element, InteractiveComponent, | 4 | Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity, |
| 3 | InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement, | 5 | IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext, |
| 4 | Styled, ViewContext, | ||
| 5 | }; | 6 | }; |
| 6 | use futures::FutureExt; | 7 | use futures::FutureExt; |
| 7 | use util::ResultExt; | 8 | use util::ResultExt; |
| 8 | 9 | ||
| 9 | pub struct Img<V: 'static> { | 10 | #[derive(Clone, Debug)] |
| 10 | interactivity: Interactivity<V>, | 11 | pub enum ImageSource { |
| 11 | uri: Option<SharedString>, | 12 | /// Image content will be loaded from provided URI at render time. |
| 13 | Uri(SharedString), | ||
| 14 | Data(Arc<ImageData>), | ||
| 15 | } | ||
| 16 | |||
| 17 | impl From<SharedString> for ImageSource { | ||
| 18 | fn from(value: SharedString) -> Self { | ||
| 19 | Self::Uri(value) | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | impl From<Arc<ImageData>> for ImageSource { | ||
| 24 | fn from(value: Arc<ImageData>) -> Self { | ||
| 25 | Self::Data(value) | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | pub struct Img { | ||
| 30 | interactivity: Interactivity, | ||
| 31 | source: Option<ImageSource>, | ||
| 12 | grayscale: bool, | 32 | grayscale: bool, |
| 13 | } | 33 | } |
| 14 | 34 | ||
| 15 | pub fn img<V: 'static>() -> Img<V> { | 35 | pub fn img() -> Img { |
| 16 | Img { | 36 | Img { |
| 17 | interactivity: Interactivity::default(), | 37 | interactivity: Interactivity::default(), |
| 18 | uri: None, | 38 | source: None, |
| 19 | grayscale: false, | 39 | grayscale: false, |
| 20 | } | 40 | } |
| 21 | } | 41 | } |
| 22 | 42 | ||
| 23 | impl<V> Img<V> | 43 | impl Img { |
| 24 | where | ||
| 25 | V: 'static, | ||
| 26 | { | ||
| 27 | pub fn uri(mut self, uri: impl Into<SharedString>) -> Self { | 44 | pub fn uri(mut self, uri: impl Into<SharedString>) -> Self { |
| 28 | self.uri = Some(uri.into()); | 45 | self.source = Some(ImageSource::from(uri.into())); |
| 46 | self | ||
| 47 | } | ||
| 48 | pub fn data(mut self, data: Arc<ImageData>) -> Self { | ||
| 49 | self.source = Some(ImageSource::from(data)); | ||
| 29 | self | 50 | self |
| 30 | } | 51 | } |
| 31 | 52 | ||
| 53 | pub fn source(mut self, source: impl Into<ImageSource>) -> Self { | ||
| 54 | self.source = Some(source.into()); | ||
| 55 | self | ||
| 56 | } | ||
| 32 | pub fn grayscale(mut self, grayscale: bool) -> Self { | 57 | pub fn grayscale(mut self, grayscale: bool) -> Self { |
| 33 | self.grayscale = grayscale; | 58 | self.grayscale = grayscale; |
| 34 | self | 59 | self |
| 35 | } | 60 | } |
| 36 | } | 61 | } |
| 37 | 62 | ||
| 38 | impl<V> Component<V> for Img<V> { | 63 | impl Element for Img { |
| 39 | fn render(self) -> AnyElement<V> { | 64 | type State = InteractiveElementState; |
| 40 | AnyElement::new(self) | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | impl<V> Element<V> for Img<V> { | ||
| 45 | type ElementState = InteractiveElementState; | ||
| 46 | |||
| 47 | fn element_id(&self) -> Option<crate::ElementId> { | ||
| 48 | self.interactivity.element_id.clone() | ||
| 49 | } | ||
| 50 | 65 | ||
| 51 | fn layout( | 66 | fn layout( |
| 52 | &mut self, | 67 | &mut self, |
| 53 | _view_state: &mut V, | 68 | element_state: Option<Self::State>, |
| 54 | element_state: Option<Self::ElementState>, | 69 | cx: &mut WindowContext, |
| 55 | cx: &mut ViewContext<V>, | 70 | ) -> (LayoutId, Self::State) { |
| 56 | ) -> (LayoutId, Self::ElementState) { | ||
| 57 | self.interactivity.layout(element_state, cx, |style, cx| { | 71 | self.interactivity.layout(element_state, cx, |style, cx| { |
| 58 | cx.request_layout(&style, None) | 72 | cx.request_layout(&style, None) |
| 59 | }) | 73 | }) |
| 60 | } | 74 | } |
| 61 | 75 | ||
| 62 | fn paint( | 76 | fn paint( |
| 63 | &mut self, | 77 | self, |
| 64 | bounds: Bounds<Pixels>, | 78 | bounds: Bounds<Pixels>, |
| 65 | _view_state: &mut V, | 79 | element_state: &mut Self::State, |
| 66 | element_state: &mut Self::ElementState, | 80 | cx: &mut WindowContext, |
| 67 | cx: &mut ViewContext<V>, | ||
| 68 | ) { | 81 | ) { |
| 69 | self.interactivity.paint( | 82 | self.interactivity.paint( |
| 70 | bounds, | 83 | bounds, |
| @@ -74,42 +87,59 @@ impl<V> Element<V> for Img<V> { · 3 unmodified lines | |||
| 74 | |style, _scroll_offset, cx| { | 87 | |style, _scroll_offset, cx| { |
| 75 | let corner_radii = style.corner_radii; | 88 | let corner_radii = style.corner_radii; |
| 76 | 89 | ||
| 77 | if let Some(uri) = self.uri.clone() { | 90 | if let Some(source) = self.source { |
| 78 | // eprintln!(">>> image_cache.get({uri}"); | 91 | let image = match source { |
| 79 | let image_future = cx.image_cache.get(uri.clone()); | 92 | ImageSource::Uri(uri) => { |
| 80 | // eprintln!("<<< image_cache.get({uri}"); | 93 | let image_future = cx.image_cache.get(uri.clone()); |
| 81 | if let Some(data) = image_future | 94 | if let Some(data) = image_future |
| 82 | .clone() | 95 | .clone() |
| 83 | .now_or_never() | 96 | .now_or_never() |
| 84 | .and_then(|result| result.ok()) | 97 | .and_then(|result| result.ok()) |
| 85 | { | 98 | { |
| 86 | let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); | 99 | data |
| 87 | cx.with_z_index(1, |cx| { | 100 | } else { |
| 88 | cx.paint_image(bounds, corner_radii, data, self.grayscale) | 101 | cx.spawn(|mut cx| async move { |
| 89 | .log_err() | 102 | if image_future.await.ok().is_some() { |
| 90 | }); | 103 | cx.on_next_frame(|cx| cx.notify()); |
| 91 | } else { | 104 | } |
| 92 | cx.spawn(|_, mut cx| async move { | 105 | }) |
| 93 | if image_future.await.ok().is_some() { | 106 | .detach(); |
| 94 | cx.on_next_frame(|cx| cx.notify()); | 107 | return; |
| 95 | } | 108 | } |
| 96 | }) | 109 | } |
| 97 | .detach() | 110 | ImageSource::Data(image) => image, |
| 98 | } | 111 | }; |
| 112 | let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); | ||
| 113 | cx.with_z_index(1, |cx| { | ||
| 114 | cx.paint_image(bounds, corner_radii, image, self.grayscale) | ||
| 115 | .log_err() | ||
| 116 | }); | ||
| 99 | } | 117 | } |
| 100 | }, | 118 | }, |
| 101 | ) | 119 | ) |
| 102 | } | 120 | } |
| 103 | } | 121 | } |
| 104 | 122 | ||
| 105 | impl<V> Styled for Img<V> { | 123 | impl IntoElement for Img { |
| 124 | type Element = Self; | ||
| 125 | |||
| 126 | fn element_id(&self) -> Option<crate::ElementId> { | ||
| 127 | self.interactivity.element_id.clone() | ||
| 128 | } | ||
| 129 | |||
| 130 | fn into_element(self) -> Self::Element { | ||
| 131 | self | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | impl Styled for Img { | ||
| 106 | fn style(&mut self) -> &mut StyleRefinement { | 136 | fn style(&mut self) -> &mut StyleRefinement { |
| 107 | &mut self.interactivity.base_style | 137 | &mut self.interactivity.base_style |
| 108 | } | 138 | } |
| 109 | } | 139 | } |
| 110 | 140 | ||
| 111 | impl<V> InteractiveComponent<V> for Img<V> { | 141 | impl InteractiveElement for Img { |
| 112 | fn interactivity(&mut self) -> &mut Interactivity<V> { | 142 | fn interactivity(&mut self) -> &mut Interactivity { |
| 113 | &mut self.interactivity | 143 | &mut self.interactivity |
| 114 | } | 144 | } |
| 115 | } | 145 | } |
| @@ -2,16 +2,16 @@ use smallvec::SmallVec; · 1 unmodified line | |||
| 2 | use taffy::style::{Display, Position}; | 2 | use taffy::style::{Display, Position}; |
| 3 | 3 | ||
| 4 | use crate::{ | 4 | use crate::{ |
| 5 | point, AnyElement, BorrowWindow, Bounds, Component, Element, LayoutId, ParentComponent, Pixels, | 5 | point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels, |
| 6 | Point, Size, Style, | 6 | Point, Size, Style, WindowContext, |
| 7 | }; | 7 | }; |
| 8 | 8 | ||
| 9 | pub struct OverlayState { | 9 | pub struct OverlayState { |
| 10 | child_layout_ids: SmallVec<[LayoutId; 4]>, | 10 | child_layout_ids: SmallVec<[LayoutId; 4]>, |
| 11 | } | 11 | } |
| 12 | 12 | ||
| 13 | pub struct Overlay<V> { | 13 | pub struct Overlay { |
| 14 | children: SmallVec<[AnyElement<V>; 2]>, | 14 | children: SmallVec<[AnyElement; 2]>, |
| 15 | anchor_corner: AnchorCorner, | 15 | anchor_corner: AnchorCorner, |
| 16 | fit_mode: OverlayFitMode, | 16 | fit_mode: OverlayFitMode, |
| 17 | // todo!(); | 17 | // todo!(); |
| @@ -21,7 +21,7 @@ pub struct Overlay<V> { · 3 unmodified lines | |||
| 21 | 21 | ||
| 22 | /// overlay gives you a floating element that will avoid overflowing the window bounds. | 22 | /// overlay gives you a floating element that will avoid overflowing the window bounds. |
| 23 | /// Its children should have no margin to avoid measurement issues. | 23 | /// Its children should have no margin to avoid measurement issues. |
| 24 | pub fn overlay<V: 'static>() -> Overlay<V> { | 24 | pub fn overlay() -> Overlay { |
| 25 | Overlay { | 25 | Overlay { |
| 26 | children: SmallVec::new(), | 26 | children: SmallVec::new(), |
| 27 | anchor_corner: AnchorCorner::TopLeft, | 27 | anchor_corner: AnchorCorner::TopLeft, |
| @@ -30,7 +30,7 @@ pub fn overlay<V: 'static>() -> Overlay<V> { · 2 unmodified lines | |||
| 30 | } | 30 | } |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | impl<V> Overlay<V> { | 33 | impl Overlay { |
| 34 | /// Sets which corner of the overlay should be anchored to the current position. | 34 | /// Sets which corner of the overlay should be anchored to the current position. |
| 35 | pub fn anchor(mut self, anchor: AnchorCorner) -> Self { | 35 | pub fn anchor(mut self, anchor: AnchorCorner) -> Self { |
| 36 | self.anchor_corner = anchor; | 36 | self.anchor_corner = anchor; |
| @@ -51,35 +51,24 @@ impl<V> Overlay<V> { · 14 unmodified lines | |||
| 51 | } | 51 | } |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | impl<V: 'static> ParentComponent<V> for Overlay<V> { | 54 | impl ParentElement for Overlay { |
| 55 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { | 55 | fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { |
| 56 | &mut self.children | 56 | &mut self.children |
| 57 | } | 57 | } |
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | impl<V: 'static> Component<V> for Overlay<V> { | 60 | impl Element for Overlay { |
| 61 | fn render(self) -> AnyElement<V> { | 61 | type State = OverlayState; |
| 62 | AnyElement::new(self) | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | impl<V: 'static> Element<V> for Overlay<V> { | ||
| 67 | type ElementState = OverlayState; | ||
| 68 | |||
| 69 | fn element_id(&self) -> Option<crate::ElementId> { | ||
| 70 | None | ||
| 71 | } | ||
| 72 | 62 | ||
| 73 | fn layout( | 63 | fn layout( |
| 74 | &mut self, | 64 | &mut self, |
| 75 | view_state: &mut V, | 65 | _: Option<Self::State>, |
| 76 | _: Option<Self::ElementState>, | 66 | cx: &mut WindowContext, |
| 77 | cx: &mut crate::ViewContext<V>, | 67 | ) -> (crate::LayoutId, Self::State) { |
| 78 | ) -> (crate::LayoutId, Self::ElementState) { | ||
| 79 | let child_layout_ids = self | 68 | let child_layout_ids = self |
| 80 | .children | 69 | .children |
| 81 | .iter_mut() | 70 | .iter_mut() |
| 82 | .map(|child| child.layout(view_state, cx)) | 71 | .map(|child| child.layout(cx)) |
| 83 | .collect::<SmallVec<_>>(); | 72 | .collect::<SmallVec<_>>(); |
| 84 | 73 | ||
| 85 | let mut overlay_style = Style::default(); | 74 | let mut overlay_style = Style::default(); |
| @@ -92,11 +81,10 @@ impl<V: 'static> Element<V> for Overlay<V> { · 6 unmodified lines | |||
| 92 | } | 81 | } |
| 93 | 82 | ||
| 94 | fn paint( | 83 | fn paint( |
| 95 | &mut self, | 84 | self, |
| 96 | bounds: crate::Bounds<crate::Pixels>, | 85 | bounds: crate::Bounds<crate::Pixels>, |
| 97 | view_state: &mut V, | 86 | element_state: &mut Self::State, |
| 98 | element_state: &mut Self::ElementState, | 87 | cx: &mut WindowContext, |
| 99 | cx: &mut crate::ViewContext<V>, | ||
| 100 | ) { | 88 | ) { |
| 101 | if element_state.child_layout_ids.is_empty() { | 89 | if element_state.child_layout_ids.is_empty() { |
| 102 | return; | 90 | return; |
| @@ -117,6 +105,7 @@ impl<V: 'static> Element<V> for Overlay<V> { · 14 unmodified lines | |||
| 117 | origin: Point::zero(), | 105 | origin: Point::zero(), |
| 118 | size: cx.viewport_size(), | 106 | size: cx.viewport_size(), |
| 119 | }; | 107 | }; |
| 108 | dbg!(limits); | ||
| 120 | 109 | ||
| 121 | match self.fit_mode { | 110 | match self.fit_mode { |
| 122 | OverlayFitMode::SnapToWindow => { | 111 | OverlayFitMode::SnapToWindow => { |
| @@ -156,13 +145,27 @@ impl<V: 'static> Element<V> for Overlay<V> { · 33 unmodified lines | |||
| 156 | } | 145 | } |
| 157 | 146 | ||
| 158 | cx.with_element_offset(desired.origin - bounds.origin, |cx| { | 147 | cx.with_element_offset(desired.origin - bounds.origin, |cx| { |
| 159 | for child in &mut self.children { | 148 | cx.break_content_mask(|cx| { |
| 160 | child.paint(view_state, cx); | 149 | for child in self.children { |
| 161 | } | 150 | child.paint(cx); |
| 151 | } | ||
| 152 | }) | ||
| 162 | }) | 153 | }) |
| 163 | } | 154 | } |
| 164 | } | 155 | } |
| 165 | 156 | ||
| 157 | impl IntoElement for Overlay { | ||
| 158 | type Element = Self; | ||
| 159 | |||
| 160 | fn element_id(&self) -> Option<crate::ElementId> { | ||
| 161 | None | ||
| 162 | } | ||
| 163 | |||
| 164 | fn into_element(self) -> Self::Element { | ||
| 165 | self | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 166 | enum Axis { | 169 | enum Axis { |
| 167 | Horizontal, | 170 | Horizontal, |
| 168 | Vertical, | 171 | Vertical, |
| @@ -1,60 +1,43 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | AnyElement, Bounds, Component, Element, ElementId, InteractiveComponent, | 2 | Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity, |
| 3 | InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement, | 3 | IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext, |
| 4 | Styled, ViewContext, | ||
| 5 | }; | 4 | }; |
| 6 | use util::ResultExt; | 5 | use util::ResultExt; |
| 7 | 6 | ||
| 8 | pub struct Svg<V: 'static> { | 7 | pub struct Svg { |
| 9 | interactivity: Interactivity<V>, | 8 | interactivity: Interactivity, |
| 10 | path: Option<SharedString>, | 9 | path: Option<SharedString>, |
| 11 | } | 10 | } |
| 12 | 11 | ||
| 13 | pub fn svg<V: 'static>() -> Svg<V> { | 12 | pub fn svg() -> Svg { |
| 14 | Svg { | 13 | Svg { |
| 15 | interactivity: Interactivity::default(), | 14 | interactivity: Interactivity::default(), |
| 16 | path: None, | 15 | path: None, |
| 17 | } | 16 | } |
| 18 | } | 17 | } |
| 19 | 18 | ||
| 20 | impl<V> Svg<V> { | 19 | impl Svg { |
| 21 | pub fn path(mut self, path: impl Into<SharedString>) -> Self { | 20 | pub fn path(mut self, path: impl Into<SharedString>) -> Self { |
| 22 | self.path = Some(path.into()); | 21 | self.path = Some(path.into()); |
| 23 | self | 22 | self |
| 24 | } | 23 | } |
| 25 | } | 24 | } |
| 26 | 25 | ||
| 27 | impl<V> Component<V> for Svg<V> { | 26 | impl Element for Svg { |
| 28 | fn render(self) -> AnyElement<V> { | 27 | type State = InteractiveElementState; |
| 29 | AnyElement::new(self) | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | impl<V> Element<V> for Svg<V> { | ||
| 34 | type ElementState = InteractiveElementState; | ||
| 35 | |||
| 36 | fn element_id(&self) -> Option<ElementId> { | ||
| 37 | self.interactivity.element_id.clone() | ||
| 38 | } | ||
| 39 | 28 | ||
| 40 | fn layout( | 29 | fn layout( |
| 41 | &mut self, | 30 | &mut self, |
| 42 | _view_state: &mut V, | 31 | element_state: Option<Self::State>, |
| 43 | element_state: Option<Self::ElementState>, | 32 | cx: &mut WindowContext, |
| 44 | cx: &mut ViewContext<V>, | 33 | ) -> (LayoutId, Self::State) { |
| 45 | ) -> (LayoutId, Self::ElementState) { | ||
| 46 | self.interactivity.layout(element_state, cx, |style, cx| { | 34 | self.interactivity.layout(element_state, cx, |style, cx| { |
| 47 | cx.request_layout(&style, None) | 35 | cx.request_layout(&style, None) |
| 48 | }) | 36 | }) |
| 49 | } | 37 | } |
| 50 | 38 | ||
| 51 | fn paint( | 39 | fn paint(self, bounds: Bounds<Pixels>, element_state: &mut Self::State, cx: &mut WindowContext) |
| 52 | &mut self, | 40 | where |
| 53 | bounds: Bounds<Pixels>, | ||
| 54 | _view_state: &mut V, | ||
| 55 | element_state: &mut Self::ElementState, | ||
| 56 | cx: &mut ViewContext<V>, | ||
| 57 | ) where | ||
| 58 | Self: Sized, | 41 | Self: Sized, |
| 59 | { | 42 | { |
| 60 | self.interactivity | 43 | self.interactivity |
| @@ -66,14 +49,26 @@ impl<V> Element<V> for Svg<V> { · 5 unmodified lines | |||
| 66 | } | 49 | } |
| 67 | } | 50 | } |
| 68 | 51 | ||
| 69 | impl<V> Styled for Svg<V> { | 52 | impl IntoElement for Svg { |
| 53 | type Element = Self; | ||
| 54 | |||
| 55 | fn element_id(&self) -> Option<ElementId> { | ||
| 56 | self.interactivity.element_id.clone() | ||
| 57 | } | ||
| 58 | |||
| 59 | fn into_element(self) -> Self::Element { | ||
| 60 | self | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | impl Styled for Svg { | ||
| 70 | fn style(&mut self) -> &mut StyleRefinement { | 65 | fn style(&mut self) -> &mut StyleRefinement { |
| 71 | &mut self.interactivity.base_style | 66 | &mut self.interactivity.base_style |
| 72 | } | 67 | } |
| 73 | } | 68 | } |
| 74 | 69 | ||
| 75 | impl<V> InteractiveComponent<V> for Svg<V> { | 70 | impl InteractiveElement for Svg { |
| 76 | fn interactivity(&mut self) -> &mut Interactivity<V> { | 71 | fn interactivity(&mut self) -> &mut Interactivity { |
| 77 | &mut self.interactivity | 72 | &mut self.interactivity |
| 78 | } | 73 | } |
| 79 | } | 74 | } |
| @@ -1,74 +1,177 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels, | 2 | Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent, |
| 3 | SharedString, Size, TextRun, ViewContext, WrappedLine, | 3 | Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine, |
| 4 | }; | 4 | }; |
| 5 | use anyhow::anyhow; | ||
| 5 | use parking_lot::{Mutex, MutexGuard}; | 6 | use parking_lot::{Mutex, MutexGuard}; |
| 6 | use smallvec::SmallVec; | 7 | use smallvec::SmallVec; |
| 7 | use std::{cell::Cell, rc::Rc, sync::Arc}; | 8 | use std::{cell::Cell, ops::Range, rc::Rc, sync::Arc}; |
| 8 | use util::ResultExt; | 9 | use util::ResultExt; |
| 9 | 10 | ||
| 10 | pub struct Text { | 11 | impl Element for &'static str { |
| 12 | type State = TextState; | ||
| 13 | |||
| 14 | fn layout( | ||
| 15 | &mut self, | ||
| 16 | _: Option<Self::State>, | ||
| 17 | cx: &mut WindowContext, | ||
| 18 | ) -> (LayoutId, Self::State) { | ||
| 19 | let mut state = TextState::default(); | ||
| 20 | let layout_id = state.layout(SharedString::from(*self), None, cx); | ||
| 21 | (layout_id, state) | ||
| 22 | } | ||
| 23 | |||
| 24 | fn paint(self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) { | ||
| 25 | state.paint(bounds, self, cx) | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | impl IntoElement for &'static str { | ||
| 30 | type Element = Self; | ||
| 31 | |||
| 32 | fn element_id(&self) -> Option<ElementId> { | ||
| 33 | None | ||
| 34 | } | ||
| 35 | |||
| 36 | fn into_element(self) -> Self::Element { | ||
| 37 | self | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | impl Element for SharedString { | ||
| 42 | type State = TextState; | ||
| 43 | |||
| 44 | fn layout( | ||
| 45 | &mut self, | ||
| 46 | _: Option<Self::State>, | ||
| 47 | cx: &mut WindowContext, | ||
| 48 | ) -> (LayoutId, Self::State) { | ||
| 49 | let mut state = TextState::default(); | ||
| 50 | let layout_id = state.layout(self.clone(), None, cx); | ||
| 51 | (layout_id, state) | ||
| 52 | } | ||
| 53 | |||
| 54 | fn paint(self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) { | ||
| 55 | let text_str: &str = self.as_ref(); | ||
| 56 | state.paint(bounds, text_str, cx) | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | impl IntoElement for SharedString { | ||
| 61 | type Element = Self; | ||
| 62 | |||
| 63 | fn element_id(&self) -> Option<ElementId> { | ||
| 64 | None | ||
| 65 | } | ||
| 66 | |||
| 67 | fn into_element(self) -> Self::Element { | ||
| 68 | self | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Renders text with runs of different styles. | ||
| 73 | /// | ||
| 74 | /// Callers are responsible for setting the correct style for each run. | ||
| 75 | /// For text with a uniform style, you can usually avoid calling this constructor | ||
| 76 | /// and just pass text directly. | ||
| 77 | pub struct StyledText { | ||
| 11 | text: SharedString, | 78 | text: SharedString, |
| 12 | runs: Option<Vec<TextRun>>, | 79 | runs: Option<Vec<TextRun>>, |
| 13 | } | 80 | } |
| 14 | 81 | ||
| 15 | impl Text { | 82 | impl StyledText { |
| 16 | /// Renders text with runs of different styles. | 83 | pub fn new(text: impl Into<SharedString>) -> Self { |
| 17 | /// | 84 | StyledText { |
| 18 | /// Callers are responsible for setting the correct style for each run. | 85 | text: text.into(), |
| 19 | /// For text with a uniform style, you can usually avoid calling this constructor | 86 | runs: None, |
| 20 | /// and just pass text directly. | ||
| 21 | pub fn styled(text: SharedString, runs: Vec<TextRun>) -> Self { | ||
| 22 | Text { | ||
| 23 | text, | ||
| 24 | runs: Some(runs), | ||
| 25 | } | 87 | } |
| 26 | } | 88 | } |
| 89 | |||
| 90 | pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self { | ||
| 91 | self.runs = Some(runs); | ||
| 92 | self | ||
| 93 | } | ||
| 27 | } | 94 | } |
| 28 | 95 | ||
| 29 | impl<V: 'static> Component<V> for Text { | 96 | impl Element for StyledText { |
| 30 | fn render(self) -> AnyElement<V> { | 97 | type State = TextState; |
| 31 | AnyElement::new(self) | 98 | |
| 99 | fn layout( | ||
| 100 | &mut self, | ||
| 101 | _: Option<Self::State>, | ||
| 102 | cx: &mut WindowContext, | ||
| 103 | ) -> (LayoutId, Self::State) { | ||
| 104 | let mut state = TextState::default(); | ||
| 105 | let layout_id = state.layout(self.text.clone(), self.runs.take(), cx); | ||
| 106 | (layout_id, state) | ||
| 107 | } | ||
| 108 | |||
| 109 | fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) { | ||
| 110 | state.paint(bounds, &self.text, cx) | ||
| 32 | } | 111 | } |
| 33 | } | 112 | } |
| 34 | 113 | ||
| 35 | impl<V: 'static> Element<V> for Text { | 114 | impl IntoElement for StyledText { |
| 36 | type ElementState = TextState; | 115 | type Element = Self; |
| 37 | 116 | ||
| 38 | fn element_id(&self) -> Option<crate::ElementId> { | 117 | fn element_id(&self) -> Option<crate::ElementId> { |
| 39 | None | 118 | None |
| 40 | } | 119 | } |
| 41 | 120 | ||
| 121 | fn into_element(self) -> Self::Element { | ||
| 122 | self | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | #[derive(Default, Clone)] | ||
| 127 | pub struct TextState(Arc<Mutex<Option<TextStateInner>>>); | ||
| 128 | |||
| 129 | struct TextStateInner { | ||
| 130 | lines: SmallVec<[WrappedLine; 1]>, | ||
| 131 | line_height: Pixels, | ||
| 132 | wrap_width: Option<Pixels>, | ||
| 133 | size: Option<Size<Pixels>>, | ||
| 134 | } | ||
| 135 | |||
| 136 | impl TextState { | ||
| 137 | fn lock(&self) -> MutexGuard<Option<TextStateInner>> { | ||
| 138 | self.0.lock() | ||
| 139 | } | ||
| 140 | |||
| 42 | fn layout( | 141 | fn layout( |
| 43 | &mut self, | 142 | &mut self, |
| 44 | _view: &mut V, | 143 | text: SharedString, |
| 45 | element_state: Option<Self::ElementState>, | 144 | runs: Option<Vec<TextRun>>, |
| 46 | cx: &mut ViewContext<V>, | 145 | cx: &mut WindowContext, |
| 47 | ) -> (LayoutId, Self::ElementState) { | 146 | ) -> LayoutId { |
| 48 | let element_state = element_state.unwrap_or_default(); | ||
| 49 | let text_system = cx.text_system().clone(); | 147 | let text_system = cx.text_system().clone(); |
| 50 | let text_style = cx.text_style(); | 148 | let text_style = cx.text_style(); |
| 51 | let font_size = text_style.font_size.to_pixels(cx.rem_size()); | 149 | let font_size = text_style.font_size.to_pixels(cx.rem_size()); |
| 52 | let line_height = text_style | 150 | let line_height = text_style |
| 53 | .line_height | 151 | .line_height |
| 54 | .to_pixels(font_size.into(), cx.rem_size()); | 152 | .to_pixels(font_size.into(), cx.rem_size()); |
| 55 | let text = self.text.clone(); | 153 | let text = SharedString::from(text); |
| 56 | 154 | ||
| 57 | let rem_size = cx.rem_size(); | 155 | let rem_size = cx.rem_size(); |
| 58 | 156 | ||
| 59 | let runs = if let Some(runs) = self.runs.take() { | 157 | let runs = if let Some(runs) = runs { |
| 60 | runs | 158 | runs |
| 61 | } else { | 159 | } else { |
| 62 | vec![text_style.to_run(text.len())] | 160 | vec![text_style.to_run(text.len())] |
| 63 | }; | 161 | }; |
| 64 | 162 | ||
| 65 | let layout_id = cx.request_measured_layout(Default::default(), rem_size, { | 163 | let layout_id = cx.request_measured_layout(Default::default(), rem_size, { |
| 66 | let element_state = element_state.clone(); | 164 | let element_state = self.clone(); |
| 165 | |||
| 67 | move |known_dimensions, available_space| { | 166 | move |known_dimensions, available_space| { |
| 68 | let wrap_width = known_dimensions.width.or(match available_space.width { | 167 | let wrap_width = if text_style.white_space == WhiteSpace::Normal { |
| 69 | crate::AvailableSpace::Definite(x) => Some(x), | 168 | known_dimensions.width.or(match available_space.width { |
| 70 | _ => None, | 169 | crate::AvailableSpace::Definite(x) => Some(x), |
| 71 | }); | 170 | _ => None, |
| 171 | }) | ||
| 172 | } else { | ||
| 173 | None | ||
| 174 | }; | ||
| 72 | 175 | ||
| 73 | if let Some(text_state) = element_state.0.lock().as_ref() { | 176 | if let Some(text_state) = element_state.0.lock().as_ref() { |
| 74 | if text_state.size.is_some() | 177 | if text_state.size.is_some() |
| @@ -80,10 +183,7 @@ impl<V: 'static> Element<V> for Text { · 5 unmodified lines | |||
| 80 | 183 | ||
| 81 | let Some(lines) = text_system | 184 | let Some(lines) = text_system |
| 82 | .shape_text( | 185 | .shape_text( |
| 83 | &text, | 186 | &text, font_size, &runs, wrap_width, // Wrap if we know the width. |
| 84 | font_size, | ||
| 85 | &runs[..], | ||
| 86 | wrap_width, // Wrap if we know the width. | ||
| 87 | ) | 187 | ) |
| 88 | .log_err() | 188 | .log_err() |
| 89 | else { | 189 | else { |
| @@ -100,7 +200,7 @@ impl<V: 'static> Element<V> for Text { · 10 unmodified lines | |||
| 100 | for line in &lines { | 200 | for line in &lines { |
| 101 | let line_size = line.size(line_height); | 201 | let line_size = line.size(line_height); |
| 102 | size.height += line_size.height; | 202 | size.height += line_size.height; |
| 103 | size.width = size.width.max(line_size.width); | 203 | size.width = size.width.max(line_size.width).ceil(); |
| 104 | } | 204 | } |
| 105 | 205 | ||
| 106 | element_state.lock().replace(TextStateInner { | 206 | element_state.lock().replace(TextStateInner { |
| @@ -114,20 +214,14 @@ impl<V: 'static> Element<V> for Text { · 7 unmodified lines | |||
| 114 | } | 214 | } |
| 115 | }); | 215 | }); |
| 116 | 216 | ||
| 117 | (layout_id, element_state) | 217 | layout_id |
| 118 | } | 218 | } |
| 119 | 219 | ||
| 120 | fn paint( | 220 | fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) { |
| 121 | &mut self, | 221 | let element_state = self.lock(); |
| 122 | bounds: Bounds<Pixels>, | ||
| 123 | _: &mut V, | ||
| 124 | element_state: &mut Self::ElementState, | ||
| 125 | cx: &mut ViewContext<V>, | ||
| 126 | ) { | ||
| 127 | let element_state = element_state.lock(); | ||
| 128 | let element_state = element_state | 222 | let element_state = element_state |
| 129 | .as_ref() | 223 | .as_ref() |
| 130 | .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text)) | 224 | .ok_or_else(|| anyhow!("measurement has not been performed on {}", text)) |
| 131 | .unwrap(); | 225 | .unwrap(); |
| 132 | 226 | ||
| 133 | let line_height = element_state.line_height; | 227 | let line_height = element_state.line_height; |
| @@ -137,108 +231,156 @@ impl<V: 'static> Element<V> for Text { · 3 unmodified lines | |||
| 137 | line_origin.y += line.size(line_height).height; | 231 | line_origin.y += line.size(line_height).height; |
| 138 | } | 232 | } |
| 139 | } | 233 | } |
| 140 | } | ||
| 141 | 234 | ||
| 142 | #[derive(Default, Clone)] | 235 | fn index_for_position(&self, bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<usize> { |
| 143 | pub struct TextState(Arc<Mutex<Option<TextStateInner>>>); | 236 | if !bounds.contains_point(&position) { |
| 237 | return None; | ||
| 238 | } | ||
| 144 | 239 | ||
| 145 | impl TextState { | 240 | let element_state = self.lock(); |
| 146 | fn lock(&self) -> MutexGuard<Option<TextStateInner>> { | 241 | let element_state = element_state |
| 147 | self.0.lock() | 242 | .as_ref() |
| 243 | .expect("measurement has not been performed"); | ||
| 244 | |||
| 245 | let line_height = element_state.line_height; | ||
| 246 | let mut line_origin = bounds.origin; | ||
| 247 | let mut line_start_ix = 0; | ||
| 248 | for line in &element_state.lines { | ||
| 249 | let line_bottom = line_origin.y + line.size(line_height).height; | ||
| 250 | if position.y > line_bottom { | ||
| 251 | line_origin.y = line_bottom; | ||
| 252 | line_start_ix += line.len() + 1; | ||
| 253 | } else { | ||
| 254 | let position_within_line = position - line_origin; | ||
| 255 | let index_within_line = | ||
| 256 | line.index_for_position(position_within_line, line_height)?; | ||
| 257 | return Some(line_start_ix + index_within_line); | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | None | ||
| 148 | } | 262 | } |
| 149 | } | 263 | } |
| 150 | 264 | ||
| 151 | struct TextStateInner { | 265 | pub struct InteractiveText { |
| 152 | lines: SmallVec<[WrappedLine; 1]>, | 266 | element_id: ElementId, |
| 153 | line_height: Pixels, | 267 | text: StyledText, |
| 154 | wrap_width: Option<Pixels>, | 268 | click_listener: Option<Box<dyn Fn(InteractiveTextClickEvent, &mut WindowContext<'_>)>>, |
| 155 | size: Option<Size<Pixels>>, | ||
| 156 | } | 269 | } |
| 157 | 270 | ||
| 158 | struct InteractiveText { | 271 | struct InteractiveTextClickEvent { |
| 159 | id: ElementId, | 272 | mouse_down_index: usize, |
| 160 | text: Text, | 273 | mouse_up_index: usize, |
| 161 | } | 274 | } |
| 162 | 275 | ||
| 163 | struct InteractiveTextState { | 276 | pub struct InteractiveTextState { |
| 164 | text_state: TextState, | 277 | text_state: TextState, |
| 165 | clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>, | 278 | mouse_down_index: Rc<Cell<Option<usize>>>, |
| 166 | } | 279 | } |
| 167 | 280 | ||
| 168 | impl<V: 'static> Element<V> for InteractiveText { | 281 | impl InteractiveText { |
| 169 | type ElementState = InteractiveTextState; | 282 | pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self { |
| 283 | Self { | ||
| 284 | element_id: id.into(), | ||
| 285 | text, | ||
| 286 | click_listener: None, | ||
| 287 | } | ||
| 288 | } | ||
| 170 | 289 | ||
| 171 | fn element_id(&self) -> Option<ElementId> { | 290 | pub fn on_click( |
| 172 | Some(self.id.clone()) | 291 | mut self, |
| 292 | ranges: Vec<Range<usize>>, | ||
| 293 | listener: impl Fn(usize, &mut WindowContext<'_>) + 'static, | ||
| 294 | ) -> Self { | ||
| 295 | self.click_listener = Some(Box::new(move |event, cx| { | ||
| 296 | for (range_ix, range) in ranges.iter().enumerate() { | ||
| 297 | if range.contains(&event.mouse_down_index) && range.contains(&event.mouse_up_index) | ||
| 298 | { | ||
| 299 | listener(range_ix, cx); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | })); | ||
| 303 | self | ||
| 173 | } | 304 | } |
| 305 | } | ||
| 306 | |||
| 307 | impl Element for InteractiveText { | ||
| 308 | type State = InteractiveTextState; | ||
| 174 | 309 | ||
| 175 | fn layout( | 310 | fn layout( |
| 176 | &mut self, | 311 | &mut self, |
| 177 | view_state: &mut V, | 312 | state: Option<Self::State>, |
| 178 | element_state: Option<Self::ElementState>, | 313 | cx: &mut WindowContext, |
| 179 | cx: &mut ViewContext<V>, | 314 | ) -> (LayoutId, Self::State) { |
| 180 | ) -> (LayoutId, Self::ElementState) { | ||
| 181 | if let Some(InteractiveTextState { | 315 | if let Some(InteractiveTextState { |
| 182 | text_state, | 316 | mouse_down_index, .. |
| 183 | clicked_range_ixs, | 317 | }) = state |
| 184 | }) = element_state | ||
| 185 | { | 318 | { |
| 186 | let (layout_id, text_state) = self.text.layout(view_state, Some(text_state), cx); | 319 | let (layout_id, text_state) = self.text.layout(None, cx); |
| 187 | let element_state = InteractiveTextState { | 320 | let element_state = InteractiveTextState { |
| 188 | text_state, | 321 | text_state, |
| 189 | clicked_range_ixs, | 322 | mouse_down_index, |
| 190 | }; | 323 | }; |
| 191 | (layout_id, element_state) | 324 | (layout_id, element_state) |
| 192 | } else { | 325 | } else { |
| 193 | let (layout_id, text_state) = self.text.layout(view_state, None, cx); | 326 | let (layout_id, text_state) = self.text.layout(None, cx); |
| 194 | let element_state = InteractiveTextState { | 327 | let element_state = InteractiveTextState { |
| 195 | text_state, | 328 | text_state, |
| 196 | clicked_range_ixs: Rc::default(), | 329 | mouse_down_index: Rc::default(), |
| 197 | }; | 330 | }; |
| 198 | (layout_id, element_state) | 331 | (layout_id, element_state) |
| 199 | } | 332 | } |
| 200 | } | 333 | } |
| 201 | 334 | ||
| 202 | fn paint( | 335 | fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) { |
| 203 | &mut self, | 336 | if let Some(click_listener) = self.click_listener { |
| 204 | bounds: Bounds<Pixels>, | 337 | let text_state = state.text_state.clone(); |
| 205 | view_state: &mut V, | 338 | let mouse_down = state.mouse_down_index.clone(); |
| 206 | element_state: &mut Self::ElementState, | 339 | if let Some(mouse_down_index) = mouse_down.get() { |
| 207 | cx: &mut ViewContext<V>, | 340 | cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { |
| 208 | ) { | 341 | if phase == DispatchPhase::Bubble { |
| 209 | self.text | 342 | if let Some(mouse_up_index) = |
| 210 | .paint(bounds, view_state, &mut element_state.text_state, cx) | 343 | text_state.index_for_position(bounds, event.position) |
| 211 | } | 344 | { |
| 212 | } | 345 | click_listener( |
| 346 | InteractiveTextClickEvent { | ||
| 347 | mouse_down_index, | ||
| 348 | mouse_up_index, | ||
| 349 | }, | ||
| 350 | cx, | ||
| 351 | ) | ||
| 352 | } | ||
| 213 | 353 | ||
| 214 | impl<V: 'static> Component<V> for SharedString { | 354 | mouse_down.take(); |
| 215 | fn render(self) -> AnyElement<V> { | 355 | cx.notify(); |
| 216 | Text { | 356 | } |
| 217 | text: self, | 357 | }); |
| 218 | runs: None, | 358 | } else { |
| 359 | cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { | ||
| 360 | if phase == DispatchPhase::Bubble { | ||
| 361 | if let Some(mouse_down_index) = | ||
| 362 | text_state.index_for_position(bounds, event.position) | ||
| 363 | { | ||
| 364 | mouse_down.set(Some(mouse_down_index)); | ||
| 365 | cx.notify(); | ||
| 366 | } | ||
| 367 | } | ||
| 368 | }); | ||
| 369 | } | ||
| 219 | } | 370 | } |
| 220 | .render() | 371 | |
| 372 | self.text.paint(bounds, &mut state.text_state, cx) | ||
| 221 | } | 373 | } |
| 222 | } | 374 | } |
| 223 | 375 | ||
| 224 | impl<V: 'static> Component<V> for &'static str { | 376 | impl IntoElement for InteractiveText { |
| 225 | fn render(self) -> AnyElement<V> { | 377 | type Element = Self; |
| 226 | Text { | 378 | |
| 227 | text: self.into(), | 379 | fn element_id(&self) -> Option<ElementId> { |
| 228 | runs: None, | 380 | Some(self.element_id.clone()) |
| 229 | } | ||
| 230 | .render() | ||
| 231 | } | 381 | } |
| 232 | } | ||
| 233 | 382 | ||
| 234 | // TODO: Figure out how to pass `String` to `child` without this. | 383 | fn into_element(self) -> Self::Element { |
| 235 | // This impl doesn't exist in the `gpui2` crate. | 384 | self |
| 236 | impl<V: 'static> Component<V> for String { | ||
| 237 | fn render(self) -> AnyElement<V> { | ||
| 238 | Text { | ||
| 239 | text: self.into(), | ||
| 240 | runs: None, | ||
| 241 | } | ||
| 242 | .render() | ||
| 243 | } | 385 | } |
| 244 | } | 386 | } |
| @@ -1,61 +1,60 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, | 2 | point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element, |
| 3 | ElementId, InteractiveComponent, InteractiveElementState, Interactivity, LayoutId, Pixels, | 3 | ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, |
| 4 | Point, Size, StyleRefinement, Styled, ViewContext, | 4 | Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, |
| 5 | }; | 5 | }; |
| 6 | use smallvec::SmallVec; | 6 | use smallvec::SmallVec; |
| 7 | use std::{cell::RefCell, cmp, mem, ops::Range, rc::Rc}; | 7 | use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; |
| 8 | use taffy::style::Overflow; | 8 | use taffy::style::Overflow; |
| 9 | 9 | ||
| 10 | /// uniform_list provides lazy rendering for a set of items that are of uniform height. | 10 | /// uniform_list provides lazy rendering for a set of items that are of uniform height. |
| 11 | /// When rendered into a container with overflow-y: hidden and a fixed (or max) height, | 11 | /// When rendered into a container with overflow-y: hidden and a fixed (or max) height, |
| 12 | /// uniform_list will only render the visibile subset of items. | 12 | /// uniform_list will only render the visible subset of items. |
| 13 | pub fn uniform_list<I, V, C>( | 13 | pub fn uniform_list<I, R, V>( |
| 14 | view: View<V>, | ||
| 14 | id: I, | 15 | id: I, |
| 15 | item_count: usize, | 16 | item_count: usize, |
| 16 | f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<C>, | 17 | f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>, |
| 17 | ) -> UniformList<V> | 18 | ) -> UniformList |
| 18 | where | 19 | where |
| 19 | I: Into<ElementId>, | 20 | I: Into<ElementId>, |
| 20 | V: 'static, | 21 | R: IntoElement, |
| 21 | C: Component<V>, | 22 | V: Render, |
| 22 | { | 23 | { |
| 23 | let id = id.into(); | 24 | let id = id.into(); |
| 24 | let mut style = StyleRefinement::default(); | 25 | let mut base_style = StyleRefinement::default(); |
| 25 | style.overflow.y = Some(Overflow::Hidden); | 26 | base_style.overflow.y = Some(Overflow::Scroll); |
| 27 | |||
| 28 | let render_range = move |range, cx: &mut WindowContext| { | ||
| 29 | view.update(cx, |this, cx| { | ||
| 30 | f(this, range, cx) | ||
| 31 | .into_iter() | ||
| 32 | .map(|component| component.into_any_element()) | ||
| 33 | .collect() | ||
| 34 | }) | ||
| 35 | }; | ||
| 26 | 36 | ||
| 27 | UniformList { | 37 | UniformList { |
| 28 | id: id.clone(), | 38 | id: id.clone(), |
| 29 | style, | ||
| 30 | item_count, | 39 | item_count, |
| 31 | item_to_measure_index: 0, | 40 | item_to_measure_index: 0, |
| 32 | render_items: Box::new(move |view, visible_range, cx| { | 41 | render_items: Box::new(render_range), |
| 33 | f(view, visible_range, cx) | ||
| 34 | .into_iter() | ||
| 35 | .map(|component| component.render()) | ||
| 36 | .collect() | ||
| 37 | }), | ||
| 38 | interactivity: Interactivity { | 42 | interactivity: Interactivity { |
| 39 | element_id: Some(id.into()), | 43 | element_id: Some(id.into()), |
| 44 | base_style, | ||
| 40 | ..Default::default() | 45 | ..Default::default() |
| 41 | }, | 46 | }, |
| 42 | scroll_handle: None, | 47 | scroll_handle: None, |
| 43 | } | 48 | } |
| 44 | } | 49 | } |
| 45 | 50 | ||
| 46 | pub struct UniformList<V: 'static> { | 51 | pub struct UniformList { |
| 47 | id: ElementId, | 52 | id: ElementId, |
| 48 | style: StyleRefinement, | ||
| 49 | item_count: usize, | 53 | item_count: usize, |
| 50 | item_to_measure_index: usize, | 54 | item_to_measure_index: usize, |
| 51 | render_items: Box< | 55 | render_items: |
| 52 | dyn for<'a> Fn( | 56 | Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>, |
| 53 | &'a mut V, | 57 | interactivity: Interactivity, |
| 54 | Range<usize>, | ||
| 55 | &'a mut ViewContext<V>, | ||
| 56 | ) -> SmallVec<[AnyElement<V>; 64]>, | ||
| 57 | >, | ||
| 58 | interactivity: Interactivity<V>, | ||
| 59 | scroll_handle: Option<UniformListScrollHandle>, | 58 | scroll_handle: Option<UniformListScrollHandle>, |
| 60 | } | 59 | } |
| 61 | 60 | ||
| @@ -89,9 +88,9 @@ impl UniformListScrollHandle { · 27 unmodified lines | |||
| 89 | } | 88 | } |
| 90 | } | 89 | } |
| 91 | 90 | ||
| 92 | impl<V: 'static> Styled for UniformList<V> { | 91 | impl Styled for UniformList { |
| 93 | fn style(&mut self) -> &mut StyleRefinement { | 92 | fn style(&mut self) -> &mut StyleRefinement { |
| 94 | &mut self.style | 93 | &mut self.interactivity.base_style |
| 95 | } | 94 | } |
| 96 | } | 95 | } |
| 97 | 96 | ||
| @@ -101,29 +100,24 @@ pub struct UniformListState { · 3 unmodified lines | |||
| 101 | item_size: Size<Pixels>, | 100 | item_size: Size<Pixels>, |
| 102 | } | 101 | } |
| 103 | 102 | ||
| 104 | impl<V: 'static> Element<V> for UniformList<V> { | 103 | impl Element for UniformList { |
| 105 | type ElementState = UniformListState; | 104 | type State = UniformListState; |
| 106 | |||
| 107 | fn element_id(&self) -> Option<crate::ElementId> { | ||
| 108 | Some(self.id.clone()) | ||
| 109 | } | ||
| 110 | 105 | ||
| 111 | fn layout( | 106 | fn layout( |
| 112 | &mut self, | 107 | &mut self, |
| 113 | view_state: &mut V, | 108 | state: Option<Self::State>, |
| 114 | element_state: Option<Self::ElementState>, | 109 | cx: &mut WindowContext, |
| 115 | cx: &mut ViewContext<V>, | 110 | ) -> (LayoutId, Self::State) { |
| 116 | ) -> (LayoutId, Self::ElementState) { | ||
| 117 | let max_items = self.item_count; | 111 | let max_items = self.item_count; |
| 118 | let rem_size = cx.rem_size(); | 112 | let rem_size = cx.rem_size(); |
| 119 | let item_size = element_state | 113 | let item_size = state |
| 120 | .as_ref() | 114 | .as_ref() |
| 121 | .map(|s| s.item_size) | 115 | .map(|s| s.item_size) |
| 122 | .unwrap_or_else(|| self.measure_item(view_state, None, cx)); | 116 | .unwrap_or_else(|| self.measure_item(None, cx)); |
| 123 | 117 | ||
| 124 | let (layout_id, interactive) = | 118 | let (layout_id, interactive) = |
| 125 | self.interactivity | 119 | self.interactivity |
| 126 | .layout(element_state.map(|s| s.interactive), cx, |style, cx| { | 120 | .layout(state.map(|s| s.interactive), cx, |style, cx| { |
| 127 | cx.request_measured_layout( | 121 | cx.request_measured_layout( |
| 128 | style, | 122 | style, |
| 129 | rem_size, | 123 | rem_size, |
| @@ -159,11 +153,10 @@ impl<V: 'static> Element<V> for UniformList<V> { · 29 unmodified lines | |||
| 159 | } | 153 | } |
| 160 | 154 | ||
| 161 | fn paint( | 155 | fn paint( |
| 162 | &mut self, | 156 | self, |
| 163 | bounds: Bounds<crate::Pixels>, | 157 | bounds: Bounds<crate::Pixels>, |
| 164 | view_state: &mut V, | 158 | element_state: &mut Self::State, |
| 165 | element_state: &mut Self::ElementState, | 159 | cx: &mut WindowContext, |
| 166 | cx: &mut ViewContext<V>, | ||
| 167 | ) { | 160 | ) { |
| 168 | let style = | 161 | let style = |
| 169 | self.interactivity | 162 | self.interactivity |
| @@ -183,14 +176,15 @@ impl<V: 'static> Element<V> for UniformList<V> { · 13 unmodified lines | |||
| 183 | height: item_size.height * self.item_count, | 176 | height: item_size.height * self.item_count, |
| 184 | }; | 177 | }; |
| 185 | 178 | ||
| 186 | let mut interactivity = mem::take(&mut self.interactivity); | ||
| 187 | let shared_scroll_offset = element_state | 179 | let shared_scroll_offset = element_state |
| 188 | .interactive | 180 | .interactive |
| 189 | .scroll_offset | 181 | .scroll_offset |
| 190 | .get_or_insert_with(Rc::default) | 182 | .get_or_insert_with(Rc::default) |
| 191 | .clone(); | 183 | .clone(); |
| 192 | 184 | ||
| 193 | interactivity.paint( | 185 | let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height; |
| 186 | |||
| 187 | self.interactivity.paint( | ||
| 194 | bounds, | 188 | bounds, |
| 195 | content_size, | 189 | content_size, |
| 196 | &mut element_state.interactive, | 190 | &mut element_state.interactive, |
| @@ -209,9 +203,6 @@ impl<V: 'static> Element<V> for UniformList<V> { · 12 unmodified lines | |||
| 209 | style.paint(bounds, cx); | 203 | style.paint(bounds, cx); |
| 210 | 204 | ||
| 211 | if self.item_count > 0 { | 205 | if self.item_count > 0 { |
| 212 | let item_height = self | ||
| 213 | .measure_item(view_state, Some(padded_bounds.size.width), cx) | ||
| 214 | .height; | ||
| 215 | if let Some(scroll_handle) = self.scroll_handle.clone() { | 206 | if let Some(scroll_handle) = self.scroll_handle.clone() { |
| 216 | scroll_handle.0.borrow_mut().replace(ScrollHandleState { | 207 | scroll_handle.0.borrow_mut().replace(ScrollHandleState { |
| 217 | item_height, | 208 | item_height, |
| @@ -219,58 +210,64 @@ impl<V: 'static> Element<V> for UniformList<V> { · 1 unmodified line | |||
| 219 | scroll_offset: shared_scroll_offset, | 210 | scroll_offset: shared_scroll_offset, |
| 220 | }); | 211 | }); |
| 221 | } | 212 | } |
| 222 | let visible_item_count = if item_height > px(0.) { | ||
| 223 | (padded_bounds.size.height / item_height).ceil() as usize + 1 | ||
| 224 | } else { | ||
| 225 | 0 | ||
| 226 | }; | ||
| 227 | 213 | ||
| 228 | let first_visible_element_ix = | 214 | let first_visible_element_ix = |
| 229 | (-scroll_offset.y / item_height).floor() as usize; | 215 | (-scroll_offset.y / item_height).floor() as usize; |
| 216 | let last_visible_element_ix = | ||
| 217 | ((-scroll_offset.y + padded_bounds.size.height) / item_height).ceil() | ||
| 218 | as usize; | ||
| 230 | let visible_range = first_visible_element_ix | 219 | let visible_range = first_visible_element_ix |
| 231 | ..cmp::min( | 220 | ..cmp::min(last_visible_element_ix, self.item_count); |
| 232 | first_visible_element_ix + visible_item_count, | ||
| 233 | self.item_count, | ||
| 234 | ); | ||
| 235 | 221 | ||
| 236 | let mut items = (self.render_items)(view_state, visible_range.clone(), cx); | 222 | let items = (self.render_items)(visible_range.clone(), cx); |
| 237 | cx.with_z_index(1, |cx| { | 223 | cx.with_z_index(1, |cx| { |
| 238 | for (item, ix) in items.iter_mut().zip(visible_range) { | 224 | let content_mask = ContentMask { |
| 239 | let item_origin = padded_bounds.origin | 225 | bounds: padded_bounds, |
| 240 | + point(px(0.), item_height * ix + scroll_offset.y); | 226 | }; |
| 241 | let available_space = size( | 227 | cx.with_content_mask(Some(content_mask), |cx| { |
| 242 | AvailableSpace::Definite(padded_bounds.size.width), | 228 | for (item, ix) in items.into_iter().zip(visible_range) { |
| 243 | AvailableSpace::Definite(item_height), | 229 | let item_origin = padded_bounds.origin |
| 244 | ); | 230 | + point(px(0.), item_height * ix + scroll_offset.y); |
| 245 | item.draw(item_origin, available_space, view_state, cx); | 231 | let available_space = size( |
| 246 | } | 232 | AvailableSpace::Definite(padded_bounds.size.width), |
| 233 | AvailableSpace::Definite(item_height), | ||
| 234 | ); | ||
| 235 | item.draw(item_origin, available_space, cx); | ||
| 236 | } | ||
| 237 | }); | ||
| 247 | }); | 238 | }); |
| 248 | } | 239 | } |
| 249 | }) | 240 | }) |
| 250 | }, | 241 | }, |
| 251 | ); | 242 | ); |
| 252 | self.interactivity = interactivity; | ||
| 253 | } | 243 | } |
| 254 | } | 244 | } |
| 255 | 245 | ||
| 256 | impl<V> UniformList<V> { | 246 | impl IntoElement for UniformList { |
| 247 | type Element = Self; | ||
| 248 | |||
| 249 | fn element_id(&self) -> Option<crate::ElementId> { | ||
| 250 | Some(self.id.clone()) | ||
| 251 | } | ||
| 252 | |||
| 253 | fn into_element(self) -> Self::Element { | ||
| 254 | self | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | impl UniformList { | ||
| 257 | pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self { | 259 | pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self { |
| 258 | self.item_to_measure_index = item_index.unwrap_or(0); | 260 | self.item_to_measure_index = item_index.unwrap_or(0); |
| 259 | self | 261 | self |
| 260 | } | 262 | } |
| 261 | 263 | ||
| 262 | fn measure_item( | 264 | fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> { |
| 263 | &self, | ||
| 264 | view_state: &mut V, | ||
| 265 | list_width: Option<Pixels>, | ||
| 266 | cx: &mut ViewContext<V>, | ||
| 267 | ) -> Size<Pixels> { | ||
| 268 | if self.item_count == 0 { | 265 | if self.item_count == 0 { |
| 269 | return Size::default(); | 266 | return Size::default(); |
| 270 | } | 267 | } |
| 271 | 268 | ||
| 272 | let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1); | 269 | let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1); |
| 273 | let mut items = (self.render_items)(view_state, item_ix..item_ix + 1, cx); | 270 | let mut items = (self.render_items)(item_ix..item_ix + 1, cx); |
| 274 | let mut item_to_measure = items.pop().unwrap(); | 271 | let mut item_to_measure = items.pop().unwrap(); |
| 275 | let available_space = size( | 272 | let available_space = size( |
| 276 | list_width.map_or(AvailableSpace::MinContent, |width| { | 273 | list_width.map_or(AvailableSpace::MinContent, |width| { |
| @@ -278,7 +275,7 @@ impl<V> UniformList<V> { · 1 unmodified line | |||
| 278 | }), | 275 | }), |
| 279 | AvailableSpace::MinContent, | 276 | AvailableSpace::MinContent, |
| 280 | ); | 277 | ); |
| 281 | item_to_measure.measure(available_space, view_state, cx) | 278 | item_to_measure.measure(available_space, cx) |
| 282 | } | 279 | } |
| 283 | 280 | ||
| 284 | pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self { | 281 | pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self { |
| @@ -287,14 +284,8 @@ impl<V> UniformList<V> { · 2 unmodified lines | |||
| 287 | } | 284 | } |
| 288 | } | 285 | } |
| 289 | 286 | ||
| 290 | impl<V> InteractiveComponent<V> for UniformList<V> { | 287 | impl InteractiveElement for UniformList { |
| 291 | fn interactivity(&mut self) -> &mut crate::Interactivity<V> { | 288 | fn interactivity(&mut self) -> &mut crate::Interactivity { |
| 292 | &mut self.interactivity | 289 | &mut self.interactivity |
| 293 | } | 290 | } |
| 294 | } | 291 | } |
| 295 | |||
| 296 | impl<V: 'static> Component<V> for UniformList<V> { | ||
| 297 | fn render(self) -> AnyElement<V> { | ||
| 298 | AnyElement::new(self) | ||
| 299 | } | ||
| 300 | } | ||
| @@ -78,8 +78,6 @@ use std::{ · 77 unmodified lines | |||
| 78 | }; | 78 | }; |
| 79 | use taffy::TaffyLayoutEngine; | 79 | use taffy::TaffyLayoutEngine; |
| 80 | 80 | ||
| 81 | type AnyBox = Box<dyn Any>; | ||
| 82 | |||
| 83 | pub trait Context { | 81 | pub trait Context { |
| 84 | type Result<T>; | 82 | type Result<T>; |
| 85 | 83 | ||
| @@ -136,7 +134,7 @@ pub trait VisualContext: Context { · 50 unmodified lines | |||
| 136 | build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, | 134 | build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, |
| 137 | ) -> Self::Result<View<V>> | 135 | ) -> Self::Result<View<V>> |
| 138 | where | 136 | where |
| 139 | V: Render; | 137 | V: 'static + Render; |
| 140 | 138 | ||
| 141 | fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()> | 139 | fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()> |
| 142 | where | 140 | where |
| @@ -1,4 +1,6 @@ | |||
| 1 | use crate::{AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext}; | 1 | use crate::{ |
| 2 | AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext, WindowContext, | ||
| 3 | }; | ||
| 2 | use std::ops::Range; | 4 | use std::ops::Range; |
| 3 | 5 | ||
| 4 | /// Implement this trait to allow views to handle textual input when implementing an editor, field, etc. | 6 | /// Implement this trait to allow views to handle textual input when implementing an editor, field, etc. |
| @@ -43,9 +45,9 @@ pub struct ElementInputHandler<V> { · 38 unmodified lines | |||
| 43 | impl<V: 'static> ElementInputHandler<V> { | 45 | impl<V: 'static> ElementInputHandler<V> { |
| 44 | /// Used in [Element::paint] with the element's bounds and a view context for its | 46 | /// Used in [Element::paint] with the element's bounds and a view context for its |
| 45 | /// containing view. | 47 | /// containing view. |
| 46 | pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self { | 48 | pub fn new(element_bounds: Bounds<Pixels>, view: View<V>, cx: &mut WindowContext) -> Self { |
| 47 | ElementInputHandler { | 49 | ElementInputHandler { |
| 48 | view: cx.view().clone(), | 50 | view, |
| 49 | element_bounds, | 51 | element_bounds, |
| 50 | cx: cx.to_async(), | 52 | cx: cx.to_async(), |
| 51 | } | 53 | } |
| @@ -1,6 +1,6 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | div, point, Component, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, | 2 | div, point, Div, Element, FocusHandle, IntoElement, Keystroke, Modifiers, Pixels, Point, |
| 3 | ViewContext, | 3 | Render, ViewContext, |
| 4 | }; | 4 | }; |
| 5 | use smallvec::SmallVec; | 5 | use smallvec::SmallVec; |
| 6 | use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; | 6 | use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; |
| @@ -64,24 +64,24 @@ pub struct Drag<S, R, V, E> · 57 unmodified lines | |||
| 64 | where | 64 | where |
| 65 | R: Fn(&mut V, &mut ViewContext<V>) -> E, | 65 | R: Fn(&mut V, &mut ViewContext<V>) -> E, |
| 66 | V: 'static, | 66 | V: 'static, |
| 67 | E: Component<()>, | 67 | E: IntoElement, |
| 68 | { | 68 | { |
| 69 | pub state: S, | 69 | pub state: S, |
| 70 | pub render_drag_handle: R, | 70 | pub render_drag_handle: R, |
| 71 | view_type: PhantomData<V>, | 71 | view_element_types: PhantomData<(V, E)>, |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | impl<S, R, V, E> Drag<S, R, V, E> | 74 | impl<S, R, V, E> Drag<S, R, V, E> |
| 75 | where | 75 | where |
| 76 | R: Fn(&mut V, &mut ViewContext<V>) -> E, | 76 | R: Fn(&mut V, &mut ViewContext<V>) -> E, |
| 77 | V: 'static, | 77 | V: 'static, |
| 78 | E: Component<()>, | 78 | E: Element, |
| 79 | { | 79 | { |
| 80 | pub fn new(state: S, render_drag_handle: R) -> Self { | 80 | pub fn new(state: S, render_drag_handle: R) -> Self { |
| 81 | Drag { | 81 | Drag { |
| 82 | state, | 82 | state, |
| 83 | render_drag_handle, | 83 | render_drag_handle, |
| 84 | view_type: PhantomData, | 84 | view_element_types: Default::default(), |
| 85 | } | 85 | } |
| 86 | } | 86 | } |
| 87 | } | 87 | } |
| @@ -194,7 +194,7 @@ impl Deref for MouseExitEvent { · 106 unmodified lines | |||
| 194 | pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>); | 194 | pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>); |
| 195 | 195 | ||
| 196 | impl Render for ExternalPaths { | 196 | impl Render for ExternalPaths { |
| 197 | type Element = Div<Self>; | 197 | type Element = Div; |
| 198 | 198 | ||
| 199 | fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element { | 199 | fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element { |
| 200 | div() // Intentionally left empty because the platform will render icons for the dragged files | 200 | div() // Intentionally left empty because the platform will render icons for the dragged files |
| @@ -286,8 +286,8 @@ pub struct FocusEvent { · 85 unmodified lines | |||
| 286 | #[cfg(test)] | 286 | #[cfg(test)] |
| 287 | mod test { | 287 | mod test { |
| 288 | use crate::{ | 288 | use crate::{ |
| 289 | self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding, | 289 | self as gpui, div, Div, FocusHandle, InteractiveElement, IntoElement, KeyBinding, |
| 290 | Keystroke, ParentComponent, Render, Stateful, TestAppContext, ViewContext, VisualContext, | 290 | Keystroke, ParentElement, Render, Stateful, TestAppContext, VisualContext, |
| 291 | }; | 291 | }; |
| 292 | 292 | ||
| 293 | struct TestView { | 293 | struct TestView { |
| @@ -299,20 +299,24 @@ mod test { · 5 unmodified lines | |||
| 299 | actions!(TestAction); | 299 | actions!(TestAction); |
| 300 | 300 | ||
| 301 | impl Render for TestView { | 301 | impl Render for TestView { |
| 302 | type Element = Stateful<Self, Div<Self>>; | 302 | type Element = Stateful<Div>; |
| 303 | 303 | ||
| 304 | fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element { | 304 | fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { |
| 305 | div().id("testview").child( | 305 | div().id("testview").child( |
| 306 | div() | 306 | div() |
| 307 | .key_context("parent") | 307 | .key_context("parent") |
| 308 | .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true) | 308 | .on_key_down(cx.listener(|this, _, _| this.saw_key_down = true)) |
| 309 | .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true) | 309 | .on_action( |
| 310 | .child(|this: &mut Self, _cx: &mut ViewContext<Self>| { | 310 | cx.listener(|this: &mut TestView, _: &TestAction, _| { |
| 311 | this.saw_action = true | ||
| 312 | }), | ||
| 313 | ) | ||
| 314 | .child( | ||
| 311 | div() | 315 | div() |
| 312 | .key_context("nested") | 316 | .key_context("nested") |
| 313 | .track_focus(&this.focus_handle) | 317 | .track_focus(&self.focus_handle) |
| 314 | .render() | 318 | .into_element(), |
| 315 | }), | 319 | ), |
| 316 | ) | 320 | ) |
| 317 | } | 321 | } |
| 318 | } | 322 | } |
| @@ -683,6 +683,9 @@ impl Drop for MacWindow { · 682 unmodified lines | |||
| 683 | this.executor | 683 | this.executor |
| 684 | .spawn(async move { | 684 | .spawn(async move { |
| 685 | unsafe { | 685 | unsafe { |
| 686 | // todo!() this panic()s when you click the red close button | ||
| 687 | // unless should_close returns false. | ||
| 688 | // (luckliy in zed it always returns false) | ||
| 686 | window.close(); | 689 | window.close(); |
| 687 | } | 690 | } |
| 688 | }) | 691 | }) |
| @@ -1,4 +1,5 @@ | |||
| 1 | pub use crate::{ | 1 | pub use crate::{ |
| 2 | BorrowAppContext, BorrowWindow, Component, Context, FocusableComponent, InteractiveComponent, | 2 | BorrowAppContext, BorrowWindow, Context, Element, FocusableElement, InteractiveElement, |
| 3 | ParentComponent, Refineable, Render, StatefulInteractiveComponent, Styled, VisualContext, | 3 | IntoElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement, Styled, |
| 4 | VisualContext, | ||
| 4 | }; | 5 | }; |
| @@ -1,9 +1,12 @@ | |||
| 1 | use std::{iter, mem, ops::Range}; | ||
| 2 | |||
| 1 | use crate::{ | 3 | use crate::{ |
| 2 | black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, | 4 | black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, |
| 3 | Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, | 5 | Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, |
| 4 | FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, | 6 | FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, |
| 5 | SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, | 7 | SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext, |
| 6 | }; | 8 | }; |
| 9 | use collections::HashSet; | ||
| 7 | use refineable::{Cascade, Refineable}; | 10 | use refineable::{Cascade, Refineable}; |
| 8 | use smallvec::SmallVec; | 11 | use smallvec::SmallVec; |
| 9 | pub use taffy::style::{ | 12 | pub use taffy::style::{ |
| @@ -128,6 +131,13 @@ pub struct BoxShadow { · 118 unmodified lines | |||
| 128 | pub spread_radius: Pixels, | 131 | pub spread_radius: Pixels, |
| 129 | } | 132 | } |
| 130 | 133 | ||
| 134 | #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] | ||
| 135 | pub enum WhiteSpace { | ||
| 136 | #[default] | ||
| 137 | Normal, | ||
| 138 | Nowrap, | ||
| 139 | } | ||
| 140 | |||
| 131 | #[derive(Refineable, Clone, Debug)] | 141 | #[derive(Refineable, Clone, Debug)] |
| 132 | #[refineable(Debug)] | 142 | #[refineable(Debug)] |
| 133 | pub struct TextStyle { | 143 | pub struct TextStyle { |
| @@ -138,7 +148,9 @@ pub struct TextStyle { · 4 unmodified lines | |||
| 138 | pub line_height: DefiniteLength, | 148 | pub line_height: DefiniteLength, |
| 139 | pub font_weight: FontWeight, | 149 | pub font_weight: FontWeight, |
| 140 | pub font_style: FontStyle, | 150 | pub font_style: FontStyle, |
| 151 | pub background_color: Option<Hsla>, | ||
| 141 | pub underline: Option<UnderlineStyle>, | 152 | pub underline: Option<UnderlineStyle>, |
| 153 | pub white_space: WhiteSpace, | ||
| 142 | } | 154 | } |
| 143 | 155 | ||
| 144 | impl Default for TextStyle { | 156 | impl Default for TextStyle { |
| @@ -151,13 +163,16 @@ impl Default for TextStyle { · 6 unmodified lines | |||
| 151 | line_height: phi(), | 163 | line_height: phi(), |
| 152 | font_weight: FontWeight::default(), | 164 | font_weight: FontWeight::default(), |
| 153 | font_style: FontStyle::default(), | 165 | font_style: FontStyle::default(), |
| 166 | background_color: None, | ||
| 154 | underline: None, | 167 | underline: None, |
| 168 | white_space: WhiteSpace::Normal, | ||
| 155 | } | 169 | } |
| 156 | } | 170 | } |
| 157 | } | 171 | } |
| 158 | 172 | ||
| 159 | impl TextStyle { | 173 | impl TextStyle { |
| 160 | pub fn highlight(mut self, style: HighlightStyle) -> Self { | 174 | pub fn highlight(mut self, style: impl Into<HighlightStyle>) -> Self { |
| 175 | let style = style.into(); | ||
| 161 | if let Some(weight) = style.font_weight { | 176 | if let Some(weight) = style.font_weight { |
| 162 | self.font_weight = weight; | 177 | self.font_weight = weight; |
| 163 | } | 178 | } |
| @@ -173,6 +188,10 @@ impl TextStyle { · 9 unmodified lines | |||
| 173 | self.color.fade_out(factor); | 188 | self.color.fade_out(factor); |
| 174 | } | 189 | } |
| 175 | 190 | ||
| 191 | if let Some(background_color) = style.background_color { | ||
| 192 | self.background_color = Some(background_color); | ||
| 193 | } | ||
| 194 | |||
| 176 | if let Some(underline) = style.underline { | 195 | if let Some(underline) = style.underline { |
| 177 | self.underline = Some(underline); | 196 | self.underline = Some(underline); |
| 178 | } | 197 | } |
| @@ -203,7 +222,7 @@ impl TextStyle { · 24 unmodified lines | |||
| 203 | style: self.font_style, | 222 | style: self.font_style, |
| 204 | }, | 223 | }, |
| 205 | color: self.color, | 224 | color: self.color, |
| 206 | background_color: None, | 225 | background_color: self.background_color, |
| 207 | underline: self.underline.clone(), | 226 | underline: self.underline.clone(), |
| 208 | } | 227 | } |
| 209 | } | 228 | } |
| @@ -214,6 +233,7 @@ pub struct HighlightStyle { · 4 unmodified lines | |||
| 214 | pub color: Option<Hsla>, | 233 | pub color: Option<Hsla>, |
| 215 | pub font_weight: Option<FontWeight>, | 234 | pub font_weight: Option<FontWeight>, |
| 216 | pub font_style: Option<FontStyle>, | 235 | pub font_style: Option<FontStyle>, |
| 236 | pub background_color: Option<Hsla>, | ||
| 217 | pub underline: Option<UnderlineStyle>, | 237 | pub underline: Option<UnderlineStyle>, |
| 218 | pub fade_out: Option<f32>, | 238 | pub fade_out: Option<f32>, |
| 219 | } | 239 | } |
| @@ -313,7 +333,7 @@ impl Style { · 93 unmodified lines | |||
| 313 | } | 333 | } |
| 314 | 334 | ||
| 315 | /// Paints the background of an element styled with this style. | 335 | /// Paints the background of an element styled with this style. |
| 316 | pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) { | 336 | pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) { |
| 317 | let rem_size = cx.rem_size(); | 337 | let rem_size = cx.rem_size(); |
| 318 | 338 | ||
| 319 | cx.with_z_index(0, |cx| { | 339 | cx.with_z_index(0, |cx| { |
| @@ -432,6 +452,7 @@ impl From<&TextStyle> for HighlightStyle { · 112 unmodified lines | |||
| 432 | color: Some(other.color), | 452 | color: Some(other.color), |
| 433 | font_weight: Some(other.font_weight), | 453 | font_weight: Some(other.font_weight), |
| 434 | font_style: Some(other.font_style), | 454 | font_style: Some(other.font_style), |
| 455 | background_color: other.background_color, | ||
| 435 | underline: other.underline.clone(), | 456 | underline: other.underline.clone(), |
| 436 | fade_out: None, | 457 | fade_out: None, |
| 437 | } | 458 | } |
| @@ -458,6 +479,10 @@ impl HighlightStyle { · 20 unmodified lines | |||
| 458 | self.font_style = other.font_style; | 479 | self.font_style = other.font_style; |
| 459 | } | 480 | } |
| 460 | 481 | ||
| 482 | if other.background_color.is_some() { | ||
| 483 | self.background_color = other.background_color; | ||
| 484 | } | ||
| 485 | |||
| 461 | if other.underline.is_some() { | 486 | if other.underline.is_some() { |
| 462 | self.underline = other.underline; | 487 | self.underline = other.underline; |
| 463 | } | 488 | } |
| @@ -481,6 +506,24 @@ impl From<Hsla> for HighlightStyle { · 17 unmodified lines | |||
| 481 | } | 506 | } |
| 482 | } | 507 | } |
| 483 | 508 | ||
| 509 | impl From<FontWeight> for HighlightStyle { | ||
| 510 | fn from(font_weight: FontWeight) -> Self { | ||
| 511 | Self { | ||
| 512 | font_weight: Some(font_weight), | ||
| 513 | ..Default::default() | ||
| 514 | } | ||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | impl From<FontStyle> for HighlightStyle { | ||
| 519 | fn from(font_style: FontStyle) -> Self { | ||
| 520 | Self { | ||
| 521 | font_style: Some(font_style), | ||
| 522 | ..Default::default() | ||
| 523 | } | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 484 | impl From<Rgba> for HighlightStyle { | 527 | impl From<Rgba> for HighlightStyle { |
| 485 | fn from(color: Rgba) -> Self { | 528 | fn from(color: Rgba) -> Self { |
| 486 | Self { | 529 | Self { |
| @@ -489,3 +532,140 @@ impl From<Rgba> for HighlightStyle { · 2 unmodified lines | |||
| 489 | } | 532 | } |
| 490 | } | 533 | } |
| 491 | } | 534 | } |
| 535 | |||
| 536 | pub fn combine_highlights( | ||
| 537 | a: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>, | ||
| 538 | b: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>, | ||
| 539 | ) -> impl Iterator<Item = (Range<usize>, HighlightStyle)> { | ||
| 540 | let mut endpoints = Vec::new(); | ||
| 541 | let mut highlights = Vec::new(); | ||
| 542 | for (range, highlight) in a.into_iter().chain(b) { | ||
| 543 | if !range.is_empty() { | ||
| 544 | let highlight_id = highlights.len(); | ||
| 545 | endpoints.push((range.start, highlight_id, true)); | ||
| 546 | endpoints.push((range.end, highlight_id, false)); | ||
| 547 | highlights.push(highlight); | ||
| 548 | } | ||
| 549 | } | ||
| 550 | endpoints.sort_unstable_by_key(|(position, _, _)| *position); | ||
| 551 | let mut endpoints = endpoints.into_iter().peekable(); | ||
| 552 | |||
| 553 | let mut active_styles = HashSet::default(); | ||
| 554 | let mut ix = 0; | ||
| 555 | iter::from_fn(move || { | ||
| 556 | while let Some((endpoint_ix, highlight_id, is_start)) = endpoints.peek() { | ||
| 557 | let prev_index = mem::replace(&mut ix, *endpoint_ix); | ||
| 558 | if ix > prev_index && !active_styles.is_empty() { | ||
| 559 | let mut current_style = HighlightStyle::default(); | ||
| 560 | for highlight_id in &active_styles { | ||
| 561 | current_style.highlight(highlights[*highlight_id]); | ||
| 562 | } | ||
| 563 | return Some((prev_index..ix, current_style)); | ||
| 564 | } | ||
| 565 | |||
| 566 | if *is_start { | ||
| 567 | active_styles.insert(*highlight_id); | ||
| 568 | } else { | ||
| 569 | active_styles.remove(highlight_id); | ||
| 570 | } | ||
| 571 | endpoints.next(); | ||
| 572 | } | ||
| 573 | None | ||
| 574 | }) | ||
| 575 | } | ||
| 576 | |||
| 577 | #[cfg(test)] | ||
| 578 | mod tests { | ||
| 579 | use crate::{blue, green, red, yellow}; | ||
| 580 | |||
| 581 | use super::*; | ||
| 582 | |||
| 583 | #[test] | ||
| 584 | fn test_combine_highlights() { | ||
| 585 | assert_eq!( | ||
| 586 | combine_highlights( | ||
| 587 | [ | ||
| 588 | (0..5, green().into()), | ||
| 589 | (4..10, FontWeight::BOLD.into()), | ||
| 590 | (15..20, yellow().into()), | ||
| 591 | ], | ||
| 592 | [ | ||
| 593 | (2..6, FontStyle::Italic.into()), | ||
| 594 | (1..3, blue().into()), | ||
| 595 | (21..23, red().into()), | ||
| 596 | ] | ||
| 597 | ) | ||
| 598 | .collect::<Vec<_>>(), | ||
| 599 | [ | ||
| 600 | ( | ||
| 601 | 0..1, | ||
| 602 | HighlightStyle { | ||
| 603 | color: Some(green()), | ||
| 604 | ..Default::default() | ||
| 605 | } | ||
| 606 | ), | ||
| 607 | ( | ||
| 608 | 1..2, | ||
| 609 | HighlightStyle { | ||
| 610 | color: Some(blue()), | ||
| 611 | ..Default::default() | ||
| 612 | } | ||
| 613 | ), | ||
| 614 | ( | ||
| 615 | 2..3, | ||
| 616 | HighlightStyle { | ||
| 617 | color: Some(blue()), | ||
| 618 | font_style: Some(FontStyle::Italic), | ||
| 619 | ..Default::default() | ||
| 620 | } | ||
| 621 | ), | ||
| 622 | ( | ||
| 623 | 3..4, | ||
| 624 | HighlightStyle { | ||
| 625 | color: Some(green()), | ||
| 626 | font_style: Some(FontStyle::Italic), | ||
| 627 | ..Default::default() | ||
| 628 | } | ||
| 629 | ), | ||
| 630 | ( | ||
| 631 | 4..5, | ||
| 632 | HighlightStyle { | ||
| 633 | color: Some(green()), | ||
| 634 | font_weight: Some(FontWeight::BOLD), | ||
| 635 | font_style: Some(FontStyle::Italic), | ||
| 636 | ..Default::default() | ||
| 637 | } | ||
| 638 | ), | ||
| 639 | ( | ||
| 640 | 5..6, | ||
| 641 | HighlightStyle { | ||
| 642 | font_weight: Some(FontWeight::BOLD), | ||
| 643 | font_style: Some(FontStyle::Italic), | ||
| 644 | ..Default::default() | ||
| 645 | } | ||
| 646 | ), | ||
| 647 | ( | ||
| 648 | 6..10, | ||
| 649 | HighlightStyle { | ||
| 650 | font_weight: Some(FontWeight::BOLD), | ||
| 651 | ..Default::default() | ||
| 652 | } | ||
| 653 | ), | ||
| 654 | ( | ||
| 655 | 15..20, | ||
| 656 | HighlightStyle { | ||
| 657 | color: Some(yellow()), | ||
| 658 | ..Default::default() | ||
| 659 | } | ||
| 660 | ), | ||
| 661 | ( | ||
| 662 | 21..23, | ||
| 663 | HighlightStyle { | ||
| 664 | color: Some(red()), | ||
| 665 | ..Default::default() | ||
| 666 | } | ||
| 667 | ) | ||
| 668 | ] | ||
| 669 | ); | ||
| 670 | } | ||
| 671 | } | ||
| @@ -1,7 +1,7 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, | 2 | self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, |
| 3 | DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, | 3 | DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, |
| 4 | SharedString, StyleRefinement, Visibility, | 4 | SharedString, StyleRefinement, Visibility, WhiteSpace, |
| 5 | }; | 5 | }; |
| 6 | use crate::{BoxShadow, TextStyleRefinement}; | 6 | use crate::{BoxShadow, TextStyleRefinement}; |
| 7 | use smallvec::{smallvec, SmallVec}; | 7 | use smallvec::{smallvec, SmallVec}; |
| @@ -101,6 +101,24 @@ pub trait Styled: Sized { · 93 unmodified lines | |||
| 101 | self | 101 | self |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | /// Sets the whitespace of the element to `normal`. | ||
| 105 | /// [Docs](https://tailwindcss.com/docs/whitespace#normal) | ||
| 106 | fn whitespace_normal(mut self) -> Self { | ||
| 107 | self.text_style() | ||
| 108 | .get_or_insert_with(Default::default) | ||
| 109 | .white_space = Some(WhiteSpace::Normal); | ||
| 110 | self | ||
| 111 | } | ||
| 112 | |||
| 113 | /// Sets the whitespace of the element to `nowrap`. | ||
| 114 | /// [Docs](https://tailwindcss.com/docs/whitespace#nowrap) | ||
| 115 | fn whitespace_nowrap(mut self) -> Self { | ||
| 116 | self.text_style() | ||
| 117 | .get_or_insert_with(Default::default) | ||
| 118 | .white_space = Some(WhiteSpace::Nowrap); | ||
| 119 | self | ||
| 120 | } | ||
| 121 | |||
| 104 | /// Sets the flex direction of the element to `column`. | 122 | /// Sets the flex direction of the element to `column`. |
| 105 | /// [Docs](https://tailwindcss.com/docs/flex-direction#column) | 123 | /// [Docs](https://tailwindcss.com/docs/flex-direction#column) |
| 106 | fn flex_col(mut self) -> Self { | 124 | fn flex_col(mut self) -> Self { |
| @@ -343,6 +361,13 @@ pub trait Styled: Sized { · 236 unmodified lines | |||
| 343 | self | 361 | self |
| 344 | } | 362 | } |
| 345 | 363 | ||
| 364 | fn text_bg(mut self, bg: impl Into<Hsla>) -> Self { | ||
| 365 | self.text_style() | ||
| 366 | .get_or_insert_with(Default::default) | ||
| 367 | .background_color = Some(bg.into()); | ||
| 368 | self | ||
| 369 | } | ||
| 370 | |||
| 346 | fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self { | 371 | fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self { |
| 347 | self.text_style() | 372 | self.text_style() |
| 348 | .get_or_insert_with(Default::default) | 373 | .get_or_insert_with(Default::default) |
| @@ -196,7 +196,10 @@ impl TextSystem { · 195 unmodified lines | |||
| 196 | let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new(); | 196 | let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new(); |
| 197 | for run in runs { | 197 | for run in runs { |
| 198 | if let Some(last_run) = decoration_runs.last_mut() { | 198 | if let Some(last_run) = decoration_runs.last_mut() { |
| 199 | if last_run.color == run.color && last_run.underline == run.underline { | 199 | if last_run.color == run.color |
| 200 | && last_run.underline == run.underline | ||
| 201 | && last_run.background_color == run.background_color | ||
| 202 | { | ||
| 200 | last_run.len += run.len as u32; | 203 | last_run.len += run.len as u32; |
| 201 | continue; | 204 | continue; |
| 202 | } | 205 | } |
| @@ -204,6 +207,7 @@ impl TextSystem { · 1 unmodified line | |||
| 204 | decoration_runs.push(DecorationRun { | 207 | decoration_runs.push(DecorationRun { |
| 205 | len: run.len as u32, | 208 | len: run.len as u32, |
| 206 | color: run.color, | 209 | color: run.color, |
| 210 | background_color: run.background_color, | ||
| 207 | underline: run.underline.clone(), | 211 | underline: run.underline.clone(), |
| 208 | }); | 212 | }); |
| 209 | } | 213 | } |
| @@ -254,13 +258,16 @@ impl TextSystem { · 44 unmodified lines | |||
| 254 | } | 258 | } |
| 255 | 259 | ||
| 256 | if decoration_runs.last().map_or(false, |last_run| { | 260 | if decoration_runs.last().map_or(false, |last_run| { |
| 257 | last_run.color == run.color && last_run.underline == run.underline | 261 | last_run.color == run.color |
| 262 | && last_run.underline == run.underline | ||
| 263 | && last_run.background_color == run.background_color | ||
| 258 | }) { | 264 | }) { |
| 259 | decoration_runs.last_mut().unwrap().len += run_len_within_line as u32; | 265 | decoration_runs.last_mut().unwrap().len += run_len_within_line as u32; |
| 260 | } else { | 266 | } else { |
| 261 | decoration_runs.push(DecorationRun { | 267 | decoration_runs.push(DecorationRun { |
| 262 | len: run_len_within_line as u32, | 268 | len: run_len_within_line as u32, |
| 263 | color: run.color, | 269 | color: run.color, |
| 270 | background_color: run.background_color, | ||
| 264 | underline: run.underline.clone(), | 271 | underline: run.underline.clone(), |
| 265 | }); | 272 | }); |
| 266 | } | 273 | } |
| @@ -283,7 +290,15 @@ impl TextSystem { · 16 unmodified lines | |||
| 283 | text: SharedString::from(line_text), | 290 | text: SharedString::from(line_text), |
| 284 | }); | 291 | }); |
| 285 | 292 | ||
| 286 | line_start = line_end + 1; // Skip `\n` character. | 293 | // Skip `\n` character. |
| 294 | line_start = line_end + 1; | ||
| 295 | if let Some(run) = runs.peek_mut() { | ||
| 296 | run.len = run.len.saturating_sub(1); | ||
| 297 | if run.len == 0 { | ||
| 298 | runs.next(); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 287 | font_runs.clear(); | 302 | font_runs.clear(); |
| 288 | } | 303 | } |
| 289 | 304 | ||
| @@ -1,6 +1,7 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | black, point, px, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString, | 2 | black, point, px, size, transparent_black, BorrowWindow, Bounds, Corners, Edges, Hsla, |
| 3 | UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout, | 3 | LineLayout, Pixels, Point, Result, SharedString, UnderlineStyle, WindowContext, WrapBoundary, |
| 4 | WrappedLineLayout, | ||
| 4 | }; | 5 | }; |
| 5 | use derive_more::{Deref, DerefMut}; | 6 | use derive_more::{Deref, DerefMut}; |
| 6 | use smallvec::SmallVec; | 7 | use smallvec::SmallVec; |
| @@ -10,6 +11,7 @@ use std::sync::Arc; · 3 unmodified lines | |||
| 10 | pub struct DecorationRun { | 11 | pub struct DecorationRun { |
| 11 | pub len: u32, | 12 | pub len: u32, |
| 12 | pub color: Hsla, | 13 | pub color: Hsla, |
| 14 | pub background_color: Option<Hsla>, | ||
| 13 | pub underline: Option<UnderlineStyle>, | 15 | pub underline: Option<UnderlineStyle>, |
| 14 | } | 16 | } |
| 15 | 17 | ||
| @@ -38,7 +40,6 @@ impl ShapedLine { · 22 unmodified lines | |||
| 38 | &self.layout, | 40 | &self.layout, |
| 39 | line_height, | 41 | line_height, |
| 40 | &self.decoration_runs, | 42 | &self.decoration_runs, |
| 41 | None, | ||
| 42 | &[], | 43 | &[], |
| 43 | cx, | 44 | cx, |
| 44 | )?; | 45 | )?; |
| @@ -72,7 +73,6 @@ impl WrappedLine { · 27 unmodified lines | |||
| 72 | &self.layout.unwrapped_layout, | 73 | &self.layout.unwrapped_layout, |
| 73 | line_height, | 74 | line_height, |
| 74 | &self.decoration_runs, | 75 | &self.decoration_runs, |
| 75 | self.wrap_width, | ||
| 76 | &self.wrap_boundaries, | 76 | &self.wrap_boundaries, |
| 77 | cx, | 77 | cx, |
| 78 | )?; | 78 | )?; |
| @@ -86,7 +86,6 @@ fn paint_line( · 7 unmodified lines | |||
| 86 | layout: &LineLayout, | 86 | layout: &LineLayout, |
| 87 | line_height: Pixels, | 87 | line_height: Pixels, |
| 88 | decoration_runs: &[DecorationRun], | 88 | decoration_runs: &[DecorationRun], |
| 89 | wrap_width: Option<Pixels>, | ||
| 90 | wrap_boundaries: &[WrapBoundary], | 89 | wrap_boundaries: &[WrapBoundary], |
| 91 | cx: &mut WindowContext<'_>, | 90 | cx: &mut WindowContext<'_>, |
| 92 | ) -> Result<()> { | 91 | ) -> Result<()> { |
| @@ -97,6 +96,7 @@ fn paint_line( · 4 unmodified lines | |||
| 97 | let mut run_end = 0; | 96 | let mut run_end = 0; |
| 98 | let mut color = black(); | 97 | let mut color = black(); |
| 99 | let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None; | 98 | let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None; |
| 99 | let mut current_background: Option<(Point<Pixels>, Hsla)> = None; | ||
| 100 | let text_system = cx.text_system().clone(); | 100 | let text_system = cx.text_system().clone(); |
| 101 | let mut glyph_origin = origin; | 101 | let mut glyph_origin = origin; |
| 102 | let mut prev_glyph_position = Point::default(); | 102 | let mut prev_glyph_position = Point::default(); |
| @@ -110,12 +110,28 @@ fn paint_line( · 7 unmodified lines | |||
| 110 | 110 | ||
| 111 | if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) { | 111 | if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) { |
| 112 | wraps.next(); | 112 | wraps.next(); |
| 113 | if let Some((underline_origin, underline_style)) = current_underline.take() { | 113 | if let Some((background_origin, background_color)) = current_background.as_mut() { |
| 114 | cx.paint_quad( | ||
| 115 | Bounds { | ||
| 116 | origin: *background_origin, | ||
| 117 | size: size(glyph_origin.x - background_origin.x, line_height), | ||
| 118 | }, | ||
| 119 | Corners::default(), | ||
| 120 | *background_color, | ||
| 121 | Edges::default(), | ||
| 122 | transparent_black(), | ||
| 123 | ); | ||
| 124 | background_origin.x = origin.x; | ||
| 125 | background_origin.y += line_height; | ||
| 126 | } | ||
| 127 | if let Some((underline_origin, underline_style)) = current_underline.as_mut() { | ||
| 114 | cx.paint_underline( | 128 | cx.paint_underline( |
| 115 | underline_origin, | 129 | *underline_origin, |
| 116 | glyph_origin.x - underline_origin.x, | 130 | glyph_origin.x - underline_origin.x, |
| 117 | &underline_style, | 131 | underline_style, |
| 118 | )?; | 132 | ); |
| 133 | underline_origin.x = origin.x; | ||
| 134 | underline_origin.y += line_height; | ||
| 119 | } | 135 | } |
| 120 | 136 | ||
| 121 | glyph_origin.x = origin.x; | 137 | glyph_origin.x = origin.x; |
| @@ -123,9 +139,20 @@ fn paint_line( · 1 unmodified line | |||
| 123 | } | 139 | } |
| 124 | prev_glyph_position = glyph.position; | 140 | prev_glyph_position = glyph.position; |
| 125 | 141 | ||
| 142 | let mut finished_background: Option<(Point<Pixels>, Hsla)> = None; | ||
| 126 | let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None; | 143 | let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None; |
| 127 | if glyph.index >= run_end { | 144 | if glyph.index >= run_end { |
| 128 | if let Some(style_run) = decoration_runs.next() { | 145 | if let Some(style_run) = decoration_runs.next() { |
| 146 | if let Some((_, background_color)) = &mut current_background { | ||
| 147 | if style_run.background_color.as_ref() != Some(background_color) { | ||
| 148 | finished_background = current_background.take(); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | if let Some(run_background) = style_run.background_color { | ||
| 152 | current_background | ||
| 153 | .get_or_insert((point(glyph_origin.x, glyph_origin.y), run_background)); | ||
| 154 | } | ||
| 155 | |||
| 129 | if let Some((_, underline_style)) = &mut current_underline { | 156 | if let Some((_, underline_style)) = &mut current_underline { |
| 130 | if style_run.underline.as_ref() != Some(underline_style) { | 157 | if style_run.underline.as_ref() != Some(underline_style) { |
| 131 | finished_underline = current_underline.take(); | 158 | finished_underline = current_underline.take(); |
| @@ -135,7 +162,7 @@ fn paint_line( · 3 unmodified lines | |||
| 135 | current_underline.get_or_insert(( | 162 | current_underline.get_or_insert(( |
| 136 | point( | 163 | point( |
| 137 | glyph_origin.x, | 164 | glyph_origin.x, |
| 138 | origin.y + baseline_offset.y + (layout.descent * 0.618), | 165 | glyph_origin.y + baseline_offset.y + (layout.descent * 0.618), |
| 139 | ), | 166 | ), |
| 140 | UnderlineStyle { | 167 | UnderlineStyle { |
| 141 | color: Some(run_underline.color.unwrap_or(style_run.color)), | 168 | color: Some(run_underline.color.unwrap_or(style_run.color)), |
| @@ -149,16 +176,30 @@ fn paint_line( · 7 unmodified lines | |||
| 149 | color = style_run.color; | 176 | color = style_run.color; |
| 150 | } else { | 177 | } else { |
| 151 | run_end = layout.len; | 178 | run_end = layout.len; |
| 179 | finished_background = current_background.take(); | ||
| 152 | finished_underline = current_underline.take(); | 180 | finished_underline = current_underline.take(); |
| 153 | } | 181 | } |
| 154 | } | 182 | } |
| 155 | 183 | ||
| 184 | if let Some((background_origin, background_color)) = finished_background { | ||
| 185 | cx.paint_quad( | ||
| 186 | Bounds { | ||
| 187 | origin: background_origin, | ||
| 188 | size: size(glyph_origin.x - background_origin.x, line_height), | ||
| 189 | }, | ||
| 190 | Corners::default(), | ||
| 191 | background_color, | ||
| 192 | Edges::default(), | ||
| 193 | transparent_black(), | ||
| 194 | ); | ||
| 195 | } | ||
| 196 | |||
| 156 | if let Some((underline_origin, underline_style)) = finished_underline { | 197 | if let Some((underline_origin, underline_style)) = finished_underline { |
| 157 | cx.paint_underline( | 198 | cx.paint_underline( |
| 158 | underline_origin, | 199 | underline_origin, |
| 159 | glyph_origin.x - underline_origin.x, | 200 | glyph_origin.x - underline_origin.x, |
| 160 | &underline_style, | 201 | &underline_style, |
| 161 | )?; | 202 | ); |
| 162 | } | 203 | } |
| 163 | 204 | ||
| 164 | let max_glyph_bounds = Bounds { | 205 | let max_glyph_bounds = Bounds { |
| @@ -188,13 +229,32 @@ fn paint_line( · 23 unmodified lines | |||
| 188 | } | 229 | } |
| 189 | } | 230 | } |
| 190 | 231 | ||
| 232 | let mut last_line_end_x = origin.x + layout.width; | ||
| 233 | if let Some(boundary) = wrap_boundaries.last() { | ||
| 234 | let run = &layout.runs[boundary.run_ix]; | ||
| 235 | let glyph = &run.glyphs[boundary.glyph_ix]; | ||
| 236 | last_line_end_x -= glyph.position.x; | ||
| 237 | } | ||
| 238 | |||
| 239 | if let Some((background_origin, background_color)) = current_background.take() { | ||
| 240 | cx.paint_quad( | ||
| 241 | Bounds { | ||
| 242 | origin: background_origin, | ||
| 243 | size: size(last_line_end_x - background_origin.x, line_height), | ||
| 244 | }, | ||
| 245 | Corners::default(), | ||
| 246 | background_color, | ||
| 247 | Edges::default(), | ||
| 248 | transparent_black(), | ||
| 249 | ); | ||
| 250 | } | ||
| 251 | |||
| 191 | if let Some((underline_start, underline_style)) = current_underline.take() { | 252 | if let Some((underline_start, underline_style)) = current_underline.take() { |
| 192 | let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width); | ||
| 193 | cx.paint_underline( | 253 | cx.paint_underline( |
| 194 | underline_start, | 254 | underline_start, |
| 195 | line_end_x - underline_start.x, | 255 | last_line_end_x - underline_start.x, |
| 196 | &underline_style, | 256 | &underline_style, |
| 197 | )?; | 257 | ); |
| 198 | } | 258 | } |
| 199 | 259 | ||
| 200 | Ok(()) | 260 | Ok(()) |
| @@ -198,6 +198,41 @@ impl WrappedLineLayout { · 197 unmodified lines | |||
| 198 | pub fn runs(&self) -> &[ShapedRun] { | 198 | pub fn runs(&self) -> &[ShapedRun] { |
| 199 | &self.unwrapped_layout.runs | 199 | &self.unwrapped_layout.runs |
| 200 | } | 200 | } |
| 201 | |||
| 202 | pub fn index_for_position( | ||
| 203 | &self, | ||
| 204 | position: Point<Pixels>, | ||
| 205 | line_height: Pixels, | ||
| 206 | ) -> Option<usize> { | ||
| 207 | let wrapped_line_ix = (position.y / line_height) as usize; | ||
| 208 | |||
| 209 | let wrapped_line_start_x = if wrapped_line_ix > 0 { | ||
| 210 | let wrap_boundary_ix = wrapped_line_ix - 1; | ||
| 211 | let wrap_boundary = self.wrap_boundaries[wrap_boundary_ix]; | ||
| 212 | let run = &self.unwrapped_layout.runs[wrap_boundary.run_ix]; | ||
| 213 | run.glyphs[wrap_boundary.glyph_ix].position.x | ||
| 214 | } else { | ||
| 215 | Pixels::ZERO | ||
| 216 | }; | ||
| 217 | |||
| 218 | let wrapped_line_end_x = if wrapped_line_ix < self.wrap_boundaries.len() { | ||
| 219 | let next_wrap_boundary_ix = wrapped_line_ix; | ||
| 220 | let next_wrap_boundary = self.wrap_boundaries[next_wrap_boundary_ix]; | ||
| 221 | let run = &self.unwrapped_layout.runs[next_wrap_boundary.run_ix]; | ||
| 222 | run.glyphs[next_wrap_boundary.glyph_ix].position.x | ||
| 223 | } else { | ||
| 224 | self.unwrapped_layout.width | ||
| 225 | }; | ||
| 226 | |||
| 227 | let mut position_in_unwrapped_line = position; | ||
| 228 | position_in_unwrapped_line.x += wrapped_line_start_x; | ||
| 229 | if position_in_unwrapped_line.x > wrapped_line_end_x { | ||
| 230 | None | ||
| 231 | } else { | ||
| 232 | self.unwrapped_layout | ||
| 233 | .index_for_x(position_in_unwrapped_line.x) | ||
| 234 | } | ||
| 235 | } | ||
| 201 | } | 236 | } |
| 202 | 237 | ||
| 203 | pub(crate) struct LineLayoutCache { | 238 | pub(crate) struct LineLayoutCache { |
| @@ -1,23 +1,17 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, | 2 | private::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow, |
| 3 | BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, | 3 | Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement, |
| 4 | FocusableView, LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel, | 4 | LayoutId, Model, Pixels, Point, Render, Size, ViewContext, VisualContext, WeakModel, |
| 5 | WindowContext, | 5 | WindowContext, |
| 6 | }; | 6 | }; |
| 7 | use anyhow::{Context, Result}; | 7 | use anyhow::{Context, Result}; |
| 8 | use std::{ | 8 | use std::{ |
| 9 | any::{Any, TypeId}, | 9 | any::TypeId, |
| 10 | hash::{Hash, Hasher}, | 10 | hash::{Hash, Hasher}, |
| 11 | }; | 11 | }; |
| 12 | 12 | ||
| 13 | pub trait Render: 'static + Sized { | ||
| 14 | type Element: Element<Self> + 'static; | ||
| 15 | |||
| 16 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element; | ||
| 17 | } | ||
| 18 | |||
| 19 | pub struct View<V> { | 13 | pub struct View<V> { |
| 20 | pub(crate) model: Model<V>, | 14 | pub model: Model<V>, |
| 21 | } | 15 | } |
| 22 | 16 | ||
| 23 | impl<V> Sealed for View<V> {} | 17 | impl<V> Sealed for View<V> {} |
| @@ -65,15 +59,15 @@ impl<V: 'static> View<V> { · 41 unmodified lines | |||
| 65 | self.model.read(cx) | 59 | self.model.read(cx) |
| 66 | } | 60 | } |
| 67 | 61 | ||
| 68 | pub fn render_with<C>(&self, component: C) -> RenderViewWith<C, V> | 62 | // pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V> |
| 69 | where | 63 | // where |
| 70 | C: 'static + Component<V>, | 64 | // E: 'static + Element, |
| 71 | { | 65 | // { |
| 72 | RenderViewWith { | 66 | // RenderViewWith { |
| 73 | view: self.clone(), | 67 | // view: self.clone(), |
| 74 | component: Some(component), | 68 | // element: Some(component), |
| 75 | } | 69 | // } |
| 76 | } | 70 | // } |
| 77 | 71 | ||
| 78 | pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle | 72 | pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle |
| 79 | where | 73 | where |
| @@ -83,6 +77,24 @@ impl<V: 'static> View<V> { · 3 unmodified lines | |||
| 83 | } | 77 | } |
| 84 | } | 78 | } |
| 85 | 79 | ||
| 80 | impl<V: Render> Element for View<V> { | ||
| 81 | type State = Option<AnyElement>; | ||
| 82 | |||
| 83 | fn layout( | ||
| 84 | &mut self, | ||
| 85 | _state: Option<Self::State>, | ||
| 86 | cx: &mut WindowContext, | ||
| 87 | ) -> (LayoutId, Self::State) { | ||
| 88 | let mut element = self.update(cx, |view, cx| view.render(cx).into_any()); | ||
| 89 | let layout_id = element.layout(cx); | ||
| 90 | (layout_id, Some(element)) | ||
| 91 | } | ||
| 92 | |||
| 93 | fn paint(self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) { | ||
| 94 | element.take().unwrap().paint(cx); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 86 | impl<V> Clone for View<V> { | 98 | impl<V> Clone for View<V> { |
| 87 | fn clone(&self) -> Self { | 99 | fn clone(&self) -> Self { |
| 88 | Self { | 100 | Self { |
| @@ -105,12 +117,6 @@ impl<V> PartialEq for View<V> { · 16 unmodified lines | |||
| 105 | 117 | ||
| 106 | impl<V> Eq for View<V> {} | 118 | impl<V> Eq for View<V> {} |
| 107 | 119 | ||
| 108 | impl<V: Render, ParentViewState: 'static> Component<ParentViewState> for View<V> { | ||
| 109 | fn render(self) -> AnyElement<ParentViewState> { | ||
| 110 | AnyElement::new(AnyView::from(self)) | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | pub struct WeakView<V> { | 120 | pub struct WeakView<V> { |
| 115 | pub(crate) model: WeakModel<V>, | 121 | pub(crate) model: WeakModel<V>, |
| 116 | } | 122 | } |
| @@ -163,8 +169,8 @@ impl<V> Eq for WeakView<V> {} · 46 unmodified lines | |||
| 163 | #[derive(Clone, Debug)] | 169 | #[derive(Clone, Debug)] |
| 164 | pub struct AnyView { | 170 | pub struct AnyView { |
| 165 | model: AnyModel, | 171 | model: AnyModel, |
| 166 | layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>), | 172 | layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), |
| 167 | paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), | 173 | paint: fn(&AnyView, AnyElement, &mut WindowContext), |
| 168 | } | 174 | } |
| 169 | 175 | ||
| 170 | impl AnyView { | 176 | impl AnyView { |
| @@ -202,21 +208,15 @@ impl AnyView { · 31 unmodified lines | |||
| 202 | cx: &mut WindowContext, | 208 | cx: &mut WindowContext, |
| 203 | ) { | 209 | ) { |
| 204 | cx.with_absolute_element_offset(origin, |cx| { | 210 | cx.with_absolute_element_offset(origin, |cx| { |
| 205 | let (layout_id, mut rendered_element) = (self.layout)(self, cx); | 211 | let (layout_id, rendered_element) = (self.layout)(self, cx); |
| 206 | cx.window | 212 | cx.window |
| 207 | .layout_engine | 213 | .layout_engine |
| 208 | .compute_layout(layout_id, available_space); | 214 | .compute_layout(layout_id, available_space); |
| 209 | (self.paint)(self, &mut rendered_element, cx); | 215 | (self.paint)(self, rendered_element, cx); |
| 210 | }) | 216 | }) |
| 211 | } | 217 | } |
| 212 | } | 218 | } |
| 213 | 219 | ||
| 214 | impl<V: 'static> Component<V> for AnyView { | ||
| 215 | fn render(self) -> AnyElement<V> { | ||
| 216 | AnyElement::new(self) | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | impl<V: Render> From<View<V>> for AnyView { | 220 | impl<V: Render> From<View<V>> for AnyView { |
| 221 | fn from(value: View<V>) -> Self { | 221 | fn from(value: View<V>) -> Self { |
| 222 | AnyView { | 222 | AnyView { |
| @@ -227,37 +227,51 @@ impl<V: Render> From<View<V>> for AnyView { · 4 unmodified lines | |||
| 227 | } | 227 | } |
| 228 | } | 228 | } |
| 229 | 229 | ||
| 230 | impl<ParentViewState: 'static> Element<ParentViewState> for AnyView { | 230 | impl Element for AnyView { |
| 231 | type ElementState = Box<dyn Any>; | 231 | type State = Option<AnyElement>; |
| 232 | |||
| 233 | fn layout( | ||
| 234 | &mut self, | ||
| 235 | _state: Option<Self::State>, | ||
| 236 | cx: &mut WindowContext, | ||
| 237 | ) -> (LayoutId, Self::State) { | ||
| 238 | let (layout_id, state) = (self.layout)(self, cx); | ||
| 239 | (layout_id, Some(state)) | ||
| 240 | } | ||
| 241 | |||
| 242 | fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) { | ||
| 243 | (self.paint)(&self, state.take().unwrap(), cx) | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | impl<V: 'static + Render> IntoElement for View<V> { | ||
| 248 | type Element = View<V>; | ||
| 232 | 249 | ||
| 233 | fn element_id(&self) -> Option<ElementId> { | 250 | fn element_id(&self) -> Option<ElementId> { |
| 234 | Some(self.model.entity_id.into()) | 251 | Some(ElementId::from_entity_id(self.model.entity_id)) |
| 235 | } | 252 | } |
| 236 | 253 | ||
| 237 | fn layout( | 254 | fn into_element(self) -> Self::Element { |
| 238 | &mut self, | 255 | self |
| 239 | _view_state: &mut ParentViewState, | ||
| 240 | _element_state: Option<Self::ElementState>, | ||
| 241 | cx: &mut ViewContext<ParentViewState>, | ||
| 242 | ) -> (LayoutId, Self::ElementState) { | ||
| 243 | (self.layout)(self, cx) | ||
| 244 | } | 256 | } |
| 257 | } | ||
| 245 | 258 | ||
| 246 | fn paint( | 259 | impl IntoElement for AnyView { |
| 247 | &mut self, | 260 | type Element = Self; |
| 248 | _bounds: Bounds<Pixels>, | 261 | |
| 249 | _view_state: &mut ParentViewState, | 262 | fn element_id(&self) -> Option<ElementId> { |
| 250 | rendered_element: &mut Self::ElementState, | 263 | Some(ElementId::from_entity_id(self.model.entity_id)) |
| 251 | cx: &mut ViewContext<ParentViewState>, | 264 | } |
| 252 | ) { | 265 | |
| 253 | (self.paint)(self, rendered_element, cx) | 266 | fn into_element(self) -> Self::Element { |
| 267 | self | ||
| 254 | } | 268 | } |
| 255 | } | 269 | } |
| 256 | 270 | ||
| 257 | pub struct AnyWeakView { | 271 | pub struct AnyWeakView { |
| 258 | model: AnyWeakModel, | 272 | model: AnyWeakModel, |
| 259 | layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>), | 273 | layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), |
| 260 | paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), | 274 | paint: fn(&AnyView, AnyElement, &mut WindowContext), |
| 261 | } | 275 | } |
| 262 | 276 | ||
| 263 | impl AnyWeakView { | 277 | impl AnyWeakView { |
| @@ -271,7 +285,7 @@ impl AnyWeakView { · 7 unmodified lines | |||
| 271 | } | 285 | } |
| 272 | } | 286 | } |
| 273 | 287 | ||
| 274 | impl<V: Render> From<WeakView<V>> for AnyWeakView { | 288 | impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView { |
| 275 | fn from(view: WeakView<V>) -> Self { | 289 | fn from(view: WeakView<V>) -> Self { |
| 276 | Self { | 290 | Self { |
| 277 | model: view.model.into(), | 291 | model: view.model.into(), |
| @@ -281,97 +295,36 @@ impl<V: Render> From<WeakView<V>> for AnyWeakView { · 3 unmodified lines | |||
| 281 | } | 295 | } |
| 282 | } | 296 | } |
| 283 | 297 | ||
| 284 | // impl<T, E> Render for T | 298 | impl<T, E> Render for T |
| 285 | // where | ||
| 286 | // T: 'static + FnMut(&mut WindowContext) -> E, | ||
| 287 | // E: 'static + Send + Element<T>, | ||
| 288 | // { | ||
| 289 | // type Element = E; | ||
| 290 | |||
| 291 | // fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | ||
| 292 | // (self)(cx) | ||
| 293 | // } | ||
| 294 | // } | ||
| 295 | |||
| 296 | pub struct RenderViewWith<C, V> { | ||
| 297 | view: View<V>, | ||
| 298 | component: Option<C>, | ||
| 299 | } | ||
| 300 | |||
| 301 | impl<C, ParentViewState, ViewState> Component<ParentViewState> for RenderViewWith<C, ViewState> | ||
| 302 | where | ||
| 303 | C: 'static + Component<ViewState>, | ||
| 304 | ParentViewState: 'static, | ||
| 305 | ViewState: 'static, | ||
| 306 | { | ||
| 307 | fn render(self) -> AnyElement<ParentViewState> { | ||
| 308 | AnyElement::new(self) | ||
| 309 | } | ||
| 310 | } | ||
| 311 | |||
| 312 | impl<C, ParentViewState, ViewState> Element<ParentViewState> for RenderViewWith<C, ViewState> | ||
| 313 | where | 299 | where |
| 314 | C: 'static + Component<ViewState>, | 300 | T: 'static + FnMut(&mut WindowContext) -> E, |
| 315 | ParentViewState: 'static, | 301 | E: 'static + Send + Element, |
| 316 | ViewState: 'static, | ||
| 317 | { | 302 | { |
| 318 | type ElementState = AnyElement<ViewState>; | 303 | type Element = E; |
| 319 | |||
| 320 | fn element_id(&self) -> Option<ElementId> { | ||
| 321 | Some(self.view.entity_id().into()) | ||
| 322 | } | ||
| 323 | 304 | ||
| 324 | fn layout( | 305 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 325 | &mut self, | 306 | (self)(cx) |
| 326 | _: &mut ParentViewState, | ||
| 327 | _: Option<Self::ElementState>, | ||
| 328 | cx: &mut ViewContext<ParentViewState>, | ||
| 329 | ) -> (LayoutId, Self::ElementState) { | ||
| 330 | self.view.update(cx, |view, cx| { | ||
| 331 | let mut element = self.component.take().unwrap().render(); | ||
| 332 | let layout_id = element.layout(view, cx); | ||
| 333 | (layout_id, element) | ||
| 334 | }) | ||
| 335 | } | ||
| 336 | |||
| 337 | fn paint( | ||
| 338 | &mut self, | ||
| 339 | _: Bounds<Pixels>, | ||
| 340 | _: &mut ParentViewState, | ||
| 341 | element: &mut Self::ElementState, | ||
| 342 | cx: &mut ViewContext<ParentViewState>, | ||
| 343 | ) { | ||
| 344 | self.view.update(cx, |view, cx| element.paint(view, cx)) | ||
| 345 | } | 307 | } |
| 346 | } | 308 | } |
| 347 | 309 | ||
| 348 | mod any_view { | 310 | mod any_view { |
| 349 | use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext}; | 311 | use crate::{AnyElement, AnyView, Element, LayoutId, Render, WindowContext}; |
| 350 | use std::any::Any; | ||
| 351 | 312 | ||
| 352 | pub(crate) fn layout<V: Render>( | 313 | pub(crate) fn layout<V: 'static + Render>( |
| 353 | view: &AnyView, | 314 | view: &AnyView, |
| 354 | cx: &mut WindowContext, | 315 | cx: &mut WindowContext, |
| 355 | ) -> (LayoutId, Box<dyn Any>) { | 316 | ) -> (LayoutId, AnyElement) { |
| 356 | cx.with_element_id(Some(view.model.entity_id), |cx| { | 317 | let view = view.clone().downcast::<V>().unwrap(); |
| 357 | let view = view.clone().downcast::<V>().unwrap(); | 318 | let mut element = view.update(cx, |view, cx| view.render(cx).into_any()); |
| 358 | view.update(cx, |view, cx| { | 319 | let layout_id = element.layout(cx); |
| 359 | let mut element = AnyElement::new(view.render(cx)); | 320 | (layout_id, element) |
| 360 | let layout_id = element.layout(view, cx); | ||
| 361 | (layout_id, Box::new(element) as Box<dyn Any>) | ||
| 362 | }) | ||
| 363 | }) | ||
| 364 | } | 321 | } |
| 365 | 322 | ||
| 366 | pub(crate) fn paint<V: Render>( | 323 | pub(crate) fn paint<V: 'static + Render>( |
| 367 | view: &AnyView, | 324 | _view: &AnyView, |
| 368 | element: &mut Box<dyn Any>, | 325 | element: AnyElement, |
| 369 | cx: &mut WindowContext, | 326 | cx: &mut WindowContext, |
| 370 | ) { | 327 | ) { |
| 371 | cx.with_element_id(Some(view.model.entity_id), |cx| { | 328 | element.paint(cx); |
| 372 | let view = view.clone().downcast::<V>().unwrap(); | ||
| 373 | let element = element.downcast_mut::<AnyElement<V>>().unwrap(); | ||
| 374 | view.update(cx, |view, cx| element.paint(view, cx)) | ||
| 375 | }) | ||
| 376 | } | 329 | } |
| 377 | } | 330 | } |
| @@ -1,5 +1,5 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | key_dispatch::DispatchActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, | 2 | key_dispatch::DispatchActionListener, px, size, Action, AnyDrag, AnyView, AppContext, |
| 3 | AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, | 3 | AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, |
| 4 | DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, | 4 | DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, |
| 5 | EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, | 5 | EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, |
| @@ -187,17 +187,17 @@ impl Drop for FocusHandle { · 181 unmodified lines | |||
| 187 | 187 | ||
| 188 | /// FocusableView allows users of your view to easily | 188 | /// FocusableView allows users of your view to easily |
| 189 | /// focus it (using cx.focus_view(view)) | 189 | /// focus it (using cx.focus_view(view)) |
| 190 | pub trait FocusableView: Render { | 190 | pub trait FocusableView: 'static + Render { |
| 191 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle; | 191 | fn focus_handle(&self, cx: &AppContext) -> FocusHandle; |
| 192 | } | 192 | } |
| 193 | 193 | ||
| 194 | /// ManagedView is a view (like a Modal, Popover, Menu, etc.) | 194 | /// ManagedView is a view (like a Modal, Popover, Menu, etc.) |
| 195 | /// where the lifecycle of the view is handled by another view. | 195 | /// where the lifecycle of the view is handled by another view. |
| 196 | pub trait ManagedView: FocusableView + EventEmitter<Manager> {} | 196 | pub trait ManagedView: FocusableView + EventEmitter<DismissEvent> {} |
| 197 | 197 | ||
| 198 | impl<M: FocusableView + EventEmitter<Manager>> ManagedView for M {} | 198 | impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {} |
| 199 | 199 | ||
| 200 | pub enum Manager { | 200 | pub enum DismissEvent { |
| 201 | Dismiss, | 201 | Dismiss, |
| 202 | } | 202 | } |
| 203 | 203 | ||
| @@ -230,9 +230,15 @@ pub struct Window { · 26 unmodified lines | |||
| 230 | pub(crate) focus: Option<FocusId>, | 230 | pub(crate) focus: Option<FocusId>, |
| 231 | } | 231 | } |
| 232 | 232 | ||
| 233 | pub(crate) struct ElementStateBox { | ||
| 234 | inner: Box<dyn Any>, | ||
| 235 | #[cfg(debug_assertions)] | ||
| 236 | type_name: &'static str, | ||
| 237 | } | ||
| 238 | |||
| 233 | // #[derive(Default)] | 239 | // #[derive(Default)] |
| 234 | pub(crate) struct Frame { | 240 | pub(crate) struct Frame { |
| 235 | pub(crate) element_states: HashMap<GlobalElementId, AnyBox>, | 241 | pub(crate) element_states: HashMap<GlobalElementId, ElementStateBox>, |
| 236 | mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>, | 242 | mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>, |
| 237 | pub(crate) dispatch_tree: DispatchTree, | 243 | pub(crate) dispatch_tree: DispatchTree, |
| 238 | pub(crate) focus_listeners: Vec<AnyFocusListener>, | 244 | pub(crate) focus_listeners: Vec<AnyFocusListener>, |
| @@ -875,7 +881,7 @@ impl<'a> WindowContext<'a> { · 636 unmodified lines | |||
| 875 | origin: Point<Pixels>, | 881 | origin: Point<Pixels>, |
| 876 | width: Pixels, | 882 | width: Pixels, |
| 877 | style: &UnderlineStyle, | 883 | style: &UnderlineStyle, |
| 878 | ) -> Result<()> { | 884 | ) { |
| 879 | let scale_factor = self.scale_factor(); | 885 | let scale_factor = self.scale_factor(); |
| 880 | let height = if style.wavy { | 886 | let height = if style.wavy { |
| 881 | style.thickness * 3. | 887 | style.thickness * 3. |
| @@ -899,7 +905,6 @@ impl<'a> WindowContext<'a> { · 17 unmodified lines | |||
| 899 | wavy: style.wavy, | 905 | wavy: style.wavy, |
| 900 | }, | 906 | }, |
| 901 | ); | 907 | ); |
| 902 | Ok(()) | ||
| 903 | } | 908 | } |
| 904 | 909 | ||
| 905 | /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index. | 910 | /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index. |
| @@ -1436,6 +1441,89 @@ impl<'a> WindowContext<'a> { · 530 unmodified lines | |||
| 1436 | .dispatch_tree | 1441 | .dispatch_tree |
| 1437 | .bindings_for_action(action) | 1442 | .bindings_for_action(action) |
| 1438 | } | 1443 | } |
| 1444 | |||
| 1445 | pub fn listener_for<V: Render, E>( | ||
| 1446 | &self, | ||
| 1447 | view: &View<V>, | ||
| 1448 | f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static, | ||
| 1449 | ) -> impl Fn(&E, &mut WindowContext) + 'static { | ||
| 1450 | let view = view.downgrade(); | ||
| 1451 | move |e: &E, cx: &mut WindowContext| { | ||
| 1452 | view.update(cx, |view, cx| f(view, e, cx)).ok(); | ||
| 1453 | } | ||
| 1454 | } | ||
| 1455 | |||
| 1456 | pub fn constructor_for<V: Render, R>( | ||
| 1457 | &self, | ||
| 1458 | view: &View<V>, | ||
| 1459 | f: impl Fn(&mut V, &mut ViewContext<V>) -> R + 'static, | ||
| 1460 | ) -> impl Fn(&mut WindowContext) -> R + 'static { | ||
| 1461 | let view = view.clone(); | ||
| 1462 | move |cx: &mut WindowContext| view.update(cx, |view, cx| f(view, cx)) | ||
| 1463 | } | ||
| 1464 | |||
| 1465 | //========== ELEMENT RELATED FUNCTIONS =========== | ||
| 1466 | pub fn with_key_dispatch<R>( | ||
| 1467 | &mut self, | ||
| 1468 | context: KeyContext, | ||
| 1469 | focus_handle: Option<FocusHandle>, | ||
| 1470 | f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R, | ||
| 1471 | ) -> R { | ||
| 1472 | let window = &mut self.window; | ||
| 1473 | window | ||
| 1474 | .current_frame | ||
| 1475 | .dispatch_tree | ||
| 1476 | .push_node(context.clone()); | ||
| 1477 | if let Some(focus_handle) = focus_handle.as_ref() { | ||
| 1478 | window | ||
| 1479 | .current_frame | ||
| 1480 | .dispatch_tree | ||
| 1481 | .make_focusable(focus_handle.id); | ||
| 1482 | } | ||
| 1483 | let result = f(focus_handle, self); | ||
| 1484 | |||
| 1485 | self.window.current_frame.dispatch_tree.pop_node(); | ||
| 1486 | |||
| 1487 | result | ||
| 1488 | } | ||
| 1489 | |||
| 1490 | /// Register a focus listener for the current frame only. It will be cleared | ||
| 1491 | /// on the next frame render. You should use this method only from within elements, | ||
| 1492 | /// and we may want to enforce that better via a different context type. | ||
| 1493 | // todo!() Move this to `FrameContext` to emphasize its individuality? | ||
| 1494 | pub fn on_focus_changed( | ||
| 1495 | &mut self, | ||
| 1496 | listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static, | ||
| 1497 | ) { | ||
| 1498 | self.window | ||
| 1499 | .current_frame | ||
| 1500 | .focus_listeners | ||
| 1501 | .push(Box::new(move |event, cx| { | ||
| 1502 | listener(event, cx); | ||
| 1503 | })); | ||
| 1504 | } | ||
| 1505 | |||
| 1506 | /// Set an input handler, such as [ElementInputHandler], which interfaces with the | ||
| 1507 | /// platform to receive textual input with proper integration with concerns such | ||
| 1508 | /// as IME interactions. | ||
| 1509 | pub fn handle_input( | ||
| 1510 | &mut self, | ||
| 1511 | focus_handle: &FocusHandle, | ||
| 1512 | input_handler: impl PlatformInputHandler, | ||
| 1513 | ) { | ||
| 1514 | if focus_handle.is_focused(self) { | ||
| 1515 | self.window | ||
| 1516 | .platform_window | ||
| 1517 | .set_input_handler(Box::new(input_handler)); | ||
| 1518 | } | ||
| 1519 | } | ||
| 1520 | |||
| 1521 | pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) { | ||
| 1522 | let mut this = self.to_async(); | ||
| 1523 | self.window | ||
| 1524 | .platform_window | ||
| 1525 | .on_should_close(Box::new(move || this.update(|_, cx| f(cx)).unwrap_or(true))) | ||
| 1526 | } | ||
| 1439 | } | 1527 | } |
| 1440 | 1528 | ||
| 1441 | impl Context for WindowContext<'_> { | 1529 | impl Context for WindowContext<'_> { |
| @@ -1559,7 +1647,7 @@ impl VisualContext for WindowContext<'_> { · 117 unmodified lines | |||
| 1559 | build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, | 1647 | build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, |
| 1560 | ) -> Self::Result<View<V>> | 1648 | ) -> Self::Result<View<V>> |
| 1561 | where | 1649 | where |
| 1562 | V: Render, | 1650 | V: 'static + Render, |
| 1563 | { | 1651 | { |
| 1564 | let slot = self.app.entities.reserve(); | 1652 | let slot = self.app.entities.reserve(); |
| 1565 | let view = View { | 1653 | let view = View { |
| @@ -1582,7 +1670,7 @@ impl VisualContext for WindowContext<'_> { · 16 unmodified lines | |||
| 1582 | where | 1670 | where |
| 1583 | V: ManagedView, | 1671 | V: ManagedView, |
| 1584 | { | 1672 | { |
| 1585 | self.update_view(view, |_, cx| cx.emit(Manager::Dismiss)) | 1673 | self.update_view(view, |_, cx| cx.emit(DismissEvent::Dismiss)) |
| 1586 | } | 1674 | } |
| 1587 | } | 1675 | } |
| 1588 | 1676 | ||
| @@ -1617,6 +1705,10 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 28 unmodified lines | |||
| 1617 | self.borrow_mut() | 1705 | self.borrow_mut() |
| 1618 | } | 1706 | } |
| 1619 | 1707 | ||
| 1708 | fn app(&self) -> &AppContext { | ||
| 1709 | self.borrow() | ||
| 1710 | } | ||
| 1711 | |||
| 1620 | fn window(&self) -> &Window { | 1712 | fn window(&self) -> &Window { |
| 1621 | self.borrow() | 1713 | self.borrow() |
| 1622 | } | 1714 | } |
| @@ -1667,6 +1759,24 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 44 unmodified lines | |||
| 1667 | } | 1759 | } |
| 1668 | } | 1760 | } |
| 1669 | 1761 | ||
| 1762 | /// Invoke the given function with the content mask reset to that | ||
| 1763 | /// of the window. | ||
| 1764 | fn break_content_mask<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { | ||
| 1765 | let mask = ContentMask { | ||
| 1766 | bounds: Bounds { | ||
| 1767 | origin: Point::default(), | ||
| 1768 | size: self.window().viewport_size, | ||
| 1769 | }, | ||
| 1770 | }; | ||
| 1771 | self.window_mut() | ||
| 1772 | .current_frame | ||
| 1773 | .content_mask_stack | ||
| 1774 | .push(mask); | ||
| 1775 | let result = f(self); | ||
| 1776 | self.window_mut().current_frame.content_mask_stack.pop(); | ||
| 1777 | result | ||
| 1778 | } | ||
| 1779 | |||
| 1670 | /// Update the global element offset relative to the current offset. This is used to implement | 1780 | /// Update the global element offset relative to the current offset. This is used to implement |
| 1671 | /// scrolling. | 1781 | /// scrolling. |
| 1672 | fn with_element_offset<R>( | 1782 | fn with_element_offset<R>( |
| @@ -1735,10 +1845,37 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 62 unmodified lines | |||
| 1735 | .remove(&global_id) | 1845 | .remove(&global_id) |
| 1736 | }) | 1846 | }) |
| 1737 | { | 1847 | { |
| 1848 | let ElementStateBox { | ||
| 1849 | inner, | ||
| 1850 | |||
| 1851 | #[cfg(debug_assertions)] | ||
| 1852 | type_name | ||
| 1853 | } = any; | ||
| 1738 | // Using the extra inner option to avoid needing to reallocate a new box. | 1854 | // Using the extra inner option to avoid needing to reallocate a new box. |
| 1739 | let mut state_box = any | 1855 | let mut state_box = inner |
| 1740 | .downcast::<Option<S>>() | 1856 | .downcast::<Option<S>>() |
| 1741 | .expect("invalid element state type for id"); | 1857 | .map_err(|_| { |
| 1858 | #[cfg(debug_assertions)] | ||
| 1859 | { | ||
| 1860 | anyhow!( | ||
| 1861 | "invalid element state type for id, requested_type {:?}, actual type: {:?}", | ||
| 1862 | std::any::type_name::<S>(), | ||
| 1863 | type_name | ||
| 1864 | ) | ||
| 1865 | } | ||
| 1866 | |||
| 1867 | #[cfg(not(debug_assertions))] | ||
| 1868 | { | ||
| 1869 | anyhow!( | ||
| 1870 | "invalid element state type for id, requested_type {:?}", | ||
| 1871 | std::any::type_name::<S>(), | ||
| 1872 | ) | ||
| 1873 | } | ||
| 1874 | }) | ||
| 1875 | .unwrap(); | ||
| 1876 | |||
| 1877 | // Actual: Option<AnyElement> <- View | ||
| 1878 | // Requested: () <- AnyElemet | ||
| 1742 | let state = state_box | 1879 | let state = state_box |
| 1743 | .take() | 1880 | .take() |
| 1744 | .expect("element state is already on the stack"); | 1881 | .expect("element state is already on the stack"); |
| @@ -1747,14 +1884,27 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 2 unmodified lines | |||
| 1747 | cx.window_mut() | 1884 | cx.window_mut() |
| 1748 | .current_frame | 1885 | .current_frame |
| 1749 | .element_states | 1886 | .element_states |
| 1750 | .insert(global_id, state_box); | 1887 | .insert(global_id, ElementStateBox { |
| 1888 | inner: state_box, | ||
| 1889 | |||
| 1890 | #[cfg(debug_assertions)] | ||
| 1891 | type_name | ||
| 1892 | }); | ||
| 1751 | result | 1893 | result |
| 1752 | } else { | 1894 | } else { |
| 1753 | let (result, state) = f(None, cx); | 1895 | let (result, state) = f(None, cx); |
| 1754 | cx.window_mut() | 1896 | cx.window_mut() |
| 1755 | .current_frame | 1897 | .current_frame |
| 1756 | .element_states | 1898 | .element_states |
| 1757 | .insert(global_id, Box::new(Some(state))); | 1899 | .insert(global_id, |
| 1900 | ElementStateBox { | ||
| 1901 | inner: Box::new(Some(state)), | ||
| 1902 | |||
| 1903 | #[cfg(debug_assertions)] | ||
| 1904 | type_name: std::any::type_name::<S>() | ||
| 1905 | } | ||
| 1906 | |||
| 1907 | ); | ||
| 1758 | result | 1908 | result |
| 1759 | } | 1909 | } |
| 1760 | }) | 1910 | }) |
| @@ -2124,49 +2274,6 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 363 unmodified lines | |||
| 2124 | ) | 2274 | ) |
| 2125 | } | 2275 | } |
| 2126 | 2276 | ||
| 2127 | /// Register a focus listener for the current frame only. It will be cleared | ||
| 2128 | /// on the next frame render. You should use this method only from within elements, | ||
| 2129 | /// and we may want to enforce that better via a different context type. | ||
| 2130 | // todo!() Move this to `FrameContext` to emphasize its individuality? | ||
| 2131 | pub fn on_focus_changed( | ||
| 2132 | &mut self, | ||
| 2133 | listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static, | ||
| 2134 | ) { | ||
| 2135 | let handle = self.view().downgrade(); | ||
| 2136 | self.window | ||
| 2137 | .current_frame | ||
| 2138 | .focus_listeners | ||
| 2139 | .push(Box::new(move |event, cx| { | ||
| 2140 | handle | ||
| 2141 | .update(cx, |view, cx| listener(view, event, cx)) | ||
| 2142 | .log_err(); | ||
| 2143 | })); | ||
| 2144 | } | ||
| 2145 | |||
| 2146 | pub fn with_key_dispatch<R>( | ||
| 2147 | &mut self, | ||
| 2148 | context: KeyContext, | ||
| 2149 | focus_handle: Option<FocusHandle>, | ||
| 2150 | f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R, | ||
| 2151 | ) -> R { | ||
| 2152 | let window = &mut self.window; | ||
| 2153 | window | ||
| 2154 | .current_frame | ||
| 2155 | .dispatch_tree | ||
| 2156 | .push_node(context.clone()); | ||
| 2157 | if let Some(focus_handle) = focus_handle.as_ref() { | ||
| 2158 | window | ||
| 2159 | .current_frame | ||
| 2160 | .dispatch_tree | ||
| 2161 | .make_focusable(focus_handle.id); | ||
| 2162 | } | ||
| 2163 | let result = f(focus_handle, self); | ||
| 2164 | |||
| 2165 | self.window.current_frame.dispatch_tree.pop_node(); | ||
| 2166 | |||
| 2167 | result | ||
| 2168 | } | ||
| 2169 | |||
| 2170 | pub fn spawn<Fut, R>( | 2277 | pub fn spawn<Fut, R>( |
| 2171 | &mut self, | 2278 | &mut self, |
| 2172 | f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut, | 2279 | f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut, |
| @@ -2243,21 +2350,6 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 70 unmodified lines | |||
| 2243 | }); | 2350 | }); |
| 2244 | } | 2351 | } |
| 2245 | 2352 | ||
| 2246 | /// Set an input handler, such as [ElementInputHandler], which interfaces with the | ||
| 2247 | /// platform to receive textual input with proper integration with concerns such | ||
| 2248 | /// as IME interactions. | ||
| 2249 | pub fn handle_input( | ||
| 2250 | &mut self, | ||
| 2251 | focus_handle: &FocusHandle, | ||
| 2252 | input_handler: impl PlatformInputHandler, | ||
| 2253 | ) { | ||
| 2254 | if focus_handle.is_focused(self) { | ||
| 2255 | self.window | ||
| 2256 | .platform_window | ||
| 2257 | .set_input_handler(Box::new(input_handler)); | ||
| 2258 | } | ||
| 2259 | } | ||
| 2260 | |||
| 2261 | pub fn emit<Evt>(&mut self, event: Evt) | 2353 | pub fn emit<Evt>(&mut self, event: Evt) |
| 2262 | where | 2354 | where |
| 2263 | Evt: 'static, | 2355 | Evt: 'static, |
| @@ -2282,7 +2374,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 18 unmodified lines | |||
| 2282 | where | 2374 | where |
| 2283 | V: ManagedView, | 2375 | V: ManagedView, |
| 2284 | { | 2376 | { |
| 2285 | self.defer(|_, cx| cx.emit(Manager::Dismiss)) | 2377 | self.defer(|_, cx| cx.emit(DismissEvent::Dismiss)) |
| 2378 | } | ||
| 2379 | |||
| 2380 | pub fn listener<E>( | ||
| 2381 | &self, | ||
| 2382 | f: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static, | ||
| 2383 | ) -> impl Fn(&E, &mut WindowContext) + 'static { | ||
| 2384 | let view = self.view().downgrade(); | ||
| 2385 | move |e: &E, cx: &mut WindowContext| { | ||
| 2386 | view.update(cx, |view, cx| f(view, e, cx)).ok(); | ||
| 2387 | } | ||
| 2286 | } | 2388 | } |
| 2287 | } | 2389 | } |
| 2288 | 2390 | ||
| @@ -2355,7 +2457,7 @@ impl<V: 'static> VisualContext for ViewContext<'_, V> { · 66 unmodified lines | |||
| 2355 | build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, | 2457 | build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, |
| 2356 | ) -> Self::Result<View<W>> | 2458 | ) -> Self::Result<View<W>> |
| 2357 | where | 2459 | where |
| 2358 | W: Render, | 2460 | W: 'static + Render, |
| 2359 | { | 2461 | { |
| 2360 | self.window_cx.replace_root_view(build_view) | 2462 | self.window_cx.replace_root_view(build_view) |
| 2361 | } | 2463 | } |
| @@ -2567,6 +2669,12 @@ pub enum ElementId { · 205 unmodified lines | |||
| 2567 | FocusHandle(FocusId), | 2669 | FocusHandle(FocusId), |
| 2568 | } | 2670 | } |
| 2569 | 2671 | ||
| 2672 | impl ElementId { | ||
| 2673 | pub(crate) fn from_entity_id(entity_id: EntityId) -> Self { | ||
| 2674 | ElementId::View(entity_id) | ||
| 2675 | } | ||
| 2676 | } | ||
| 2677 | |||
| 2570 | impl TryInto<SharedString> for ElementId { | 2678 | impl TryInto<SharedString> for ElementId { |
| 2571 | type Error = anyhow::Error; | 2679 | type Error = anyhow::Error; |
| 2572 | 2680 | ||
| @@ -2579,12 +2687,6 @@ impl TryInto<SharedString> for ElementId { · 6 unmodified lines | |||
| 2579 | } | 2687 | } |
| 2580 | } | 2688 | } |
| 2581 | 2689 | ||
| 2582 | impl From<EntityId> for ElementId { | ||
| 2583 | fn from(id: EntityId) -> Self { | ||
| 2584 | ElementId::View(id) | ||
| 2585 | } | ||
| 2586 | } | ||
| 2587 | |||
| 2588 | impl From<usize> for ElementId { | 2690 | impl From<usize> for ElementId { |
| 2589 | fn from(id: usize) -> Self { | 2691 | fn from(id: usize) -> Self { |
| 2590 | ElementId::Integer(id) | 2692 | ElementId::Integer(id) |
| @@ -1,66 +0,0 @@ | |||
| 1 | use proc_macro::TokenStream; | ||
| 2 | use quote::quote; | ||
| 3 | use syn::{parse_macro_input, parse_quote, DeriveInput}; | ||
| 4 | |||
| 5 | pub fn derive_component(input: TokenStream) -> TokenStream { | ||
| 6 | let ast = parse_macro_input!(input as DeriveInput); | ||
| 7 | let name = &ast.ident; | ||
| 8 | |||
| 9 | let mut trait_generics = ast.generics.clone(); | ||
| 10 | let view_type = if let Some(view_type) = specified_view_type(&ast) { | ||
| 11 | quote! { #view_type } | ||
| 12 | } else { | ||
| 13 | if let Some(first_type_param) = ast.generics.params.iter().find_map(|param| { | ||
| 14 | if let syn::GenericParam::Type(type_param) = param { | ||
| 15 | Some(type_param.ident.clone()) | ||
| 16 | } else { | ||
| 17 | None | ||
| 18 | } | ||
| 19 | }) { | ||
| 20 | quote! { #first_type_param } | ||
| 21 | } else { | ||
| 22 | trait_generics.params.push(parse_quote! { V: 'static }); | ||
| 23 | quote! { V } | ||
| 24 | } | ||
| 25 | }; | ||
| 26 | |||
| 27 | let (impl_generics, _, where_clause) = trait_generics.split_for_impl(); | ||
| 28 | let (_, ty_generics, _) = ast.generics.split_for_impl(); | ||
| 29 | |||
| 30 | let expanded = quote! { | ||
| 31 | impl #impl_generics gpui::Component<#view_type> for #name #ty_generics #where_clause { | ||
| 32 | fn render(self) -> gpui::AnyElement<#view_type> { | ||
| 33 | (move |view_state: &mut #view_type, cx: &mut gpui::ViewContext<'_, #view_type>| self.render(view_state, cx)) | ||
| 34 | .render() | ||
| 35 | } | ||
| 36 | } | ||
| 37 | }; | ||
| 38 | |||
| 39 | TokenStream::from(expanded) | ||
| 40 | } | ||
| 41 | |||
| 42 | fn specified_view_type(ast: &DeriveInput) -> Option<proc_macro2::Ident> { | ||
| 43 | let component_attr = ast | ||
| 44 | .attrs | ||
| 45 | .iter() | ||
| 46 | .find(|attr| attr.path.is_ident("component"))?; | ||
| 47 | |||
| 48 | if let Ok(syn::Meta::List(meta_list)) = component_attr.parse_meta() { | ||
| 49 | meta_list.nested.iter().find_map(|nested| { | ||
| 50 | if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = nested { | ||
| 51 | if nv.path.is_ident("view_type") { | ||
| 52 | if let syn::Lit::Str(lit_str) = &nv.lit { | ||
| 53 | return Some( | ||
| 54 | lit_str | ||
| 55 | .parse::<syn::Ident>() | ||
| 56 | .expect("Failed to parse view_type"), | ||
| 57 | ); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | None | ||
| 62 | }) | ||
| 63 | } else { | ||
| 64 | None | ||
| 65 | } | ||
| 66 | } | ||
| @@ -0,0 +1,27 @@ | |||
| 1 | use proc_macro::TokenStream; | ||
| 2 | use quote::quote; | ||
| 3 | use syn::{parse_macro_input, DeriveInput}; | ||
| 4 | |||
| 5 | pub fn derive_into_element(input: TokenStream) -> TokenStream { | ||
| 6 | let ast = parse_macro_input!(input as DeriveInput); | ||
| 7 | let type_name = &ast.ident; | ||
| 8 | let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl(); | ||
| 9 | |||
| 10 | let gen = quote! { | ||
| 11 | impl #impl_generics gpui::IntoElement for #type_name #type_generics | ||
| 12 | #where_clause | ||
| 13 | { | ||
| 14 | type Element = gpui::Component<Self>; | ||
| 15 | |||
| 16 | fn element_id(&self) -> Option<ElementId> { | ||
| 17 | None | ||
| 18 | } | ||
| 19 | |||
| 20 | fn into_element(self) -> Self::Element { | ||
| 21 | gpui::Component::new(self) | ||
| 22 | } | ||
| 23 | } | ||
| 24 | }; | ||
| 25 | |||
| 26 | gen.into() | ||
| 27 | } | ||
| @@ -1,16 +1,11 @@ | |||
| 1 | mod action; | 1 | mod action; |
| 2 | mod derive_component; | 2 | mod derive_into_element; |
| 3 | mod register_action; | 3 | mod register_action; |
| 4 | mod style_helpers; | 4 | mod style_helpers; |
| 5 | mod test; | 5 | mod test; |
| 6 | 6 | ||
| 7 | use proc_macro::TokenStream; | 7 | use proc_macro::TokenStream; |
| 8 | 8 | ||
| 9 | #[proc_macro] | ||
| 10 | pub fn style_helpers(args: TokenStream) -> TokenStream { | ||
| 11 | style_helpers::style_helpers(args) | ||
| 12 | } | ||
| 13 | |||
| 14 | #[proc_macro_derive(Action)] | 9 | #[proc_macro_derive(Action)] |
| 15 | pub fn action(input: TokenStream) -> TokenStream { | 10 | pub fn action(input: TokenStream) -> TokenStream { |
| 16 | action::action(input) | 11 | action::action(input) |
| @@ -21,9 +16,14 @@ pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream { · 4 unmodified lines | |||
| 21 | register_action::register_action_macro(attr, item) | 16 | register_action::register_action_macro(attr, item) |
| 22 | } | 17 | } |
| 23 | 18 | ||
| 24 | #[proc_macro_derive(Component, attributes(component))] | 19 | #[proc_macro_derive(IntoElement)] |
| 25 | pub fn derive_component(input: TokenStream) -> TokenStream { | 20 | pub fn derive_into_element(input: TokenStream) -> TokenStream { |
| 26 | derive_component::derive_component(input) | 21 | derive_into_element::derive_into_element(input) |
| 22 | } | ||
| 23 | |||
| 24 | #[proc_macro] | ||
| 25 | pub fn style_helpers(input: TokenStream) -> TokenStream { | ||
| 26 | style_helpers::style_helpers(input) | ||
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | #[proc_macro_attribute] | 29 | #[proc_macro_attribute] |
| @@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); · 10 unmodified lines | |||
| 11 | const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); | 11 | const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); |
| 12 | 12 | ||
| 13 | impl HighlightMap { | 13 | impl HighlightMap { |
| 14 | pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self { | 14 | pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self { |
| 15 | // For each capture name in the highlight query, find the longest | 15 | // For each capture name in the highlight query, find the longest |
| 16 | // key in the theme's syntax styles that matches all of the | 16 | // key in the theme's syntax styles that matches all of the |
| 17 | // dot-separated components of the capture name. | 17 | // dot-separated components of the capture name. |
| @@ -98,9 +98,9 @@ mod tests { · 80 unmodified lines | |||
| 98 | ); | 98 | ); |
| 99 | 99 | ||
| 100 | let capture_names = &[ | 100 | let capture_names = &[ |
| 101 | "function.special".to_string(), | 101 | "function.special", |
| 102 | "function.async.rust".to_string(), | 102 | "function.async.rust", |
| 103 | "variable.builtin.self".to_string(), | 103 | "variable.builtin.self", |
| 104 | ]; | 104 | ]; |
| 105 | 105 | ||
| 106 | let map = HighlightMap::new(capture_names, &theme); | 106 | let map = HighlightMap::new(capture_names, &theme); |
| @@ -1383,7 +1383,7 @@ impl Language { · 1382 unmodified lines | |||
| 1383 | let query = Query::new(self.grammar_mut().ts_language, source)?; | 1383 | let query = Query::new(self.grammar_mut().ts_language, source)?; |
| 1384 | 1384 | ||
| 1385 | let mut override_configs_by_id = HashMap::default(); | 1385 | let mut override_configs_by_id = HashMap::default(); |
| 1386 | for (ix, name) in query.capture_names().iter().enumerate() { | 1386 | for (ix, name) in query.capture_names().iter().copied().enumerate() { |
| 1387 | if !name.starts_with('_') { | 1387 | if !name.starts_with('_') { |
| 1388 | let value = self.config.overrides.remove(name).unwrap_or_default(); | 1388 | let value = self.config.overrides.remove(name).unwrap_or_default(); |
| 1389 | for server_name in &value.opt_into_language_servers { | 1389 | for server_name in &value.opt_into_language_servers { |
| @@ -1396,7 +1396,7 @@ impl Language { · 6 unmodified lines | |||
| 1396 | } | 1396 | } |
| 1397 | } | 1397 | } |
| 1398 | 1398 | ||
| 1399 | override_configs_by_id.insert(ix as u32, (name.clone(), value)); | 1399 | override_configs_by_id.insert(ix as u32, (name.into(), value)); |
| 1400 | } | 1400 | } |
| 1401 | } | 1401 | } |
| 1402 | 1402 | ||
| @@ -1300,7 +1300,7 @@ fn assert_capture_ranges( · 1299 unmodified lines | |||
| 1300 | .collect::<Vec<_>>(); | 1300 | .collect::<Vec<_>>(); |
| 1301 | for capture in captures { | 1301 | for capture in captures { |
| 1302 | let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; | 1302 | let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; |
| 1303 | if highlight_query_capture_names.contains(&name.as_str()) { | 1303 | if highlight_query_capture_names.contains(&name) { |
| 1304 | actual_ranges.push(capture.node.byte_range()); | 1304 | actual_ranges.push(capture.node.byte_range()); |
| 1305 | } | 1305 | } |
| 1306 | } | 1306 | } |
| @@ -7,6 +7,7 @@ pub use crate::{ · 6 unmodified lines | |||
| 7 | use crate::{ | 7 | use crate::{ |
| 8 | diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, | 8 | diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, |
| 9 | language_settings::{language_settings, LanguageSettings}, | 9 | language_settings::{language_settings, LanguageSettings}, |
| 10 | markdown::parse_markdown, | ||
| 10 | outline::OutlineItem, | 11 | outline::OutlineItem, |
| 11 | syntax_map::{ | 12 | syntax_map::{ |
| 12 | SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, | 13 | SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, |
| @@ -155,12 +156,52 @@ pub struct Diagnostic { · 142 unmodified lines | |||
| 155 | pub is_unnecessary: bool, | 156 | pub is_unnecessary: bool, |
| 156 | } | 157 | } |
| 157 | 158 | ||
| 159 | pub async fn prepare_completion_documentation( | ||
| 160 | documentation: &lsp::Documentation, | ||
| 161 | language_registry: &Arc<LanguageRegistry>, | ||
| 162 | language: Option<Arc<Language>>, | ||
| 163 | ) -> Documentation { | ||
| 164 | match documentation { | ||
| 165 | lsp::Documentation::String(text) => { | ||
| 166 | if text.lines().count() <= 1 { | ||
| 167 | Documentation::SingleLine(text.clone()) | ||
| 168 | } else { | ||
| 169 | Documentation::MultiLinePlainText(text.clone()) | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { | ||
| 174 | lsp::MarkupKind::PlainText => { | ||
| 175 | if value.lines().count() <= 1 { | ||
| 176 | Documentation::SingleLine(value.clone()) | ||
| 177 | } else { | ||
| 178 | Documentation::MultiLinePlainText(value.clone()) | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | lsp::MarkupKind::Markdown => { | ||
| 183 | let parsed = parse_markdown(value, language_registry, language).await; | ||
| 184 | Documentation::MultiLineMarkdown(parsed) | ||
| 185 | } | ||
| 186 | }, | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | #[derive(Clone, Debug)] | ||
| 191 | pub enum Documentation { | ||
| 192 | Undocumented, | ||
| 193 | SingleLine(String), | ||
| 194 | MultiLinePlainText(String), | ||
| 195 | MultiLineMarkdown(ParsedMarkdown), | ||
| 196 | } | ||
| 197 | |||
| 158 | #[derive(Clone, Debug)] | 198 | #[derive(Clone, Debug)] |
| 159 | pub struct Completion { | 199 | pub struct Completion { |
| 160 | pub old_range: Range<Anchor>, | 200 | pub old_range: Range<Anchor>, |
| 161 | pub new_text: String, | 201 | pub new_text: String, |
| 162 | pub label: CodeLabel, | 202 | pub label: CodeLabel, |
| 163 | pub server_id: LanguageServerId, | 203 | pub server_id: LanguageServerId, |
| 204 | pub documentation: Option<Documentation>, | ||
| 164 | pub lsp_completion: lsp::CompletionItem, | 205 | pub lsp_completion: lsp::CompletionItem, |
| 165 | } | 206 | } |
| 166 | 207 | ||
| @@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); · 10 unmodified lines | |||
| 11 | const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); | 11 | const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); |
| 12 | 12 | ||
| 13 | impl HighlightMap { | 13 | impl HighlightMap { |
| 14 | pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self { | 14 | pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self { |
| 15 | // For each capture name in the highlight query, find the longest | 15 | // For each capture name in the highlight query, find the longest |
| 16 | // key in the theme's syntax styles that matches all of the | 16 | // key in the theme's syntax styles that matches all of the |
| 17 | // dot-separated components of the capture name. | 17 | // dot-separated components of the capture name. |
| @@ -100,9 +100,9 @@ mod tests { · 82 unmodified lines | |||
| 100 | }; | 100 | }; |
| 101 | 101 | ||
| 102 | let capture_names = &[ | 102 | let capture_names = &[ |
| 103 | "function.special".to_string(), | 103 | "function.special", |
| 104 | "function.async.rust".to_string(), | 104 | "function.async.rust", |
| 105 | "variable.builtin.self".to_string(), | 105 | "variable.builtin.self", |
| 106 | ]; | 106 | ]; |
| 107 | 107 | ||
| 108 | let map = HighlightMap::new(capture_names, &theme); | 108 | let map = HighlightMap::new(capture_names, &theme); |
| @@ -1391,7 +1391,7 @@ impl Language { · 1390 unmodified lines | |||
| 1391 | let mut override_configs_by_id = HashMap::default(); | 1391 | let mut override_configs_by_id = HashMap::default(); |
| 1392 | for (ix, name) in query.capture_names().iter().enumerate() { | 1392 | for (ix, name) in query.capture_names().iter().enumerate() { |
| 1393 | if !name.starts_with('_') { | 1393 | if !name.starts_with('_') { |
| 1394 | let value = self.config.overrides.remove(name).unwrap_or_default(); | 1394 | let value = self.config.overrides.remove(*name).unwrap_or_default(); |
| 1395 | for server_name in &value.opt_into_language_servers { | 1395 | for server_name in &value.opt_into_language_servers { |
| 1396 | if !self | 1396 | if !self |
| 1397 | .config | 1397 | .config |
| @@ -1402,7 +1402,7 @@ impl Language { · 4 unmodified lines | |||
| 1402 | } | 1402 | } |
| 1403 | } | 1403 | } |
| 1404 | 1404 | ||
| 1405 | override_configs_by_id.insert(ix as u32, (name.clone(), value)); | 1405 | override_configs_by_id.insert(ix as u32, (name.to_string(), value)); |
| 1406 | } | 1406 | } |
| 1407 | } | 1407 | } |
| 1408 | 1408 | ||
| @@ -482,6 +482,7 @@ pub async fn deserialize_completion( · 481 unmodified lines | |||
| 482 | lsp_completion.filter_text.as_deref(), | 482 | lsp_completion.filter_text.as_deref(), |
| 483 | ) | 483 | ) |
| 484 | }), | 484 | }), |
| 485 | documentation: None, | ||
| 485 | server_id: LanguageServerId(completion.server_id as usize), | 486 | server_id: LanguageServerId(completion.server_id as usize), |
| 486 | lsp_completion, | 487 | lsp_completion, |
| 487 | }) | 488 | }) |
| @@ -1300,7 +1300,7 @@ fn assert_capture_ranges( · 1299 unmodified lines | |||
| 1300 | .collect::<Vec<_>>(); | 1300 | .collect::<Vec<_>>(); |
| 1301 | for capture in captures { | 1301 | for capture in captures { |
| 1302 | let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; | 1302 | let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; |
| 1303 | if highlight_query_capture_names.contains(&name.as_str()) { | 1303 | if highlight_query_capture_names.contains(&name) { |
| 1304 | actual_ranges.push(capture.node.byte_range()); | 1304 | actual_ranges.push(capture.node.byte_range()); |
| 1305 | } | 1305 | } |
| 1306 | } | 1306 | } |
| @@ -61,12 +61,14 @@ fn build_bridge(swift_target: &SwiftTarget) { · 60 unmodified lines | |||
| 61 | 61 | ||
| 62 | let swift_package_root = swift_package_root(); | 62 | let swift_package_root = swift_package_root(); |
| 63 | let swift_target_folder = swift_target_folder(); | 63 | let swift_target_folder = swift_target_folder(); |
| 64 | let swift_cache_folder = swift_cache_folder(); | ||
| 64 | if !Command::new("swift") | 65 | if !Command::new("swift") |
| 65 | .arg("build") | 66 | .arg("build") |
| 66 | .arg("--disable-automatic-resolution") | 67 | .arg("--disable-automatic-resolution") |
| 67 | .args(["--configuration", &env::var("PROFILE").unwrap()]) | 68 | .args(["--configuration", &env::var("PROFILE").unwrap()]) |
| 68 | .args(["--triple", &swift_target.target.triple]) | 69 | .args(["--triple", &swift_target.target.triple]) |
| 69 | .args(["--build-path".into(), swift_target_folder]) | 70 | .args(["--build-path".into(), swift_target_folder]) |
| 71 | .args(["--cache-path".into(), swift_cache_folder]) | ||
| 70 | .current_dir(&swift_package_root) | 72 | .current_dir(&swift_package_root) |
| 71 | .status() | 73 | .status() |
| 72 | .unwrap() | 74 | .unwrap() |
| @@ -133,9 +135,17 @@ fn swift_package_root() -> PathBuf { · 60 unmodified lines | |||
| 133 | } | 135 | } |
| 134 | 136 | ||
| 135 | fn swift_target_folder() -> PathBuf { | 137 | fn swift_target_folder() -> PathBuf { |
| 138 | let target = env::var("TARGET").unwrap(); | ||
| 136 | env::current_dir() | 139 | env::current_dir() |
| 137 | .unwrap() | 140 | .unwrap() |
| 138 | .join(format!("../../target/{SWIFT_PACKAGE_NAME}")) | 141 | .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_target")) |
| 142 | } | ||
| 143 | |||
| 144 | fn swift_cache_folder() -> PathBuf { | ||
| 145 | let target = env::var("TARGET").unwrap(); | ||
| 146 | env::current_dir() | ||
| 147 | .unwrap() | ||
| 148 | .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_cache")) | ||
| 139 | } | 149 | } |
| 140 | 150 | ||
| 141 | fn copy_dir(source: &Path, destination: &Path) { | 151 | fn copy_dir(source: &Path, destination: &Path) { |
| @@ -73,6 +73,7 @@ impl RealNodeRuntime { · 72 unmodified lines | |||
| 73 | let npm_file = node_dir.join("bin/npm"); | 73 | let npm_file = node_dir.join("bin/npm"); |
| 74 | 74 | ||
| 75 | let result = Command::new(&node_binary) | 75 | let result = Command::new(&node_binary) |
| 76 | .env_clear() | ||
| 76 | .arg(npm_file) | 77 | .arg(npm_file) |
| 77 | .arg("--version") | 78 | .arg("--version") |
| 78 | .stdin(Stdio::null()) | 79 | .stdin(Stdio::null()) |
| @@ -149,6 +150,7 @@ impl NodeRuntime for RealNodeRuntime { · 70 unmodified lines | |||
| 149 | } | 150 | } |
| 150 | 151 | ||
| 151 | let mut command = Command::new(node_binary); | 152 | let mut command = Command::new(node_binary); |
| 153 | command.env_clear(); | ||
| 152 | command.env("PATH", env_path); | 154 | command.env("PATH", env_path); |
| 153 | command.arg(npm_file).arg(subcommand); | 155 | command.arg(npm_file).arg(subcommand); |
| 154 | command.args(["--cache".into(), installation_path.join("cache")]); | 156 | command.args(["--cache".into(), installation_path.join("cache")]); |
| @@ -200,11 +202,11 @@ impl NodeRuntime for RealNodeRuntime { · 45 unmodified lines | |||
| 200 | &[ | 202 | &[ |
| 201 | name, | 203 | name, |
| 202 | "--json", | 204 | "--json", |
| 203 | "-fetch-retry-mintimeout", | 205 | "--fetch-retry-mintimeout", |
| 204 | "2000", | 206 | "2000", |
| 205 | "-fetch-retry-maxtimeout", | 207 | "--fetch-retry-maxtimeout", |
| 206 | "5000", | 208 | "5000", |
| 207 | "-fetch-timeout", | 209 | "--fetch-timeout", |
| 208 | "5000", | 210 | "5000", |
| 209 | ], | 211 | ], |
| 210 | ) | 212 | ) |
| @@ -229,11 +231,11 @@ impl NodeRuntime for RealNodeRuntime { · 18 unmodified lines | |||
| 229 | 231 | ||
| 230 | let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect(); | 232 | let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect(); |
| 231 | arguments.extend_from_slice(&[ | 233 | arguments.extend_from_slice(&[ |
| 232 | "-fetch-retry-mintimeout", | 234 | "--fetch-retry-mintimeout", |
| 233 | "2000", | 235 | "2000", |
| 234 | "-fetch-retry-maxtimeout", | 236 | "--fetch-retry-maxtimeout", |
| 235 | "5000", | 237 | "5000", |
| 236 | "-fetch-timeout", | 238 | "--fetch-timeout", |
| 237 | "5000", | 239 | "5000", |
| 238 | ]); | 240 | ]); |
| 239 | 241 | ||
| @@ -1,10 +1,10 @@ | |||
| 1 | use editor::Editor; | 1 | use editor::Editor; |
| 2 | use gpui::{ | 2 | use gpui::{ |
| 3 | div, prelude::*, uniform_list, AppContext, Component, Div, FocusHandle, FocusableView, | 3 | div, prelude::*, uniform_list, AppContext, Div, FocusHandle, FocusableView, MouseButton, |
| 4 | MouseButton, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext, | 4 | MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext, |
| 5 | }; | 5 | }; |
| 6 | use std::{cmp, sync::Arc}; | 6 | use std::{cmp, sync::Arc}; |
| 7 | use ui::{prelude::*, v_stack, Divider, Label, TextColor}; | 7 | use ui::{prelude::*, v_stack, Color, Divider, Label}; |
| 8 | 8 | ||
| 9 | pub struct Picker<D: PickerDelegate> { | 9 | pub struct Picker<D: PickerDelegate> { |
| 10 | pub delegate: D, | 10 | pub delegate: D, |
| @@ -15,7 +15,7 @@ pub struct Picker<D: PickerDelegate> { · 4 unmodified lines | |||
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | pub trait PickerDelegate: Sized + 'static { | 17 | pub trait PickerDelegate: Sized + 'static { |
| 18 | type ListItem: Component<Picker<Self>>; | 18 | type ListItem: IntoElement; |
| 19 | 19 | ||
| 20 | fn match_count(&self) -> usize; | 20 | fn match_count(&self) -> usize; |
| 21 | fn selected_index(&self) -> usize; | 21 | fn selected_index(&self) -> usize; |
| @@ -32,7 +32,7 @@ pub trait PickerDelegate: Sized + 'static { · 10 unmodified lines | |||
| 32 | ix: usize, | 32 | ix: usize, |
| 33 | selected: bool, | 33 | selected: bool, |
| 34 | cx: &mut ViewContext<Picker<Self>>, | 34 | cx: &mut ViewContext<Picker<Self>>, |
| 35 | ) -> Self::ListItem; | 35 | ) -> Option<Self::ListItem>; |
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | impl<D: PickerDelegate> FocusableView for Picker<D> { | 38 | impl<D: PickerDelegate> FocusableView for Picker<D> { |
| @@ -114,6 +114,7 @@ impl<D: PickerDelegate> Picker<D> { · 75 unmodified lines | |||
| 114 | } | 114 | } |
| 115 | 115 | ||
| 116 | fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { | 116 | fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { |
| 117 | dbg!("canceling!"); | ||
| 117 | self.delegate.dismissed(cx); | 118 | self.delegate.dismissed(cx); |
| 118 | } | 119 | } |
| 119 | 120 | ||
| @@ -181,20 +182,20 @@ impl<D: PickerDelegate> Picker<D> { · 61 unmodified lines | |||
| 181 | } | 182 | } |
| 182 | 183 | ||
| 183 | impl<D: PickerDelegate> Render for Picker<D> { | 184 | impl<D: PickerDelegate> Render for Picker<D> { |
| 184 | type Element = Div<Self>; | 185 | type Element = Div; |
| 185 | 186 | ||
| 186 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | 187 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { |
| 187 | div() | 188 | div() |
| 188 | .key_context("picker") | 189 | .key_context("picker") |
| 189 | .size_full() | 190 | .size_full() |
| 190 | .elevation_2(cx) | 191 | .elevation_2(cx) |
| 191 | .on_action(Self::select_next) | 192 | .on_action(cx.listener(Self::select_next)) |
| 192 | .on_action(Self::select_prev) | 193 | .on_action(cx.listener(Self::select_prev)) |
| 193 | .on_action(Self::select_first) | 194 | .on_action(cx.listener(Self::select_first)) |
| 194 | .on_action(Self::select_last) | 195 | .on_action(cx.listener(Self::select_last)) |
| 195 | .on_action(Self::cancel) | 196 | .on_action(cx.listener(Self::cancel)) |
| 196 | .on_action(Self::confirm) | 197 | .on_action(cx.listener(Self::confirm)) |
| 197 | .on_action(Self::secondary_confirm) | 198 | .on_action(cx.listener(Self::secondary_confirm)) |
| 198 | .child( | 199 | .child( |
| 199 | v_stack() | 200 | v_stack() |
| 200 | .py_0p5() | 201 | .py_0p5() |
| @@ -208,31 +209,37 @@ impl<D: PickerDelegate> Render for Picker<D> { · 7 unmodified lines | |||
| 208 | .p_1() | 209 | .p_1() |
| 209 | .grow() | 210 | .grow() |
| 210 | .child( | 211 | .child( |
| 211 | uniform_list("candidates", self.delegate.match_count(), { | 212 | uniform_list( |
| 212 | move |this: &mut Self, visible_range, cx| { | 213 | cx.view().clone(), |
| 213 | let selected_ix = this.delegate.selected_index(); | 214 | "candidates", |
| 214 | visible_range | 215 | self.delegate.match_count(), |
| 215 | .map(|ix| { | 216 | { |
| 216 | div() | 217 | let selected_index = self.delegate.selected_index(); |
| 217 | .on_mouse_down( | 218 | |
| 218 | MouseButton::Left, | 219 | move |picker, visible_range, cx| { |
| 219 | move |this: &mut Self, event, cx| { | 220 | visible_range |
| 220 | this.handle_click( | 221 | .map(|ix| { |
| 221 | ix, | 222 | div() |
| 222 | event.modifiers.command, | 223 | .on_mouse_down( |
| 223 | cx, | 224 | MouseButton::Left, |
| 224 | ) | 225 | cx.listener(move |this, event: &MouseDownEvent, cx| { |
| 225 | }, | 226 | this.handle_click( |
| 226 | ) | 227 | ix, |
| 227 | .child(this.delegate.render_match( | 228 | event.modifiers.command, |
| 228 | ix, | 229 | cx, |
| 229 | ix == selected_ix, | 230 | ) |
| 230 | cx, | 231 | }), |
| 231 | )) | 232 | ) |
| 232 | }) | 233 | .children(picker.delegate.render_match( |
| 233 | .collect() | 234 | ix, |
| 234 | } | 235 | ix == selected_index, |
| 235 | }) | 236 | cx, |
| 237 | )) | ||
| 238 | }) | ||
| 239 | .collect() | ||
| 240 | } | ||
| 241 | }, | ||
| 242 | ) | ||
| 236 | .track_scroll(self.scroll_handle.clone()), | 243 | .track_scroll(self.scroll_handle.clone()), |
| 237 | ) | 244 | ) |
| 238 | .max_h_72() | 245 | .max_h_72() |
| @@ -244,7 +251,7 @@ impl<D: PickerDelegate> Render for Picker<D> { · 5 unmodified lines | |||
| 244 | v_stack().p_1().grow().child( | 251 | v_stack().p_1().grow().child( |
| 245 | div() | 252 | div() |
| 246 | .px_1() | 253 | .px_1() |
| 247 | .child(Label::new("No matches").color(TextColor::Muted)), | 254 | .child(Label::new("No matches").color(Color::Muted)), |
| 248 | ), | 255 | ), |
| 249 | ) | 256 | ) |
| 250 | }) | 257 | }) |
| @@ -13,7 +13,7 @@ mod worktree_tests; · 12 unmodified lines | |||
| 13 | use anyhow::{anyhow, Context, Result}; | 13 | use anyhow::{anyhow, Context, Result}; |
| 14 | use client::{proto, Client, Collaborator, TypedEnvelope, UserStore}; | 14 | use client::{proto, Client, Collaborator, TypedEnvelope, UserStore}; |
| 15 | use clock::ReplicaId; | 15 | use clock::ReplicaId; |
| 16 | use collections::{hash_map, BTreeMap, HashMap, HashSet}; | 16 | use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque}; |
| 17 | use copilot::Copilot; | 17 | use copilot::Copilot; |
| 18 | use futures::{ | 18 | use futures::{ |
| 19 | channel::{ | 19 | channel::{ |
| @@ -62,7 +62,10 @@ use serde::Serialize; · 42 unmodified lines | |||
| 62 | use settings::SettingsStore; | 62 | use settings::SettingsStore; |
| 63 | use sha2::{Digest, Sha256}; | 63 | use sha2::{Digest, Sha256}; |
| 64 | use similar::{ChangeTag, TextDiff}; | 64 | use similar::{ChangeTag, TextDiff}; |
| 65 | use smol::channel::{Receiver, Sender}; | 65 | use smol::{ |
| 66 | channel::{Receiver, Sender}, | ||
| 67 | lock::Semaphore, | ||
| 68 | }; | ||
| 66 | use std::{ | 69 | use std::{ |
| 67 | cmp::{self, Ordering}, | 70 | cmp::{self, Ordering}, |
| 68 | convert::TryInto, | 71 | convert::TryInto, |
| @@ -557,6 +560,7 @@ enum SearchMatchCandidate { · 488 unmodified lines | |||
| 557 | }, | 560 | }, |
| 558 | Path { | 561 | Path { |
| 559 | worktree_id: WorktreeId, | 562 | worktree_id: WorktreeId, |
| 563 | is_ignored: bool, | ||
| 560 | path: Arc<Path>, | 564 | path: Arc<Path>, |
| 561 | }, | 565 | }, |
| 562 | } | 566 | } |
| @@ -5742,13 +5746,18 @@ impl Project { · 5179 unmodified lines | |||
| 5742 | .await | 5746 | .await |
| 5743 | .log_err(); | 5747 | .log_err(); |
| 5744 | } | 5748 | } |
| 5749 | |||
| 5745 | background | 5750 | background |
| 5746 | .scoped(|scope| { | 5751 | .scoped(|scope| { |
| 5752 | let max_concurrent_workers = Arc::new(Semaphore::new(workers)); | ||
| 5753 | |||
| 5747 | for worker_ix in 0..workers { | 5754 | for worker_ix in 0..workers { |
| 5748 | let worker_start_ix = worker_ix * paths_per_worker; | 5755 | let worker_start_ix = worker_ix * paths_per_worker; |
| 5749 | let worker_end_ix = worker_start_ix + paths_per_worker; | 5756 | let worker_end_ix = worker_start_ix + paths_per_worker; |
| 5750 | let unnamed_buffers = opened_buffers.clone(); | 5757 | let unnamed_buffers = opened_buffers.clone(); |
| 5758 | let limiter = Arc::clone(&max_concurrent_workers); | ||
| 5751 | scope.spawn(async move { | 5759 | scope.spawn(async move { |
| 5760 | let _guard = limiter.acquire().await; | ||
| 5752 | let mut snapshot_start_ix = 0; | 5761 | let mut snapshot_start_ix = 0; |
| 5753 | let mut abs_path = PathBuf::new(); | 5762 | let mut abs_path = PathBuf::new(); |
| 5754 | for snapshot in snapshots { | 5763 | for snapshot in snapshots { |
| @@ -5797,6 +5806,7 @@ impl Project { · 42 unmodified lines | |||
| 5797 | let project_path = SearchMatchCandidate::Path { | 5806 | let project_path = SearchMatchCandidate::Path { |
| 5798 | worktree_id: snapshot.id(), | 5807 | worktree_id: snapshot.id(), |
| 5799 | path: entry.path.clone(), | 5808 | path: entry.path.clone(), |
| 5809 | is_ignored: entry.is_ignored, | ||
| 5800 | }; | 5810 | }; |
| 5801 | if matching_paths_tx.send(project_path).await.is_err() { | 5811 | if matching_paths_tx.send(project_path).await.is_err() { |
| 5802 | break; | 5812 | break; |
| @@ -5809,6 +5819,94 @@ impl Project { · 6 unmodified lines | |||
| 5809 | } | 5819 | } |
| 5810 | }); | 5820 | }); |
| 5811 | } | 5821 | } |
| 5822 | |||
| 5823 | if query.include_ignored() { | ||
| 5824 | for snapshot in snapshots { | ||
| 5825 | for ignored_entry in snapshot | ||
| 5826 | .entries(query.include_ignored()) | ||
| 5827 | .filter(|e| e.is_ignored) | ||
| 5828 | { | ||
| 5829 | let limiter = Arc::clone(&max_concurrent_workers); | ||
| 5830 | scope.spawn(async move { | ||
| 5831 | let _guard = limiter.acquire().await; | ||
| 5832 | let mut ignored_paths_to_process = | ||
| 5833 | VecDeque::from([snapshot.abs_path().join(&ignored_entry.path)]); | ||
| 5834 | while let Some(ignored_abs_path) = | ||
| 5835 | ignored_paths_to_process.pop_front() | ||
| 5836 | { | ||
| 5837 | if !query.file_matches(Some(&ignored_abs_path)) | ||
| 5838 | || snapshot.is_path_excluded(&ignored_abs_path) | ||
| 5839 | { | ||
| 5840 | continue; | ||
| 5841 | } | ||
| 5842 | if let Some(fs_metadata) = fs | ||
| 5843 | .metadata(&ignored_abs_path) | ||
| 5844 | .await | ||
| 5845 | .with_context(|| { | ||
| 5846 | format!("fetching fs metadata for {ignored_abs_path:?}") | ||
| 5847 | }) | ||
| 5848 | .log_err() | ||
| 5849 | .flatten() | ||
| 5850 | { | ||
| 5851 | if fs_metadata.is_dir { | ||
| 5852 | if let Some(mut subfiles) = fs | ||
| 5853 | .read_dir(&ignored_abs_path) | ||
| 5854 | .await | ||
| 5855 | .with_context(|| { | ||
| 5856 | format!( | ||
| 5857 | "listing ignored path {ignored_abs_path:?}" | ||
| 5858 | ) | ||
| 5859 | }) | ||
| 5860 | .log_err() | ||
| 5861 | { | ||
| 5862 | while let Some(subfile) = subfiles.next().await { | ||
| 5863 | if let Some(subfile) = subfile.log_err() { | ||
| 5864 | ignored_paths_to_process.push_back(subfile); | ||
| 5865 | } | ||
| 5866 | } | ||
| 5867 | } | ||
| 5868 | } else if !fs_metadata.is_symlink { | ||
| 5869 | let matches = if let Some(file) = fs | ||
| 5870 | .open_sync(&ignored_abs_path) | ||
| 5871 | .await | ||
| 5872 | .with_context(|| { | ||
| 5873 | format!( | ||
| 5874 | "Opening ignored path {ignored_abs_path:?}" | ||
| 5875 | ) | ||
| 5876 | }) | ||
| 5877 | .log_err() | ||
| 5878 | { | ||
| 5879 | query.detect(file).unwrap_or(false) | ||
| 5880 | } else { | ||
| 5881 | false | ||
| 5882 | }; | ||
| 5883 | if matches { | ||
| 5884 | let project_path = SearchMatchCandidate::Path { | ||
| 5885 | worktree_id: snapshot.id(), | ||
| 5886 | path: Arc::from( | ||
| 5887 | ignored_abs_path | ||
| 5888 | .strip_prefix(snapshot.abs_path()) | ||
| 5889 | .expect( | ||
| 5890 | "scanning worktree-related files", | ||
| 5891 | ), | ||
| 5892 | ), | ||
| 5893 | is_ignored: true, | ||
| 5894 | }; | ||
| 5895 | if matching_paths_tx | ||
| 5896 | .send(project_path) | ||
| 5897 | .await | ||
| 5898 | .is_err() | ||
| 5899 | { | ||
| 5900 | return; | ||
| 5901 | } | ||
| 5902 | } | ||
| 5903 | } | ||
| 5904 | } | ||
| 5905 | } | ||
| 5906 | }); | ||
| 5907 | } | ||
| 5908 | } | ||
| 5909 | } | ||
| 5812 | }) | 5910 | }) |
| 5813 | .await; | 5911 | .await; |
| 5814 | } | 5912 | } |
| @@ -5917,11 +6015,24 @@ impl Project { · 102 unmodified lines | |||
| 5917 | let (buffers_tx, buffers_rx) = smol::channel::bounded(1024); | 6015 | let (buffers_tx, buffers_rx) = smol::channel::bounded(1024); |
| 5918 | let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel(); | 6016 | let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel(); |
| 5919 | cx.spawn(|this, cx| async move { | 6017 | cx.spawn(|this, cx| async move { |
| 5920 | let mut buffers = vec![]; | 6018 | let mut buffers = Vec::new(); |
| 6019 | let mut ignored_buffers = Vec::new(); | ||
| 5921 | while let Some(entry) = matching_paths_rx.next().await { | 6020 | while let Some(entry) = matching_paths_rx.next().await { |
| 5922 | buffers.push(entry); | 6021 | if matches!( |
| 6022 | entry, | ||
| 6023 | SearchMatchCandidate::Path { | ||
| 6024 | is_ignored: true, | ||
| 6025 | .. | ||
| 6026 | } | ||
| 6027 | ) { | ||
| 6028 | ignored_buffers.push(entry); | ||
| 6029 | } else { | ||
| 6030 | buffers.push(entry); | ||
| 6031 | } | ||
| 5923 | } | 6032 | } |
| 5924 | buffers.sort_by_key(|candidate| candidate.path()); | 6033 | buffers.sort_by_key(|candidate| candidate.path()); |
| 6034 | ignored_buffers.sort_by_key(|candidate| candidate.path()); | ||
| 6035 | buffers.extend(ignored_buffers); | ||
| 5925 | let matching_paths = buffers.clone(); | 6036 | let matching_paths = buffers.clone(); |
| 5926 | let _ = sorted_buffers_tx.send(buffers); | 6037 | let _ = sorted_buffers_tx.send(buffers); |
| 5927 | for (index, candidate) in matching_paths.into_iter().enumerate() { | 6038 | for (index, candidate) in matching_paths.into_iter().enumerate() { |
| @@ -5933,7 +6044,9 @@ impl Project { · 5 unmodified lines | |||
| 5933 | cx.spawn(|mut cx| async move { | 6044 | cx.spawn(|mut cx| async move { |
| 5934 | let buffer = match candidate { | 6045 | let buffer = match candidate { |
| 5935 | SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer), | 6046 | SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer), |
| 5936 | SearchMatchCandidate::Path { worktree_id, path } => this | 6047 | SearchMatchCandidate::Path { |
| 6048 | worktree_id, path, .. | ||
| 6049 | } => this | ||
| 5937 | .update(&mut cx, |this, cx| { | 6050 | .update(&mut cx, |this, cx| { |
| 5938 | this.open_buffer((worktree_id, path), cx) | 6051 | this.open_buffer((worktree_id, path), cx) |
| 5939 | }) | 6052 | }) |
| @@ -2226,7 +2226,7 @@ impl LocalSnapshot { · 2225 unmodified lines | |||
| 2226 | paths | 2226 | paths |
| 2227 | } | 2227 | } |
| 2228 | 2228 | ||
| 2229 | fn is_abs_path_excluded(&self, abs_path: &Path) -> bool { | 2229 | pub fn is_path_excluded(&self, abs_path: &Path) -> bool { |
| 2230 | self.file_scan_exclusions | 2230 | self.file_scan_exclusions |
| 2231 | .iter() | 2231 | .iter() |
| 2232 | .any(|exclude_matcher| exclude_matcher.is_match(abs_path)) | 2232 | .any(|exclude_matcher| exclude_matcher.is_match(abs_path)) |
| @@ -2399,26 +2399,9 @@ impl BackgroundScannerState { · 166 unmodified lines | |||
| 2399 | self.snapshot.check_invariants(false); | 2399 | self.snapshot.check_invariants(false); |
| 2400 | } | 2400 | } |
| 2401 | 2401 | ||
| 2402 | fn reload_repositories(&mut self, changed_paths: &[Arc<Path>], fs: &dyn Fs) { | 2402 | fn reload_repositories(&mut self, dot_git_dirs_to_reload: &HashSet<PathBuf>, fs: &dyn Fs) { |
| 2403 | let scan_id = self.snapshot.scan_id; | 2403 | let scan_id = self.snapshot.scan_id; |
| 2404 | 2404 | for dot_git_dir in dot_git_dirs_to_reload { | |
| 2405 | // Find each of the .git directories that contain any of the given paths. | ||
| 2406 | let mut prev_dot_git_dir = None; | ||
| 2407 | for changed_path in changed_paths { | ||
| 2408 | let Some(dot_git_dir) = changed_path | ||
| 2409 | .ancestors() | ||
| 2410 | .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT)) | ||
| 2411 | else { | ||
| 2412 | continue; | ||
| 2413 | }; | ||
| 2414 | |||
| 2415 | // Avoid processing the same repository multiple times, if multiple paths | ||
| 2416 | // within it have changed. | ||
| 2417 | if prev_dot_git_dir == Some(dot_git_dir) { | ||
| 2418 | continue; | ||
| 2419 | } | ||
| 2420 | prev_dot_git_dir = Some(dot_git_dir); | ||
| 2421 | |||
| 2422 | // If there is already a repository for this .git directory, reload | 2405 | // If there is already a repository for this .git directory, reload |
| 2423 | // the status for all of its files. | 2406 | // the status for all of its files. |
| 2424 | let repository = self | 2407 | let repository = self |
| @@ -2430,7 +2413,7 @@ impl BackgroundScannerState { · 5 unmodified lines | |||
| 2430 | }); | 2413 | }); |
| 2431 | match repository { | 2414 | match repository { |
| 2432 | None => { | 2415 | None => { |
| 2433 | self.build_git_repository(dot_git_dir.into(), fs); | 2416 | self.build_git_repository(Arc::from(dot_git_dir.as_path()), fs); |
| 2434 | } | 2417 | } |
| 2435 | Some((entry_id, repository)) => { | 2418 | Some((entry_id, repository)) => { |
| 2436 | if repository.git_dir_scan_id == scan_id { | 2419 | if repository.git_dir_scan_id == scan_id { |
| @@ -2444,7 +2427,7 @@ impl BackgroundScannerState { · 7 unmodified lines | |||
| 2444 | continue; | 2427 | continue; |
| 2445 | }; | 2428 | }; |
| 2446 | 2429 | ||
| 2447 | log::info!("reload git repository {:?}", dot_git_dir); | 2430 | log::info!("reload git repository {dot_git_dir:?}"); |
| 2448 | let repository = repository.repo_ptr.lock(); | 2431 | let repository = repository.repo_ptr.lock(); |
| 2449 | let branch = repository.branch_name(); | 2432 | let branch = repository.branch_name(); |
| 2450 | repository.reload_index(); | 2433 | repository.reload_index(); |
| @@ -2475,7 +2458,9 @@ impl BackgroundScannerState { · 24 unmodified lines | |||
| 2475 | ids_to_preserve.insert(work_directory_id); | 2458 | ids_to_preserve.insert(work_directory_id); |
| 2476 | } else { | 2459 | } else { |
| 2477 | let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path); | 2460 | let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path); |
| 2478 | if snapshot.is_abs_path_excluded(&git_dir_abs_path) | 2461 | let git_dir_excluded = snapshot.is_path_excluded(&entry.git_dir_path) |
| 2462 | || snapshot.is_path_excluded(&git_dir_abs_path); | ||
| 2463 | if git_dir_excluded | ||
| 2479 | && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None)) | 2464 | && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None)) |
| 2480 | { | 2465 | { |
| 2481 | ids_to_preserve.insert(work_directory_id); | 2466 | ids_to_preserve.insert(work_directory_id); |
| @@ -3314,11 +3299,26 @@ impl BackgroundScanner { · 832 unmodified lines | |||
| 3314 | }; | 3299 | }; |
| 3315 | 3300 | ||
| 3316 | let mut relative_paths = Vec::with_capacity(abs_paths.len()); | 3301 | let mut relative_paths = Vec::with_capacity(abs_paths.len()); |
| 3302 | let mut dot_git_paths_to_reload = HashSet::default(); | ||
| 3317 | abs_paths.sort_unstable(); | 3303 | abs_paths.sort_unstable(); |
| 3318 | abs_paths.dedup_by(|a, b| a.starts_with(&b)); | 3304 | abs_paths.dedup_by(|a, b| a.starts_with(&b)); |
| 3319 | abs_paths.retain(|abs_path| { | 3305 | abs_paths.retain(|abs_path| { |
| 3320 | let snapshot = &self.state.lock().snapshot; | 3306 | let snapshot = &self.state.lock().snapshot; |
| 3321 | { | 3307 | { |
| 3308 | let mut is_git_related = false; | ||
| 3309 | if let Some(dot_git_dir) = abs_path | ||
| 3310 | .ancestors() | ||
| 3311 | .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT)) | ||
| 3312 | { | ||
| 3313 | let dot_git_path = dot_git_dir | ||
| 3314 | .strip_prefix(&root_canonical_path) | ||
| 3315 | .ok() | ||
| 3316 | .map(|path| path.to_path_buf()) | ||
| 3317 | .unwrap_or_else(|| dot_git_dir.to_path_buf()); | ||
| 3318 | dot_git_paths_to_reload.insert(dot_git_path.to_path_buf()); | ||
| 3319 | is_git_related = true; | ||
| 3320 | } | ||
| 3321 | |||
| 3322 | let relative_path: Arc<Path> = | 3322 | let relative_path: Arc<Path> = |
| 3323 | if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { | 3323 | if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { |
| 3324 | path.into() | 3324 | path.into() |
| @@ -3328,23 +3328,30 @@ impl BackgroundScanner { · 3 unmodified lines | |||
| 3328 | ); | 3328 | ); |
| 3329 | return false; | 3329 | return false; |
| 3330 | }; | 3330 | }; |
| 3331 | let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { | ||
| 3332 | snapshot | ||
| 3333 | .entry_for_path(parent) | ||
| 3334 | .map_or(false, |entry| entry.kind == EntryKind::Dir) | ||
| 3335 | }); | ||
| 3336 | if !parent_dir_is_loaded { | ||
| 3337 | log::debug!("ignoring event {relative_path:?} within unloaded directory"); | ||
| 3338 | return false; | ||
| 3339 | } | ||
| 3331 | 3340 | ||
| 3332 | if !is_git_related(&abs_path) { | 3341 | // FS events may come for files which parent directory is excluded, need to check ignore those. |
| 3333 | let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { | 3342 | let mut path_to_test = abs_path.clone(); |
| 3334 | snapshot | 3343 | let mut excluded_file_event = snapshot.is_path_excluded(abs_path) |
| 3335 | .entry_for_path(parent) | 3344 | || snapshot.is_path_excluded(&relative_path); |
| 3336 | .map_or(false, |entry| entry.kind == EntryKind::Dir) | 3345 | while !excluded_file_event && path_to_test.pop() { |
| 3337 | }); | 3346 | if snapshot.is_path_excluded(&path_to_test) { |
| 3338 | if !parent_dir_is_loaded { | 3347 | excluded_file_event = true; |
| 3339 | log::debug!("ignoring event {relative_path:?} within unloaded directory"); | ||
| 3340 | return false; | ||
| 3341 | } | 3348 | } |
| 3342 | if snapshot.is_abs_path_excluded(abs_path) { | 3349 | } |
| 3343 | log::debug!( | 3350 | if excluded_file_event { |
| 3344 | "ignoring FS event for path {relative_path:?} within excluded directory" | 3351 | if !is_git_related { |
| 3345 | ); | 3352 | log::debug!("ignoring FS event for excluded path {relative_path:?}"); |
| 3346 | return false; | ||
| 3347 | } | 3353 | } |
| 3354 | return false; | ||
| 3348 | } | 3355 | } |
| 3349 | 3356 | ||
| 3350 | relative_paths.push(relative_path); | 3357 | relative_paths.push(relative_path); |
| @@ -3352,31 +3359,39 @@ impl BackgroundScanner { · 1 unmodified line | |||
| 3352 | } | 3359 | } |
| 3353 | }); | 3360 | }); |
| 3354 | 3361 | ||
| 3355 | if relative_paths.is_empty() { | 3362 | if dot_git_paths_to_reload.is_empty() && relative_paths.is_empty() { |
| 3356 | return; | 3363 | return; |
| 3357 | } | 3364 | } |
| 3358 | 3365 | ||
| 3359 | log::debug!("received fs events {:?}", relative_paths); | 3366 | if !relative_paths.is_empty() { |
| 3367 | log::debug!("received fs events {:?}", relative_paths); | ||
| 3360 | 3368 | ||
| 3361 | let (scan_job_tx, scan_job_rx) = channel::unbounded(); | 3369 | let (scan_job_tx, scan_job_rx) = channel::unbounded(); |
| 3362 | self.reload_entries_for_paths( | 3370 | self.reload_entries_for_paths( |
| 3363 | root_path, | 3371 | root_path, |
| 3364 | root_canonical_path, | 3372 | root_canonical_path, |
| 3365 | &relative_paths, | 3373 | &relative_paths, |
| 3366 | abs_paths, | 3374 | abs_paths, |
| 3367 | Some(scan_job_tx.clone()), | 3375 | Some(scan_job_tx.clone()), |
| 3368 | ) | 3376 | ) |
| 3369 | .await; | 3377 | .await; |
| 3370 | drop(scan_job_tx); | 3378 | drop(scan_job_tx); |
| 3371 | self.scan_dirs(false, scan_job_rx).await; | 3379 | self.scan_dirs(false, scan_job_rx).await; |
| 3372 | 3380 | ||
| 3373 | let (scan_job_tx, scan_job_rx) = channel::unbounded(); | 3381 | let (scan_job_tx, scan_job_rx) = channel::unbounded(); |
| 3374 | self.update_ignore_statuses(scan_job_tx).await; | 3382 | self.update_ignore_statuses(scan_job_tx).await; |
| 3375 | self.scan_dirs(false, scan_job_rx).await; | 3383 | self.scan_dirs(false, scan_job_rx).await; |
| 3384 | } | ||
| 3376 | 3385 | ||
| 3377 | { | 3386 | { |
| 3378 | let mut state = self.state.lock(); | 3387 | let mut state = self.state.lock(); |
| 3379 | state.reload_repositories(&relative_paths, self.fs.as_ref()); | 3388 | if !dot_git_paths_to_reload.is_empty() { |
| 3389 | if relative_paths.is_empty() { | ||
| 3390 | state.snapshot.scan_id += 1; | ||
| 3391 | } | ||
| 3392 | log::debug!("reloading repositories: {dot_git_paths_to_reload:?}"); | ||
| 3393 | state.reload_repositories(&dot_git_paths_to_reload, self.fs.as_ref()); | ||
| 3394 | } | ||
| 3380 | state.snapshot.completed_scan_id = state.snapshot.scan_id; | 3395 | state.snapshot.completed_scan_id = state.snapshot.scan_id; |
| 3381 | for (_, entry_id) in mem::take(&mut state.removed_entry_ids) { | 3396 | for (_, entry_id) in mem::take(&mut state.removed_entry_ids) { |
| 3382 | state.scanned_dirs.remove(&entry_id); | 3397 | state.scanned_dirs.remove(&entry_id); |
| @@ -3516,7 +3531,7 @@ impl BackgroundScanner { · 133 unmodified lines | |||
| 3516 | let state = self.state.lock(); | 3531 | let state = self.state.lock(); |
| 3517 | let snapshot = &state.snapshot; | 3532 | let snapshot = &state.snapshot; |
| 3518 | root_abs_path = snapshot.abs_path().clone(); | 3533 | root_abs_path = snapshot.abs_path().clone(); |
| 3519 | if snapshot.is_abs_path_excluded(&job.abs_path) { | 3534 | if snapshot.is_path_excluded(&job.abs_path) { |
| 3520 | log::error!("skipping excluded directory {:?}", job.path); | 3535 | log::error!("skipping excluded directory {:?}", job.path); |
| 3521 | return Ok(()); | 3536 | return Ok(()); |
| 3522 | } | 3537 | } |
| @@ -3588,7 +3603,7 @@ impl BackgroundScanner { · 65 unmodified lines | |||
| 3588 | 3603 | ||
| 3589 | { | 3604 | { |
| 3590 | let mut state = self.state.lock(); | 3605 | let mut state = self.state.lock(); |
| 3591 | if state.snapshot.is_abs_path_excluded(&child_abs_path) { | 3606 | if state.snapshot.is_path_excluded(&child_abs_path) { |
| 3592 | let relative_path = job.path.join(child_name); | 3607 | let relative_path = job.path.join(child_name); |
| 3593 | log::debug!("skipping excluded child entry {relative_path:?}"); | 3608 | log::debug!("skipping excluded child entry {relative_path:?}"); |
| 3594 | state.remove_path(&relative_path); | 3609 | state.remove_path(&relative_path); |
| @@ -4130,12 +4145,6 @@ impl BackgroundScanner { · 535 unmodified lines | |||
| 4130 | } | 4145 | } |
| 4131 | } | 4146 | } |
| 4132 | 4147 | ||
| 4133 | fn is_git_related(abs_path: &Path) -> bool { | ||
| 4134 | abs_path | ||
| 4135 | .components() | ||
| 4136 | .any(|c| c.as_os_str() == *DOT_GIT || c.as_os_str() == *GITIGNORE) | ||
| 4137 | } | ||
| 4138 | |||
| 4139 | fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag { | 4148 | fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag { |
| 4140 | let mut result = root_char_bag; | 4149 | let mut result = root_char_bag; |
| 4141 | result.extend( | 4150 | result.extend( |
| @@ -990,6 +990,145 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { · 989 unmodified lines | |||
| 990 | }); | 990 | }); |
| 991 | } | 991 | } |
| 992 | 992 | ||
| 993 | #[gpui::test] | ||
| 994 | async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) { | ||
| 995 | init_test(cx); | ||
| 996 | let dir = temp_tree(json!({ | ||
| 997 | ".git": { | ||
| 998 | "HEAD": "ref: refs/heads/main\n", | ||
| 999 | "foo": "bar", | ||
| 1000 | }, | ||
| 1001 | ".gitignore": "**/target\n/node_modules\ntest_output\n", | ||
| 1002 | "target": { | ||
| 1003 | "index": "blah2" | ||
| 1004 | }, | ||
| 1005 | "node_modules": { | ||
| 1006 | ".DS_Store": "", | ||
| 1007 | "prettier": { | ||
| 1008 | "package.json": "{}", | ||
| 1009 | }, | ||
| 1010 | }, | ||
| 1011 | "src": { | ||
| 1012 | ".DS_Store": "", | ||
| 1013 | "foo": { | ||
| 1014 | "foo.rs": "mod another;\n", | ||
| 1015 | "another.rs": "// another", | ||
| 1016 | }, | ||
| 1017 | "bar": { | ||
| 1018 | "bar.rs": "// bar", | ||
| 1019 | }, | ||
| 1020 | "lib.rs": "mod foo;\nmod bar;\n", | ||
| 1021 | }, | ||
| 1022 | ".DS_Store": "", | ||
| 1023 | })); | ||
| 1024 | cx.update(|cx| { | ||
| 1025 | cx.update_global::<SettingsStore, _, _>(|store, cx| { | ||
| 1026 | store.update_user_settings::<ProjectSettings>(cx, |project_settings| { | ||
| 1027 | project_settings.file_scan_exclusions = Some(vec![ | ||
| 1028 | "**/.git".to_string(), | ||
| 1029 | "node_modules/".to_string(), | ||
| 1030 | "build_output".to_string(), | ||
| 1031 | ]); | ||
| 1032 | }); | ||
| 1033 | }); | ||
| 1034 | }); | ||
| 1035 | |||
| 1036 | let tree = Worktree::local( | ||
| 1037 | build_client(cx), | ||
| 1038 | dir.path(), | ||
| 1039 | true, | ||
| 1040 | Arc::new(RealFs), | ||
| 1041 | Default::default(), | ||
| 1042 | &mut cx.to_async(), | ||
| 1043 | ) | ||
| 1044 | .await | ||
| 1045 | .unwrap(); | ||
| 1046 | cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) | ||
| 1047 | .await; | ||
| 1048 | tree.flush_fs_events(cx).await; | ||
| 1049 | tree.read_with(cx, |tree, _| { | ||
| 1050 | check_worktree_entries( | ||
| 1051 | tree, | ||
| 1052 | &[ | ||
| 1053 | ".git/HEAD", | ||
| 1054 | ".git/foo", | ||
| 1055 | "node_modules/.DS_Store", | ||
| 1056 | "node_modules/prettier", | ||
| 1057 | "node_modules/prettier/package.json", | ||
| 1058 | ], | ||
| 1059 | &["target", "node_modules"], | ||
| 1060 | &[ | ||
| 1061 | ".DS_Store", | ||
| 1062 | "src/.DS_Store", | ||
| 1063 | "src/lib.rs", | ||
| 1064 | "src/foo/foo.rs", | ||
| 1065 | "src/foo/another.rs", | ||
| 1066 | "src/bar/bar.rs", | ||
| 1067 | ".gitignore", | ||
| 1068 | ], | ||
| 1069 | ) | ||
| 1070 | }); | ||
| 1071 | |||
| 1072 | let new_excluded_dir = dir.path().join("build_output"); | ||
| 1073 | let new_ignored_dir = dir.path().join("test_output"); | ||
| 1074 | std::fs::create_dir_all(&new_excluded_dir) | ||
| 1075 | .unwrap_or_else(|e| panic!("Failed to create a {new_excluded_dir:?} directory: {e}")); | ||
| 1076 | std::fs::create_dir_all(&new_ignored_dir) | ||
| 1077 | .unwrap_or_else(|e| panic!("Failed to create a {new_ignored_dir:?} directory: {e}")); | ||
| 1078 | let node_modules_dir = dir.path().join("node_modules"); | ||
| 1079 | let dot_git_dir = dir.path().join(".git"); | ||
| 1080 | let src_dir = dir.path().join("src"); | ||
| 1081 | for existing_dir in [&node_modules_dir, &dot_git_dir, &src_dir] { | ||
| 1082 | assert!( | ||
| 1083 | existing_dir.is_dir(), | ||
| 1084 | "Expect {existing_dir:?} to be present in the FS already" | ||
| 1085 | ); | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | for directory_for_new_file in [ | ||
| 1089 | new_excluded_dir, | ||
| 1090 | new_ignored_dir, | ||
| 1091 | node_modules_dir, | ||
| 1092 | dot_git_dir, | ||
| 1093 | src_dir, | ||
| 1094 | ] { | ||
| 1095 | std::fs::write(directory_for_new_file.join("new_file"), "new file contents") | ||
| 1096 | .unwrap_or_else(|e| { | ||
| 1097 | panic!("Failed to create in {directory_for_new_file:?} a new file: {e}") | ||
| 1098 | }); | ||
| 1099 | } | ||
| 1100 | tree.flush_fs_events(cx).await; | ||
| 1101 | |||
| 1102 | tree.read_with(cx, |tree, _| { | ||
| 1103 | check_worktree_entries( | ||
| 1104 | tree, | ||
| 1105 | &[ | ||
| 1106 | ".git/HEAD", | ||
| 1107 | ".git/foo", | ||
| 1108 | ".git/new_file", | ||
| 1109 | "node_modules/.DS_Store", | ||
| 1110 | "node_modules/prettier", | ||
| 1111 | "node_modules/prettier/package.json", | ||
| 1112 | "node_modules/new_file", | ||
| 1113 | "build_output", | ||
| 1114 | "build_output/new_file", | ||
| 1115 | "test_output/new_file", | ||
| 1116 | ], | ||
| 1117 | &["target", "node_modules", "test_output"], | ||
| 1118 | &[ | ||
| 1119 | ".DS_Store", | ||
| 1120 | "src/.DS_Store", | ||
| 1121 | "src/lib.rs", | ||
| 1122 | "src/foo/foo.rs", | ||
| 1123 | "src/foo/another.rs", | ||
| 1124 | "src/bar/bar.rs", | ||
| 1125 | "src/new_file", | ||
| 1126 | ".gitignore", | ||
| 1127 | ], | ||
| 1128 | ) | ||
| 1129 | }); | ||
| 1130 | } | ||
| 1131 | |||
| 993 | #[gpui::test(iterations = 30)] | 1132 | #[gpui::test(iterations = 30)] |
| 994 | async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { | 1133 | async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { |
| 995 | init_test(cx); | 1134 | init_test(cx); |
| @@ -10,7 +10,7 @@ use futures::future; · 9 unmodified lines | |||
| 10 | use gpui::{AppContext, AsyncAppContext, Model}; | 10 | use gpui::{AppContext, AsyncAppContext, Model}; |
| 11 | use language::{ | 11 | use language::{ |
| 12 | language_settings::{language_settings, InlayHintKind}, | 12 | language_settings::{language_settings, InlayHintKind}, |
| 13 | point_from_lsp, point_to_lsp, | 13 | point_from_lsp, point_to_lsp, prepare_completion_documentation, |
| 14 | proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, | 14 | proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, |
| 15 | range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, | 15 | range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, |
| 16 | CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, | 16 | CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, |
| @@ -1339,7 +1339,7 @@ impl LspCommand for GetCompletions { · 1322 unmodified lines | |||
| 1339 | async fn response_from_lsp( | 1339 | async fn response_from_lsp( |
| 1340 | self, | 1340 | self, |
| 1341 | completions: Option<lsp::CompletionResponse>, | 1341 | completions: Option<lsp::CompletionResponse>, |
| 1342 | _: Model<Project>, | 1342 | project: Model<Project>, |
| 1343 | buffer: Model<Buffer>, | 1343 | buffer: Model<Buffer>, |
| 1344 | server_id: LanguageServerId, | 1344 | server_id: LanguageServerId, |
| 1345 | mut cx: AsyncAppContext, | 1345 | mut cx: AsyncAppContext, |
| @@ -1359,7 +1359,8 @@ impl LspCommand for GetCompletions { · 13 unmodified lines | |||
| 1359 | Default::default() | 1359 | Default::default() |
| 1360 | }; | 1360 | }; |
| 1361 | 1361 | ||
| 1362 | let completions = buffer.update(&mut cx, |buffer, _| { | 1362 | let completions = buffer.update(&mut cx, |buffer, cx| { |
| 1363 | let language_registry = project.read(cx).languages().clone(); | ||
| 1363 | let language = buffer.language().cloned(); | 1364 | let language = buffer.language().cloned(); |
| 1364 | let snapshot = buffer.snapshot(); | 1365 | let snapshot = buffer.snapshot(); |
| 1365 | let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left); | 1366 | let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left); |
| @@ -1443,14 +1444,29 @@ impl LspCommand for GetCompletions { · 77 unmodified lines | |||
| 1443 | } | 1444 | } |
| 1444 | }; | 1445 | }; |
| 1445 | 1446 | ||
| 1447 | let language_registry = language_registry.clone(); | ||
| 1446 | let language = language.clone(); | 1448 | let language = language.clone(); |
| 1447 | LineEnding::normalize(&mut new_text); | 1449 | LineEnding::normalize(&mut new_text); |
| 1448 | Some(async move { | 1450 | Some(async move { |
| 1449 | let mut label = None; | 1451 | let mut label = None; |
| 1450 | if let Some(language) = language { | 1452 | if let Some(language) = language.as_ref() { |
| 1451 | language.process_completion(&mut lsp_completion).await; | 1453 | language.process_completion(&mut lsp_completion).await; |
| 1452 | label = language.label_for_completion(&lsp_completion).await; | 1454 | label = language.label_for_completion(&lsp_completion).await; |
| 1453 | } | 1455 | } |
| 1456 | |||
| 1457 | let documentation = if let Some(lsp_docs) = &lsp_completion.documentation { | ||
| 1458 | Some( | ||
| 1459 | prepare_completion_documentation( | ||
| 1460 | lsp_docs, | ||
| 1461 | &language_registry, | ||
| 1462 | language.clone(), | ||
| 1463 | ) | ||
| 1464 | .await, | ||
| 1465 | ) | ||
| 1466 | } else { | ||
| 1467 | None | ||
| 1468 | }; | ||
| 1469 | |||
| 1454 | Completion { | 1470 | Completion { |
| 1455 | old_range, | 1471 | old_range, |
| 1456 | new_text, | 1472 | new_text, |
| @@ -1460,6 +1476,7 @@ impl LspCommand for GetCompletions { · 3 unmodified lines | |||
| 1460 | lsp_completion.filter_text.as_deref(), | 1476 | lsp_completion.filter_text.as_deref(), |
| 1461 | ) | 1477 | ) |
| 1462 | }), | 1478 | }), |
| 1479 | documentation, | ||
| 1463 | server_id, | 1480 | server_id, |
| 1464 | lsp_completion, | 1481 | lsp_completion, |
| 1465 | } | 1482 | } |
| @@ -13,7 +13,7 @@ mod worktree_tests; · 12 unmodified lines | |||
| 13 | use anyhow::{anyhow, Context as _, Result}; | 13 | use anyhow::{anyhow, Context as _, Result}; |
| 14 | use client::{proto, Client, Collaborator, TypedEnvelope, UserStore}; | 14 | use client::{proto, Client, Collaborator, TypedEnvelope, UserStore}; |
| 15 | use clock::ReplicaId; | 15 | use clock::ReplicaId; |
| 16 | use collections::{hash_map, BTreeMap, HashMap, HashSet}; | 16 | use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque}; |
| 17 | use copilot::Copilot; | 17 | use copilot::Copilot; |
| 18 | use futures::{ | 18 | use futures::{ |
| 19 | channel::{ | 19 | channel::{ |
| @@ -63,6 +63,7 @@ use settings::{Settings, SettingsStore}; · 43 unmodified lines | |||
| 63 | use sha2::{Digest, Sha256}; | 63 | use sha2::{Digest, Sha256}; |
| 64 | use similar::{ChangeTag, TextDiff}; | 64 | use similar::{ChangeTag, TextDiff}; |
| 65 | use smol::channel::{Receiver, Sender}; | 65 | use smol::channel::{Receiver, Sender}; |
| 66 | use smol::lock::Semaphore; | ||
| 66 | use std::{ | 67 | use std::{ |
| 67 | cmp::{self, Ordering}, | 68 | cmp::{self, Ordering}, |
| 68 | convert::TryInto, | 69 | convert::TryInto, |
| @@ -557,6 +558,7 @@ enum SearchMatchCandidate { · 488 unmodified lines | |||
| 557 | }, | 558 | }, |
| 558 | Path { | 559 | Path { |
| 559 | worktree_id: WorktreeId, | 560 | worktree_id: WorktreeId, |
| 561 | is_ignored: bool, | ||
| 560 | path: Arc<Path>, | 562 | path: Arc<Path>, |
| 561 | }, | 563 | }, |
| 562 | } | 564 | } |
| @@ -5815,11 +5817,15 @@ impl Project { · 5252 unmodified lines | |||
| 5815 | } | 5817 | } |
| 5816 | executor | 5818 | executor |
| 5817 | .scoped(|scope| { | 5819 | .scoped(|scope| { |
| 5820 | let max_concurrent_workers = Arc::new(Semaphore::new(workers)); | ||
| 5821 | |||
| 5818 | for worker_ix in 0..workers { | 5822 | for worker_ix in 0..workers { |
| 5819 | let worker_start_ix = worker_ix * paths_per_worker; | 5823 | let worker_start_ix = worker_ix * paths_per_worker; |
| 5820 | let worker_end_ix = worker_start_ix + paths_per_worker; | 5824 | let worker_end_ix = worker_start_ix + paths_per_worker; |
| 5821 | let unnamed_buffers = opened_buffers.clone(); | 5825 | let unnamed_buffers = opened_buffers.clone(); |
| 5826 | let limiter = Arc::clone(&max_concurrent_workers); | ||
| 5822 | scope.spawn(async move { | 5827 | scope.spawn(async move { |
| 5828 | let _guard = limiter.acquire().await; | ||
| 5823 | let mut snapshot_start_ix = 0; | 5829 | let mut snapshot_start_ix = 0; |
| 5824 | let mut abs_path = PathBuf::new(); | 5830 | let mut abs_path = PathBuf::new(); |
| 5825 | for snapshot in snapshots { | 5831 | for snapshot in snapshots { |
| @@ -5868,6 +5874,7 @@ impl Project { · 42 unmodified lines | |||
| 5868 | let project_path = SearchMatchCandidate::Path { | 5874 | let project_path = SearchMatchCandidate::Path { |
| 5869 | worktree_id: snapshot.id(), | 5875 | worktree_id: snapshot.id(), |
| 5870 | path: entry.path.clone(), | 5876 | path: entry.path.clone(), |
| 5877 | is_ignored: entry.is_ignored, | ||
| 5871 | }; | 5878 | }; |
| 5872 | if matching_paths_tx.send(project_path).await.is_err() { | 5879 | if matching_paths_tx.send(project_path).await.is_err() { |
| 5873 | break; | 5880 | break; |
| @@ -5880,6 +5887,94 @@ impl Project { · 6 unmodified lines | |||
| 5880 | } | 5887 | } |
| 5881 | }); | 5888 | }); |
| 5882 | } | 5889 | } |
| 5890 | |||
| 5891 | if query.include_ignored() { | ||
| 5892 | for snapshot in snapshots { | ||
| 5893 | for ignored_entry in snapshot | ||
| 5894 | .entries(query.include_ignored()) | ||
| 5895 | .filter(|e| e.is_ignored) | ||
| 5896 | { | ||
| 5897 | let limiter = Arc::clone(&max_concurrent_workers); | ||
| 5898 | scope.spawn(async move { | ||
| 5899 | let _guard = limiter.acquire().await; | ||
| 5900 | let mut ignored_paths_to_process = | ||
| 5901 | VecDeque::from([snapshot.abs_path().join(&ignored_entry.path)]); | ||
| 5902 | while let Some(ignored_abs_path) = | ||
| 5903 | ignored_paths_to_process.pop_front() | ||
| 5904 | { | ||
| 5905 | if !query.file_matches(Some(&ignored_abs_path)) | ||
| 5906 | || snapshot.is_path_excluded(&ignored_abs_path) | ||
| 5907 | { | ||
| 5908 | continue; | ||
| 5909 | } | ||
| 5910 | if let Some(fs_metadata) = fs | ||
| 5911 | .metadata(&ignored_abs_path) | ||
| 5912 | .await | ||
| 5913 | .with_context(|| { | ||
| 5914 | format!("fetching fs metadata for {ignored_abs_path:?}") | ||
| 5915 | }) | ||
| 5916 | .log_err() | ||
| 5917 | .flatten() | ||
| 5918 | { | ||
| 5919 | if fs_metadata.is_dir { | ||
| 5920 | if let Some(mut subfiles) = fs | ||
| 5921 | .read_dir(&ignored_abs_path) | ||
| 5922 | .await | ||
| 5923 | .with_context(|| { | ||
| 5924 | format!( | ||
| 5925 | "listing ignored path {ignored_abs_path:?}" | ||
| 5926 | ) | ||
| 5927 | }) | ||
| 5928 | .log_err() | ||
| 5929 | { | ||
| 5930 | while let Some(subfile) = subfiles.next().await { | ||
| 5931 | if let Some(subfile) = subfile.log_err() { | ||
| 5932 | ignored_paths_to_process.push_back(subfile); | ||
| 5933 | } | ||
| 5934 | } | ||
| 5935 | } | ||
| 5936 | } else if !fs_metadata.is_symlink { | ||
| 5937 | let matches = if let Some(file) = fs | ||
| 5938 | .open_sync(&ignored_abs_path) | ||
| 5939 | .await | ||
| 5940 | .with_context(|| { | ||
| 5941 | format!( | ||
| 5942 | "Opening ignored path {ignored_abs_path:?}" | ||
| 5943 | ) | ||
| 5944 | }) | ||
| 5945 | .log_err() | ||
| 5946 | { | ||
| 5947 | query.detect(file).unwrap_or(false) | ||
| 5948 | } else { | ||
| 5949 | false | ||
| 5950 | }; | ||
| 5951 | if matches { | ||
| 5952 | let project_path = SearchMatchCandidate::Path { | ||
| 5953 | worktree_id: snapshot.id(), | ||
| 5954 | path: Arc::from( | ||
| 5955 | ignored_abs_path | ||
| 5956 | .strip_prefix(snapshot.abs_path()) | ||
| 5957 | .expect( | ||
| 5958 | "scanning worktree-related files", | ||
| 5959 | ), | ||
| 5960 | ), | ||
| 5961 | is_ignored: true, | ||
| 5962 | }; | ||
| 5963 | if matching_paths_tx | ||
| 5964 | .send(project_path) | ||
| 5965 | .await | ||
| 5966 | .is_err() | ||
| 5967 | { | ||
| 5968 | return; | ||
| 5969 | } | ||
| 5970 | } | ||
| 5971 | } | ||
| 5972 | } | ||
| 5973 | } | ||
| 5974 | }); | ||
| 5975 | } | ||
| 5976 | } | ||
| 5977 | } | ||
| 5883 | }) | 5978 | }) |
| 5884 | .await; | 5979 | .await; |
| 5885 | } | 5980 | } |
| @@ -5986,11 +6081,24 @@ impl Project { · 100 unmodified lines | |||
| 5986 | let (buffers_tx, buffers_rx) = smol::channel::bounded(1024); | 6081 | let (buffers_tx, buffers_rx) = smol::channel::bounded(1024); |
| 5987 | let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel(); | 6082 | let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel(); |
| 5988 | cx.spawn(move |this, cx| async move { | 6083 | cx.spawn(move |this, cx| async move { |
| 5989 | let mut buffers = vec![]; | 6084 | let mut buffers = Vec::new(); |
| 6085 | let mut ignored_buffers = Vec::new(); | ||
| 5990 | while let Some(entry) = matching_paths_rx.next().await { | 6086 | while let Some(entry) = matching_paths_rx.next().await { |
| 5991 | buffers.push(entry); | 6087 | if matches!( |
| 6088 | entry, | ||
| 6089 | SearchMatchCandidate::Path { | ||
| 6090 | is_ignored: true, | ||
| 6091 | .. | ||
| 6092 | } | ||
| 6093 | ) { | ||
| 6094 | ignored_buffers.push(entry); | ||
| 6095 | } else { | ||
| 6096 | buffers.push(entry); | ||
| 6097 | } | ||
| 5992 | } | 6098 | } |
| 5993 | buffers.sort_by_key(|candidate| candidate.path()); | 6099 | buffers.sort_by_key(|candidate| candidate.path()); |
| 6100 | ignored_buffers.sort_by_key(|candidate| candidate.path()); | ||
| 6101 | buffers.extend(ignored_buffers); | ||
| 5994 | let matching_paths = buffers.clone(); | 6102 | let matching_paths = buffers.clone(); |
| 5995 | let _ = sorted_buffers_tx.send(buffers); | 6103 | let _ = sorted_buffers_tx.send(buffers); |
| 5996 | for (index, candidate) in matching_paths.into_iter().enumerate() { | 6104 | for (index, candidate) in matching_paths.into_iter().enumerate() { |
| @@ -6002,7 +6110,9 @@ impl Project { · 5 unmodified lines | |||
| 6002 | cx.spawn(move |mut cx| async move { | 6110 | cx.spawn(move |mut cx| async move { |
| 6003 | let buffer = match candidate { | 6111 | let buffer = match candidate { |
| 6004 | SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer), | 6112 | SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer), |
| 6005 | SearchMatchCandidate::Path { worktree_id, path } => this | 6113 | SearchMatchCandidate::Path { |
| 6114 | worktree_id, path, .. | ||
| 6115 | } => this | ||
| 6006 | .update(&mut cx, |this, cx| { | 6116 | .update(&mut cx, |this, cx| { |
| 6007 | this.open_buffer((worktree_id, path), cx) | 6117 | this.open_buffer((worktree_id, path), cx) |
| 6008 | })? | 6118 | })? |
| @@ -2222,7 +2222,7 @@ impl LocalSnapshot { · 2221 unmodified lines | |||
| 2222 | paths | 2222 | paths |
| 2223 | } | 2223 | } |
| 2224 | 2224 | ||
| 2225 | fn is_abs_path_excluded(&self, abs_path: &Path) -> bool { | 2225 | pub fn is_path_excluded(&self, abs_path: &Path) -> bool { |
| 2226 | self.file_scan_exclusions | 2226 | self.file_scan_exclusions |
| 2227 | .iter() | 2227 | .iter() |
| 2228 | .any(|exclude_matcher| exclude_matcher.is_match(abs_path)) | 2228 | .any(|exclude_matcher| exclude_matcher.is_match(abs_path)) |
| @@ -2395,26 +2395,10 @@ impl BackgroundScannerState { · 166 unmodified lines | |||
| 2395 | self.snapshot.check_invariants(false); | 2395 | self.snapshot.check_invariants(false); |
| 2396 | } | 2396 | } |
| 2397 | 2397 | ||
| 2398 | fn reload_repositories(&mut self, changed_paths: &[Arc<Path>], fs: &dyn Fs) { | 2398 | fn reload_repositories(&mut self, dot_git_dirs_to_reload: &HashSet<PathBuf>, fs: &dyn Fs) { |
| 2399 | let scan_id = self.snapshot.scan_id; | 2399 | let scan_id = self.snapshot.scan_id; |
| 2400 | 2400 | ||
| 2401 | // Find each of the .git directories that contain any of the given paths. | 2401 | for dot_git_dir in dot_git_dirs_to_reload { |
| 2402 | let mut prev_dot_git_dir = None; | ||
| 2403 | for changed_path in changed_paths { | ||
| 2404 | let Some(dot_git_dir) = changed_path | ||
| 2405 | .ancestors() | ||
| 2406 | .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT)) | ||
| 2407 | else { | ||
| 2408 | continue; | ||
| 2409 | }; | ||
| 2410 | |||
| 2411 | // Avoid processing the same repository multiple times, if multiple paths | ||
| 2412 | // within it have changed. | ||
| 2413 | if prev_dot_git_dir == Some(dot_git_dir) { | ||
| 2414 | continue; | ||
| 2415 | } | ||
| 2416 | prev_dot_git_dir = Some(dot_git_dir); | ||
| 2417 | |||
| 2418 | // If there is already a repository for this .git directory, reload | 2402 | // If there is already a repository for this .git directory, reload |
| 2419 | // the status for all of its files. | 2403 | // the status for all of its files. |
| 2420 | let repository = self | 2404 | let repository = self |
| @@ -2426,7 +2410,7 @@ impl BackgroundScannerState { · 5 unmodified lines | |||
| 2426 | }); | 2410 | }); |
| 2427 | match repository { | 2411 | match repository { |
| 2428 | None => { | 2412 | None => { |
| 2429 | self.build_git_repository(dot_git_dir.into(), fs); | 2413 | self.build_git_repository(Arc::from(dot_git_dir.as_path()), fs); |
| 2430 | } | 2414 | } |
| 2431 | Some((entry_id, repository)) => { | 2415 | Some((entry_id, repository)) => { |
| 2432 | if repository.git_dir_scan_id == scan_id { | 2416 | if repository.git_dir_scan_id == scan_id { |
| @@ -2440,7 +2424,7 @@ impl BackgroundScannerState { · 7 unmodified lines | |||
| 2440 | continue; | 2424 | continue; |
| 2441 | }; | 2425 | }; |
| 2442 | 2426 | ||
| 2443 | log::info!("reload git repository {:?}", dot_git_dir); | 2427 | log::info!("reload git repository {dot_git_dir:?}"); |
| 2444 | let repository = repository.repo_ptr.lock(); | 2428 | let repository = repository.repo_ptr.lock(); |
| 2445 | let branch = repository.branch_name(); | 2429 | let branch = repository.branch_name(); |
| 2446 | repository.reload_index(); | 2430 | repository.reload_index(); |
| @@ -2471,7 +2455,9 @@ impl BackgroundScannerState { · 24 unmodified lines | |||
| 2471 | ids_to_preserve.insert(work_directory_id); | 2455 | ids_to_preserve.insert(work_directory_id); |
| 2472 | } else { | 2456 | } else { |
| 2473 | let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path); | 2457 | let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path); |
| 2474 | if snapshot.is_abs_path_excluded(&git_dir_abs_path) | 2458 | let git_dir_excluded = snapshot.is_path_excluded(&entry.git_dir_path) |
| 2459 | || snapshot.is_path_excluded(&git_dir_abs_path); | ||
| 2460 | if git_dir_excluded | ||
| 2475 | && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None)) | 2461 | && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None)) |
| 2476 | { | 2462 | { |
| 2477 | ids_to_preserve.insert(work_directory_id); | 2463 | ids_to_preserve.insert(work_directory_id); |
| @@ -3303,11 +3289,26 @@ impl BackgroundScanner { · 825 unmodified lines | |||
| 3303 | }; | 3289 | }; |
| 3304 | 3290 | ||
| 3305 | let mut relative_paths = Vec::with_capacity(abs_paths.len()); | 3291 | let mut relative_paths = Vec::with_capacity(abs_paths.len()); |
| 3292 | let mut dot_git_paths_to_reload = HashSet::default(); | ||
| 3306 | abs_paths.sort_unstable(); | 3293 | abs_paths.sort_unstable(); |
| 3307 | abs_paths.dedup_by(|a, b| a.starts_with(&b)); | 3294 | abs_paths.dedup_by(|a, b| a.starts_with(&b)); |
| 3308 | abs_paths.retain(|abs_path| { | 3295 | abs_paths.retain(|abs_path| { |
| 3309 | let snapshot = &self.state.lock().snapshot; | 3296 | let snapshot = &self.state.lock().snapshot; |
| 3310 | { | 3297 | { |
| 3298 | let mut is_git_related = false; | ||
| 3299 | if let Some(dot_git_dir) = abs_path | ||
| 3300 | .ancestors() | ||
| 3301 | .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT)) | ||
| 3302 | { | ||
| 3303 | let dot_git_path = dot_git_dir | ||
| 3304 | .strip_prefix(&root_canonical_path) | ||
| 3305 | .ok() | ||
| 3306 | .map(|path| path.to_path_buf()) | ||
| 3307 | .unwrap_or_else(|| dot_git_dir.to_path_buf()); | ||
| 3308 | dot_git_paths_to_reload.insert(dot_git_path.to_path_buf()); | ||
| 3309 | is_git_related = true; | ||
| 3310 | } | ||
| 3311 | |||
| 3311 | let relative_path: Arc<Path> = | 3312 | let relative_path: Arc<Path> = |
| 3312 | if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { | 3313 | if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { |
| 3313 | path.into() | 3314 | path.into() |
| @@ -3318,22 +3319,30 @@ impl BackgroundScanner { · 4 unmodified lines | |||
| 3318 | return false; | 3319 | return false; |
| 3319 | }; | 3320 | }; |
| 3320 | 3321 | ||
| 3321 | if !is_git_related(&abs_path) { | 3322 | let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { |
| 3322 | let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { | 3323 | snapshot |
| 3323 | snapshot | 3324 | .entry_for_path(parent) |
| 3324 | .entry_for_path(parent) | 3325 | .map_or(false, |entry| entry.kind == EntryKind::Dir) |
| 3325 | .map_or(false, |entry| entry.kind == EntryKind::Dir) | 3326 | }); |
| 3326 | }); | 3327 | if !parent_dir_is_loaded { |
| 3327 | if !parent_dir_is_loaded { | 3328 | log::debug!("ignoring event {relative_path:?} within unloaded directory"); |
| 3328 | log::debug!("ignoring event {relative_path:?} within unloaded directory"); | 3329 | return false; |
| 3329 | return false; | 3330 | } |
| 3331 | |||
| 3332 | // FS events may come for files which parent directory is excluded, need to check ignore those. | ||
| 3333 | let mut path_to_test = abs_path.clone(); | ||
| 3334 | let mut excluded_file_event = snapshot.is_path_excluded(abs_path) | ||
| 3335 | || snapshot.is_path_excluded(&relative_path); | ||
| 3336 | while !excluded_file_event && path_to_test.pop() { | ||
| 3337 | if snapshot.is_path_excluded(&path_to_test) { | ||
| 3338 | excluded_file_event = true; | ||
| 3330 | } | 3339 | } |
| 3331 | if snapshot.is_abs_path_excluded(abs_path) { | 3340 | } |
| 3332 | log::debug!( | 3341 | if excluded_file_event { |
| 3333 | "ignoring FS event for path {relative_path:?} within excluded directory" | 3342 | if !is_git_related { |
| 3334 | ); | 3343 | log::debug!("ignoring FS event for excluded path {relative_path:?}"); |
| 3335 | return false; | ||
| 3336 | } | 3344 | } |
| 3345 | return false; | ||
| 3337 | } | 3346 | } |
| 3338 | 3347 | ||
| 3339 | relative_paths.push(relative_path); | 3348 | relative_paths.push(relative_path); |
| @@ -3341,31 +3350,39 @@ impl BackgroundScanner { · 1 unmodified line | |||
| 3341 | } | 3350 | } |
| 3342 | }); | 3351 | }); |
| 3343 | 3352 | ||
| 3344 | if relative_paths.is_empty() { | 3353 | if dot_git_paths_to_reload.is_empty() && relative_paths.is_empty() { |
| 3345 | return; | 3354 | return; |
| 3346 | } | 3355 | } |
| 3347 | 3356 | ||
| 3348 | log::debug!("received fs events {:?}", relative_paths); | 3357 | if !relative_paths.is_empty() { |
| 3358 | log::debug!("received fs events {:?}", relative_paths); | ||
| 3349 | 3359 | ||
| 3350 | let (scan_job_tx, scan_job_rx) = channel::unbounded(); | 3360 | let (scan_job_tx, scan_job_rx) = channel::unbounded(); |
| 3351 | self.reload_entries_for_paths( | 3361 | self.reload_entries_for_paths( |
| 3352 | root_path, | 3362 | root_path, |
| 3353 | root_canonical_path, | 3363 | root_canonical_path, |
| 3354 | &relative_paths, | 3364 | &relative_paths, |
| 3355 | abs_paths, | 3365 | abs_paths, |
| 3356 | Some(scan_job_tx.clone()), | 3366 | Some(scan_job_tx.clone()), |
| 3357 | ) | 3367 | ) |
| 3358 | .await; | 3368 | .await; |
| 3359 | drop(scan_job_tx); | 3369 | drop(scan_job_tx); |
| 3360 | self.scan_dirs(false, scan_job_rx).await; | 3370 | self.scan_dirs(false, scan_job_rx).await; |
| 3361 | 3371 | ||
| 3362 | let (scan_job_tx, scan_job_rx) = channel::unbounded(); | 3372 | let (scan_job_tx, scan_job_rx) = channel::unbounded(); |
| 3363 | self.update_ignore_statuses(scan_job_tx).await; | 3373 | self.update_ignore_statuses(scan_job_tx).await; |
| 3364 | self.scan_dirs(false, scan_job_rx).await; | 3374 | self.scan_dirs(false, scan_job_rx).await; |
| 3375 | } | ||
| 3365 | 3376 | ||
| 3366 | { | 3377 | { |
| 3367 | let mut state = self.state.lock(); | 3378 | let mut state = self.state.lock(); |
| 3368 | state.reload_repositories(&relative_paths, self.fs.as_ref()); | 3379 | if !dot_git_paths_to_reload.is_empty() { |
| 3380 | if relative_paths.is_empty() { | ||
| 3381 | state.snapshot.scan_id += 1; | ||
| 3382 | } | ||
| 3383 | log::debug!("reloading repositories: {dot_git_paths_to_reload:?}"); | ||
| 3384 | state.reload_repositories(&dot_git_paths_to_reload, self.fs.as_ref()); | ||
| 3385 | } | ||
| 3369 | state.snapshot.completed_scan_id = state.snapshot.scan_id; | 3386 | state.snapshot.completed_scan_id = state.snapshot.scan_id; |
| 3370 | for (_, entry_id) in mem::take(&mut state.removed_entry_ids) { | 3387 | for (_, entry_id) in mem::take(&mut state.removed_entry_ids) { |
| 3371 | state.scanned_dirs.remove(&entry_id); | 3388 | state.scanned_dirs.remove(&entry_id); |
| @@ -3505,7 +3522,7 @@ impl BackgroundScanner { · 133 unmodified lines | |||
| 3505 | let state = self.state.lock(); | 3522 | let state = self.state.lock(); |
| 3506 | let snapshot = &state.snapshot; | 3523 | let snapshot = &state.snapshot; |
| 3507 | root_abs_path = snapshot.abs_path().clone(); | 3524 | root_abs_path = snapshot.abs_path().clone(); |
| 3508 | if snapshot.is_abs_path_excluded(&job.abs_path) { | 3525 | if snapshot.is_path_excluded(&job.abs_path) { |
| 3509 | log::error!("skipping excluded directory {:?}", job.path); | 3526 | log::error!("skipping excluded directory {:?}", job.path); |
| 3510 | return Ok(()); | 3527 | return Ok(()); |
| 3511 | } | 3528 | } |
| @@ -3577,7 +3594,7 @@ impl BackgroundScanner { · 65 unmodified lines | |||
| 3577 | 3594 | ||
| 3578 | { | 3595 | { |
| 3579 | let mut state = self.state.lock(); | 3596 | let mut state = self.state.lock(); |
| 3580 | if state.snapshot.is_abs_path_excluded(&child_abs_path) { | 3597 | if state.snapshot.is_path_excluded(&child_abs_path) { |
| 3581 | let relative_path = job.path.join(child_name); | 3598 | let relative_path = job.path.join(child_name); |
| 3582 | log::debug!("skipping excluded child entry {relative_path:?}"); | 3599 | log::debug!("skipping excluded child entry {relative_path:?}"); |
| 3583 | state.remove_path(&relative_path); | 3600 | state.remove_path(&relative_path); |
| @@ -4119,12 +4136,6 @@ impl BackgroundScanner { · 535 unmodified lines | |||
| 4119 | } | 4136 | } |
| 4120 | } | 4137 | } |
| 4121 | 4138 | ||
| 4122 | fn is_git_related(abs_path: &Path) -> bool { | ||
| 4123 | abs_path | ||
| 4124 | .components() | ||
| 4125 | .any(|c| c.as_os_str() == *DOT_GIT || c.as_os_str() == *GITIGNORE) | ||
| 4126 | } | ||
| 4127 | |||
| 4128 | fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag { | 4139 | fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag { |
| 4129 | let mut result = root_char_bag; | 4140 | let mut result = root_char_bag; |
| 4130 | result.extend( | 4141 | result.extend( |
| @@ -992,6 +992,146 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { · 991 unmodified lines | |||
| 992 | }); | 992 | }); |
| 993 | } | 993 | } |
| 994 | 994 | ||
| 995 | #[gpui::test] | ||
| 996 | async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) { | ||
| 997 | init_test(cx); | ||
| 998 | cx.executor().allow_parking(); | ||
| 999 | let dir = temp_tree(json!({ | ||
| 1000 | ".git": { | ||
| 1001 | "HEAD": "ref: refs/heads/main\n", | ||
| 1002 | "foo": "bar", | ||
| 1003 | }, | ||
| 1004 | ".gitignore": "**/target\n/node_modules\ntest_output\n", | ||
| 1005 | "target": { | ||
| 1006 | "index": "blah2" | ||
| 1007 | }, | ||
| 1008 | "node_modules": { | ||
| 1009 | ".DS_Store": "", | ||
| 1010 | "prettier": { | ||
| 1011 | "package.json": "{}", | ||
| 1012 | }, | ||
| 1013 | }, | ||
| 1014 | "src": { | ||
| 1015 | ".DS_Store": "", | ||
| 1016 | "foo": { | ||
| 1017 | "foo.rs": "mod another;\n", | ||
| 1018 | "another.rs": "// another", | ||
| 1019 | }, | ||
| 1020 | "bar": { | ||
| 1021 | "bar.rs": "// bar", | ||
| 1022 | }, | ||
| 1023 | "lib.rs": "mod foo;\nmod bar;\n", | ||
| 1024 | }, | ||
| 1025 | ".DS_Store": "", | ||
| 1026 | })); | ||
| 1027 | cx.update(|cx| { | ||
| 1028 | cx.update_global::<SettingsStore, _>(|store, cx| { | ||
| 1029 | store.update_user_settings::<ProjectSettings>(cx, |project_settings| { | ||
| 1030 | project_settings.file_scan_exclusions = Some(vec![ | ||
| 1031 | "**/.git".to_string(), | ||
| 1032 | "node_modules/".to_string(), | ||
| 1033 | "build_output".to_string(), | ||
| 1034 | ]); | ||
| 1035 | }); | ||
| 1036 | }); | ||
| 1037 | }); | ||
| 1038 | |||
| 1039 | let tree = Worktree::local( | ||
| 1040 | build_client(cx), | ||
| 1041 | dir.path(), | ||
| 1042 | true, | ||
| 1043 | Arc::new(RealFs), | ||
| 1044 | Default::default(), | ||
| 1045 | &mut cx.to_async(), | ||
| 1046 | ) | ||
| 1047 | .await | ||
| 1048 | .unwrap(); | ||
| 1049 | cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) | ||
| 1050 | .await; | ||
| 1051 | tree.flush_fs_events(cx).await; | ||
| 1052 | tree.read_with(cx, |tree, _| { | ||
| 1053 | check_worktree_entries( | ||
| 1054 | tree, | ||
| 1055 | &[ | ||
| 1056 | ".git/HEAD", | ||
| 1057 | ".git/foo", | ||
| 1058 | "node_modules/.DS_Store", | ||
| 1059 | "node_modules/prettier", | ||
| 1060 | "node_modules/prettier/package.json", | ||
| 1061 | ], | ||
| 1062 | &["target", "node_modules"], | ||
| 1063 | &[ | ||
| 1064 | ".DS_Store", | ||
| 1065 | "src/.DS_Store", | ||
| 1066 | "src/lib.rs", | ||
| 1067 | "src/foo/foo.rs", | ||
| 1068 | "src/foo/another.rs", | ||
| 1069 | "src/bar/bar.rs", | ||
| 1070 | ".gitignore", | ||
| 1071 | ], | ||
| 1072 | ) | ||
| 1073 | }); | ||
| 1074 | |||
| 1075 | let new_excluded_dir = dir.path().join("build_output"); | ||
| 1076 | let new_ignored_dir = dir.path().join("test_output"); | ||
| 1077 | std::fs::create_dir_all(&new_excluded_dir) | ||
| 1078 | .unwrap_or_else(|e| panic!("Failed to create a {new_excluded_dir:?} directory: {e}")); | ||
| 1079 | std::fs::create_dir_all(&new_ignored_dir) | ||
| 1080 | .unwrap_or_else(|e| panic!("Failed to create a {new_ignored_dir:?} directory: {e}")); | ||
| 1081 | let node_modules_dir = dir.path().join("node_modules"); | ||
| 1082 | let dot_git_dir = dir.path().join(".git"); | ||
| 1083 | let src_dir = dir.path().join("src"); | ||
| 1084 | for existing_dir in [&node_modules_dir, &dot_git_dir, &src_dir] { | ||
| 1085 | assert!( | ||
| 1086 | existing_dir.is_dir(), | ||
| 1087 | "Expect {existing_dir:?} to be present in the FS already" | ||
| 1088 | ); | ||
| 1089 | } | ||
| 1090 | |||
| 1091 | for directory_for_new_file in [ | ||
| 1092 | new_excluded_dir, | ||
| 1093 | new_ignored_dir, | ||
| 1094 | node_modules_dir, | ||
| 1095 | dot_git_dir, | ||
| 1096 | src_dir, | ||
| 1097 | ] { | ||
| 1098 | std::fs::write(directory_for_new_file.join("new_file"), "new file contents") | ||
| 1099 | .unwrap_or_else(|e| { | ||
| 1100 | panic!("Failed to create in {directory_for_new_file:?} a new file: {e}") | ||
| 1101 | }); | ||
| 1102 | } | ||
| 1103 | tree.flush_fs_events(cx).await; | ||
| 1104 | |||
| 1105 | tree.read_with(cx, |tree, _| { | ||
| 1106 | check_worktree_entries( | ||
| 1107 | tree, | ||
| 1108 | &[ | ||
| 1109 | ".git/HEAD", | ||
| 1110 | ".git/foo", | ||
| 1111 | ".git/new_file", | ||
| 1112 | "node_modules/.DS_Store", | ||
| 1113 | "node_modules/prettier", | ||
| 1114 | "node_modules/prettier/package.json", | ||
| 1115 | "node_modules/new_file", | ||
| 1116 | "build_output", | ||
| 1117 | "build_output/new_file", | ||
| 1118 | "test_output/new_file", | ||
| 1119 | ], | ||
| 1120 | &["target", "node_modules", "test_output"], | ||
| 1121 | &[ | ||
| 1122 | ".DS_Store", | ||
| 1123 | "src/.DS_Store", | ||
| 1124 | "src/lib.rs", | ||
| 1125 | "src/foo/foo.rs", | ||
| 1126 | "src/foo/another.rs", | ||
| 1127 | "src/bar/bar.rs", | ||
| 1128 | "src/new_file", | ||
| 1129 | ".gitignore", | ||
| 1130 | ], | ||
| 1131 | ) | ||
| 1132 | }); | ||
| 1133 | } | ||
| 1134 | |||
| 995 | #[gpui::test(iterations = 30)] | 1135 | #[gpui::test(iterations = 30)] |
| 996 | async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { | 1136 | async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { |
| 997 | init_test(cx); | 1137 | init_test(cx); |
| @@ -1056,7 +1196,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { · 58 unmodified lines | |||
| 1056 | async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) { | 1196 | async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) { |
| 1057 | init_test(cx); | 1197 | init_test(cx); |
| 1058 | cx.executor().allow_parking(); | 1198 | cx.executor().allow_parking(); |
| 1059 | let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); | 1199 | let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); |
| 1060 | 1200 | ||
| 1061 | let fs_fake = FakeFs::new(cx.background_executor.clone()); | 1201 | let fs_fake = FakeFs::new(cx.background_executor.clone()); |
| 1062 | fs_fake | 1202 | fs_fake |
| @@ -1096,7 +1236,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) { · 33 unmodified lines | |||
| 1096 | assert!(tree.entry_for_path("a/b/").unwrap().is_dir()); | 1236 | assert!(tree.entry_for_path("a/b/").unwrap().is_dir()); |
| 1097 | }); | 1237 | }); |
| 1098 | 1238 | ||
| 1099 | let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); | 1239 | let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); |
| 1100 | 1240 | ||
| 1101 | let fs_real = Arc::new(RealFs); | 1241 | let fs_real = Arc::new(RealFs); |
| 1102 | let temp_root = temp_tree(json!({ | 1242 | let temp_root = temp_tree(json!({ |
| @@ -2181,7 +2321,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) { · 1078 unmodified lines | |||
| 2181 | 2321 | ||
| 2182 | fn build_client(cx: &mut TestAppContext) -> Arc<Client> { | 2322 | fn build_client(cx: &mut TestAppContext) -> Arc<Client> { |
| 2183 | let http_client = FakeHttpClient::with_404_response(); | 2323 | let http_client = FakeHttpClient::with_404_response(); |
| 2184 | cx.read(|cx| Client::new(http_client, cx)) | 2324 | cx.update(|cx| Client::new(http_client, cx)) |
| 2185 | } | 2325 | } |
| 2186 | 2326 | ||
| 2187 | #[track_caller] | 2327 | #[track_caller] |
| @@ -9,7 +9,6 @@ path = "src/project_panel.rs" · 8 unmodified lines | |||
| 9 | doctest = false | 9 | doctest = false |
| 10 | 10 | ||
| 11 | [dependencies] | 11 | [dependencies] |
| 12 | context_menu = { path = "../context_menu" } | ||
| 13 | collections = { path = "../collections" } | 12 | collections = { path = "../collections" } |
| 14 | db = { path = "../db2", package = "db2" } | 13 | db = { path = "../db2", package = "db2" } |
| 15 | editor = { path = "../editor2", package = "editor2" } | 14 | editor = { path = "../editor2", package = "editor2" } |
| @@ -9,10 +9,9 @@ use file_associations::FileAssociations; · 8 unmodified lines | |||
| 9 | use anyhow::{anyhow, Result}; | 9 | use anyhow::{anyhow, Result}; |
| 10 | use gpui::{ | 10 | use gpui::{ |
| 11 | actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, | 11 | actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, |
| 12 | ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, FocusableView, | 12 | ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, |
| 13 | InteractiveComponent, Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render, | 13 | Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled, |
| 14 | Stateful, StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View, | 14 | Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext, |
| 15 | ViewContext, VisualContext as _, WeakView, WindowContext, | ||
| 16 | }; | 15 | }; |
| 17 | use menu::{Confirm, SelectNext, SelectPrev}; | 16 | use menu::{Confirm, SelectNext, SelectPrev}; |
| 18 | use project::{ | 17 | use project::{ |
| @@ -30,7 +29,7 @@ use std::{ · 11 unmodified lines | |||
| 30 | sync::Arc, | 29 | sync::Arc, |
| 31 | }; | 30 | }; |
| 32 | use theme::ActiveTheme as _; | 31 | use theme::ActiveTheme as _; |
| 33 | use ui::{h_stack, v_stack, IconElement, Label}; | 32 | use ui::{v_stack, IconElement, Label, ListItem}; |
| 34 | use unicase::UniCase; | 33 | use unicase::UniCase; |
| 35 | use util::{maybe, ResultExt, TryFutureExt}; | 34 | use util::{maybe, ResultExt, TryFutureExt}; |
| 36 | use workspace::{ | 35 | use workspace::{ |
| @@ -247,7 +246,6 @@ impl ProjectPanel { · 210 unmodified lines | |||
| 247 | let mut old_dock_position = this.position(cx); | 246 | let mut old_dock_position = this.position(cx); |
| 248 | ProjectPanelSettings::register(cx); | 247 | ProjectPanelSettings::register(cx); |
| 249 | cx.observe_global::<SettingsStore>(move |this, cx| { | 248 | cx.observe_global::<SettingsStore>(move |this, cx| { |
| 250 | dbg!("OLA!"); | ||
| 251 | let new_dock_position = this.position(cx); | 249 | let new_dock_position = this.position(cx); |
| 252 | if new_dock_position != old_dock_position { | 250 | if new_dock_position != old_dock_position { |
| 253 | old_dock_position = new_dock_position; | 251 | old_dock_position = new_dock_position; |
| @@ -372,7 +370,7 @@ impl ProjectPanel { · 118 unmodified lines | |||
| 372 | _entry_id: ProjectEntryId, | 370 | _entry_id: ProjectEntryId, |
| 373 | _cx: &mut ViewContext<Self>, | 371 | _cx: &mut ViewContext<Self>, |
| 374 | ) { | 372 | ) { |
| 375 | todo!() | 373 | // todo!() |
| 376 | // let project = self.project.read(cx); | 374 | // let project = self.project.read(cx); |
| 377 | 375 | ||
| 378 | // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { | 376 | // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { |
| @@ -645,6 +643,7 @@ impl ProjectPanel { · 266 unmodified lines | |||
| 645 | } | 643 | } |
| 646 | 644 | ||
| 647 | fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { | 645 | fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { |
| 646 | dbg!("odd"); | ||
| 648 | self.edit_state = None; | 647 | self.edit_state = None; |
| 649 | self.update_visible_entries(None, cx); | 648 | self.update_visible_entries(None, cx); |
| 650 | cx.focus(&self.focus_handle); | 649 | cx.focus(&self.focus_handle); |
| @@ -1335,13 +1334,19 @@ impl ProjectPanel { · 684 unmodified lines | |||
| 1335 | } | 1334 | } |
| 1336 | } | 1335 | } |
| 1337 | 1336 | ||
| 1338 | fn render_entry_visual_element( | 1337 | fn render_entry( |
| 1339 | details: &EntryDetails, | 1338 | &self, |
| 1340 | editor: Option<&View<Editor>>, | 1339 | entry_id: ProjectEntryId, |
| 1341 | padding: Pixels, | 1340 | details: EntryDetails, |
| 1341 | // dragged_entry_destination: &mut Option<Arc<Path>>, | ||
| 1342 | cx: &mut ViewContext<Self>, | 1342 | cx: &mut ViewContext<Self>, |
| 1343 | ) -> Div<Self> { | 1343 | ) -> ListItem { |
| 1344 | let kind = details.kind; | ||
| 1345 | let settings = ProjectPanelSettings::get_global(cx); | ||
| 1344 | let show_editor = details.is_editing && !details.is_processing; | 1346 | let show_editor = details.is_editing && !details.is_processing; |
| 1347 | let is_selected = self | ||
| 1348 | .selection | ||
| 1349 | .map_or(false, |selection| selection.entry_id == entry_id); | ||
| 1345 | 1350 | ||
| 1346 | let theme = cx.theme(); | 1351 | let theme = cx.theme(); |
| 1347 | let filename_text_color = details | 1352 | let filename_text_color = details |
| @@ -1354,14 +1359,17 @@ impl ProjectPanel { · 6 unmodified lines | |||
| 1354 | }) | 1359 | }) |
| 1355 | .unwrap_or(theme.status().info); | 1360 | .unwrap_or(theme.status().info); |
| 1356 | 1361 | ||
| 1357 | h_stack() | 1362 | ListItem::new(entry_id.to_proto() as usize) |
| 1363 | .indent_level(details.depth) | ||
| 1364 | .indent_step_size(px(settings.indent_size)) | ||
| 1365 | .selected(is_selected) | ||
| 1358 | .child(if let Some(icon) = &details.icon { | 1366 | .child(if let Some(icon) = &details.icon { |
| 1359 | div().child(IconElement::from_path(icon.to_string())) | 1367 | div().child(IconElement::from_path(icon.to_string())) |
| 1360 | } else { | 1368 | } else { |
| 1361 | div() | 1369 | div() |
| 1362 | }) | 1370 | }) |
| 1363 | .child( | 1371 | .child( |
| 1364 | if let (Some(editor), true) = (editor, show_editor) { | 1372 | if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { |
| 1365 | div().w_full().child(editor.clone()) | 1373 | div().w_full().child(editor.clone()) |
| 1366 | } else { | 1374 | } else { |
| 1367 | div() | 1375 | div() |
| @@ -1370,34 +1378,7 @@ impl ProjectPanel { · 2 unmodified lines | |||
| 1370 | } | 1378 | } |
| 1371 | .ml_1(), | 1379 | .ml_1(), |
| 1372 | ) | 1380 | ) |
| 1373 | .pl(padding) | 1381 | .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| { |
| 1374 | } | ||
| 1375 | |||
| 1376 | fn render_entry( | ||
| 1377 | &self, | ||
| 1378 | entry_id: ProjectEntryId, | ||
| 1379 | details: EntryDetails, | ||
| 1380 | // dragged_entry_destination: &mut Option<Arc<Path>>, | ||
| 1381 | cx: &mut ViewContext<Self>, | ||
| 1382 | ) -> Stateful<Self, Div<Self>> { | ||
| 1383 | let kind = details.kind; | ||
| 1384 | let settings = ProjectPanelSettings::get_global(cx); | ||
| 1385 | const INDENT_SIZE: Pixels = px(16.0); | ||
| 1386 | let padding = INDENT_SIZE + details.depth as f32 * px(settings.indent_size); | ||
| 1387 | let show_editor = details.is_editing && !details.is_processing; | ||
| 1388 | let is_selected = self | ||
| 1389 | .selection | ||
| 1390 | .map_or(false, |selection| selection.entry_id == entry_id); | ||
| 1391 | |||
| 1392 | Self::render_entry_visual_element(&details, Some(&self.filename_editor), padding, cx) | ||
| 1393 | .id(entry_id.to_proto() as usize) | ||
| 1394 | .w_full() | ||
| 1395 | .cursor_pointer() | ||
| 1396 | .when(is_selected, |this| { | ||
| 1397 | this.bg(cx.theme().colors().element_selected) | ||
| 1398 | }) | ||
| 1399 | .hover(|style| style.bg(cx.theme().colors().element_hover)) | ||
| 1400 | .on_click(move |this, event, cx| { | ||
| 1401 | if !show_editor { | 1382 | if !show_editor { |
| 1402 | if kind.is_dir() { | 1383 | if kind.is_dir() { |
| 1403 | this.toggle_expanded(entry_id, cx); | 1384 | this.toggle_expanded(entry_id, cx); |
| @@ -1409,10 +1390,10 @@ impl ProjectPanel { · 5 unmodified lines | |||
| 1409 | } | 1390 | } |
| 1410 | } | 1391 | } |
| 1411 | } | 1392 | } |
| 1412 | }) | 1393 | })) |
| 1413 | .on_mouse_down(MouseButton::Right, move |this, event, cx| { | 1394 | .on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { |
| 1414 | this.deploy_context_menu(event.position, entry_id, cx); | 1395 | this.deploy_context_menu(event.position, entry_id, cx); |
| 1415 | }) | 1396 | })) |
| 1416 | // .on_drop::<ProjectEntryId>(|this, event, cx| { | 1397 | // .on_drop::<ProjectEntryId>(|this, event, cx| { |
| 1417 | // this.move_entry( | 1398 | // this.move_entry( |
| 1418 | // *dragged_entry, | 1399 | // *dragged_entry, |
| @@ -1425,9 +1406,9 @@ impl ProjectPanel { · 6 unmodified lines | |||
| 1425 | } | 1406 | } |
| 1426 | 1407 | ||
| 1427 | impl Render for ProjectPanel { | 1408 | impl Render for ProjectPanel { |
| 1428 | type Element = Focusable<Self, Stateful<Self, Div<Self>>>; | 1409 | type Element = Focusable<Stateful<Div>>; |
| 1429 | 1410 | ||
| 1430 | fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element { | 1411 | fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { |
| 1431 | let has_worktree = self.visible_entries.len() != 0; | 1412 | let has_worktree = self.visible_entries.len() != 0; |
| 1432 | 1413 | ||
| 1433 | if has_worktree { | 1414 | if has_worktree { |
| @@ -1435,40 +1416,43 @@ impl Render for ProjectPanel { · 1 unmodified line | |||
| 1435 | .id("project-panel") | 1416 | .id("project-panel") |
| 1436 | .size_full() | 1417 | .size_full() |
| 1437 | .key_context("ProjectPanel") | 1418 | .key_context("ProjectPanel") |
| 1438 | .on_action(Self::select_next) | 1419 | .on_action(cx.listener(Self::select_next)) |
| 1439 | .on_action(Self::select_prev) | 1420 | .on_action(cx.listener(Self::select_prev)) |
| 1440 | .on_action(Self::expand_selected_entry) | 1421 | .on_action(cx.listener(Self::expand_selected_entry)) |
| 1441 | .on_action(Self::collapse_selected_entry) | 1422 | .on_action(cx.listener(Self::collapse_selected_entry)) |
| 1442 | .on_action(Self::collapse_all_entries) | 1423 | .on_action(cx.listener(Self::collapse_all_entries)) |
| 1443 | .on_action(Self::new_file) | 1424 | .on_action(cx.listener(Self::new_file)) |
| 1444 | .on_action(Self::new_directory) | 1425 | .on_action(cx.listener(Self::new_directory)) |
| 1445 | .on_action(Self::rename) | 1426 | .on_action(cx.listener(Self::rename)) |
| 1446 | .on_action(Self::delete) | 1427 | .on_action(cx.listener(Self::delete)) |
| 1447 | .on_action(Self::confirm) | 1428 | .on_action(cx.listener(Self::confirm)) |
| 1448 | .on_action(Self::open_file) | 1429 | .on_action(cx.listener(Self::open_file)) |
| 1449 | .on_action(Self::cancel) | 1430 | .on_action(cx.listener(Self::cancel)) |
| 1450 | .on_action(Self::cut) | 1431 | .on_action(cx.listener(Self::cut)) |
| 1451 | .on_action(Self::copy) | 1432 | .on_action(cx.listener(Self::copy)) |
| 1452 | .on_action(Self::copy_path) | 1433 | .on_action(cx.listener(Self::copy_path)) |
| 1453 | .on_action(Self::copy_relative_path) | 1434 | .on_action(cx.listener(Self::copy_relative_path)) |
| 1454 | .on_action(Self::paste) | 1435 | .on_action(cx.listener(Self::paste)) |
| 1455 | .on_action(Self::reveal_in_finder) | 1436 | .on_action(cx.listener(Self::reveal_in_finder)) |
| 1456 | .on_action(Self::open_in_terminal) | 1437 | .on_action(cx.listener(Self::open_in_terminal)) |
| 1457 | .on_action(Self::new_search_in_directory) | 1438 | .on_action(cx.listener(Self::new_search_in_directory)) |
| 1458 | .track_focus(&self.focus_handle) | 1439 | .track_focus(&self.focus_handle) |
| 1459 | .child( | 1440 | .child( |
| 1460 | uniform_list( | 1441 | uniform_list( |
| 1442 | cx.view().clone(), | ||
| 1461 | "entries", | 1443 | "entries", |
| 1462 | self.visible_entries | 1444 | self.visible_entries |
| 1463 | .iter() | 1445 | .iter() |
| 1464 | .map(|(_, worktree_entries)| worktree_entries.len()) | 1446 | .map(|(_, worktree_entries)| worktree_entries.len()) |
| 1465 | .sum(), | 1447 | .sum(), |
| 1466 | |this: &mut Self, range, cx| { | 1448 | { |
| 1467 | let mut items = Vec::new(); | 1449 | |this, range, cx| { |
| 1468 | this.for_each_visible_entry(range, cx, |id, details, cx| { | 1450 | let mut items = Vec::new(); |
| 1469 | items.push(this.render_entry(id, details, cx)); | 1451 | this.for_each_visible_entry(range, cx, |id, details, cx| { |
| 1470 | }); | 1452 | items.push(this.render_entry(id, details, cx)); |
| 1471 | items | 1453 | }); |
| 1454 | items | ||
| 1455 | } | ||
| 1472 | }, | 1456 | }, |
| 1473 | ) | 1457 | ) |
| 1474 | .size_full() | 1458 | .size_full() |
| @@ -56,12 +56,12 @@ pub struct Mention { · 55 unmodified lines | |||
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | impl RichText { | 58 | impl RichText { |
| 59 | pub fn element<V: 'static>( | 59 | pub fn element( |
| 60 | &self, | 60 | &self, |
| 61 | // syntax: Arc<SyntaxTheme>, | 61 | // syntax: Arc<SyntaxTheme>, |
| 62 | // style: RichTextStyle, | 62 | // style: RichTextStyle, |
| 63 | // cx: &mut ViewContext<V>, | 63 | // cx: &mut ViewContext<V>, |
| 64 | ) -> AnyElement<V> { | 64 | ) -> AnyElement { |
| 65 | todo!(); | 65 | todo!(); |
| 66 | 66 | ||
| 67 | // let mut region_id = 0; | 67 | // let mut region_id = 0; |
| @@ -1767,16 +1767,13 @@ impl View for ProjectSearchBar { · 1766 unmodified lines | |||
| 1767 | render_option_button_icon("icons/word_search.svg", SearchOptions::WHOLE_WORD, cx) | 1767 | render_option_button_icon("icons/word_search.svg", SearchOptions::WHOLE_WORD, cx) |
| 1768 | }); | 1768 | }); |
| 1769 | 1769 | ||
| 1770 | let mut include_ignored = is_semantic_disabled.then(|| { | 1770 | let include_ignored = is_semantic_disabled.then(|| { |
| 1771 | render_option_button_icon( | 1771 | render_option_button_icon( |
| 1772 | // TODO proper icon | 1772 | "icons/file_icons/git.svg", |
| 1773 | "icons/case_insensitive.svg", | ||
| 1774 | SearchOptions::INCLUDE_IGNORED, | 1773 | SearchOptions::INCLUDE_IGNORED, |
| 1775 | cx, | 1774 | cx, |
| 1776 | ) | 1775 | ) |
| 1777 | }); | 1776 | }); |
| 1778 | // TODO not implemented yet | ||
| 1779 | let _ = include_ignored.take(); | ||
| 1780 | 1777 | ||
| 1781 | let search_button_for_mode = |mode, side, cx: &mut ViewContext<ProjectSearchBar>| { | 1778 | let search_button_for_mode = |mode, side, cx: &mut ViewContext<ProjectSearchBar>| { |
| 1782 | let is_active = if let Some(search) = self.active_project_search.as_ref() { | 1779 | let is_active = if let Some(search) = self.active_project_search.as_ref() { |
| @@ -0,0 +1,40 @@ | |||
| 1 | [package] | ||
| 2 | name = "search2" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | publish = false | ||
| 6 | |||
| 7 | [lib] | ||
| 8 | path = "src/search.rs" | ||
| 9 | doctest = false | ||
| 10 | |||
| 11 | [dependencies] | ||
| 12 | bitflags = "1" | ||
| 13 | collections = { path = "../collections" } | ||
| 14 | editor = { package = "editor2", path = "../editor2" } | ||
| 15 | gpui = { package = "gpui2", path = "../gpui2" } | ||
| 16 | language = { package = "language2", path = "../language2" } | ||
| 17 | menu = { package = "menu2", path = "../menu2" } | ||
| 18 | project = { package = "project2", path = "../project2" } | ||
| 19 | settings = { package = "settings2", path = "../settings2" } | ||
| 20 | theme = { package = "theme2", path = "../theme2" } | ||
| 21 | util = { path = "../util" } | ||
| 22 | ui = {package = "ui2", path = "../ui2"} | ||
| 23 | workspace = { package = "workspace2", path = "../workspace2" } | ||
| 24 | #semantic_index = { path = "../semantic_index" } | ||
| 25 | anyhow.workspace = true | ||
| 26 | futures.workspace = true | ||
| 27 | log.workspace = true | ||
| 28 | postage.workspace = true | ||
| 29 | serde.workspace = true | ||
| 30 | serde_derive.workspace = true | ||
| 31 | smallvec.workspace = true | ||
| 32 | smol.workspace = true | ||
| 33 | serde_json.workspace = true | ||
| 34 | [dev-dependencies] | ||
| 35 | client = { package = "client2", path = "../client2", features = ["test-support"] } | ||
| 36 | editor = { package = "editor2", path = "../editor2", features = ["test-support"] } | ||
| 37 | gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } | ||
| 38 | |||
| 39 | workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] } | ||
| 40 | unindent.workspace = true | ||
| @@ -0,0 +1,1747 @@ | |||
| 1 | use crate::{ | ||
| 2 | history::SearchHistory, | ||
| 3 | mode::{next_mode, SearchMode}, | ||
| 4 | search_bar::{render_nav_button, render_search_mode_button}, | ||
| 5 | ActivateRegexMode, ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, | ||
| 6 | ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch, | ||
| 7 | ToggleCaseSensitive, ToggleReplace, ToggleWholeWord, | ||
| 8 | }; | ||
| 9 | use collections::HashMap; | ||
| 10 | use editor::{Editor, EditorMode}; | ||
| 11 | use futures::channel::oneshot; | ||
| 12 | use gpui::{ | ||
| 13 | actions, div, red, Action, AppContext, Div, EventEmitter, InteractiveElement as _, IntoElement, | ||
| 14 | ParentElement as _, Render, Styled, Subscription, Task, View, ViewContext, VisualContext as _, | ||
| 15 | WeakView, WindowContext, | ||
| 16 | }; | ||
| 17 | use project::search::SearchQuery; | ||
| 18 | use serde::Deserialize; | ||
| 19 | use std::{any::Any, sync::Arc}; | ||
| 20 | |||
| 21 | use ui::{h_stack, ButtonGroup, Icon, IconButton, IconElement}; | ||
| 22 | use util::ResultExt; | ||
| 23 | use workspace::{ | ||
| 24 | item::ItemHandle, | ||
| 25 | searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle}, | ||
| 26 | ToolbarItemLocation, ToolbarItemView, | ||
| 27 | }; | ||
| 28 | |||
| 29 | #[derive(PartialEq, Clone, Deserialize, Default, Action)] | ||
| 30 | pub struct Deploy { | ||
| 31 | pub focus: bool, | ||
| 32 | } | ||
| 33 | |||
| 34 | actions!(Dismiss, FocusEditor); | ||
| 35 | |||
| 36 | pub enum Event { | ||
| 37 | UpdateLocation, | ||
| 38 | } | ||
| 39 | |||
| 40 | pub fn init(cx: &mut AppContext) { | ||
| 41 | cx.observe_new_views(|editor: &mut Editor, cx| BufferSearchBar::register(editor, cx)) | ||
| 42 | .detach(); | ||
| 43 | } | ||
| 44 | |||
| 45 | pub struct BufferSearchBar { | ||
| 46 | query_editor: View<Editor>, | ||
| 47 | replacement_editor: View<Editor>, | ||
| 48 | active_searchable_item: Option<Box<dyn SearchableItemHandle>>, | ||
| 49 | active_match_index: Option<usize>, | ||
| 50 | active_searchable_item_subscription: Option<Subscription>, | ||
| 51 | active_search: Option<Arc<SearchQuery>>, | ||
| 52 | searchable_items_with_matches: | ||
| 53 | HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>, | ||
| 54 | pending_search: Option<Task<()>>, | ||
| 55 | search_options: SearchOptions, | ||
| 56 | default_options: SearchOptions, | ||
| 57 | query_contains_error: bool, | ||
| 58 | dismissed: bool, | ||
| 59 | search_history: SearchHistory, | ||
| 60 | current_mode: SearchMode, | ||
| 61 | replace_enabled: bool, | ||
| 62 | } | ||
| 63 | |||
| 64 | impl EventEmitter<Event> for BufferSearchBar {} | ||
| 65 | impl EventEmitter<workspace::ToolbarItemEvent> for BufferSearchBar {} | ||
| 66 | impl Render for BufferSearchBar { | ||
| 67 | type Element = Div; | ||
| 68 | fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { | ||
| 69 | // let query_container_style = if self.query_contains_error { | ||
| 70 | // theme.search.invalid_editor | ||
| 71 | // } else { | ||
| 72 | // theme.search.editor.input.container | ||
| 73 | // }; | ||
| 74 | if self.dismissed { | ||
| 75 | return div(); | ||
| 76 | } | ||
| 77 | let supported_options = self.supported_options(); | ||
| 78 | |||
| 79 | let previous_query_keystrokes = cx | ||
| 80 | .bindings_for_action(&PreviousHistoryQuery {}) | ||
| 81 | .into_iter() | ||
| 82 | .next() | ||
| 83 | .map(|binding| { | ||
| 84 | binding | ||
| 85 | .keystrokes() | ||
| 86 | .iter() | ||
| 87 | .map(|k| k.to_string()) | ||
| 88 | .collect::<Vec<_>>() | ||
| 89 | }); | ||
| 90 | let next_query_keystrokes = cx | ||
| 91 | .bindings_for_action(&NextHistoryQuery {}) | ||
| 92 | .into_iter() | ||
| 93 | .next() | ||
| 94 | .map(|binding| { | ||
| 95 | binding | ||
| 96 | .keystrokes() | ||
| 97 | .iter() | ||
| 98 | .map(|k| k.to_string()) | ||
| 99 | .collect::<Vec<_>>() | ||
| 100 | }); | ||
| 101 | let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) { | ||
| 102 | (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => { | ||
| 103 | format!( | ||
| 104 | "Search ({}/{} for previous/next query)", | ||
| 105 | previous_query_keystrokes.join(" "), | ||
| 106 | next_query_keystrokes.join(" ") | ||
| 107 | ) | ||
| 108 | } | ||
| 109 | (None, Some(next_query_keystrokes)) => { | ||
| 110 | format!( | ||
| 111 | "Search ({} for next query)", | ||
| 112 | next_query_keystrokes.join(" ") | ||
| 113 | ) | ||
| 114 | } | ||
| 115 | (Some(previous_query_keystrokes), None) => { | ||
| 116 | format!( | ||
| 117 | "Search ({} for previous query)", | ||
| 118 | previous_query_keystrokes.join(" ") | ||
| 119 | ) | ||
| 120 | } | ||
| 121 | (None, None) => String::new(), | ||
| 122 | }; | ||
| 123 | let new_placeholder_text = Arc::from(new_placeholder_text); | ||
| 124 | self.query_editor.update(cx, |editor, cx| { | ||
| 125 | editor.set_placeholder_text(new_placeholder_text, cx); | ||
| 126 | }); | ||
| 127 | self.replacement_editor.update(cx, |editor, cx| { | ||
| 128 | editor.set_placeholder_text("Replace with...", cx); | ||
| 129 | }); | ||
| 130 | |||
| 131 | let search_button_for_mode = |mode| { | ||
| 132 | let is_active = self.current_mode == mode; | ||
| 133 | |||
| 134 | render_search_mode_button( | ||
| 135 | mode, | ||
| 136 | is_active, | ||
| 137 | cx.listener(move |this, _, cx| { | ||
| 138 | this.activate_search_mode(mode, cx); | ||
| 139 | }), | ||
| 140 | ) | ||
| 141 | }; | ||
| 142 | let search_option_button = |option| { | ||
| 143 | let is_active = self.search_options.contains(option); | ||
| 144 | option.as_button(is_active) | ||
| 145 | }; | ||
| 146 | let match_count = self | ||
| 147 | .active_searchable_item | ||
| 148 | .as_ref() | ||
| 149 | .and_then(|searchable_item| { | ||
| 150 | if self.query(cx).is_empty() { | ||
| 151 | return None; | ||
| 152 | } | ||
| 153 | let matches = self | ||
| 154 | .searchable_items_with_matches | ||
| 155 | .get(&searchable_item.downgrade())?; | ||
| 156 | let message = if let Some(match_ix) = self.active_match_index { | ||
| 157 | format!("{}/{}", match_ix + 1, matches.len()) | ||
| 158 | } else { | ||
| 159 | "No matches".to_string() | ||
| 160 | }; | ||
| 161 | |||
| 162 | Some(ui::Label::new(message)) | ||
| 163 | }); | ||
| 164 | let nav_button_for_direction = |icon, direction| { | ||
| 165 | render_nav_button( | ||
| 166 | icon, | ||
| 167 | self.active_match_index.is_some(), | ||
| 168 | cx.listener(move |this, _, cx| match direction { | ||
| 169 | Direction::Prev => this.select_prev_match(&Default::default(), cx), | ||
| 170 | Direction::Next => this.select_next_match(&Default::default(), cx), | ||
| 171 | }), | ||
| 172 | ) | ||
| 173 | }; | ||
| 174 | let should_show_replace_input = self.replace_enabled && supported_options.replacement; | ||
| 175 | let replace_all = should_show_replace_input | ||
| 176 | .then(|| super::render_replace_button(ReplaceAll, ui::Icon::ReplaceAll)); | ||
| 177 | let replace_next = should_show_replace_input | ||
| 178 | .then(|| super::render_replace_button(ReplaceNext, ui::Icon::Replace)); | ||
| 179 | let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx); | ||
| 180 | |||
| 181 | h_stack() | ||
| 182 | .key_context("BufferSearchBar") | ||
| 183 | .when(in_replace, |this| { | ||
| 184 | this.key_context("in_replace") | ||
| 185 | .on_action(cx.listener(Self::replace_next)) | ||
| 186 | .on_action(cx.listener(Self::replace_all)) | ||
| 187 | }) | ||
| 188 | .on_action(cx.listener(Self::previous_history_query)) | ||
| 189 | .on_action(cx.listener(Self::next_history_query)) | ||
| 190 | .on_action(cx.listener(Self::dismiss)) | ||
| 191 | .w_full() | ||
| 192 | .p_1() | ||
| 193 | .child( | ||
| 194 | div() | ||
| 195 | .flex() | ||
| 196 | .flex_1() | ||
| 197 | .border_1() | ||
| 198 | .border_color(red()) | ||
| 199 | .rounded_md() | ||
| 200 | .items_center() | ||
| 201 | .child(IconElement::new(Icon::MagnifyingGlass)) | ||
| 202 | .child(self.query_editor.clone()) | ||
| 203 | .children( | ||
| 204 | supported_options | ||
| 205 | .case | ||
| 206 | .then(|| search_option_button(SearchOptions::CASE_SENSITIVE)), | ||
| 207 | ) | ||
| 208 | .children( | ||
| 209 | supported_options | ||
| 210 | .word | ||
| 211 | .then(|| search_option_button(SearchOptions::WHOLE_WORD)), | ||
| 212 | ), | ||
| 213 | ) | ||
| 214 | .child( | ||
| 215 | h_stack() | ||
| 216 | .flex_none() | ||
| 217 | .child(ButtonGroup::new(vec![ | ||
| 218 | search_button_for_mode(SearchMode::Text), | ||
| 219 | search_button_for_mode(SearchMode::Regex), | ||
| 220 | ])) | ||
| 221 | .when(supported_options.replacement, |this| { | ||
| 222 | this.child(super::toggle_replace_button(self.replace_enabled)) | ||
| 223 | }), | ||
| 224 | ) | ||
| 225 | .child( | ||
| 226 | h_stack() | ||
| 227 | .gap_0p5() | ||
| 228 | .flex_1() | ||
| 229 | .when(self.replace_enabled, |this| { | ||
| 230 | this.child(self.replacement_editor.clone()) | ||
| 231 | .children(replace_next) | ||
| 232 | .children(replace_all) | ||
| 233 | }), | ||
| 234 | ) | ||
| 235 | .child( | ||
| 236 | h_stack() | ||
| 237 | .gap_0p5() | ||
| 238 | .flex_none() | ||
| 239 | .child(self.render_action_button()) | ||
| 240 | .children(match_count) | ||
| 241 | .child(nav_button_for_direction( | ||
| 242 | ui::Icon::ChevronLeft, | ||
| 243 | Direction::Prev, | ||
| 244 | )) | ||
| 245 | .child(nav_button_for_direction( | ||
| 246 | ui::Icon::ChevronRight, | ||
| 247 | Direction::Next, | ||
| 248 | )), | ||
| 249 | ) | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | impl ToolbarItemView for BufferSearchBar { | ||
| 254 | fn set_active_pane_item( | ||
| 255 | &mut self, | ||
| 256 | item: Option<&dyn ItemHandle>, | ||
| 257 | cx: &mut ViewContext<Self>, | ||
| 258 | ) -> ToolbarItemLocation { | ||
| 259 | cx.notify(); | ||
| 260 | self.active_searchable_item_subscription.take(); | ||
| 261 | self.active_searchable_item.take(); | ||
| 262 | |||
| 263 | self.pending_search.take(); | ||
| 264 | |||
| 265 | if let Some(searchable_item_handle) = | ||
| 266 | item.and_then(|item| item.to_searchable_item_handle(cx)) | ||
| 267 | { | ||
| 268 | let this = cx.view().downgrade(); | ||
| 269 | |||
| 270 | searchable_item_handle | ||
| 271 | .subscribe_to_search_events( | ||
| 272 | cx, | ||
| 273 | Box::new(move |search_event, cx| { | ||
| 274 | if let Some(this) = this.upgrade() { | ||
| 275 | this.update(cx, |this, cx| { | ||
| 276 | this.on_active_searchable_item_event(search_event, cx) | ||
| 277 | }); | ||
| 278 | } | ||
| 279 | }), | ||
| 280 | ) | ||
| 281 | .detach(); | ||
| 282 | |||
| 283 | self.active_searchable_item = Some(searchable_item_handle); | ||
| 284 | let _ = self.update_matches(cx); | ||
| 285 | if !self.dismissed { | ||
| 286 | return ToolbarItemLocation::Secondary; | ||
| 287 | } | ||
| 288 | } | ||
| 289 | ToolbarItemLocation::Hidden | ||
| 290 | } | ||
| 291 | |||
| 292 | fn row_count(&self, _: &WindowContext<'_>) -> usize { | ||
| 293 | 1 | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | impl BufferSearchBar { | ||
| 298 | pub fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) { | ||
| 299 | if editor.mode() != EditorMode::Full { | ||
| 300 | return; | ||
| 301 | }; | ||
| 302 | |||
| 303 | let handle = cx.view().downgrade(); | ||
| 304 | |||
| 305 | editor.register_action(move |a: &Deploy, cx| { | ||
| 306 | let Some(pane) = handle.upgrade().and_then(|editor| editor.read(cx).pane(cx)) else { | ||
| 307 | return; | ||
| 308 | }; | ||
| 309 | |||
| 310 | pane.update(cx, |this, cx| { | ||
| 311 | this.toolbar().update(cx, |this, cx| { | ||
| 312 | if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() { | ||
| 313 | search_bar.update(cx, |this, cx| { | ||
| 314 | if this.is_dismissed() { | ||
| 315 | this.show(cx); | ||
| 316 | } else { | ||
| 317 | this.dismiss(&Dismiss, cx); | ||
| 318 | } | ||
| 319 | }); | ||
| 320 | return; | ||
| 321 | } | ||
| 322 | let view = cx.build_view(|cx| BufferSearchBar::new(cx)); | ||
| 323 | this.add_item(view.clone(), cx); | ||
| 324 | view.update(cx, |this, cx| this.deploy(a, cx)); | ||
| 325 | cx.notify(); | ||
| 326 | }) | ||
| 327 | }); | ||
| 328 | }); | ||
| 329 | fn register_action<A: Action>( | ||
| 330 | editor: &mut Editor, | ||
| 331 | handle: WeakView<Editor>, | ||
| 332 | update: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>), | ||
| 333 | ) { | ||
| 334 | editor.register_action(move |action: &A, cx| { | ||
| 335 | let Some(pane) = handle.upgrade().and_then(|editor| editor.read(cx).pane(cx)) | ||
| 336 | else { | ||
| 337 | return; | ||
| 338 | }; | ||
| 339 | pane.update(cx, move |this, cx| { | ||
| 340 | this.toolbar().update(cx, move |this, cx| { | ||
| 341 | if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() { | ||
| 342 | search_bar.update(cx, move |this, cx| update(this, action, cx)); | ||
| 343 | cx.notify(); | ||
| 344 | } | ||
| 345 | }) | ||
| 346 | }); | ||
| 347 | }); | ||
| 348 | } | ||
| 349 | |||
| 350 | let handle = cx.view().downgrade(); | ||
| 351 | register_action( | ||
| 352 | editor, | ||
| 353 | handle.clone(), | ||
| 354 | |this, action: &ToggleCaseSensitive, cx| { | ||
| 355 | if this.supported_options().case { | ||
| 356 | this.toggle_case_sensitive(action, cx); | ||
| 357 | } | ||
| 358 | }, | ||
| 359 | ); | ||
| 360 | register_action( | ||
| 361 | editor, | ||
| 362 | handle.clone(), | ||
| 363 | |this, action: &ToggleWholeWord, cx| { | ||
| 364 | if this.supported_options().word { | ||
| 365 | this.toggle_whole_word(action, cx); | ||
| 366 | } | ||
| 367 | }, | ||
| 368 | ); | ||
| 369 | register_action( | ||
| 370 | editor, | ||
| 371 | handle.clone(), | ||
| 372 | |this, action: &ToggleReplace, cx| { | ||
| 373 | if this.supported_options().replacement { | ||
| 374 | this.toggle_replace(action, cx); | ||
| 375 | } | ||
| 376 | }, | ||
| 377 | ); | ||
| 378 | register_action(editor, handle.clone(), |this, _: &ActivateRegexMode, cx| { | ||
| 379 | if this.supported_options().regex { | ||
| 380 | this.activate_search_mode(SearchMode::Regex, cx); | ||
| 381 | } | ||
| 382 | }); | ||
| 383 | register_action(editor, handle.clone(), |this, _: &ActivateTextMode, cx| { | ||
| 384 | this.activate_search_mode(SearchMode::Text, cx); | ||
| 385 | }); | ||
| 386 | register_action(editor, handle.clone(), |this, action: &CycleMode, cx| { | ||
| 387 | if this.supported_options().regex { | ||
| 388 | // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting | ||
| 389 | // cycling. | ||
| 390 | this.cycle_mode(action, cx) | ||
| 391 | } | ||
| 392 | }); | ||
| 393 | register_action( | ||
| 394 | editor, | ||
| 395 | handle.clone(), | ||
| 396 | |this, action: &SelectNextMatch, cx| { | ||
| 397 | this.select_next_match(action, cx); | ||
| 398 | }, | ||
| 399 | ); | ||
| 400 | register_action( | ||
| 401 | editor, | ||
| 402 | handle.clone(), | ||
| 403 | |this, action: &SelectPrevMatch, cx| { | ||
| 404 | this.select_prev_match(action, cx); | ||
| 405 | }, | ||
| 406 | ); | ||
| 407 | register_action( | ||
| 408 | editor, | ||
| 409 | handle.clone(), | ||
| 410 | |this, action: &SelectAllMatches, cx| { | ||
| 411 | this.select_all_matches(action, cx); | ||
| 412 | }, | ||
| 413 | ); | ||
| 414 | register_action(editor, handle.clone(), |this, _: &editor::Cancel, cx| { | ||
| 415 | if !this.dismissed { | ||
| 416 | this.dismiss(&Dismiss, cx); | ||
| 417 | return; | ||
| 418 | } | ||
| 419 | cx.propagate(); | ||
| 420 | }); | ||
| 421 | } | ||
| 422 | pub fn new(cx: &mut ViewContext<Self>) -> Self { | ||
| 423 | let query_editor = cx.build_view(|cx| Editor::single_line(cx)); | ||
| 424 | cx.subscribe(&query_editor, Self::on_query_editor_event) | ||
| 425 | .detach(); | ||
| 426 | let replacement_editor = cx.build_view(|cx| Editor::single_line(cx)); | ||
| 427 | cx.subscribe(&replacement_editor, Self::on_query_editor_event) | ||
| 428 | .detach(); | ||
| 429 | Self { | ||
| 430 | query_editor, | ||
| 431 | replacement_editor, | ||
| 432 | active_searchable_item: None, | ||
| 433 | active_searchable_item_subscription: None, | ||
| 434 | active_match_index: None, | ||
| 435 | searchable_items_with_matches: Default::default(), | ||
| 436 | default_options: SearchOptions::NONE, | ||
| 437 | search_options: SearchOptions::NONE, | ||
| 438 | pending_search: None, | ||
| 439 | query_contains_error: false, | ||
| 440 | dismissed: true, | ||
| 441 | search_history: SearchHistory::default(), | ||
| 442 | current_mode: SearchMode::default(), | ||
| 443 | active_search: None, | ||
| 444 | replace_enabled: false, | ||
| 445 | } | ||
| 446 | } | ||
| 447 | |||
| 448 | pub fn is_dismissed(&self) -> bool { | ||
| 449 | self.dismissed | ||
| 450 | } | ||
| 451 | |||
| 452 | pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) { | ||
| 453 | self.dismissed = true; | ||
| 454 | for searchable_item in self.searchable_items_with_matches.keys() { | ||
| 455 | if let Some(searchable_item) = | ||
| 456 | WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) | ||
| 457 | { | ||
| 458 | searchable_item.clear_matches(cx); | ||
| 459 | } | ||
| 460 | } | ||
| 461 | if let Some(active_editor) = self.active_searchable_item.as_ref() { | ||
| 462 | let handle = active_editor.focus_handle(cx); | ||
| 463 | cx.focus(&handle); | ||
| 464 | } | ||
| 465 | cx.emit(Event::UpdateLocation); | ||
| 466 | cx.notify(); | ||
| 467 | } | ||
| 468 | |||
| 469 | pub fn deploy(&mut self, deploy: &Deploy, cx: &mut ViewContext<Self>) -> bool { | ||
| 470 | if self.show(cx) { | ||
| 471 | self.search_suggested(cx); | ||
| 472 | if deploy.focus { | ||
| 473 | self.select_query(cx); | ||
| 474 | let handle = cx.focus_handle(); | ||
| 475 | cx.focus(&handle); | ||
| 476 | } | ||
| 477 | return true; | ||
| 478 | } | ||
| 479 | |||
| 480 | false | ||
| 481 | } | ||
| 482 | |||
| 483 | pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool { | ||
| 484 | if self.active_searchable_item.is_none() { | ||
| 485 | return false; | ||
| 486 | } | ||
| 487 | self.dismissed = false; | ||
| 488 | cx.notify(); | ||
| 489 | cx.emit(Event::UpdateLocation); | ||
| 490 | true | ||
| 491 | } | ||
| 492 | |||
| 493 | fn supported_options(&self) -> workspace::searchable::SearchOptions { | ||
| 494 | self.active_searchable_item | ||
| 495 | .as_deref() | ||
| 496 | .map(SearchableItemHandle::supported_options) | ||
| 497 | .unwrap_or_default() | ||
| 498 | } | ||
| 499 | pub fn search_suggested(&mut self, cx: &mut ViewContext<Self>) { | ||
| 500 | let search = self | ||
| 501 | .query_suggestion(cx) | ||
| 502 | .map(|suggestion| self.search(&suggestion, Some(self.default_options), cx)); | ||
| 503 | |||
| 504 | if let Some(search) = search { | ||
| 505 | cx.spawn(|this, mut cx| async move { | ||
| 506 | search.await?; | ||
| 507 | this.update(&mut cx, |this, cx| this.activate_current_match(cx)) | ||
| 508 | }) | ||
| 509 | .detach_and_log_err(cx); | ||
| 510 | } | ||
| 511 | } | ||
| 512 | |||
| 513 | pub fn activate_current_match(&mut self, cx: &mut ViewContext<Self>) { | ||
| 514 | if let Some(match_ix) = self.active_match_index { | ||
| 515 | if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { | ||
| 516 | if let Some(matches) = self | ||
| 517 | .searchable_items_with_matches | ||
| 518 | .get(&active_searchable_item.downgrade()) | ||
| 519 | { | ||
| 520 | active_searchable_item.activate_match(match_ix, matches, cx) | ||
| 521 | } | ||
| 522 | } | ||
| 523 | } | ||
| 524 | } | ||
| 525 | |||
| 526 | pub fn select_query(&mut self, cx: &mut ViewContext<Self>) { | ||
| 527 | self.query_editor.update(cx, |query_editor, cx| { | ||
| 528 | query_editor.select_all(&Default::default(), cx); | ||
| 529 | }); | ||
| 530 | } | ||
| 531 | |||
| 532 | pub fn query(&self, cx: &WindowContext) -> String { | ||
| 533 | self.query_editor.read(cx).text(cx) | ||
| 534 | } | ||
| 535 | pub fn replacement(&self, cx: &WindowContext) -> String { | ||
| 536 | self.replacement_editor.read(cx).text(cx) | ||
| 537 | } | ||
| 538 | pub fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<String> { | ||
| 539 | self.active_searchable_item | ||
| 540 | .as_ref() | ||
| 541 | .map(|searchable_item| searchable_item.query_suggestion(cx)) | ||
| 542 | .filter(|suggestion| !suggestion.is_empty()) | ||
| 543 | } | ||
| 544 | |||
| 545 | pub fn set_replacement(&mut self, replacement: Option<&str>, cx: &mut ViewContext<Self>) { | ||
| 546 | if replacement.is_none() { | ||
| 547 | self.replace_enabled = false; | ||
| 548 | return; | ||
| 549 | } | ||
| 550 | self.replace_enabled = true; | ||
| 551 | self.replacement_editor | ||
| 552 | .update(cx, |replacement_editor, cx| { | ||
| 553 | replacement_editor | ||
| 554 | .buffer() | ||
| 555 | .update(cx, |replacement_buffer, cx| { | ||
| 556 | let len = replacement_buffer.len(cx); | ||
| 557 | replacement_buffer.edit([(0..len, replacement.unwrap())], None, cx); | ||
| 558 | }); | ||
| 559 | }); | ||
| 560 | } | ||
| 561 | |||
| 562 | pub fn search( | ||
| 563 | &mut self, | ||
| 564 | query: &str, | ||
| 565 | options: Option<SearchOptions>, | ||
| 566 | cx: &mut ViewContext<Self>, | ||
| 567 | ) -> oneshot::Receiver<()> { | ||
| 568 | let options = options.unwrap_or(self.default_options); | ||
| 569 | if query != self.query(cx) || self.search_options != options { | ||
| 570 | self.query_editor.update(cx, |query_editor, cx| { | ||
| 571 | query_editor.buffer().update(cx, |query_buffer, cx| { | ||
| 572 | let len = query_buffer.len(cx); | ||
| 573 | query_buffer.edit([(0..len, query)], None, cx); | ||
| 574 | }); | ||
| 575 | }); | ||
| 576 | self.search_options = options; | ||
| 577 | self.query_contains_error = false; | ||
| 578 | self.clear_matches(cx); | ||
| 579 | cx.notify(); | ||
| 580 | } | ||
| 581 | self.update_matches(cx) | ||
| 582 | } | ||
| 583 | |||
| 584 | fn render_action_button(&self) -> impl IntoElement { | ||
| 585 | // let tooltip_style = theme.tooltip.clone(); | ||
| 586 | |||
| 587 | // let style = theme.search.action_button.clone(); | ||
| 588 | |||
| 589 | IconButton::new(0, ui::Icon::SelectAll) | ||
| 590 | .on_click(|_, cx| cx.dispatch_action(Box::new(SelectAllMatches))) | ||
| 591 | } | ||
| 592 | |||
| 593 | pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) { | ||
| 594 | assert_ne!( | ||
| 595 | mode, | ||
| 596 | SearchMode::Semantic, | ||
| 597 | "Semantic search is not supported in buffer search" | ||
| 598 | ); | ||
| 599 | if mode == self.current_mode { | ||
| 600 | return; | ||
| 601 | } | ||
| 602 | self.current_mode = mode; | ||
| 603 | let _ = self.update_matches(cx); | ||
| 604 | cx.notify(); | ||
| 605 | } | ||
| 606 | |||
| 607 | pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) { | ||
| 608 | if let Some(active_editor) = self.active_searchable_item.as_ref() { | ||
| 609 | let handle = active_editor.focus_handle(cx); | ||
| 610 | cx.focus(&handle); | ||
| 611 | } | ||
| 612 | } | ||
| 613 | |||
| 614 | fn toggle_search_option(&mut self, search_option: SearchOptions, cx: &mut ViewContext<Self>) { | ||
| 615 | self.search_options.toggle(search_option); | ||
| 616 | self.default_options = self.search_options; | ||
| 617 | let _ = self.update_matches(cx); | ||
| 618 | cx.notify(); | ||
| 619 | } | ||
| 620 | |||
| 621 | pub fn set_search_options( | ||
| 622 | &mut self, | ||
| 623 | search_options: SearchOptions, | ||
| 624 | cx: &mut ViewContext<Self>, | ||
| 625 | ) { | ||
| 626 | self.search_options = search_options; | ||
| 627 | cx.notify(); | ||
| 628 | } | ||
| 629 | |||
| 630 | fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext<Self>) { | ||
| 631 | self.select_match(Direction::Next, 1, cx); | ||
| 632 | } | ||
| 633 | |||
| 634 | fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext<Self>) { | ||
| 635 | self.select_match(Direction::Prev, 1, cx); | ||
| 636 | } | ||
| 637 | |||
| 638 | fn select_all_matches(&mut self, _: &SelectAllMatches, cx: &mut ViewContext<Self>) { | ||
| 639 | if !self.dismissed && self.active_match_index.is_some() { | ||
| 640 | if let Some(searchable_item) = self.active_searchable_item.as_ref() { | ||
| 641 | if let Some(matches) = self | ||
| 642 | .searchable_items_with_matches | ||
| 643 | .get(&searchable_item.downgrade()) | ||
| 644 | { | ||
| 645 | searchable_item.select_matches(matches, cx); | ||
| 646 | self.focus_editor(&FocusEditor, cx); | ||
| 647 | } | ||
| 648 | } | ||
| 649 | } | ||
| 650 | } | ||
| 651 | |||
| 652 | pub fn select_match(&mut self, direction: Direction, count: usize, cx: &mut ViewContext<Self>) { | ||
| 653 | if let Some(index) = self.active_match_index { | ||
| 654 | if let Some(searchable_item) = self.active_searchable_item.as_ref() { | ||
| 655 | if let Some(matches) = self | ||
| 656 | .searchable_items_with_matches | ||
| 657 | .get(&searchable_item.downgrade()) | ||
| 658 | { | ||
| 659 | let new_match_index = searchable_item | ||
| 660 | .match_index_for_direction(matches, index, direction, count, cx); | ||
| 661 | |||
| 662 | searchable_item.update_matches(matches, cx); | ||
| 663 | searchable_item.activate_match(new_match_index, matches, cx); | ||
| 664 | } | ||
| 665 | } | ||
| 666 | } | ||
| 667 | } | ||
| 668 | |||
| 669 | pub fn select_last_match(&mut self, cx: &mut ViewContext<Self>) { | ||
| 670 | if let Some(searchable_item) = self.active_searchable_item.as_ref() { | ||
| 671 | if let Some(matches) = self | ||
| 672 | .searchable_items_with_matches | ||
| 673 | .get(&searchable_item.downgrade()) | ||
| 674 | { | ||
| 675 | if matches.len() == 0 { | ||
| 676 | return; | ||
| 677 | } | ||
| 678 | let new_match_index = matches.len() - 1; | ||
| 679 | searchable_item.update_matches(matches, cx); | ||
| 680 | searchable_item.activate_match(new_match_index, matches, cx); | ||
| 681 | } | ||
| 682 | } | ||
| 683 | } | ||
| 684 | |||
| 685 | fn on_query_editor_event( | ||
| 686 | &mut self, | ||
| 687 | _: View<Editor>, | ||
| 688 | event: &editor::EditorEvent, | ||
| 689 | cx: &mut ViewContext<Self>, | ||
| 690 | ) { | ||
| 691 | if let editor::EditorEvent::Edited { .. } = event { | ||
| 692 | self.query_contains_error = false; | ||
| 693 | self.clear_matches(cx); | ||
| 694 | let search = self.update_matches(cx); | ||
| 695 | cx.spawn(|this, mut cx| async move { | ||
| 696 | search.await?; | ||
| 697 | this.update(&mut cx, |this, cx| this.activate_current_match(cx)) | ||
| 698 | }) | ||
| 699 | .detach_and_log_err(cx); | ||
| 700 | } | ||
| 701 | } | ||
| 702 | |||
| 703 | fn on_active_searchable_item_event(&mut self, event: &SearchEvent, cx: &mut ViewContext<Self>) { | ||
| 704 | match event { | ||
| 705 | SearchEvent::MatchesInvalidated => { | ||
| 706 | let _ = self.update_matches(cx); | ||
| 707 | } | ||
| 708 | SearchEvent::ActiveMatchChanged => self.update_match_index(cx), | ||
| 709 | } | ||
| 710 | } | ||
| 711 | |||
| 712 | fn toggle_case_sensitive(&mut self, _: &ToggleCaseSensitive, cx: &mut ViewContext<Self>) { | ||
| 713 | self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx) | ||
| 714 | } | ||
| 715 | fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) { | ||
| 716 | self.toggle_search_option(SearchOptions::WHOLE_WORD, cx) | ||
| 717 | } | ||
| 718 | fn clear_matches(&mut self, cx: &mut ViewContext<Self>) { | ||
| 719 | let mut active_item_matches = None; | ||
| 720 | for (searchable_item, matches) in self.searchable_items_with_matches.drain() { | ||
| 721 | if let Some(searchable_item) = | ||
| 722 | WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) | ||
| 723 | { | ||
| 724 | if Some(&searchable_item) == self.active_searchable_item.as_ref() { | ||
| 725 | active_item_matches = Some((searchable_item.downgrade(), matches)); | ||
| 726 | } else { | ||
| 727 | searchable_item.clear_matches(cx); | ||
| 728 | } | ||
| 729 | } | ||
| 730 | } | ||
| 731 | |||
| 732 | self.searchable_items_with_matches | ||
| 733 | .extend(active_item_matches); | ||
| 734 | } | ||
| 735 | |||
| 736 | fn update_matches(&mut self, cx: &mut ViewContext<Self>) -> oneshot::Receiver<()> { | ||
| 737 | let (done_tx, done_rx) = oneshot::channel(); | ||
| 738 | let query = self.query(cx); | ||
| 739 | self.pending_search.take(); | ||
| 740 | |||
| 741 | if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { | ||
| 742 | if query.is_empty() { | ||
| 743 | self.active_match_index.take(); | ||
| 744 | active_searchable_item.clear_matches(cx); | ||
| 745 | let _ = done_tx.send(()); | ||
| 746 | cx.notify(); | ||
| 747 | } else { | ||
| 748 | let query: Arc<_> = if self.current_mode == SearchMode::Regex { | ||
| 749 | match SearchQuery::regex( | ||
| 750 | query, | ||
| 751 | self.search_options.contains(SearchOptions::WHOLE_WORD), | ||
| 752 | self.search_options.contains(SearchOptions::CASE_SENSITIVE), | ||
| 753 | false, | ||
| 754 | Vec::new(), | ||
| 755 | Vec::new(), | ||
| 756 | ) { | ||
| 757 | Ok(query) => query.with_replacement(self.replacement(cx)), | ||
| 758 | Err(_) => { | ||
| 759 | self.query_contains_error = true; | ||
| 760 | cx.notify(); | ||
| 761 | return done_rx; | ||
| 762 | } | ||
| 763 | } | ||
| 764 | } else { | ||
| 765 | match SearchQuery::text( | ||
| 766 | query, | ||
| 767 | self.search_options.contains(SearchOptions::WHOLE_WORD), | ||
| 768 | self.search_options.contains(SearchOptions::CASE_SENSITIVE), | ||
| 769 | false, | ||
| 770 | Vec::new(), | ||
| 771 | Vec::new(), | ||
| 772 | ) { | ||
| 773 | Ok(query) => query.with_replacement(self.replacement(cx)), | ||
| 774 | Err(_) => { | ||
| 775 | self.query_contains_error = true; | ||
| 776 | cx.notify(); | ||
| 777 | return done_rx; | ||
| 778 | } | ||
| 779 | } | ||
| 780 | } | ||
| 781 | .into(); | ||
| 782 | self.active_search = Some(query.clone()); | ||
| 783 | let query_text = query.as_str().to_string(); | ||
| 784 | |||
| 785 | let matches = active_searchable_item.find_matches(query, cx); | ||
| 786 | |||
| 787 | let active_searchable_item = active_searchable_item.downgrade(); | ||
| 788 | self.pending_search = Some(cx.spawn(|this, mut cx| async move { | ||
| 789 | let matches = matches.await; | ||
| 790 | |||
| 791 | this.update(&mut cx, |this, cx| { | ||
| 792 | if let Some(active_searchable_item) = | ||
| 793 | WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx) | ||
| 794 | { | ||
| 795 | this.searchable_items_with_matches | ||
| 796 | .insert(active_searchable_item.downgrade(), matches); | ||
| 797 | |||
| 798 | this.update_match_index(cx); | ||
| 799 | this.search_history.add(query_text); | ||
| 800 | if !this.dismissed { | ||
| 801 | let matches = this | ||
| 802 | .searchable_items_with_matches | ||
| 803 | .get(&active_searchable_item.downgrade()) | ||
| 804 | .unwrap(); | ||
| 805 | active_searchable_item.update_matches(matches, cx); | ||
| 806 | let _ = done_tx.send(()); | ||
| 807 | } | ||
| 808 | cx.notify(); | ||
| 809 | } | ||
| 810 | }) | ||
| 811 | .log_err(); | ||
| 812 | })); | ||
| 813 | } | ||
| 814 | } | ||
| 815 | done_rx | ||
| 816 | } | ||
| 817 | |||
| 818 | fn update_match_index(&mut self, cx: &mut ViewContext<Self>) { | ||
| 819 | let new_index = self | ||
| 820 | .active_searchable_item | ||
| 821 | .as_ref() | ||
| 822 | .and_then(|searchable_item| { | ||
| 823 | let matches = self | ||
| 824 | .searchable_items_with_matches | ||
| 825 | .get(&searchable_item.downgrade())?; | ||
| 826 | searchable_item.active_match_index(matches, cx) | ||
| 827 | }); | ||
| 828 | if new_index != self.active_match_index { | ||
| 829 | self.active_match_index = new_index; | ||
| 830 | cx.notify(); | ||
| 831 | } | ||
| 832 | } | ||
| 833 | |||
| 834 | fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) { | ||
| 835 | if let Some(new_query) = self.search_history.next().map(str::to_string) { | ||
| 836 | let _ = self.search(&new_query, Some(self.search_options), cx); | ||
| 837 | } else { | ||
| 838 | self.search_history.reset_selection(); | ||
| 839 | let _ = self.search("", Some(self.search_options), cx); | ||
| 840 | } | ||
| 841 | } | ||
| 842 | |||
| 843 | fn previous_history_query(&mut self, _: &PreviousHistoryQuery, cx: &mut ViewContext<Self>) { | ||
| 844 | if self.query(cx).is_empty() { | ||
| 845 | if let Some(new_query) = self.search_history.current().map(str::to_string) { | ||
| 846 | let _ = self.search(&new_query, Some(self.search_options), cx); | ||
| 847 | return; | ||
| 848 | } | ||
| 849 | } | ||
| 850 | |||
| 851 | if let Some(new_query) = self.search_history.previous().map(str::to_string) { | ||
| 852 | let _ = self.search(&new_query, Some(self.search_options), cx); | ||
| 853 | } | ||
| 854 | } | ||
| 855 | fn cycle_mode(&mut self, _: &CycleMode, cx: &mut ViewContext<Self>) { | ||
| 856 | self.activate_search_mode(next_mode(&self.current_mode, false), cx); | ||
| 857 | } | ||
| 858 | fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) { | ||
| 859 | if let Some(_) = &self.active_searchable_item { | ||
| 860 | self.replace_enabled = !self.replace_enabled; | ||
| 861 | if !self.replace_enabled { | ||
| 862 | let handle = self.query_editor.focus_handle(cx); | ||
| 863 | cx.focus(&handle); | ||
| 864 | } | ||
| 865 | cx.notify(); | ||
| 866 | } | ||
| 867 | } | ||
| 868 | fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) { | ||
| 869 | let mut should_propagate = true; | ||
| 870 | if !self.dismissed && self.active_search.is_some() { | ||
| 871 | if let Some(searchable_item) = self.active_searchable_item.as_ref() { | ||
| 872 | if let Some(query) = self.active_search.as_ref() { | ||
| 873 | if let Some(matches) = self | ||
| 874 | .searchable_items_with_matches | ||
| 875 | .get(&searchable_item.downgrade()) | ||
| 876 | { | ||
| 877 | if let Some(active_index) = self.active_match_index { | ||
| 878 | let query = query | ||
| 879 | .as_ref() | ||
| 880 | .clone() | ||
| 881 | .with_replacement(self.replacement(cx)); | ||
| 882 | searchable_item.replace(&matches[active_index], &query, cx); | ||
| 883 | self.select_next_match(&SelectNextMatch, cx); | ||
| 884 | } | ||
| 885 | should_propagate = false; | ||
| 886 | self.focus_editor(&FocusEditor, cx); | ||
| 887 | } | ||
| 888 | } | ||
| 889 | } | ||
| 890 | } | ||
| 891 | if !should_propagate { | ||
| 892 | cx.stop_propagation(); | ||
| 893 | } | ||
| 894 | } | ||
| 895 | pub fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) { | ||
| 896 | if !self.dismissed && self.active_search.is_some() { | ||
| 897 | if let Some(searchable_item) = self.active_searchable_item.as_ref() { | ||
| 898 | if let Some(query) = self.active_search.as_ref() { | ||
| 899 | if let Some(matches) = self | ||
| 900 | .searchable_items_with_matches | ||
| 901 | .get(&searchable_item.downgrade()) | ||
| 902 | { | ||
| 903 | let query = query | ||
| 904 | .as_ref() | ||
| 905 | .clone() | ||
| 906 | .with_replacement(self.replacement(cx)); | ||
| 907 | for m in matches { | ||
| 908 | searchable_item.replace(m, &query, cx); | ||
| 909 | } | ||
| 910 | } | ||
| 911 | } | ||
| 912 | } | ||
| 913 | } | ||
| 914 | } | ||
| 915 | } | ||
| 916 | |||
| 917 | #[cfg(test)] | ||
| 918 | mod tests { | ||
| 919 | use std::ops::Range; | ||
| 920 | |||
| 921 | use super::*; | ||
| 922 | use editor::{DisplayPoint, Editor}; | ||
| 923 | use gpui::{Context, EmptyView, Hsla, TestAppContext, VisualTestContext}; | ||
| 924 | use language::Buffer; | ||
| 925 | use smol::stream::StreamExt as _; | ||
| 926 | use unindent::Unindent as _; | ||
| 927 | |||
| 928 | fn init_globals(cx: &mut TestAppContext) { | ||
| 929 | cx.update(|cx| { | ||
| 930 | let store = settings::SettingsStore::test(cx); | ||
| 931 | cx.set_global(store); | ||
| 932 | editor::init(cx); | ||
| 933 | |||
| 934 | language::init(cx); | ||
| 935 | theme::init(theme::LoadThemes::JustBase, cx); | ||
| 936 | }); | ||
| 937 | } | ||
| 938 | fn init_test( | ||
| 939 | cx: &mut TestAppContext, | ||
| 940 | ) -> ( | ||
| 941 | View<Editor>, | ||
| 942 | View<BufferSearchBar>, | ||
| 943 | &mut VisualTestContext<'_>, | ||
| 944 | ) { | ||
| 945 | init_globals(cx); | ||
| 946 | let buffer = cx.build_model(|cx| { | ||
| 947 | Buffer::new( | ||
| 948 | 0, | ||
| 949 | cx.entity_id().as_u64(), | ||
| 950 | r#" | ||
| 951 | A regular expression (shortened as regex or regexp;[1] also referred to as | ||
| 952 | rational expression[2][3]) is a sequence of characters that specifies a search | ||
| 953 | pattern in text. Usually such patterns are used by string-searching algorithms | ||
| 954 | for "find" or "find and replace" operations on strings, or for input validation. | ||
| 955 | "# | ||
| 956 | .unindent(), | ||
| 957 | ) | ||
| 958 | }); | ||
| 959 | let (_, cx) = cx.add_window_view(|_| EmptyView {}); | ||
| 960 | let editor = cx.build_view(|cx| Editor::for_buffer(buffer.clone(), None, cx)); | ||
| 961 | |||
| 962 | let search_bar = cx.build_view(|cx| { | ||
| 963 | let mut search_bar = BufferSearchBar::new(cx); | ||
| 964 | search_bar.set_active_pane_item(Some(&editor), cx); | ||
| 965 | search_bar.show(cx); | ||
| 966 | search_bar | ||
| 967 | }); | ||
| 968 | |||
| 969 | (editor, search_bar, cx) | ||
| 970 | } | ||
| 971 | |||
| 972 | #[gpui::test] | ||
| 973 | async fn test_search_simple(cx: &mut TestAppContext) { | ||
| 974 | let (editor, search_bar, cx) = init_test(cx); | ||
| 975 | // todo! osiewicz: these tests asserted on background color as well, that should be brought back. | ||
| 976 | let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| { | ||
| 977 | background_highlights | ||
| 978 | .into_iter() | ||
| 979 | .map(|(range, _)| range) | ||
| 980 | .collect::<Vec<_>>() | ||
| 981 | }; | ||
| 982 | // Search for a string that appears with different casing. | ||
| 983 | // By default, search is case-insensitive. | ||
| 984 | search_bar | ||
| 985 | .update(cx, |search_bar, cx| search_bar.search("us", None, cx)) | ||
| 986 | .await | ||
| 987 | .unwrap(); | ||
| 988 | editor.update(cx, |editor, cx| { | ||
| 989 | assert_eq!( | ||
| 990 | display_points_of(editor.all_text_background_highlights(cx)), | ||
| 991 | &[ | ||
| 992 | DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19), | ||
| 993 | DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45), | ||
| 994 | ] | ||
| 995 | ); | ||
| 996 | }); | ||
| 997 | |||
| 998 | // Switch to a case sensitive search. | ||
| 999 | search_bar.update(cx, |search_bar, cx| { | ||
| 1000 | search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); | ||
| 1001 | }); | ||
| 1002 | let mut editor_notifications = cx.notifications(&editor); | ||
| 1003 | editor_notifications.next().await; | ||
| 1004 | editor.update(cx, |editor, cx| { | ||
| 1005 | assert_eq!( | ||
| 1006 | display_points_of(editor.all_text_background_highlights(cx)), | ||
| 1007 | &[DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),] | ||
| 1008 | ); | ||
| 1009 | }); | ||
| 1010 | |||
| 1011 | // Search for a string that appears both as a whole word and | ||
| 1012 | // within other words. By default, all results are found. | ||
| 1013 | search_bar | ||
| 1014 | .update(cx, |search_bar, cx| search_bar.search("or", None, cx)) | ||
| 1015 | .await | ||
| 1016 | .unwrap(); | ||
| 1017 | editor.update(cx, |editor, cx| { | ||
| 1018 | assert_eq!( | ||
| 1019 | display_points_of(editor.all_text_background_highlights(cx)), | ||
| 1020 | &[ | ||
| 1021 | DisplayPoint::new(0, 24)..DisplayPoint::new(0, 26), | ||
| 1022 | DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43), | ||
| 1023 | DisplayPoint::new(2, 71)..DisplayPoint::new(2, 73), | ||
| 1024 | DisplayPoint::new(3, 1)..DisplayPoint::new(3, 3), | ||
| 1025 | DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13), | ||
| 1026 | DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58), | ||
| 1027 | DisplayPoint::new(3, 60)..DisplayPoint::new(3, 62), | ||
| 1028 | ] | ||
| 1029 | ); | ||
| 1030 | }); | ||
| 1031 | |||
| 1032 | // Switch to a whole word search. | ||
| 1033 | search_bar.update(cx, |search_bar, cx| { | ||
| 1034 | search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx); | ||
| 1035 | }); | ||
| 1036 | let mut editor_notifications = cx.notifications(&editor); | ||
| 1037 | editor_notifications.next().await; | ||
| 1038 | editor.update(cx, |editor, cx| { | ||
| 1039 | assert_eq!( | ||
| 1040 | display_points_of(editor.all_text_background_highlights(cx)), | ||
| 1041 | &[ | ||
| 1042 | DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43), | ||
| 1043 | DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13), | ||
| 1044 | DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58), | ||
| 1045 | ] | ||
| 1046 | ); | ||
| 1047 | }); | ||
| 1048 | |||
| 1049 | editor.update(cx, |editor, cx| { | ||
| 1050 | editor.change_selections(None, cx, |s| { | ||
| 1051 | s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]) | ||
| 1052 | }); | ||
| 1053 | }); | ||
| 1054 | search_bar.update(cx, |search_bar, cx| { | ||
| 1055 | assert_eq!(search_bar.active_match_index, Some(0)); | ||
| 1056 | search_bar.select_next_match(&SelectNextMatch, cx); | ||
| 1057 | assert_eq!( | ||
| 1058 | editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), | ||
| 1059 | [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] | ||
| 1060 | ); | ||
| 1061 | }); | ||
| 1062 | search_bar.update(cx, |search_bar, _| { | ||
| 1063 | assert_eq!(search_bar.active_match_index, Some(0)); | ||
| 1064 | }); | ||
| 1065 | |||
| 1066 | search_bar.update(cx, |search_bar, cx| { | ||
| 1067 | search_bar.select_next_match(&SelectNextMatch, cx); | ||
| 1068 | assert_eq!( | ||
| 1069 | editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), | ||
| 1070 | [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)] | ||
| 1071 | ); | ||
| 1072 | }); | ||
| 1073 | search_bar.update(cx, |search_bar, _| { | ||
| 1074 | assert_eq!(search_bar.active_match_index, Some(1)); | ||
| 1075 | }); | ||
| 1076 | |||
| 1077 | search_bar.update(cx, |search_bar, cx| { | ||
| 1078 | search_bar.select_next_match(&SelectNextMatch, cx); | ||
| 1079 | assert_eq!( | ||
| 1080 | editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), | ||
| 1081 | [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] | ||
| 1082 | ); | ||
| 1083 | }); | ||
| 1084 | search_bar.update(cx, |search_bar, _| { | ||
| 1085 | assert_eq!(search_bar.active_match_index, Some(2)); | ||
| 1086 | }); | ||
| 1087 | |||
| 1088 | search_bar.update(cx, |search_bar, cx| { | ||
| 1089 | search_bar.select_next_match(&SelectNextMatch, cx); | ||
| 1090 | assert_eq!( | ||
| 1091 | editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), | ||
| 1092 | [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] | ||
| 1093 | ); | ||
| 1094 | }); | ||
| 1095 | search_bar.update(cx, |search_bar, _| { | ||
| 1096 | assert_eq!(search_bar.active_match_index, Some(0)); | ||
| 1097 | }); | ||
| 1098 | |||
| 1099 | search_bar.update(cx, |search_bar, cx| { | ||
| 1100 | search_bar.select_prev_match(&SelectPrevMatch, cx); | ||
| 1101 | assert_eq!( | ||
| 1102 | editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), | ||
| 1103 | [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] | ||
| 1104 | ); | ||
| 1105 | }); | ||
| 1106 | search_bar.update(cx, |search_bar, _| { | ||
| 1107 | assert_eq!(search_bar.active_match_index, Some(2)); | ||
| 1108 | }); | ||
| 1109 | |||
| 1110 | search_bar.update(cx, |search_bar, cx| { | ||
| 1111 | search_bar.select_prev_match(&SelectPrevMatch, cx); | ||
| 1112 | assert_eq!( | ||
| 1113 | editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), | ||
| 1114 | [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)] | ||
| 1115 | ); | ||
| 1116 | }); | ||
| 1117 | search_bar.update(cx, |search_bar, _| { | ||
| 1118 | assert_eq!(search_bar.active_match_index, Some(1)); | ||
| 1119 | }); | ||
| 1120 | |||
| 1121 | search_bar.update(cx, |search_bar, cx| { | ||
| 1122 | search_bar.select_prev_match(&SelectPrevMatch, cx); | ||
| 1123 | assert_eq!( | ||
| 1124 | editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), | ||
| 1125 | [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] | ||
| 1126 | ); | ||
| 1127 | }); | ||
| 1128 | search_bar.update(cx, |search_bar, _| { | ||
| 1129 | assert_eq!(search_bar.active_match_index, Some(0)); | ||
| 1130 | }); | ||
| 1131 | |||
| 1132 | // Park the cursor in between matches and ensure that going to the previous match selects | ||
| 1133 | // the closest match to the left. | ||
| 1134 | editor.update(cx, |editor, cx| { | ||
| 1135 | editor.change_selections(None, cx, |s| { | ||
| 1136 | s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]) | ||
| 1137 | }); | ||
| 1138 | }); | ||
| 1139 | search_bar.update(cx, |search_bar, cx| { | ||
| 1140 | assert_eq!(search_bar.active_match_index, Some(1)); | ||
| 1141 | search_bar.select_prev_match(&SelectPrevMatch, cx); | ||
| 1142 | assert_eq!( | ||
| 1143 | editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)), | ||
| 1144 | [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] | ||
| 1145 | ); | ||
| 1146 | }); | ||
| 1147 | search_bar.update(cx, |search_bar, _| { | ||
| 1148 | assert_eq!(search_bar.active_match_index, Some(0)); | ||
| 1149 | }); | ||
598 more lines aren’t shown here.
This diff is large: its 185 lines aren’t shown here.
This diff is large: its 33 lines aren’t shown here.
This diff is large: its 2681 lines aren’t shown here.
This diff is large: its 118 lines aren’t shown here.
This diff is large: its 36 lines aren’t shown here.
This diff is large: its 16 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 4 lines aren’t shown here.
This diff is large: its 36 lines aren’t shown here.
This diff is large: its 8 lines aren’t shown here.
This diff is large: its 13 lines aren’t shown here.
This diff is large: its 45 lines aren’t shown here.
This diff is large: its 97 lines aren’t shown here.
This diff is large: its 35 lines aren’t shown here.
This diff is large: its 66 lines aren’t shown here.
This diff is large: its 18 lines aren’t shown here.
This diff is large: its 40 lines aren’t shown here.
This diff is large: its 142 lines aren’t shown here.
This diff is large: its 2 lines aren’t shown here.
This diff is large: its 102 lines aren’t shown here.
This diff is large: its 42 lines aren’t shown here.
This diff is large: its 18 lines aren’t shown here.
This diff is large: its 88 lines aren’t shown here.
This diff is large: its 764 lines aren’t shown here.
This diff is large: its 18 lines aren’t shown here.
This diff is large: its 161 lines aren’t shown here.
This diff is large: its 20 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 9 lines aren’t shown here.
This diff is large: its 39 lines aren’t shown here.
This diff is large: its 15 lines aren’t shown here.
This diff is large: its 150 lines aren’t shown here.
This diff is large: its 42 lines aren’t shown here.
This diff is large: its 6 lines aren’t shown here.
This diff is large: its 138 lines aren’t shown here.
This diff is large: its 35 lines aren’t shown here.
This diff is large: its 24 lines aren’t shown here.
This diff is large: its 43 lines aren’t shown here.
This diff is large: its 67 lines aren’t shown here.
This diff is large: its 102 lines aren’t shown here.
This diff is large: its 432 lines aren’t shown here.
This diff is large: its 268 lines aren’t shown here.
This diff is large: its 483 lines aren’t shown here.
This diff is large: its 79 lines aren’t shown here.
This diff is large: its 20 lines aren’t shown here.
This diff is large: its 46 lines aren’t shown here.
This diff is large: its 29 lines aren’t shown here.
This diff is large: its 60 lines aren’t shown here.
This diff is large: its 158 lines aren’t shown here.
This diff is large: its 193 lines aren’t shown here.
This diff is large: its 24 lines aren’t shown here.
This diff is large: its 150 lines aren’t shown here.
This diff is large: its 225 lines aren’t shown here.
This diff is large: its 270 lines aren’t shown here.
This diff is large: its 557 lines aren’t shown here.
This diff is large: its 82 lines aren’t shown here.
This diff is large: its 41 lines aren’t shown here.
This diff is large: its 205 lines aren’t shown here.
This diff is large: its 151 lines aren’t shown here.
This diff is large: its 175 lines aren’t shown here.
This diff is large: its 62 lines aren’t shown here.
This diff is large: its 85 lines aren’t shown here.
This diff is large: its 14 lines aren’t shown here.
This diff is large: its 16 lines aren’t shown here.
This diff is large: its 20 lines aren’t shown here.
This diff is large: its 24 lines aren’t shown here.
This diff is large: its 146 lines aren’t shown here.
This diff is large: its 50 lines aren’t shown here.
This diff is large: its 105 lines aren’t shown here.
This diff is large: its 22 lines aren’t shown here.
This diff is large: its 19 lines aren’t shown here.
This diff is large: its 62 lines aren’t shown here.
This diff is large: its 28 lines aren’t shown here.
This diff is large: its 35 lines aren’t shown here.
This diff is large: its 273 lines aren’t shown here.
This diff is large: its 91 lines aren’t shown here.
This diff is large: its 28 lines aren’t shown here.
This diff is large: its 15 lines aren’t shown here.
This diff is large: its 34 lines aren’t shown here.
This diff is large: its 189 lines aren’t shown here.
This diff is large: its 75 lines aren’t shown here.
This diff is large: its 1112 lines aren’t shown here.
This diff is large: its 38 lines aren’t shown here.
This diff is large: its 48 lines aren’t shown here.
This diff is large: its 8 lines aren’t shown here.
This diff is large: its 45 lines aren’t shown here.
This diff is large: its 57 lines aren’t shown here.
This diff is large: its 29 lines aren’t shown here.
This diff is large: its 28 lines aren’t shown here.
This diff is large: its 48 lines aren’t shown here.
This diff is large: its 94 lines aren’t shown here.
This diff is large: its 114 lines aren’t shown here.
This diff is large: its 267 lines aren’t shown here.
This diff is large: its 47 lines aren’t shown here.
This diff is large: its 151 lines aren’t shown here.
This diff is large: its 111 lines aren’t shown here.
This diff is large: its 49 lines aren’t shown here.
This diff is large: its 47 lines aren’t shown here.
This diff is large: its 78 lines aren’t shown here.
This diff is large: its 58 lines aren’t shown here.
This diff is large: its 64 lines aren’t shown here.
This diff is large: its 372 lines aren’t shown here.
This diff is large: its 129 lines aren’t shown here.
This diff is large: its 76 lines aren’t shown here.
This diff is large: its 54 lines aren’t shown here.
This diff is large: its 204 lines aren’t shown here.
This diff is large: its 151 lines aren’t shown here.
This diff is large: its 100 lines aren’t shown here.
This diff is large: its 61 lines aren’t shown here.
This diff is large: its 219 lines aren’t shown here.
This diff is large: its 127 lines aren’t shown here.
This diff is large: its 101 lines aren’t shown here.
This diff is large: its 399 lines aren’t shown here.
This diff is large: its 39 lines aren’t shown here.
This diff is large: its 17 lines aren’t shown here.
This diff is large: its 30 lines aren’t shown here.
This diff is large: its 16 lines aren’t shown here.
This diff is large: its 103 lines aren’t shown here.
This diff is large: its 34 lines aren’t shown here.
This diff is large: its 33 lines aren’t shown here.
This diff is large: its 235 lines aren’t shown here.
This diff is large: its 248 lines aren’t shown here.
This diff is large: its 120 lines aren’t shown here.
This diff is large: its 45 lines aren’t shown here.
This diff is large: its 152 lines aren’t shown here.
This diff is large: its 129 lines aren’t shown here.
This diff is large: its 95 lines aren’t shown here.
This diff is large: its 798 lines aren’t shown here.
This diff is large: its 25 lines aren’t shown here.
This diff is large: its 39 lines aren’t shown here.
This diff is large: its 13 lines aren’t shown here.
This diff is large: its 82 lines aren’t shown here.
This diff is large: its 56 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 51 lines aren’t shown here.
This diff is large: its 4 lines aren’t shown here.
This diff is large: its 48 lines aren’t shown here.
This diff is large: its 35 lines aren’t shown here.
This diff is large: its 39 lines aren’t shown here.
This diff is large: its 56 lines aren’t shown here.
This diff is large: its 56 lines aren’t shown here.
This diff is large: its 11 lines aren’t shown here.
This diff is large: its 51 lines aren’t shown here.
This diff is large: its 4 lines aren’t shown here.
This diff is large: its 138 lines aren’t shown here.
This diff is large: its 35 lines aren’t shown here.
This diff is large: its 8 lines aren’t shown here.
This diff is large: its 8 lines aren’t shown here.
This diff is large: its 37 lines aren’t shown here.
This diff is large: its 160 lines aren’t shown here.
This diff is large: its 32 lines aren’t shown here.