History

Merge branch 'main' into refine-keybindings

Nate Butler committed 2 years ago

Commit f33cd3d2 parents 35e0b53 132ad59250 files · +16192 −14519Browse files

Unified · Split

250 files
  1. .cargo/ci-config.toml +12 −0
  2. .github/actions/run_tests/action.yml +1 −5
  3. .github/workflows/ci.yml +4 −3
  4. .github/workflows/release_nightly.yml +1 −1
  5. Cargo.lock +64 −18
  6. Cargo.toml +7 −3
  7. Procfile.zed2 +4 −0
  8. assets/settings/default.json +1 −1
  9. assets/settings/initial_user_settings.json +2 −1
  10. crates/auto_update2/src/auto_update.rs +8 −3
  11. crates/auto_update2/src/update_notification.rs +41 −72
  12. crates/call2/Cargo.toml +5 −1
  13. crates/call2/src/call2.rs +216 −7
  14. crates/call2/src/participant.rs +1 −1
  15. crates/call2/src/room.rs +1 −2
  16. crates/call2/src/shared_screen.rs +157 −0
  17. crates/channel2/Cargo.toml +1 −1
  18. crates/client/src/telemetry.rs +26 −12
  19. crates/client2/src/client2.rs +1 −2
  20. crates/client2/src/telemetry.rs +50 −13
  21. crates/collab/Cargo.toml +1 −1
  22. crates/collab2/Cargo.toml +1 −1
  23. crates/collab2/src/bin/dotenv2.rs +0 −0
  24. crates/collab2/src/bin/seed2.rs +0 −0
  25. crates/collab2/src/tests/test_server.rs +2 −1
  26. crates/collab_ui2/Cargo.toml +1 −1
  27. crates/collab_ui2/src/collab_panel.rs +1383 −1286
  28. crates/collab_ui2/src/collab_panel/contact_finder.rs +116 −150
  29. crates/collab_ui2/src/collab_titlebar_item.rs +163 −14
  30. crates/collab_ui2/src/collab_ui.rs +36 −28
  31. crates/collab_ui2/src/face_pile.rs +36 −69
  32. crates/collab_ui2/src/notifications.rs +8 −8
  33. crates/collab_ui2/src/notifications/incoming_call_notification.rs +198 −150
  34. crates/command_palette2/src/command_palette.rs +14 −23
  35. crates/diagnostics2/src/diagnostics.rs +19 −18
  36. crates/diagnostics2/src/items.rs +14 −14
  37. crates/diagnostics2/src/toolbar_controls.rs +6 −6
  38. crates/editor/src/editor.rs +45 −22
  39. crates/editor/src/inlay_hint_cache.rs +4 −2
  40. crates/editor2/src/display_map/block_map.rs +3 −3
  41. crates/editor2/src/editor.rs +530 −558
  42. crates/editor2/src/editor_tests.rs +1 −70
  43. crates/editor2/src/element.rs +844 −837
  44. crates/editor2/src/hover_popover.rs +929 −946
  45. crates/editor2/src/inlay_hint_cache.rs +4 −2
  46. crates/editor2/src/items.rs +23 −27
  47. crates/file_finder2/src/file_finder.rs +13 −23
  48. crates/fuzzy2/src/strings.rs +28 −0
  49. crates/go_to_line2/src/go_to_line.rs +22 −21
  50. crates/gpui2/src/app.rs +27 −8
  51. crates/gpui2/src/app/async_context.rs +6 −6
  52. crates/gpui2/src/app/entity_map.rs +5 −5
  53. crates/gpui2/src/app/test_context.rs +14 −15
  54. crates/gpui2/src/element.rs +356 −215
  55. crates/gpui2/src/elements/div.rs +290 −342
  56. crates/gpui2/src/elements/img.rs +87 −57
  57. crates/gpui2/src/elements/overlay.rs +35 −32
  58. crates/gpui2/src/elements/svg.rs +28 −33
  59. crates/gpui2/src/elements/text.rs +253 −111
  60. crates/gpui2/src/elements/uniform_list.rs +81 −90
  61. crates/gpui2/src/gpui2.rs +1 −3
  62. crates/gpui2/src/input.rs +5 −3
  63. crates/gpui2/src/interactive.rs +21 −17
  64. crates/gpui2/src/platform/mac/window.rs +3 −0
  65. crates/gpui2/src/prelude.rs +3 −2
  66. crates/gpui2/src/style.rs +184 −4
  67. crates/gpui2/src/styled.rs +26 −1
  68. crates/gpui2/src/text_system.rs +18 −3
  69. crates/gpui2/src/text_system/line.rs +74 −14
  70. crates/gpui2/src/text_system/line_layout.rs +35 −0
  71. crates/gpui2/src/view.rs +88 −135
  72. crates/gpui2/src/window.rs +182 −80
  73. crates/gpui2_macros/src/derive_component.rs +0 −66
  74. crates/gpui2_macros/src/derive_into_element.rs +27 −0
  75. crates/gpui2_macros/src/gpui2_macros.rs +9 −9
  76. crates/language/src/highlight_map.rs +4 −4
  77. crates/language/src/language.rs +2 −2
  78. crates/language/src/syntax_map/syntax_map_tests.rs +1 −1
  79. crates/language2/src/buffer.rs +41 −0
  80. crates/language2/src/highlight_map.rs +4 −4
  81. crates/language2/src/language2.rs +2 −2
  82. crates/language2/src/proto.rs +1 −0
  83. crates/language2/src/syntax_map/syntax_map_tests.rs +1 −1
  84. crates/live_kit_client2/build.rs +11 −1
  85. crates/node_runtime/src/node_runtime.rs +8 −6
  86. crates/picker2/src/picker2.rs +46 −39
  87. crates/project/src/project.rs +118 −5
  88. crates/project/src/worktree.rs +71 −62
  89. crates/project/src/worktree_tests.rs +139 −0
  90. crates/project2/src/lsp_command.rs +21 −4
  91. crates/project2/src/project2.rs +114 −4
  92. crates/project2/src/worktree.rs +72 −61
  93. crates/project2/src/worktree_tests.rs +143 −3
  94. crates/project_panel2/Cargo.toml +0 −1
  95. crates/project_panel2/src/project_panel.rs +57 −73
  96. crates/rich_text2/src/rich_text.rs +2 −2
  97. crates/search/src/project_search.rs +2 −5
  98. crates/search2/Cargo.toml +40 −0
  99. crates/search2/src/buffer_search.rs +1747 −0
  100. crates/search2/src/history.rs +184 −0
  101. crates/search2/src/mode.rs +32 −0
  102. crates/search2/src/project_search.rs +2680 −0
  103. crates/search2/src/search.rs +117 −0
  104. crates/search2/src/search_bar.rs +35 −0
  105. crates/semantic_index/src/semantic_index_tests.rs +2 −2
  106. crates/story/Cargo.toml +10 −0
  107. crates/story/src/lib.rs +3 −0
  108. crates/story/src/story.rs +35 −0
  109. crates/storybook2/Cargo.toml +1 −0
  110. crates/storybook2/src/stories.rs +0 −2
  111. crates/storybook2/src/stories/colors.rs +0 −44
  112. crates/storybook2/src/stories/focus.rs +31 −27
  113. crates/storybook2/src/stories/kitchen_sink.rs +7 −5
  114. crates/storybook2/src/stories/picker.rs +16 −19
  115. crates/storybook2/src/stories/scroll.rs +2 −2
  116. crates/storybook2/src/stories/text.rs +19 −3
  117. crates/storybook2/src/stories/z_index.rs +50 −49
  118. crates/storybook2/src/story.rs +0 −1
  119. crates/storybook2/src/story_selector.rs +7 −60
  120. crates/storybook2/src/storybook2.rs +2 −6
  121. crates/storybook3/Cargo.toml +0 −17
  122. crates/storybook3/src/storybook3.rs +0 −87
  123. crates/terminal_view2/src/terminal_element.rs +289 −273
  124. crates/terminal_view2/src/terminal_panel.rs +2 −2
  125. crates/terminal_view2/src/terminal_view.rs +32 −42
  126. crates/theme2/Cargo.toml +3 −2
  127. crates/theme2/src/one_themes.rs +2 −2
  128. crates/theme2/src/settings.rs +1 −1
  129. crates/theme2/src/story.rs +0 −38
  130. crates/theme2/src/styles.rs +6 −0
  131. crates/theme2/src/styles/players.rs +2 −138
  132. crates/theme2/src/styles/stories/color.rs +41 −0
  133. crates/theme2/src/styles/stories/mod.rs +5 −0
  134. crates/theme2/src/styles/stories/players.rs +137 −0
  135. crates/theme2/src/theme2.rs +12 −5
  136. crates/ui2/Cargo.toml +6 −1
  137. crates/ui2/docs/hello-world.md +5 −5
  138. crates/ui2/src/components.rs +10 −26
  139. crates/ui2/src/components/avatar.rs +36 −40
  140. crates/ui2/src/components/button.rs +93 −262
  141. crates/ui2/src/components/checkbox.rs +138 −78
  142. crates/ui2/src/components/context_menu.rs +96 −224
  143. crates/ui2/src/components/details.rs +0 −78
  144. crates/ui2/src/components/disclosure.rs +19 −0
  145. crates/ui2/src/components/divider.rs +21 −2
  146. crates/ui2/src/components/elevated_surface.rs +0 −28
  147. crates/ui2/src/components/facepile.rs +0 −59
  148. crates/ui2/src/components/icon.rs +36 −37
  149. crates/ui2/src/components/icon_button.rs +75 −82
  150. crates/ui2/src/components/indicator.rs +0 −23
  151. crates/ui2/src/components/input.rs +41 −60
  152. crates/ui2/src/components/keybinding.rs +35 −158
  153. crates/ui2/src/components/label.rs +62 −129
  154. crates/ui2/src/components/list.rs +167 −199
  155. crates/ui2/src/components/modal.rs +0 −81
  156. crates/ui2/src/components/notification_toast.rs +0 −40
  157. crates/ui2/src/components/palette.rs +0 −204
  158. crates/ui2/src/components/panel.rs +0 −150
  159. crates/ui2/src/components/player.rs +0 −174
  160. crates/ui2/src/components/player_stack.rs +0 −61
  161. crates/ui2/src/components/popover.rs +84 −0
  162. crates/ui2/src/components/slot.rs +2 −2
  163. crates/ui2/src/components/stack.rs +2 −2
  164. crates/ui2/src/components/stories.rs +19 −0
  165. crates/ui2/src/components/stories/avatar.rs +23 −0
  166. crates/ui2/src/components/stories/button.rs +145 −0
  167. crates/ui2/src/components/stories/checkbox.rs +49 −0
  168. crates/ui2/src/components/stories/context_menu.rs +104 −0
  169. crates/ui2/src/components/stories/icon.rs +21 −0
  170. crates/ui2/src/components/stories/input.rs +18 −0
  171. crates/ui2/src/components/stories/keybinding.rs +61 −0
  172. crates/ui2/src/components/stories/label.rs +27 −0
  173. crates/ui2/src/components/stories/list_item.rs +34 −0
  174. crates/ui2/src/components/tab.rs +0 −272
  175. crates/ui2/src/components/toast.rs +0 −90
  176. crates/ui2/src/components/toggle.rs +0 −20
  177. crates/ui2/src/components/tool_divider.rs +0 −14
  178. crates/ui2/src/components/tooltip.rs +4 −8
  179. crates/ui2/src/prelude.rs +13 −144
  180. crates/ui2/src/settings.rs +0 −74
  181. crates/ui2/src/static_data.rs +0 −1111
  182. crates/ui2/src/story.rs +0 −37
  183. crates/ui2/src/styled_ext.rs +6 −8
  184. crates/ui2/src/styles.rs +7 −0
  185. crates/ui2/src/styles/color.rs +44 −0
  186. crates/ui2/src/styles/docs/elevation.md +1 −42
  187. crates/ui2/src/styles/elevation.rs +4 −4
  188. crates/ui2/src/styles/typography.rs +27 −0
  189. crates/ui2/src/to_extract.rs +0 −47
  190. crates/ui2/src/to_extract/assistant_panel.rs +0 −93
  191. crates/ui2/src/to_extract/breadcrumb.rs +0 −113
  192. crates/ui2/src/to_extract/buffer.rs +0 −266
  193. crates/ui2/src/to_extract/buffer_search.rs +0 −46
  194. crates/ui2/src/to_extract/chat_panel.rs +0 −150
  195. crates/ui2/src/to_extract/collab_panel.rs +0 −110
  196. crates/ui2/src/to_extract/command_palette.rs +0 −48
  197. crates/ui2/src/to_extract/copilot.rs +0 −46
  198. crates/ui2/src/to_extract/editor_pane.rs +0 −77
  199. crates/ui2/src/to_extract/language_selector.rs +0 −57
  200. crates/ui2/src/to_extract/multi_buffer.rs +0 −63
  201. crates/ui2/src/to_extract/notifications_panel.rs +0 −371
  202. crates/ui2/src/to_extract/panes.rs +0 −128
  203. crates/ui2/src/to_extract/project_panel.rs +0 −75
  204. crates/ui2/src/to_extract/recent_projects.rs +0 −53
  205. crates/ui2/src/to_extract/status_bar.rs +0 −203
  206. crates/ui2/src/to_extract/tab_bar.rs +0 −150
  207. crates/ui2/src/to_extract/terminal.rs +0 −99
  208. crates/ui2/src/to_extract/theme_selector.rs +0 −60
  209. crates/ui2/src/to_extract/title_bar.rs +0 −218
  210. crates/ui2/src/to_extract/toolbar.rs +0 −126
  211. crates/ui2/src/to_extract/traffic_lights.rs +0 −100
  212. crates/ui2/src/to_extract/workspace.rs +0 −398
  213. crates/ui2/src/ui2.rs +2 −26
  214. crates/util/src/channel.rs +1 −2
  215. crates/workspace/src/workspace.rs +7 −3
  216. crates/workspace2/Cargo.toml +1 −1
  217. crates/workspace2/src/dock.rs +15 −23
  218. crates/workspace2/src/item.rs +5 −5
  219. crates/workspace2/src/modal_layer.rs +6 −5
  220. crates/workspace2/src/notifications.rs +92 −45
  221. crates/workspace2/src/pane.rs +96 −47
  222. crates/workspace2/src/pane_group.rs +20 −24
  223. crates/workspace2/src/searchable.rs +11 −12
  224. crates/workspace2/src/shared_screen.rs +0 −151
  225. crates/workspace2/src/status_bar.rs +88 −7
  226. crates/workspace2/src/toolbar.rs +66 −5
  227. crates/workspace2/src/workspace2.rs +290 −198
  228. crates/zed/Cargo.toml +2 −2
  229. crates/zed/src/languages.rs +12 −1
  230. crates/zed/src/languages/elixir/embedding.scm +2 −2
  231. crates/zed/src/languages/nu.rs +81 −0
  232. crates/zed/src/languages/uiua.rs +55 −0
  233. crates/zed/src/languages/uiua/config.toml +10 −0
  234. crates/zed/src/languages/uiua/highlights.scm +50 −0
  235. crates/zed/src/languages/uiua/indents.scm +3 −0
  236. crates/zed/src/main.rs +13 −4
  237. crates/zed2/Cargo.toml +4 −4
  238. crates/zed2/src/languages.rs +12 −1
  239. crates/zed2/src/languages/nu.rs +55 −0
  240. crates/zed2/src/languages/uiua.rs +55 −0
  241. crates/zed2/src/languages/uiua/config.toml +10 −0
  242. crates/zed2/src/languages/uiua/highlights.scm +50 −0
  243. crates/zed2/src/languages/uiua/indents.scm +3 −0
  244. crates/zed2/src/main.rs +37 −25
  245. crates/zed2/src/zed2.rs +13 −8
  246. script/bump-nightly +2 −0
  247. script/bump-zed-minor-versions +0 −1
  248. script/bundle +6 −3
  249. script/deploy-docs +159 −0
  250. script/zed-local +6 −3
.cargo/ci-config.toml+12−0
@@ -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"]
.github/actions/run_tests/action.yml+1−5
@@ -19,16 +19,12 @@ runs: · 18 unmodified lines
1919
2020 - name: Limit target directory size
2121 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
2323
2424 - name: Run check
25− env:
26− RUSTFLAGS: -D warnings
2725 shell: bash -euxo pipefail {0}
2826 run: cargo check --tests --workspace
2927
3028 - name: Run tests
31− env:
32− RUSTFLAGS: -D warnings
3329 shell: bash -euxo pipefail {0}
3430 run: cargo nextest run --workspace --no-fail-fast
.github/workflows/ci.yml+4−3
@@ -29,6 +29,9 @@ jobs: · 28 unmodified lines
2929 clean: false
3030 submodules: "recursive"
3131
32+ - name: Set up default .cargo/config.toml
33+ run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
34+
3235 - name: Run rustfmt
3336 uses: ./.github/actions/check_formatting
3437
@@ -87,7 +90,7 @@ jobs: · 52 unmodified lines
8790 submodules: "recursive"
8891
8992 - 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
9194
9295 - name: Determine version and release channel
9396 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
@@ -131,8 +134,6 @@ jobs: · 37 unmodified lines
131134
132135 - uses: softprops/action-gh-release@v1
133136 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?
136137 if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
137138 with:
138139 draft: true
.github/workflows/release_nightly.yml+1−1
@@ -79,7 +79,7 @@ jobs: · 78 unmodified lines
7979 submodules: "recursive"
8080
8181 - 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
8383
8484 - name: Set release channel to nightly
8585 run: |
Cargo.lock+64−18
@@ -1186,12 +1186,14 @@ version = "0.1.0" · 1185 unmodified lines
11861186 dependencies = [
11871187 "anyhow",
11881188 "async-broadcast",
1189+ "async-trait",
11891190 "audio2",
11901191 "client2",
11911192 "collections",
11921193 "fs2",
11931194 "futures 0.3.28",
11941195 "gpui2",
1196+ "image",
11951197 "language2",
11961198 "live_kit_client2",
11971199 "log",
@@ -1203,7 +1205,10 @@ dependencies = [ · 5 unmodified lines
12031205 "serde_derive",
12041206 "serde_json",
12051207 "settings2",
1208+ "smallvec",
1209+ "ui2",
12061210 "util",
1211+ "workspace2",
12071212 ]
12081213
12091214 [[package]]
@@ -1398,7 +1403,7 @@ dependencies = [ · 188 unmodified lines
13981403 "smol",
13991404 "sum_tree",
14001405 "tempfile",
1401− "text",
1406+ "text2",
14021407 "thiserror",
14031408 "time",
14041409 "tiny_http",
@@ -1664,7 +1669,7 @@ dependencies = [ · 259 unmodified lines
16641669
16651670 [[package]]
16661671 name = "collab"
1667−version = "0.28.0"
1672+version = "0.29.0"
16681673 dependencies = [
16691674 "anyhow",
16701675 "async-trait",
@@ -1869,7 +1874,7 @@ dependencies = [ · 198 unmodified lines
18691874 "editor2",
18701875 "feature_flags2",
18711876 "futures 0.3.28",
1872− "fuzzy",
1877+ "fuzzy2",
18731878 "gpui2",
18741879 "language2",
18751880 "lazy_static",
@@ -6763,7 +6768,6 @@ dependencies = [ · 4887 unmodified lines
67636768 "anyhow",
67646769 "client2",
67656770 "collections",
6766− "context_menu",
67676771 "db2",
67686772 "editor2",
67696773 "futures 0.3.28",
@@ -8032,6 +8036,35 @@ dependencies = [ · 1262 unmodified lines
80328036 "workspace",
80338037 ]
80348038
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+
80358068 [[package]]
80368069 name = "security-framework"
80378070 version = "2.9.2"
@@ -8855,6 +8888,13 @@ version = "1.1.0" · 817 unmodified lines
88558888 source = "registry+https://github.com/rust-lang/crates.io-index"
88568889 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
88578890
8891+[[package]]
8892+name = "story"
8893+version = "0.1.0"
8894+dependencies = [
8895+ "gpui2",
8896+]
8897+
88588898 [[package]]
88598899 name = "storybook2"
88608900 version = "0.1.0"
@@ -8876,6 +8916,7 @@ dependencies = [ · 15 unmodified lines
88768916 "settings2",
88778917 "simplelog",
88788918 "smallvec",
8919+ "story",
88798920 "strum",
88808921 "theme",
88818922 "theme2",
@@ -8883,17 +8924,6 @@ dependencies = [ · 1 unmodified line
88838924 "util",
88848925 ]
88858926
8886−[[package]]
8887−name = "storybook3"
8888−version = "0.1.0"
8889−dependencies = [
8890− "anyhow",
8891− "gpui2",
8892− "settings2",
8893− "theme2",
8894− "ui2",
8895−]
8896−
88978927 [[package]]
88988928 name = "stringprep"
88998929 version = "0.1.4"
@@ -9397,6 +9427,7 @@ dependencies = [ · 497 unmodified lines
93979427 "serde_derive",
93989428 "serde_json",
93999429 "settings2",
9430+ "story",
94009431 "toml 0.5.11",
94019432 "util",
94029433 "uuid 1.4.1",
@@ -9899,7 +9930,7 @@ dependencies = [ · 496 unmodified lines
98999930 [[package]]
99009931 name = "tree-sitter"
99019932 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"
99039934 dependencies = [
99049935 "cc",
99059936 "regex",
@@ -10147,6 +10178,15 @@ dependencies = [ · 241 unmodified lines
1014710178 "tree-sitter",
1014810179 ]
1014910180
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+
1015010190 [[package]]
1015110191 name = "tree-sitter-vue"
1015210192 version = "0.0.1"
@@ -10240,6 +10280,7 @@ dependencies = [ · 87 unmodified lines
1024010280 "serde",
1024110281 "settings2",
1024210282 "smallvec",
10283+ "story",
1024310284 "strum",
1024410285 "theme2",
1024510286 ]
@@ -11354,6 +11395,7 @@ version = "0.1.0" · 1108 unmodified lines
1135411395 dependencies = [
1135511396 "anyhow",
1135611397 "async-recursion 1.0.5",
11398+ "async-trait",
1135711399 "bincode",
1135811400 "call2",
1135911401 "client2",
@@ -11466,7 +11508,7 @@ dependencies = [ · 106 unmodified lines
1146611508
1146711509 [[package]]
1146811510 name = "zed"
11469−version = "0.114.0"
11511+version = "0.115.0"
1147011512 dependencies = [
1147111513 "activity_indicator",
1147211514 "ai",
@@ -11583,6 +11625,7 @@ dependencies = [ · 110 unmodified lines
1158311625 "tree-sitter-svelte",
1158411626 "tree-sitter-toml",
1158511627 "tree-sitter-typescript",
11628+ "tree-sitter-uiua",
1158611629 "tree-sitter-vue",
1158711630 "tree-sitter-yaml",
1158811631 "unindent",
@@ -11614,9 +11657,11 @@ dependencies = [ · 25 unmodified lines
1161411657 "async-recursion 0.3.2",
1161511658 "async-tar",
1161611659 "async-trait",
11660+ "audio2",
1161711661 "auto_update2",
1161811662 "backtrace",
1161911663 "call2",
11664+ "channel2",
1162011665 "chrono",
1162111666 "cli",
1162211667 "client2",
@@ -11634,7 +11679,6 @@ dependencies = [ · 11 unmodified lines
1163411679 "fs2",
1163511680 "fsevent",
1163611681 "futures 0.3.28",
11637− "fuzzy",
1163811682 "go_to_line2",
1163911683 "gpui2",
1164011684 "ignore",
@@ -11662,6 +11706,7 @@ dependencies = [ · 21 unmodified lines
1166211706 "rsa 0.4.0",
1166311707 "rust-embed",
1166411708 "schemars",
11709+ "search2",
1166511710 "serde",
1166611711 "serde_derive",
1166711712 "serde_json",
@@ -11704,6 +11749,7 @@ dependencies = [ · 36 unmodified lines
1170411749 "tree-sitter-svelte",
1170511750 "tree-sitter-toml",
1170611751 "tree-sitter-typescript",
11752+ "tree-sitter-uiua",
1170711753 "tree-sitter-vue",
1170811754 "tree-sitter-yaml",
1170911755 "unindent",
Cargo.toml+7−3
@@ -90,6 +90,7 @@ members = [ · 89 unmodified lines
9090 "crates/rpc",
9191 "crates/rpc2",
9292 "crates/search",
93+ "crates/search2",
9394 "crates/settings",
9495 "crates/settings2",
9596 "crates/snippet",
@@ -97,7 +98,6 @@ members = [ · 1 unmodified line
9798 "crates/sqlez_macros",
9899 "crates/rich_text",
99100 "crates/storybook2",
100− "crates/storybook3",
101101 "crates/sum_tree",
102102 "crates/terminal",
103103 "crates/terminal2",
@@ -110,6 +110,7 @@ members = [ · 6 unmodified lines
110110 "crates/ui2",
111111 "crates/util",
112112 "crates/semantic_index",
113+ "crates/story",
113114 "crates/vim",
114115 "crates/vcs_menu",
115116 "crates/workspace2",
@@ -194,8 +195,10 @@ tree-sitter-lua = "0.0.14" · 78 unmodified lines
194195 tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
195196 tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
196197 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+
197200 [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" }
199202 async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
200203
201204 # 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
207210
208211 [profile.dev]
209212 split-debuginfo = "unpacked"
213+debug = "limited"
210214
211215 [profile.dev.package.taffy]
212216 opt-level = 3
213217
214218 [profile.release]
215−debug = true
219+debug = "limited"
216220 lto = "thin"
217221 codegen-units = 1
Procfile.zed2+4−0
@@ -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
assets/settings/default.json+1−1
@@ -43,7 +43,7 @@ · 42 unmodified lines
4343 "calt": false
4444 },
4545 // The default font size for text in the UI
46− "ui_font_size": 14,
46+ "ui_font_size": 16,
4747 // The factor to grow the active pane by. Defaults to 1.0
4848 // which gives the same size as all other panes.
4949 "active_pane_magnification": 1.0,
assets/settings/initial_user_settings.json+2−1
@@ -7,5 +7,6 @@ · 6 unmodified lines
77 // custom settings, run the `open default settings` command
88 // from the command palette or from `Zed` application menu.
99 {
10− "buffer_font_size": 15
10+ "ui_font_size": 16,
11+ "buffer_font_size": 16
1112 }
crates/auto_update2/src/auto_update.rs+8−3
@@ -84,8 +84,8 @@ impl Settings for AutoUpdateSetting { · 83 unmodified lines
8484 pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
8585 AutoUpdateSetting::register(cx);
8686
87− cx.observe_new_views(|wokrspace: &mut Workspace, _cx| {
88− wokrspace
87+ cx.observe_new_views(|workspace: &mut Workspace, _cx| {
88+ workspace
8989 .register_action(|_, action: &Check, cx| check(action, cx))
9090 .register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
9191 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
9494 })
9595 .detach();
9696 });
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+ // });
97102 })
98103 .detach();
99104
@@ -131,7 +136,7 @@ pub fn check(_: &Check, cx: &mut AppContext) { · 31 unmodified lines
131136 }
132137 }
133138
134−fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
139+pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
135140 if let Some(auto_updater) = AutoUpdater::get(cx) {
136141 let auto_updater = auto_updater.read(cx);
137142 let server_url = &auto_updater.server_url;
crates/auto_update2/src/update_notification.rs+41−72
@@ -1,87 +1,56 @@
1−use gpui::{div, Div, EventEmitter, ParentComponent, Render, SemanticVersion, ViewContext};
2−use menu::Cancel;
3−use workspace::notifications::NotificationEvent;
1+use gpui::{
2+ div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render,
3+ SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
4+};
5+use util::channel::ReleaseChannel;
6+use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
47
58 pub struct UpdateNotification {
6− _version: SemanticVersion,
9+ version: SemanticVersion,
710 }
811
9−impl EventEmitter<NotificationEvent> for UpdateNotification {}
12+impl EventEmitter<DismissEvent> for UpdateNotification {}
1013
1114 impl Render for UpdateNotification {
12− type Element = Div<Self>;
13−
14− fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
15− div().child("Updated zed!")
16− // let theme = theme::current(cx).clone();
17− // let theme = &theme.update_notification;
18−
19− // let app_name = cx.global::<ReleaseChannel>().display_name();
20−
21− // MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
22− // Flex::column()
23− // .with_child(
24− // Flex::row()
25− // .with_child(
26− // Text::new(
27− // format!("Updated to {app_name} {}", self.version),
28− // theme.message.text.clone(),
29− // )
30− // .contained()
31− // .with_style(theme.message.container)
32− // .aligned()
33− // .top()
34− // .left()
35− // .flex(1., true),
36− // )
37− // .with_child(
38− // MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
39− // let style = theme.dismiss_button.style_for(state);
40− // Svg::new("icons/x.svg")
41− // .with_color(style.color)
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")
15+ type Element = Div;
16+
17+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
18+ let app_name = cx.global::<ReleaseChannel>().display_name();
19+
20+ v_stack()
21+ .elevation_3(cx)
22+ .p_4()
23+ .child(
24+ h_stack()
25+ .justify_between()
26+ .child(Label::new(format!(
27+ "Updated to {app_name} {}",
28+ self.version
29+ )))
30+ .child(
31+ div()
32+ .id("cancel")
33+ .child(IconElement::new(Icon::Close))
34+ .cursor_pointer()
35+ .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
36+ ),
37+ )
38+ .child(
39+ div()
40+ .id("notes")
41+ .child(Label::new("View the release notes"))
42+ .cursor_pointer()
43+ .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
44+ )
7645 }
7746 }
7847
7948 impl UpdateNotification {
8049 pub fn new(version: SemanticVersion) -> Self {
81− Self { _version: version }
50+ Self { version }
8251 }
8352
84− pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
85− cx.emit(NotificationEvent::Dismiss);
53+ pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
54+ cx.emit(DismissEvent::Dismiss);
8655 }
8756 }
crates/call2/Cargo.toml+5−1
@@ -31,15 +31,19 @@ media = { path = "../media" } · 30 unmodified lines
3131 project = { package = "project2", path = "../project2" }
3232 settings = { package = "settings2", path = "../settings2" }
3333 util = { path = "../util" }
34−
34+ui = {package = "ui2", path = "../ui2"}
35+workspace = {package = "workspace2", path = "../workspace2"}
36+async-trait.workspace = true
3537 anyhow.workspace = true
3638 async-broadcast = "0.4"
3739 futures.workspace = true
40+image = "0.23"
3841 postage.workspace = true
3942 schemars.workspace = true
4043 serde.workspace = true
4144 serde_json.workspace = true
4245 serde_derive.workspace = true
46+smallvec.workspace = true
4347
4448 [dev-dependencies]
4549 client = { package = "client2", path = "../client2", features = ["test-support"] }
crates/call2/src/call2.rs+216−7
@@ -1,25 +1,32 @@
11 pub mod call_settings;
22 pub mod participant;
33 pub mod room;
4+mod shared_screen;
45
56 use anyhow::{anyhow, Result};
7+use async_trait::async_trait;
68 use audio::Audio;
79 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+};
914 use collections::HashSet;
1015 use futures::{channel::oneshot, future::Shared, Future, FutureExt};
1116 use gpui::{
1217 AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
13− WeakModel,
18+ View, ViewContext, VisualContext, WeakModel, WeakView,
1419 };
20+pub use participant::ParticipantLocation;
1521 use postage::watch;
1622 use project::Project;
1723 use room::Event;
24+pub use room::Room;
1825 use settings::Settings;
26+use shared_screen::SharedScreen;
1927 use std::sync::Arc;
20−
21−pub use participant::ParticipantLocation;
22−pub use room::Room;
28+use util::ResultExt;
29+use workspace::{item::ItemHandle, CallHandler, Pane, Workspace};
2330
2431 pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
2532 CallSettings::register(cx);
@@ -464,7 +471,7 @@ impl ActiveCall { · 438 unmodified lines
464471 &self.pending_invites
465472 }
466473
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) {
468475 if let Some(room) = self.room() {
469476 let room = room.read(cx);
470477 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
477484 room_id: u64,
478485 channel_id: Option<u64>,
479486 client: &Arc<Client>,
480− cx: &AppContext,
487+ cx: &mut AppContext,
481488 ) {
482489 let telemetry = client.telemetry();
483490 let telemetry_settings = *TelemetrySettings::get_global(cx);
@@ -505,6 +512,208 @@ pub fn report_call_event_for_channel( · 21 unmodified lines
505512 )
506513 }
507514
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+
508717 #[cfg(test)]
509718 mod test {
510719 use gpui::TestAppContext;
crates/call2/src/participant.rs+1−1
@@ -4,7 +4,7 @@ use client::{proto, User}; · 3 unmodified lines
44 use collections::HashMap;
55 use gpui::WeakModel;
66 pub use live_kit_client::Frame;
7−use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
7+pub(crate) use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
88 use project::Project;
99 use std::sync::Arc;
1010
crates/call2/src/room.rs+1−2
@@ -21,7 +21,7 @@ use live_kit_client::{ · 20 unmodified lines
2121 };
2222 use postage::{sink::Sink, stream::Stream, watch};
2323 use project::Project;
24−use settings::Settings;
24+use settings::Settings as _;
2525 use std::{future::Future, mem, sync::Arc, time::Duration};
2626 use util::{post_inc, ResultExt, TryFutureExt};
2727
@@ -1267,7 +1267,6 @@ impl Room { · 1239 unmodified lines
12671267 .ok_or_else(|| anyhow!("live-kit was not initialized"))?
12681268 .await
12691269 };
1270−
12711270 let publication = publish_track.await;
12721271 this.upgrade()
12731272 .ok_or_else(|| anyhow!("room was dropped"))?
crates/call2/src/shared_screen.rs+157−0
@@ -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+}
crates/channel2/Cargo.toml+1−1
@@ -18,7 +18,7 @@ db = { package = "db2", path = "../db2" } · 17 unmodified lines
1818 gpui = { package = "gpui2", path = "../gpui2" }
1919 util = { path = "../util" }
2020 rpc = { package = "rpc2", path = "../rpc2" }
21−text = { path = "../text" }
21+text = { package = "text2", path = "../text2" }
2222 language = { package = "language2", path = "../language2" }
2323 settings = { package = "settings2", path = "../settings2" }
2424 feature_flags = { package = "feature_flags2", path = "../feature_flags2" }
crates/client/src/telemetry.rs+26−12
@@ -109,6 +109,10 @@ pub enum ClickhouseEvent { · 108 unmodified lines
109109 virtual_memory_in_bytes: u64,
110110 milliseconds_since_first_event: i64,
111111 },
112+ App {
113+ operation: &'static str,
114+ milliseconds_since_first_event: i64,
115+ },
112116 }
113117
114118 #[cfg(debug_assertions)]
@@ -168,13 +172,8 @@ impl Telemetry { · 53 unmodified lines
168172 let mut state = self.state.lock();
169173 state.installation_id = installation_id.map(|id| id.into());
170174 state.session_id = Some(session_id.into());
171− let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
172175 drop(state);
173176
174− if has_clickhouse_events {
175− self.flush_clickhouse_events();
176− }
177−
178177 let this = self.clone();
179178 cx.spawn(|mut cx| async move {
180179 // Avoiding calling `System::new_all()`, as there have been crashes related to it
@@ -256,7 +255,7 @@ impl Telemetry { · 75 unmodified lines
256255 milliseconds_since_first_event: self.milliseconds_since_first_event(),
257256 };
258257
259− self.report_clickhouse_event(event, telemetry_settings)
258+ self.report_clickhouse_event(event, telemetry_settings, false)
260259 }
261260
262261 pub fn report_copilot_event(
@@ -273,7 +272,7 @@ impl Telemetry { · 10 unmodified lines
273272 milliseconds_since_first_event: self.milliseconds_since_first_event(),
274273 };
275274
276− self.report_clickhouse_event(event, telemetry_settings)
275+ self.report_clickhouse_event(event, telemetry_settings, false)
277276 }
278277
279278 pub fn report_assistant_event(
@@ -290,7 +289,7 @@ impl Telemetry { · 10 unmodified lines
290289 milliseconds_since_first_event: self.milliseconds_since_first_event(),
291290 };
292291
293− self.report_clickhouse_event(event, telemetry_settings)
292+ self.report_clickhouse_event(event, telemetry_settings, false)
294293 }
295294
296295 pub fn report_call_event(
@@ -307,7 +306,7 @@ impl Telemetry { · 10 unmodified lines
307306 milliseconds_since_first_event: self.milliseconds_since_first_event(),
308307 };
309308
310− self.report_clickhouse_event(event, telemetry_settings)
309+ self.report_clickhouse_event(event, telemetry_settings, false)
311310 }
312311
313312 pub fn report_cpu_event(
@@ -322,7 +321,7 @@ impl Telemetry { · 8 unmodified lines
322321 milliseconds_since_first_event: self.milliseconds_since_first_event(),
323322 };
324323
325− self.report_clickhouse_event(event, telemetry_settings)
324+ self.report_clickhouse_event(event, telemetry_settings, false)
326325 }
327326
328327 pub fn report_memory_event(
@@ -337,7 +336,21 @@ impl Telemetry { · 8 unmodified lines
337336 milliseconds_since_first_event: self.milliseconds_since_first_event(),
338337 };
339338
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)
341354 }
342355
343356 fn milliseconds_since_first_event(&self) -> i64 {
@@ -358,6 +371,7 @@ impl Telemetry { · 14 unmodified lines
358371 self: &Arc<Self>,
359372 event: ClickhouseEvent,
360373 telemetry_settings: TelemetrySettings,
374+ immediate_flush: bool,
361375 ) {
362376 if !telemetry_settings.metrics {
363377 return;
@@ -370,7 +384,7 @@ impl Telemetry { · 6 unmodified lines
370384 .push(ClickhouseEventWrapper { signed_in, event });
371385
372386 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 {
374388 drop(state);
375389 self.flush_clickhouse_events();
376390 } else {
crates/client2/src/client2.rs+1−2
@@ -382,7 +382,7 @@ impl settings::Settings for TelemetrySettings { · 381 unmodified lines
382382 }
383383
384384 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> {
386386 Arc::new(Self {
387387 id: AtomicU64::new(0),
388388 peer: Peer::new(0),
@@ -551,7 +551,6 @@ impl Client { · 162 unmodified lines
551551 F: 'static + Future<Output = Result<()>>,
552552 {
553553 let message_type_id = TypeId::of::<M>();
554−
555554 let mut state = self.state.write();
556555 state
557556 .models_by_message_type
crates/client2/src/telemetry.rs+50−13
@@ -1,5 +1,6 @@
11 use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
22 use chrono::{DateTime, Utc};
3+use futures::Future;
34 use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
45 use lazy_static::lazy_static;
56 use parking_lot::Mutex;
@@ -107,6 +108,10 @@ pub enum ClickhouseEvent { · 101 unmodified lines
107108 virtual_memory_in_bytes: u64,
108109 milliseconds_since_first_event: i64,
109110 },
111+ App {
112+ operation: &'static str,
113+ milliseconds_since_first_event: i64,
114+ },
110115 }
111116
112117 #[cfg(debug_assertions)]
@@ -122,12 +127,13 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); · 9 unmodified lines
122127 const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
123128
124129 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> {
126131 let release_channel = if cx.has_global::<ReleaseChannel>() {
127132 Some(cx.global::<ReleaseChannel>().display_name())
128133 } else {
129134 None
130135 };
136+
131137 // TODO: Replace all hardware stuff with nested SystemSpecs json
132138 let this = Arc::new(Self {
133139 http_client: client,
@@ -147,9 +153,30 @@ impl Telemetry { · 13 unmodified lines
147153 }),
148154 });
149155
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+
150163 this
151164 }
152165
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+
153180 pub fn log_file_path(&self) -> Option<PathBuf> {
154181 Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
155182 }
@@ -163,13 +190,8 @@ impl Telemetry { · 7 unmodified lines
163190 let mut state = self.state.lock();
164191 state.installation_id = installation_id.map(|id| id.into());
165192 state.session_id = Some(session_id.into());
166− let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
167193 drop(state);
168194
169− if has_clickhouse_events {
170− self.flush_clickhouse_events();
171− }
172−
173195 let this = self.clone();
174196 cx.spawn(|cx| async move {
175197 // Avoiding calling `System::new_all()`, as there have been crashes related to it
@@ -257,7 +279,7 @@ impl Telemetry { · 81 unmodified lines
257279 milliseconds_since_first_event: self.milliseconds_since_first_event(),
258280 };
259281
260− self.report_clickhouse_event(event, telemetry_settings)
282+ self.report_clickhouse_event(event, telemetry_settings, false)
261283 }
262284
263285 pub fn report_copilot_event(
@@ -274,7 +296,7 @@ impl Telemetry { · 10 unmodified lines
274296 milliseconds_since_first_event: self.milliseconds_since_first_event(),
275297 };
276298
277− self.report_clickhouse_event(event, telemetry_settings)
299+ self.report_clickhouse_event(event, telemetry_settings, false)
278300 }
279301
280302 pub fn report_assistant_event(
@@ -291,7 +313,7 @@ impl Telemetry { · 10 unmodified lines
291313 milliseconds_since_first_event: self.milliseconds_since_first_event(),
292314 };
293315
294− self.report_clickhouse_event(event, telemetry_settings)
316+ self.report_clickhouse_event(event, telemetry_settings, false)
295317 }
296318
297319 pub fn report_call_event(
@@ -308,7 +330,7 @@ impl Telemetry { · 10 unmodified lines
308330 milliseconds_since_first_event: self.milliseconds_since_first_event(),
309331 };
310332
311− self.report_clickhouse_event(event, telemetry_settings)
333+ self.report_clickhouse_event(event, telemetry_settings, false)
312334 }
313335
314336 pub fn report_cpu_event(
@@ -323,7 +345,7 @@ impl Telemetry { · 8 unmodified lines
323345 milliseconds_since_first_event: self.milliseconds_since_first_event(),
324346 };
325347
326− self.report_clickhouse_event(event, telemetry_settings)
348+ self.report_clickhouse_event(event, telemetry_settings, false)
327349 }
328350
329351 pub fn report_memory_event(
@@ -338,7 +360,21 @@ impl Telemetry { · 8 unmodified lines
338360 milliseconds_since_first_event: self.milliseconds_since_first_event(),
339361 };
340362
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)
342378 }
343379
344380 fn milliseconds_since_first_event(&self) -> i64 {
@@ -359,6 +395,7 @@ impl Telemetry { · 14 unmodified lines
359395 self: &Arc<Self>,
360396 event: ClickhouseEvent,
361397 telemetry_settings: TelemetrySettings,
398+ immediate_flush: bool,
362399 ) {
363400 if !telemetry_settings.metrics {
364401 return;
@@ -371,7 +408,7 @@ impl Telemetry { · 6 unmodified lines
371408 .push(ClickhouseEventWrapper { signed_in, event });
372409
373410 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 {
375412 drop(state);
376413 self.flush_clickhouse_events();
377414 } else {
crates/collab/Cargo.toml+1−1
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"] · 2 unmodified lines
33 default-run = "collab"
44 edition = "2021"
55 name = "collab"
6−version = "0.28.0"
6+version = "0.29.0"
77 publish = false
88
99 [[bin]]
crates/collab2/Cargo.toml+1−1
@@ -10,7 +10,7 @@ publish = false · 9 unmodified lines
1010 name = "collab2"
1111
1212 [[bin]]
13−name = "seed"
13+name = "seed2"
1414 required-features = ["seed-support"]
1515
1616 [dependencies]
crates/collab2/src/bin/dotenv2.rsrenamed from crates/collab2/src/bin/dotenv.rs+0−0

File renamed without changes.

crates/collab2/src/bin/seed2.rsrenamed from crates/collab2/src/bin/seed.rs+0−0

File renamed without changes.

crates/collab2/src/tests/test_server.rs+2−1
@@ -149,7 +149,7 @@ impl TestServer { · 148 unmodified lines
149149 .user_id
150150 };
151151 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));
153153 let server = self.server.clone();
154154 let db = self.app_state.db.clone();
155155 let connection_killers = self.connection_killers.clone();
@@ -221,6 +221,7 @@ impl TestServer { · 65 unmodified lines
221221 fs: fs.clone(),
222222 build_window_options: |_, _, _| Default::default(),
223223 node_runtime: FakeNodeRuntime::new(),
224+ call_factory: |_, _| Box::new(workspace::TestCallHandler),
224225 });
225226
226227 cx.update(|cx| {
crates/collab_ui2/Cargo.toml+1−1
@@ -33,7 +33,7 @@ collections = { path = "../collections" } · 32 unmodified lines
3333 # drag_and_drop = { path = "../drag_and_drop" }
3434 editor = { package="editor2", path = "../editor2" }
3535 #feedback = { path = "../feedback" }
36−fuzzy = { path = "../fuzzy" }
36+fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
3737 gpui = { package = "gpui2", path = "../gpui2" }
3838 language = { package = "language2", path = "../language2" }
3939 menu = { package = "menu2", path = "../menu2" }
crates/collab_ui2/src/collab_panel.rs+1383−1286
@@ -1,5 +1,6 @@
1+#![allow(unused)]
12 // mod channel_modal;
2−// mod contact_finder;
3+mod contact_finder;
34
45 // use crate::{
56 // channel_view::{self, ChannelView},
@@ -15,7 +16,7 @@ · 9 unmodified lines
1516 // proto::{self, PeerId},
1617 // Client, Contact, User, UserStore,
1718 // };
18−// use contact_finder::ContactFinder;
19+use contact_finder::ContactFinder;
1920 // use context_menu::{ContextMenu, ContextMenuItem};
2021 // use db::kvp::KEY_VALUE_STORE;
2122 // use drag_and_drop::{DragAndDrop, Draggable};
@@ -155,20 +156,30 @@ actions!( · 133 unmodified lines
155156
156157 const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
157158
158−use std::sync::Arc;
159+use std::{iter::once, mem, sync::Arc};
159160
161+use call::ActiveCall;
162+use channel::{Channel, ChannelId, ChannelStore};
163+use client::{Client, Contact, User, UserStore};
160164 use db::kvp::KEY_VALUE_STORE;
165+use editor::Editor;
166+use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
167+use fuzzy::{match_strings, StringMatchCandidate};
161168 use gpui::{
162− actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,
163− Focusable, FocusableView, InteractiveComponent, ParentComponent, Render, View, ViewContext,
164− VisualContext, WeakView,
169+ actions, div, img, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,
170+ Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render,
171+ RenderOnce, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
165172 };
166173 use project::Fs;
167174 use serde_derive::{Deserialize, Serialize};
168175 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};
170180 use workspace::{
171181 dock::{DockPosition, Panel, PanelEvent},
182+ notifications::NotifyResultExt,
172183 Workspace,
173184 };
174185
@@ -266,26 +277,26 @@ pub fn init(cx: &mut AppContext) { · 91 unmodified lines
266277 // );
267278 }
268279
269−// #[derive(Debug)]
270−// pub enum ChannelEditingState {
271−// Create {
272−// location: Option<ChannelId>,
273−// pending_name: Option<String>,
274−// },
275−// Rename {
276−// location: ChannelId,
277−// pending_name: Option<String>,
278−// },
279−// }
280+#[derive(Debug)]
281+pub enum ChannelEditingState {
282+ Create {
283+ location: Option<ChannelId>,
284+ pending_name: Option<String>,
285+ },
286+ Rename {
287+ location: ChannelId,
288+ pending_name: Option<String>,
289+ },
290+}
280291
281−// impl ChannelEditingState {
282−// fn pending_name(&self) -> Option<&str> {
283−// match self {
284−// ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
285−// ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
286−// }
287−// }
288−// }
292+impl ChannelEditingState {
293+ fn pending_name(&self) -> Option<&str> {
294+ match self {
295+ ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
296+ ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
297+ }
298+ }
299+}
289300
290301 pub struct CollabPanel {
291302 width: Option<f32>,
@@ -294,22 +305,22 @@ pub struct CollabPanel { · 2 unmodified lines
294305 // channel_clipboard: Option<ChannelMoveClipboard>,
295306 // pending_serialization: Task<Option<()>>,
296307 // context_menu: ViewHandle<ContextMenu>,
297− // filter_editor: ViewHandle<Editor>,
308+ filter_editor: View<Editor>,
298309 // channel_name_editor: ViewHandle<Editor>,
299− // channel_editing_state: Option<ChannelEditingState>,
300− // entries: Vec<ListEntry>,
310+ channel_editing_state: Option<ChannelEditingState>,
311+ entries: Vec<ListEntry>,
301312 // selection: Option<usize>,
302− // user_store: ModelHandle<UserStore>,
303− // client: Arc<Client>,
304− // channel_store: ModelHandle<ChannelStore>,
313+ channel_store: Model<ChannelStore>,
314+ user_store: Model<UserStore>,
315+ client: Arc<Client>,
305316 // project: ModelHandle<Project>,
306− // match_candidates: Vec<StringMatchCandidate>,
317+ match_candidates: Vec<StringMatchCandidate>,
307318 // list_state: ListState<Self>,
308− // subscriptions: Vec<Subscription>,
309− // collapsed_sections: Vec<Section>,
310− // collapsed_channels: Vec<ChannelId>,
319+ subscriptions: Vec<Subscription>,
320+ collapsed_sections: Vec<Section>,
321+ collapsed_channels: Vec<ChannelId>,
311322 // drag_target_channel: ChannelDragTarget,
312− _workspace: WeakView<Workspace>,
323+ workspace: WeakView<Workspace>,
313324 // context_menu_on_selected: bool,
314325 }
315326
@@ -333,58 +344,58 @@ struct SerializedCollabPanel { · 17 unmodified lines
333344 // Dismissed,
334345 // }
335346
336−// #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
337−// enum Section {
338−// ActiveCall,
339−// Channels,
340−// ChannelInvites,
341−// ContactRequests,
342−// Contacts,
343−// Online,
344−// Offline,
345−// }
347+#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
348+enum Section {
349+ ActiveCall,
350+ Channels,
351+ ChannelInvites,
352+ ContactRequests,
353+ Contacts,
354+ Online,
355+ Offline,
356+}
346357
347−// #[derive(Clone, Debug)]
348−// enum ListEntry {
349−// Header(Section),
350−// CallParticipant {
351−// user: Arc<User>,
352−// peer_id: Option<PeerId>,
353−// is_pending: bool,
354−// },
355−// ParticipantProject {
356−// project_id: u64,
357−// worktree_root_names: Vec<String>,
358−// host_user_id: u64,
359−// is_last: bool,
360−// },
361−// ParticipantScreen {
362−// peer_id: Option<PeerId>,
363−// is_last: bool,
364−// },
365−// IncomingRequest(Arc<User>),
366−// OutgoingRequest(Arc<User>),
367−// ChannelInvite(Arc<Channel>),
368−// Channel {
369−// channel: Arc<Channel>,
370−// depth: usize,
371−// has_children: bool,
372−// },
373−// ChannelNotes {
374−// channel_id: ChannelId,
375−// },
376−// ChannelChat {
377−// channel_id: ChannelId,
378−// },
379−// ChannelEditor {
380−// depth: usize,
381−// },
382−// Contact {
383−// contact: Arc<Contact>,
384−// calling: bool,
385−// },
386−// ContactPlaceholder,
387−// }
358+#[derive(Clone, Debug)]
359+enum ListEntry {
360+ Header(Section),
361+ // CallParticipant {
362+ // user: Arc<User>,
363+ // peer_id: Option<PeerId>,
364+ // is_pending: bool,
365+ // },
366+ // ParticipantProject {
367+ // project_id: u64,
368+ // worktree_root_names: Vec<String>,
369+ // host_user_id: u64,
370+ // is_last: bool,
371+ // },
372+ // ParticipantScreen {
373+ // peer_id: Option<PeerId>,
374+ // is_last: bool,
375+ // },
376+ IncomingRequest(Arc<User>),
377+ OutgoingRequest(Arc<User>),
378+ // ChannelInvite(Arc<Channel>),
379+ Channel {
380+ channel: Arc<Channel>,
381+ depth: usize,
382+ has_children: bool,
383+ },
384+ // ChannelNotes {
385+ // channel_id: ChannelId,
386+ // },
387+ // ChannelChat {
388+ // channel_id: ChannelId,
389+ // },
390+ ChannelEditor {
391+ depth: usize,
392+ },
393+ Contact {
394+ contact: Arc<Contact>,
395+ calling: bool,
396+ },
397+ ContactPlaceholder,
398+}
388399
389400 // impl Entity for CollabPanel {
390401 // type Event = Event;
@@ -395,16 +406,11 @@ impl CollabPanel { · 4 unmodified lines
395406 cx.build_view(|cx| {
396407 // let view_id = cx.view_id();
397408
398− // let filter_editor = cx.add_view(|cx| {
399− // let mut editor = Editor::single_line(
400− // Some(Arc::new(|theme| {
401− // theme.collab_panel.user_query_editor.clone()
402− // })),
403− // cx,
404− // );
405− // editor.set_placeholder_text("Filter channels, contacts", cx);
406− // editor
407− // });
409+ let filter_editor = cx.build_view(|cx| {
410+ let mut editor = Editor::single_line(cx);
411+ editor.set_placeholder_text("Filter channels, contacts", cx);
412+ editor
413+ });
408414
409415 // cx.subscribe(&filter_editor, |this, _, event, cx| {
410416 // if let editor::Event::BufferEdited = event {
@@ -583,7 +589,7 @@ impl CollabPanel { · 172 unmodified lines
583589 // }
584590 // });
585591
586− let this = Self {
592+ let mut this = Self {
587593 width: None,
588594 focus_handle: cx.focus_handle(),
589595 // channel_clipboard: None,
@@ -591,25 +597,25 @@ impl CollabPanel { · 1 unmodified line
591597 // pending_serialization: Task::ready(None),
592598 // context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
593599 // channel_name_editor,
594− // filter_editor,
595− // entries: Vec::default(),
596− // channel_editing_state: None,
600+ filter_editor,
601+ entries: Vec::default(),
602+ channel_editing_state: None,
597603 // selection: None,
598− // user_store: workspace.user_store().clone(),
599− // channel_store: ChannelStore::global(cx),
604+ channel_store: ChannelStore::global(cx),
605+ user_store: workspace.user_store().clone(),
600606 // project: workspace.project().clone(),
601− // subscriptions: Vec::default(),
602− // match_candidates: Vec::default(),
603− // collapsed_sections: vec![Section::Offline],
604− // collapsed_channels: Vec::default(),
605− _workspace: workspace.weak_handle(),
606− // client: workspace.app_state().client.clone(),
607+ subscriptions: Vec::default(),
608+ match_candidates: Vec::default(),
609+ collapsed_sections: vec![Section::Offline],
610+ collapsed_channels: Vec::default(),
611+ workspace: workspace.weak_handle(),
612+ client: workspace.app_state().client.clone(),
607613 // context_menu_on_selected: true,
608614 // drag_target_channel: ChannelDragTarget::None,
609615 // list_state,
610616 };
611617
612− // this.update_entries(false, cx);
618+ this.update_entries(false, cx);
613619
614620 // // Update the dock position when the setting changes.
615621 // let mut old_dock_position = this.position(cx);
@@ -626,10 +632,10 @@ impl CollabPanel { · 10 unmodified lines
626632 // );
627633
628634 // let active_call = ActiveCall::global(cx);
629− // this.subscriptions
630− // .push(cx.observe(&this.user_store, |this, _, cx| {
631− // this.update_entries(true, cx)
632− // }));
635+ this.subscriptions
636+ .push(cx.observe(&this.user_store, |this, _, cx| {
637+ this.update_entries(true, cx)
638+ }));
633639 // this.subscriptions
634640 // .push(cx.observe(&this.channel_store, |this, _, cx| {
635641 // this.update_entries(true, cx)
@@ -663,6 +669,9 @@ impl CollabPanel { · 27 unmodified lines
663669 })
664670 }
665671
672+ fn contacts(&self, cx: &AppContext) -> Option<Vec<Arc<Contact>>> {
673+ Some(self.user_store.read(cx).contacts().to_owned())
674+ }
666675 pub async fn load(
667676 workspace: WeakView<Workspace>,
668677 mut cx: AsyncWindowContext,
@@ -715,449 +724,449 @@ impl CollabPanel { · 46 unmodified lines
715724 // );
716725 // }
717726
718− // fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
719− // let channel_store = self.channel_store.read(cx);
720− // let user_store = self.user_store.read(cx);
721− // let query = self.filter_editor.read(cx).text(cx);
722− // let executor = cx.background().clone();
723−
724− // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
725− // let old_entries = mem::take(&mut self.entries);
726− // let mut scroll_to_top = false;
727−
728− // if let Some(room) = ActiveCall::global(cx).read(cx).room() {
729− // self.entries.push(ListEntry::Header(Section::ActiveCall));
730− // if !old_entries
731− // .iter()
732− // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
733− // {
734− // scroll_to_top = true;
735− // }
736−
737− // if !self.collapsed_sections.contains(&Section::ActiveCall) {
738− // let room = room.read(cx);
739−
740− // if let Some(channel_id) = room.channel_id() {
741− // self.entries.push(ListEntry::ChannelNotes { channel_id });
742− // self.entries.push(ListEntry::ChannelChat { channel_id })
743− // }
744−
745− // // Populate the active user.
746− // if let Some(user) = user_store.current_user() {
747− // self.match_candidates.clear();
748− // self.match_candidates.push(StringMatchCandidate {
749− // id: 0,
750− // string: user.github_login.clone(),
751− // char_bag: user.github_login.chars().collect(),
752− // });
753− // let matches = executor.block(match_strings(
754− // &self.match_candidates,
755− // &query,
756− // true,
757− // usize::MAX,
758− // &Default::default(),
759− // executor.clone(),
760− // ));
761− // if !matches.is_empty() {
762− // let user_id = user.id;
763− // self.entries.push(ListEntry::CallParticipant {
764− // user,
765− // peer_id: None,
766− // is_pending: false,
767− // });
768− // let mut projects = room.local_participant().projects.iter().peekable();
769− // while let Some(project) = projects.next() {
770− // self.entries.push(ListEntry::ParticipantProject {
771− // project_id: project.id,
772− // worktree_root_names: project.worktree_root_names.clone(),
773− // host_user_id: user_id,
774− // is_last: projects.peek().is_none() && !room.is_screen_sharing(),
775− // });
776− // }
777− // if room.is_screen_sharing() {
778− // self.entries.push(ListEntry::ParticipantScreen {
779− // peer_id: None,
780− // is_last: true,
781− // });
782− // }
783− // }
784− // }
785−
786− // // Populate remote participants.
787− // self.match_candidates.clear();
788− // self.match_candidates
789− // .extend(room.remote_participants().iter().map(|(_, participant)| {
790− // StringMatchCandidate {
791− // id: participant.user.id as usize,
792− // string: participant.user.github_login.clone(),
793− // char_bag: participant.user.github_login.chars().collect(),
794− // }
795− // }));
796− // let matches = executor.block(match_strings(
797− // &self.match_candidates,
798− // &query,
799− // true,
800− // usize::MAX,
801− // &Default::default(),
802− // executor.clone(),
803− // ));
804− // for mat in matches {
805− // let user_id = mat.candidate_id as u64;
806− // let participant = &room.remote_participants()[&user_id];
807− // self.entries.push(ListEntry::CallParticipant {
808− // user: participant.user.clone(),
809− // peer_id: Some(participant.peer_id),
810− // is_pending: false,
811− // });
812− // let mut projects = participant.projects.iter().peekable();
813− // while let Some(project) = projects.next() {
814− // self.entries.push(ListEntry::ParticipantProject {
815− // project_id: project.id,
816− // worktree_root_names: project.worktree_root_names.clone(),
817− // host_user_id: participant.user.id,
818− // is_last: projects.peek().is_none()
819− // && participant.video_tracks.is_empty(),
820− // });
821− // }
822− // if !participant.video_tracks.is_empty() {
823− // self.entries.push(ListEntry::ParticipantScreen {
824− // peer_id: Some(participant.peer_id),
825− // is_last: true,
826− // });
827− // }
828− // }
829−
830− // // Populate pending participants.
831− // self.match_candidates.clear();
832− // self.match_candidates
833− // .extend(room.pending_participants().iter().enumerate().map(
834− // |(id, participant)| StringMatchCandidate {
835− // id,
836− // string: participant.github_login.clone(),
837− // char_bag: participant.github_login.chars().collect(),
838− // },
839− // ));
840− // let matches = executor.block(match_strings(
841− // &self.match_candidates,
842− // &query,
843− // true,
844− // usize::MAX,
845− // &Default::default(),
846− // executor.clone(),
847− // ));
848− // self.entries
849− // .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
850− // user: room.pending_participants()[mat.candidate_id].clone(),
851− // peer_id: None,
852− // is_pending: true,
853− // }));
854− // }
855− // }
856−
857− // let mut request_entries = Vec::new();
858−
859− // if cx.has_flag::<ChannelsAlpha>() {
860− // self.entries.push(ListEntry::Header(Section::Channels));
861−
862− // if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
863− // self.match_candidates.clear();
864− // self.match_candidates
865− // .extend(channel_store.ordered_channels().enumerate().map(
866− // |(ix, (_, channel))| StringMatchCandidate {
867− // id: ix,
868− // string: channel.name.clone(),
869− // char_bag: channel.name.chars().collect(),
870− // },
871− // ));
872− // let matches = executor.block(match_strings(
873− // &self.match_candidates,
874− // &query,
875− // true,
876− // usize::MAX,
877− // &Default::default(),
878− // executor.clone(),
879− // ));
880− // if let Some(state) = &self.channel_editing_state {
881− // if matches!(state, ChannelEditingState::Create { location: None, .. }) {
882− // self.entries.push(ListEntry::ChannelEditor { depth: 0 });
883− // }
884− // }
885− // let mut collapse_depth = None;
886− // for mat in matches {
887− // let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
888− // let depth = channel.parent_path.len();
889−
890− // if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
891− // collapse_depth = Some(depth);
892− // } else if let Some(collapsed_depth) = collapse_depth {
893− // if depth > collapsed_depth {
894− // continue;
895− // }
896− // if self.is_channel_collapsed(channel.id) {
897− // collapse_depth = Some(depth);
898− // } else {
899− // collapse_depth = None;
900− // }
901− // }
902−
903− // let has_children = channel_store
904− // .channel_at_index(mat.candidate_id + 1)
905− // .map_or(false, |next_channel| {
906− // next_channel.parent_path.ends_with(&[channel.id])
907− // });
908−
909− // match &self.channel_editing_state {
910− // Some(ChannelEditingState::Create {
911− // location: parent_id,
912− // ..
913− // }) if *parent_id == Some(channel.id) => {
914− // self.entries.push(ListEntry::Channel {
915− // channel: channel.clone(),
916− // depth,
917− // has_children: false,
918− // });
919− // self.entries
920− // .push(ListEntry::ChannelEditor { depth: depth + 1 });
921− // }
922− // Some(ChannelEditingState::Rename {
923− // location: parent_id,
924− // ..
925− // }) if parent_id == &channel.id => {
926− // self.entries.push(ListEntry::ChannelEditor { depth });
927− // }
928− // _ => {
929− // self.entries.push(ListEntry::Channel {
930− // channel: channel.clone(),
931− // depth,
932− // has_children,
933− // });
934− // }
935− // }
936− // }
937− // }
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(&section) {
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());
727+ fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
728+ let channel_store = self.channel_store.read(cx);
729+ let user_store = self.user_store.read(cx);
730+ let query = self.filter_editor.read(cx).text(cx);
731+ let executor = cx.background_executor().clone();
732+
733+ // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
734+ let _old_entries = mem::take(&mut self.entries);
735+ // let mut scroll_to_top = false;
736+
737+ // if let Some(room) = ActiveCall::global(cx).read(cx).room() {
738+ // self.entries.push(ListEntry::Header(Section::ActiveCall));
739+ // if !old_entries
740+ // .iter()
741+ // .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
742+ // {
743+ // scroll_to_top = true;
744+ // }
745+
746+ // if !self.collapsed_sections.contains(&Section::ActiveCall) {
747+ // let room = room.read(cx);
748+
749+ // if let Some(channel_id) = room.channel_id() {
750+ // self.entries.push(ListEntry::ChannelNotes { channel_id });
751+ // self.entries.push(ListEntry::ChannelChat { channel_id })
752+ // }
753+
754+ // // Populate the active user.
755+ // if let Some(user) = user_store.current_user() {
756+ // self.match_candidates.clear();
757+ // self.match_candidates.push(StringMatchCandidate {
758+ // id: 0,
759+ // string: user.github_login.clone(),
760+ // char_bag: user.github_login.chars().collect(),
761+ // });
762+ // let matches = executor.block(match_strings(
763+ // &self.match_candidates,
764+ // &query,
765+ // true,
766+ // usize::MAX,
767+ // &Default::default(),
768+ // executor.clone(),
769+ // ));
770+ // if !matches.is_empty() {
771+ // let user_id = user.id;
772+ // self.entries.push(ListEntry::CallParticipant {
773+ // user,
774+ // peer_id: None,
775+ // is_pending: false,
776+ // });
777+ // let mut projects = room.local_participant().projects.iter().peekable();
778+ // while let Some(project) = projects.next() {
779+ // self.entries.push(ListEntry::ParticipantProject {
780+ // project_id: project.id,
781+ // worktree_root_names: project.worktree_root_names.clone(),
782+ // host_user_id: user_id,
783+ // is_last: projects.peek().is_none() && !room.is_screen_sharing(),
784+ // });
785+ // }
786+ // if room.is_screen_sharing() {
787+ // self.entries.push(ListEntry::ParticipantScreen {
788+ // peer_id: None,
789+ // is_last: true,
790+ // });
791+ // }
792+ // }
793+ // }
794+
795+ // // Populate remote participants.
796+ // self.match_candidates.clear();
797+ // self.match_candidates
798+ // .extend(room.remote_participants().iter().map(|(_, participant)| {
799+ // StringMatchCandidate {
800+ // id: participant.user.id as usize,
801+ // string: participant.user.github_login.clone(),
802+ // char_bag: participant.user.github_login.chars().collect(),
803+ // }
804+ // }));
805+ // let matches = executor.block(match_strings(
806+ // &self.match_candidates,
807+ // &query,
808+ // true,
809+ // usize::MAX,
810+ // &Default::default(),
811+ // executor.clone(),
812+ // ));
813+ // for mat in matches {
814+ // let user_id = mat.candidate_id as u64;
815+ // let participant = &room.remote_participants()[&user_id];
816+ // self.entries.push(ListEntry::CallParticipant {
817+ // user: participant.user.clone(),
818+ // peer_id: Some(participant.peer_id),
819+ // is_pending: false,
820+ // });
821+ // let mut projects = participant.projects.iter().peekable();
822+ // while let Some(project) = projects.next() {
823+ // self.entries.push(ListEntry::ParticipantProject {
824+ // project_id: project.id,
825+ // worktree_root_names: project.worktree_root_names.clone(),
826+ // host_user_id: participant.user.id,
827+ // is_last: projects.peek().is_none()
828+ // && participant.video_tracks.is_empty(),
829+ // });
830+ // }
831+ // if !participant.video_tracks.is_empty() {
832+ // self.entries.push(ListEntry::ParticipantScreen {
833+ // peer_id: Some(participant.peer_id),
834+ // is_last: true,
835+ // });
836+ // }
837+ // }
838+
839+ // // Populate pending participants.
840+ // self.match_candidates.clear();
841+ // self.match_candidates
842+ // .extend(room.pending_participants().iter().enumerate().map(
843+ // |(id, participant)| StringMatchCandidate {
844+ // id,
845+ // string: participant.github_login.clone(),
846+ // char_bag: participant.github_login.chars().collect(),
847+ // },
848+ // ));
849+ // let matches = executor.block(match_strings(
850+ // &self.match_candidates,
851+ // &query,
852+ // true,
853+ // usize::MAX,
854+ // &Default::default(),
855+ // executor.clone(),
856+ // ));
857+ // self.entries
858+ // .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
859+ // user: room.pending_participants()[mat.candidate_id].clone(),
860+ // peer_id: None,
861+ // is_pending: true,
862+ // }));
863+ // }
864+ // }
865+
866+ let mut request_entries = Vec::new();
867+
868+ if cx.has_flag::<ChannelsAlpha>() {
869+ self.entries.push(ListEntry::Header(Section::Channels));
870+
871+ if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
872+ self.match_candidates.clear();
873+ self.match_candidates
874+ .extend(channel_store.ordered_channels().enumerate().map(
875+ |(ix, (_, channel))| StringMatchCandidate {
876+ id: ix,
877+ string: channel.name.clone(),
878+ char_bag: channel.name.chars().collect(),
879+ },
880+ ));
881+ let matches = executor.block(match_strings(
882+ &self.match_candidates,
883+ &query,
884+ true,
885+ usize::MAX,
886+ &Default::default(),
887+ executor.clone(),
888+ ));
889+ if let Some(state) = &self.channel_editing_state {
890+ if matches!(state, ChannelEditingState::Create { location: None, .. }) {
891+ self.entries.push(ListEntry::ChannelEditor { depth: 0 });
892+ }
893+ }
894+ let mut collapse_depth = None;
895+ for mat in matches {
896+ let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
897+ let depth = channel.parent_path.len();
898+
899+ if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
900+ collapse_depth = Some(depth);
901+ } else if let Some(collapsed_depth) = collapse_depth {
902+ if depth > collapsed_depth {
903+ continue;
904+ }
905+ if self.is_channel_collapsed(channel.id) {
906+ collapse_depth = Some(depth);
907+ } else {
908+ collapse_depth = None;
909+ }
910+ }
911+
912+ let has_children = channel_store
913+ .channel_at_index(mat.candidate_id + 1)
914+ .map_or(false, |next_channel| {
915+ next_channel.parent_path.ends_with(&[channel.id])
916+ });
917+
918+ match &self.channel_editing_state {
919+ Some(ChannelEditingState::Create {
920+ location: parent_id,
921+ ..
922+ }) if *parent_id == Some(channel.id) => {
923+ self.entries.push(ListEntry::Channel {
924+ channel: channel.clone(),
925+ depth,
926+ has_children: false,
927+ });
928+ self.entries
929+ .push(ListEntry::ChannelEditor { depth: depth + 1 });
930+ }
931+ Some(ChannelEditingState::Rename {
932+ location: parent_id,
933+ ..
934+ }) if parent_id == &channel.id => {
935+ self.entries.push(ListEntry::ChannelEditor { depth });
936+ }
937+ _ => {
938+ self.entries.push(ListEntry::Channel {
939+ channel: channel.clone(),
940+ depth,
941+ has_children,
942+ });
943+ }
944+ }
945+ }
946+ }
1116947
1117− // if scroll_to_top {
1118− // self.list_state.scroll_to(ListOffset::default());
1119− // } else {
1120− // // Attempt to maintain the same scroll position.
1121− // if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
1122− // let new_scroll_top = self
1123− // .entries
1124− // .iter()
1125− // .position(|entry| entry == old_top_entry)
1126− // .map(|item_ix| ListOffset {
1127− // item_ix,
1128− // offset_in_item: old_scroll_top.offset_in_item,
1129− // })
1130− // .or_else(|| {
1131− // let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
1132− // let item_ix = self
1133− // .entries
1134− // .iter()
1135− // .position(|entry| entry == entry_after_old_top)?;
1136− // Some(ListOffset {
1137− // item_ix,
1138− // offset_in_item: 0.,
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− // });
948+ // let channel_invites = channel_store.channel_invitations();
949+ // if !channel_invites.is_empty() {
950+ // self.match_candidates.clear();
951+ // self.match_candidates
952+ // .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
953+ // StringMatchCandidate {
954+ // id: ix,
955+ // string: channel.name.clone(),
956+ // char_bag: channel.name.chars().collect(),
957+ // }
958+ // }));
959+ // let matches = executor.block(match_strings(
960+ // &self.match_candidates,
961+ // &query,
962+ // true,
963+ // usize::MAX,
964+ // &Default::default(),
965+ // executor.clone(),
966+ // ));
967+ // request_entries.extend(matches.iter().map(|mat| {
968+ // ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
969+ // }));
1153970
1154− // self.list_state
1155− // .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
1156− // }
1157− // }
971+ // if !request_entries.is_empty() {
972+ // self.entries
973+ // .push(ListEntry::Header(Section::ChannelInvites));
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(&section) {
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+ // }
11581167
1159− // cx.notify();
1160− // }
1168+ cx.notify();
1169+ }
11611170
11621171 // fn render_call_participant(
11631172 // user: &User,
@@ -1455,389 +1464,6 @@ impl CollabPanel { · 291 unmodified lines
14551464 // }
14561465 // }
14571466
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−
18411467 // fn render_contact_placeholder(
18421468 // &self,
18431469 // theme: &theme::CollabPanel,
@@ -1933,335 +1559,6 @@ impl CollabPanel { · 89 unmodified lines
19331559 // .into_any()
19341560 // }
19351561
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−
22651562 // fn render_channel_notes(
22661563 // &self,
22671564 // channel_id: ChannelId,
@@ -2948,9 +2245,9 @@ impl CollabPanel { · 680 unmodified lines
29482245 // cx.focus_self();
29492246 // }
29502247
2951− // fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
2952− // self.collapsed_channels.binary_search(&channel_id).is_ok()
2953− // }
2248+ fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
2249+ self.collapsed_channels.binary_search(&channel_id).is_ok()
2250+ }
29542251
29552252 // fn leave_call(cx: &mut ViewContext<Self>) {
29562253 // ActiveCall::global(cx)
@@ -2958,19 +2255,17 @@ impl CollabPanel { · 1 unmodified line
29582255 // .detach_and_log_err(cx);
29592256 // }
29602257
2961− // fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
2962− // if let Some(workspace) = self.workspace.upgrade(cx) {
2963− // workspace.update(cx, |workspace, cx| {
2964− // workspace.toggle_modal(cx, |_, cx| {
2965− // cx.add_view(|cx| {
2966− // let mut finder = ContactFinder::new(self.user_store.clone(), cx);
2967− // finder.set_query(self.filter_editor.read(cx).text(cx), cx);
2968− // finder
2969− // })
2970− // });
2971− // });
2972− // }
2973− // }
2258+ fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
2259+ if let Some(workspace) = self.workspace.upgrade() {
2260+ workspace.update(cx, |workspace, cx| {
2261+ workspace.toggle_modal(cx, |cx| {
2262+ let mut finder = ContactFinder::new(self.user_store.clone(), cx);
2263+ finder.set_query(self.filter_editor.read(cx).text(cx), cx);
2264+ finder
2265+ });
2266+ });
2267+ }
2268+ }
29742269
29752270 // fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
29762271 // self.channel_editing_state = Some(ChannelEditingState::Create {
@@ -3247,6 +2542,802 @@ impl CollabPanel { · 270 unmodified lines
32472542 // let item = ClipboardItem::new(channel.link());
32482543 // cx.write_to_clipboard(item)
32492544 // }
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(&section);
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+ }
32503341 }
32513342
32523343 // fn render_tree_branch(
@@ -3295,13 +3386,19 @@ impl CollabPanel { · 42 unmodified lines
32953386 // }
32963387
32973388 impl Render for CollabPanel {
3298− type Element = Focusable<Self, Div<Self>>;
3389+ type Element = Focusable<Div>;
32993390
3300− fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
3391+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
33013392 div()
33023393 .key_context("CollabPanel")
33033394 .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+ })
33053402 }
33063403 }
33073404
crates/collab_ui2/src/collab_panel/contact_finder.rs+116−150
@@ -1,37 +1,34 @@
11 use client::{ContactRequestStatus, User, UserStore};
22 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,
46 };
5−use picker::{Picker, PickerDelegate, PickerEvent};
7+use picker::{Picker, PickerDelegate};
68 use std::sync::Arc;
7−use util::TryFutureExt;
8−use workspace::Modal;
9+use theme::ActiveTheme as _;
10+use ui::{h_stack, v_stack, Label};
11+use util::{ResultExt as _, TryFutureExt};
912
1013 pub fn init(cx: &mut AppContext) {
11− Picker::<ContactFinderDelegate>::init(cx);
12− cx.add_action(ContactFinder::dismiss)
14+ //Picker::<ContactFinderDelegate>::init(cx);
15+ //cx.add_action(ContactFinder::dismiss)
1316 }
1417
1518 pub struct ContactFinder {
16− picker: ViewHandle<Picker<ContactFinderDelegate>>,
19+ picker: View<Picker<ContactFinderDelegate>>,
1720 has_focus: bool,
1821 }
1922
2023 impl ContactFinder {
21− pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
22− let picker = cx.add_view(|cx| {
23− Picker::new(
24− ContactFinderDelegate {
25− user_store,
26− potential_contacts: Arc::from([]),
27− selected_index: 0,
28− },
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();
24+ pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
25+ let delegate = ContactFinderDelegate {
26+ parent: cx.view().downgrade(),
27+ user_store,
28+ potential_contacts: Arc::from([]),
29+ selected_index: 0,
30+ };
31+ let picker = cx.build_view(|cx| Picker::new(delegate, cx));
3532
3633 Self {
3734 picker,
@@ -41,105 +38,72 @@ impl ContactFinder { · 3 unmodified lines
4138
4239 pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
4340 self.picker.update(cx, |picker, cx| {
44− picker.set_query(query, cx);
41+ // todo!()
42+ // picker.set_query(query, cx);
4543 });
4644 }
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;
5545 }
5646
57−impl View for ContactFinder {
58− fn ui_name() -> &'static str {
59− "ContactFinder"
60− }
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()
47+impl Render for ContactFinder {
48+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
49+ fn render_mode_button(text: &'static str) -> AnyElement {
50+ Label::new(text).into_any_element()
7651 }
7752
78− Flex::column()
79− .with_child(
80− Flex::column()
81− .with_child(
82− Label::new("Contacts", theme.title.text.clone())
83− .contained()
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),
53+ v_stack()
54+ .child(
55+ v_stack()
56+ .child(Label::new("Contacts"))
57+ .child(h_stack().children([render_mode_button("Invite new contacts")]))
58+ .bg(cx.theme().colors().element_background),
9959 )
100− .constrained()
101− .with_max_height(theme.max_height)
102− .with_max_width(theme.max_width)
103− .contained()
104− .with_style(theme.modal)
105− .into_any()
60+ .child(self.picker.clone())
61+ .w_96()
10662 }
10763
108− fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
109− self.has_focus = true;
110− if cx.is_self_focused() {
111− cx.focus(&self.picker)
112− }
113− }
64+ // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
65+ // self.has_focus = true;
66+ // if cx.is_self_focused() {
67+ // cx.focus(&self.picker)
68+ // }
69+ // }
11470
115− fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
116− self.has_focus = false;
117− }
71+ // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
72+ // self.has_focus = false;
73+ // }
74+
75+ type Element = Div;
11876 }
11977
120−impl Modal for ContactFinder {
121− fn has_focus(&self) -> bool {
122− self.has_focus
123− }
78+// impl Modal for ContactFinder {
79+// fn has_focus(&self) -> bool {
80+// self.has_focus
81+// }
12482
125− fn dismiss_on_event(event: &Self::Event) -> bool {
126− match event {
127− PickerEvent::Dismiss => true,
128− }
129− }
130−}
83+// fn dismiss_on_event(event: &Self::Event) -> bool {
84+// match event {
85+// PickerEvent::Dismiss => true,
86+// }
87+// }
88+// }
13189
13290 pub struct ContactFinderDelegate {
91+ parent: WeakView<ContactFinder>,
13392 potential_contacts: Arc<[Arc<User>]>,
134− user_store: ModelHandle<UserStore>,
93+ user_store: Model<UserStore>,
13594 selected_index: usize,
13695 }
13796
138−impl PickerDelegate for ContactFinderDelegate {
139− fn placeholder_text(&self) -> Arc<str> {
140− "Search collaborator by username...".into()
97+impl EventEmitter<DismissEvent> for ContactFinder {}
98+
99+impl FocusableView for ContactFinder {
100+ fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
101+ self.picker.focus_handle(cx)
141102 }
103+}
142104
105+impl PickerDelegate for ContactFinderDelegate {
106+ type ListItem = Div;
143107 fn match_count(&self) -> usize {
144108 self.potential_contacts.len()
145109 }
@@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate { · 6 unmodified lines
152116 self.selected_index = ix;
153117 }
154118
119+ fn placeholder_text(&self) -> Arc<str> {
120+ "Search collaborator by username...".into()
121+ }
122+
155123 fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
156124 let search_users = self
157125 .user_store
@@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate { · 3 unmodified lines
161129 async {
162130 let potential_contacts = search_users.await?;
163131 picker.update(&mut cx, |picker, cx| {
164− picker.delegate_mut().potential_contacts = potential_contacts.into();
132+ picker.delegate.potential_contacts = potential_contacts.into();
165133 cx.notify();
166134 })?;
167135 anyhow::Ok(())
@@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate { · 23 unmodified lines
191159 }
192160
193161 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();
195166 }
196167
197168 fn render_match(
198169 &self,
199170 ix: usize,
200− mouse_state: &mut MouseState,
201171 selected: bool,
202− cx: &gpui::AppContext,
203− ) -> AnyElement<Picker<Self>> {
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;
172+ cx: &mut ViewContext<Picker<Self>>,
173+ ) -> Option<Self::ListItem> {
207174 let user = &self.potential_contacts[ix];
208175 let request_status = self.user_store.read(cx).contact_request_status(user);
209176
@@ -214,48 +181,47 @@ impl PickerDelegate for ContactFinderDelegate { · 4 unmodified lines
214181 ContactRequestStatus::RequestSent => Some("icons/x.svg"),
215182 ContactRequestStatus::RequestAccepted => None,
216183 };
217− let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
218− &theme.disabled_contact_button
219− } else {
220− &theme.contact_button
221− };
222− let style = tabbed_modal
223− .picker
224− .item
225− .in_state(selected)
226− .style_for(mouse_state);
227− Flex::row()
228− .with_children(user.avatar.clone().map(|avatar| {
229− Image::from_data(avatar)
230− .with_style(theme.contact_avatar)
231− .aligned()
232− .left()
233− }))
234− .with_child(
235− Label::new(user.github_login.clone(), style.label.clone())
236− .contained()
237− .with_style(theme.contact_username)
238− .aligned()
239− .left(),
240− )
241− .with_children(icon_path.map(|icon_path| {
242− Svg::new(icon_path)
243− .with_color(button_style.color)
244− .constrained()
245− .with_width(button_style.icon_width)
246− .aligned()
247− .contained()
248− .with_style(button_style.container)
249− .constrained()
250− .with_width(button_style.button_width)
251− .with_height(button_style.button_width)
252− .aligned()
253− .flex_float()
254− }))
255− .contained()
256− .with_style(style.container)
257− .constrained()
258− .with_height(tabbed_modal.row_height)
259− .into_any()
184+ dbg!(icon_path);
185+ Some(
186+ div()
187+ .flex_1()
188+ .justify_between()
189+ .children(user.avatar.clone().map(|avatar| img().data(avatar)))
190+ .child(Label::new(user.github_login.clone()))
191+ .children(icon_path.map(|icon_path| svg().path(icon_path))),
192+ )
193+ // Flex::row()
194+ // .with_children(user.avatar.clone().map(|avatar| {
195+ // Image::from_data(avatar)
196+ // .with_style(theme.contact_avatar)
197+ // .aligned()
198+ // .left()
199+ // }))
200+ // .with_child(
201+ // Label::new(user.github_login.clone(), style.label.clone())
202+ // .contained()
203+ // .with_style(theme.contact_username)
204+ // .aligned()
205+ // .left(),
206+ // )
207+ // .with_children(icon_path.map(|icon_path| {
208+ // Svg::new(icon_path)
209+ // .with_color(button_style.color)
210+ // .constrained()
211+ // .with_width(button_style.icon_width)
212+ // .aligned()
213+ // .contained()
214+ // .with_style(button_style.container)
215+ // .constrained()
216+ // .with_width(button_style.button_width)
217+ // .with_height(button_style.button_width)
218+ // .aligned()
219+ // .flex_float()
220+ // }))
221+ // .contained()
222+ // .with_style(style.container)
223+ // .constrained()
224+ // .with_height(tabbed_modal.row_height)
225+ // .into_any()
260226 }
261227 }
crates/collab_ui2/src/collab_titlebar_item.rs+163−14
@@ -31,14 +31,17 @@ use std::sync::Arc; · 30 unmodified lines
3131 use call::ActiveCall;
3232 use client::{Client, UserStore};
3333 use gpui::{
34− div, px, rems, AppContext, Component, Div, InteractiveComponent, Model, ParentComponent,
35− Render, Stateful, StatefulInteractiveComponent, Styled, Subscription, ViewContext,
36− VisualContext, WeakView, WindowBounds,
34+ div, px, rems, AppContext, Div, Element, InteractiveElement, IntoElement, Model, MouseButton,
35+ ParentElement, Render, RenderOnce, Stateful, StatefulInteractiveElement, Styled, Subscription,
36+ ViewContext, VisualContext, WeakView, WindowBounds,
3737 };
3838 use project::Project;
3939 use theme::ActiveTheme;
40−use ui::{h_stack, Button, ButtonVariant, KeyBinding, Label, TextColor, Tooltip};
41−use workspace::Workspace;
40+use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
41+use util::ResultExt;
42+use workspace::{notifications::NotifyResultExt, Workspace};
43+
44+use crate::face_pile::FacePile;
4245
4346 // const MAX_PROJECT_NAME_LENGTH: usize = 40;
4447 // const MAX_BRANCH_NAME_LENGTH: usize = 40;
@@ -82,9 +85,44 @@ pub struct CollabTitlebarItem { · 37 unmodified lines
8285 }
8386
8487 impl Render for CollabTitlebarItem {
85− type Element = Stateful<Self, Div<Self>>;
88+ type Element = Stateful<Div>;
8689
8790 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();
88126 h_stack()
89127 .id("titlebar")
90128 .justify_between()
@@ -100,7 +138,7 @@ impl Render for CollabTitlebarItem { · 9 unmodified lines
100138 |s| s.pl(px(68.)),
101139 )
102140 .bg(cx.theme().colors().title_bar_background)
103− .on_click(|_, event, cx| {
141+ .on_click(|event, cx| {
104142 if event.up.click_count == 2 {
105143 cx.zoom_window();
106144 }
@@ -111,31 +149,37 @@ impl Render for CollabTitlebarItem { · 4 unmodified lines
111149 // TODO - Add player menu
112150 .child(
113151 div()
152+ .border()
153+ .border_color(gpui::red())
114154 .id("project_owner_indicator")
115155 .child(
116156 Button::new("player")
117157 .variant(ButtonVariant::Ghost)
118− .color(Some(TextColor::Player(0))),
158+ .color(Some(Color::Player(0))),
119159 )
120− .tooltip(move |_, cx| Tooltip::text("Toggle following", cx)),
160+ .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
121161 )
122162 // TODO - Add project menu
123163 .child(
124164 div()
165+ .border()
166+ .border_color(gpui::red())
125167 .id("titlebar_project_menu_button")
126168 .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)),
128170 )
129171 // TODO - Add git menu
130172 .child(
131173 div()
174+ .border()
175+ .border_color(gpui::red())
132176 .id("titlebar_git_menu_button")
133177 .child(
134178 Button::new("branch_name")
135179 .variant(ButtonVariant::Ghost)
136− .color(Some(TextColor::Muted)),
180+ .color(Some(Color::Muted)),
137181 )
138− .tooltip(move |_, cx| {
182+ .tooltip(move |cx| {
139183 cx.build_view(|_| {
140184 Tooltip::new("Recent Branches")
141185 .key_binding(KeyBinding::new(gpui::KeyBinding::new(
@@ -149,8 +193,113 @@ impl Render for CollabTitlebarItem { · 7 unmodified lines
149193 .into()
150194 }),
151195 ),
152− ) // self.titlebar_item
153− .child(h_stack().child(Label::new("Right side titlebar item")))
196+ )
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+ })
154303 }
155304 }
156305
crates/collab_ui2/src/collab_ui.rs+36−28
@@ -7,11 +7,14 @@ pub mod notification_panel; · 6 unmodified lines
77 pub mod notifications;
88 mod panel_settings;
99
10−use std::sync::Arc;
10+use std::{rc::Rc, sync::Arc};
1111
1212 pub use collab_panel::CollabPanel;
1313 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+};
1518 pub use panel_settings::{
1619 ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
1720 };
@@ -23,7 +26,7 @@ use workspace::AppState; · 5 unmodified lines
2326 // [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
2427 // );
2528
26−pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {
29+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
2730 CollaborationPanelSettings::register(cx);
2831 ChatPanelSettings::register(cx);
2932 NotificationPanelSettings::register(cx);
@@ -32,7 +35,7 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) { · 2 unmodified lines
3235 collab_titlebar_item::init(cx);
3336 collab_panel::init(cx);
3437 // chat_panel::init(cx);
35− // notifications::init(&app_state, cx);
38+ notifications::init(&app_state, cx);
3639
3740 // cx.add_global_action(toggle_screen_sharing);
3841 // cx.add_global_action(toggle_mute);
@@ -95,31 +98,36 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) { · 56 unmodified lines
9598 // }
9699 // }
97100
98−// fn notification_window_options(
99−// screen: Rc<dyn Screen>,
100−// window_size: Vector2F,
101−// ) -> WindowOptions<'static> {
102−// const NOTIFICATION_PADDING: f32 = 16.;
101+fn notification_window_options(
102+ screen: Rc<dyn PlatformDisplay>,
103+ window_size: Size<Pixels>,
104+) -> WindowOptions {
105+ let notification_margin_width = GlobalPixels::from(16.);
106+ let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.);
103107
104−// let screen_bounds = screen.content_bounds();
105−// WindowOptions {
106−// bounds: WindowBounds::Fixed(RectF::new(
107−// screen_bounds.upper_right()
108−// + vec2f(
109−// -NOTIFICATION_PADDING - window_size.x(),
110−// NOTIFICATION_PADDING,
111−// ),
112−// window_size,
113−// )),
114−// titlebar: None,
115−// center: false,
116−// focus: false,
117−// show: true,
118−// kind: WindowKind::PopUp,
119−// is_movable: false,
120−// screen: Some(screen),
121−// }
122−// }
108+ let screen_bounds = screen.bounds();
109+ let size: Size<GlobalPixels> = window_size.into();
110+
111+ // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument.
112+ let bounds = gpui::Bounds::<GlobalPixels> {
113+ origin: screen_bounds.upper_right()
114+ - point(
115+ size.width + notification_margin_width,
116+ notification_margin_height,
117+ ),
118+ size: window_size.into(),
119+ };
120+ WindowOptions {
121+ bounds: WindowBounds::Fixed(bounds),
122+ titlebar: None,
123+ center: false,
124+ focus: false,
125+ show: true,
126+ kind: WindowKind::PopUp,
127+ is_movable: false,
128+ display_id: Some(screen.id()),
129+ }
130+}
123131
124132 // fn render_avatar<T: 'static>(
125133 // avatar: Option<Arc<ImageData>>,
crates/collab_ui2/src/face_pile.rs+36−69
@@ -1,54 +1,48 @@
1−// use std::ops::Range;
1+use gpui::{
2+ div, AnyElement, Div, IntoElement as _, ParentElement as _, RenderOnce, Styled, WindowContext,
3+};
24
3−// use gpui::{
4−// geometry::{
5−// rect::RectF,
6−// vector::{vec2f, Vector2F},
7−// },
8−// json::ToJson,
9−// serde_json::{self, json},
10−// AnyElement, Axis, Element, View, ViewContext,
11−// };
5+#[derive(Default)]
6+pub(crate) struct FacePile {
7+ faces: Vec<AnyElement>,
8+}
129
13−// pub(crate) struct FacePile<V: View> {
14−// overlap: f32,
15−// faces: Vec<AnyElement<V>>,
16−// }
10+impl RenderOnce for FacePile {
11+ type Rendered = Div;
1712
18−// impl<V: View> FacePile<V> {
19−// pub fn new(overlap: f32) -> Self {
20−// Self {
21−// overlap,
22−// faces: Vec::new(),
23−// }
24−// }
25−// }
13+ fn render(self, _: &mut WindowContext) -> Self::Rendered {
14+ let player_count = self.faces.len();
15+ let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
16+ let isnt_last = ix < player_count - 1;
2617
27−// impl<V: View> Element<V> for FacePile<V> {
28−// type LayoutState = ();
29−// type PaintState = ();
18+ div().when(isnt_last, |div| div.neg_mr_1()).child(player)
19+ });
20+ div().p_1().flex().items_center().children(player_list)
21+ }
22+}
3023
24+// impl Element for FacePile {
25+// type State = ();
3126 // fn layout(
3227 // &mut self,
33−// constraint: gpui::SizeConstraint,
34−// view: &mut V,
35−// cx: &mut ViewContext<V>,
36−// ) -> (Vector2F, Self::LayoutState) {
37−// debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
38−
28+// state: Option<Self::State>,
29+// cx: &mut WindowContext,
30+// ) -> (LayoutId, Self::State) {
3931 // let mut width = 0.;
4032 // let mut max_height = 0.;
33+// let mut faces = Vec::with_capacity(self.faces.len());
4134 // for face in &mut self.faces {
42−// let layout = face.layout(constraint, view, cx);
35+// let layout = face.layout(cx);
4336 // width += layout.x();
4437 // max_height = f32::max(max_height, layout.y());
38+// faces.push(layout);
4539 // }
4640 // width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
47−
48−// (
49−// Vector2F::new(width, max_height.clamp(1., constraint.max.y())),
50−// (),
51−// )
41+// (cx.request_layout(&Style::default(), faces), ())
42+// // (
43+// // Vector2F::new(width, max_height.clamp(1., constraint.max.y())),
44+// // (),
45+// // ))
5246 // }
5347
5448 // fn paint(
@@ -77,37 +71,10 @@ · 22 unmodified lines
7771
7872 // ()
7973 // }
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−// }
10774 // }
10875
109−// impl<V: View> Extend<AnyElement<V>> for FacePile<V> {
110−// fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
111−// self.faces.extend(children);
112−// }
113−// }
76+impl Extend<AnyElement> for FacePile {
77+ fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) {
78+ self.faces.extend(children);
79+ }
80+}
crates/collab_ui2/src/notifications.rs+8−8
@@ -1,11 +1,11 @@
1−// use gpui::AppContext;
2−// use std::sync::Arc;
3−// use workspace::AppState;
1+use gpui::AppContext;
2+use std::sync::Arc;
3+use workspace::AppState;
44
5−// pub mod incoming_call_notification;
5+pub mod incoming_call_notification;
66 // pub mod project_shared_notification;
77
8−// pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
9−// incoming_call_notification::init(app_state, cx);
10−// project_shared_notification::init(app_state, cx);
11−// }
8+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
9+ incoming_call_notification::init(app_state, cx);
10+ //project_shared_notification::init(app_state, cx);
11+}
crates/collab_ui2/src/notifications/incoming_call_notification.rs+198−150
@@ -1,14 +1,12 @@
11 use crate::notification_window_options;
22 use call::{ActiveCall, IncomingCall};
3−use client::proto;
43 use futures::StreamExt;
54 use gpui::{
6− elements::*,
7− geometry::vector::vec2f,
8− platform::{CursorStyle, MouseButton},
9− AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
5+ div, green, px, red, AppContext, Div, Element, ParentElement, Render, RenderOnce,
6+ StatefulInteractiveElement, Styled, ViewContext, VisualContext as _, WindowHandle,
107 };
118 use std::sync::{Arc, Weak};
9+use ui::{h_stack, v_stack, Avatar, Button, Label};
1210 use util::ResultExt;
1311 use workspace::AppState;
1412
@@ -19,23 +17,44 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { · 4 unmodified lines
1917 let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
2018 while let Some(incoming_call) = incoming_call.next().await {
2119 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();
2326 }
2427
2528 if let Some(incoming_call) = incoming_call {
26− let window_size = cx.read(|cx| {
27− let theme = &theme::current(cx).incoming_call_notification;
28− vec2f(theme.window_width, theme.window_height)
29− });
29+ let unique_screens = cx.update(|cx| cx.displays()).unwrap();
30+ let window_size = gpui::Size {
31+ width: px(380.),
32+ height: px(64.),
33+ };
3034
31− for screen in cx.platform().screens() {
35+ for window in unique_screens {
36+ let options = notification_window_options(window, window_size);
3237 let window = cx
33− .add_window(notification_window_options(screen, window_size), |_| {
34− IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
35− });
36−
38+ .open_window(options, |cx| {
39+ cx.build_view(|_| {
40+ IncomingCallNotification::new(
41+ incoming_call.clone(),
42+ app_state.clone(),
43+ )
44+ })
45+ })
46+ .unwrap();
3747 notification_windows.push(window);
3848 }
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+ // }
3958 }
4059 }
4160 })
@@ -47,167 +66,196 @@ struct RespondToCall { · 5 unmodified lines
4766 accept: bool,
4867 }
4968
50−pub struct IncomingCallNotification {
69+struct IncomingCallNotificationState {
5170 call: IncomingCall,
5271 app_state: Weak<AppState>,
5372 }
5473
55−impl IncomingCallNotification {
74+pub struct IncomingCallNotification {
75+ state: Arc<IncomingCallNotificationState>,
76+}
77+impl IncomingCallNotificationState {
5678 pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
5779 Self { call, app_state }
5880 }
5981
60− fn respond(&mut self, accept: bool, cx: &mut ViewContext<Self>) {
82+ fn respond(&self, accept: bool, cx: &mut AppContext) {
6183 let active_call = ActiveCall::global(cx);
6284 if accept {
6385 let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
64− let caller_user_id = self.call.calling_user.id;
6586 let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
6687 let app_state = self.app_state.clone();
67− cx.app_context()
68− .spawn(|mut cx| async move {
69− join.await?;
70− if let Some(project_id) = initial_project_id {
71− cx.update(|cx| {
72− if let Some(app_state) = app_state.upgrade() {
73− workspace::join_remote_project(
74− project_id,
75− caller_user_id,
76− app_state,
77− cx,
78− )
79− .detach_and_log_err(cx);
80− }
81− });
82− }
83− anyhow::Ok(())
84− })
85− .detach_and_log_err(cx);
88+ let cx: &mut AppContext = cx;
89+ cx.spawn(|cx| async move {
90+ join.await?;
91+ if let Some(_project_id) = initial_project_id {
92+ cx.update(|_cx| {
93+ if let Some(_app_state) = app_state.upgrade() {
94+ // workspace::join_remote_project(
95+ // project_id,
96+ // caller_user_id,
97+ // app_state,
98+ // cx,
99+ // )
100+ // .detach_and_log_err(cx);
101+ }
102+ })
103+ .log_err();
104+ }
105+ anyhow::Ok(())
106+ })
107+ .detach_and_log_err(cx);
86108 } else {
87109 active_call.update(cx, |active_call, cx| {
88110 active_call.decline_incoming(cx).log_err();
89111 });
90112 }
91113 }
114+}
92115
93− fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
94− let theme = &theme::current(cx).incoming_call_notification;
95− let default_project = proto::ParticipantProject::default();
96− let initial_project = self
97− .call
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()
116+impl IncomingCallNotification {
117+ pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
118+ Self {
119+ state: Arc::new(IncomingCallNotificationState::new(call, app_state)),
120+ }
152121 }
153−
154− fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
155− enum Accept {}
156− enum Decline {}
157−
158− let theme = theme::current(cx);
159− Flex::column()
160− .with_child(
161− MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
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),
122+ fn render_caller(&self, cx: &mut ViewContext<Self>) -> impl Element {
123+ h_stack()
124+ .children(
125+ self.state
126+ .call
127+ .calling_user
128+ .avatar
129+ .as_ref()
130+ .map(|avatar| Avatar::data(avatar.clone())),
173131 )
174− .with_child(
175− MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
176− let theme = &theme.incoming_call_notification;
177− Label::new("Decline", theme.decline_button.text.clone())
178− .aligned()
179− .contained()
180− .with_style(theme.decline_button.container)
181− })
182− .with_cursor_style(CursorStyle::PointingHand)
183− .on_click(MouseButton::Left, |_, this, cx| {
184− this.respond(false, cx);
185− })
186− .flex(1., true),
132+ .child(
133+ v_stack()
134+ .child(Label::new(format!(
135+ "{} is sharing a project in Zed",
136+ self.state.call.calling_user.github_login
137+ )))
138+ .child(self.render_buttons(cx)),
187139 )
188− .constrained()
189− .with_width(theme.incoming_call_notification.button_width)
190− .into_any()
140+ // let theme = &theme::current(cx).incoming_call_notification;
141+ // let default_project = proto::ParticipantProject::default();
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()
191198 }
192−}
193199
194−impl Entity for IncomingCallNotification {
195− type Event = ();
196−}
200+ fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
201+ h_stack()
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+ }))
197210
198−impl View for IncomingCallNotification {
199− fn ui_name() -> &'static str {
200− "IncomingCallNotification"
201− }
211+ // enum Accept {}
212+ // enum Decline {}
202213
203− fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
204− let background = theme::current(cx).incoming_call_notification.background;
205− Flex::row()
206− .with_child(self.render_caller(cx))
207− .with_child(self.render_buttons(cx))
208− .contained()
209− .with_background_color(background)
210− .expanded()
211− .into_any()
214+ // let theme = theme::current(cx);
215+ // Flex::column()
216+ // .with_child(
217+ // MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
218+ // let theme = &theme.incoming_call_notification;
219+ // Label::new("Accept", theme.accept_button.text.clone())
220+ // .aligned()
221+ // .contained()
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()
212260 }
213261 }
crates/command_palette2/src/command_palette.rs+14−23
@@ -1,17 +1,15 @@
11 use collections::{CommandPaletteFilter, HashMap};
22 use fuzzy::{StringMatch, StringMatchCandidate};
33 use gpui::{
4− actions, div, prelude::*, Action, AppContext, Component, Div, EventEmitter, FocusHandle,
5− FocusableView, Keystroke, Manager, ParentComponent, Render, Styled, View, ViewContext,
6− VisualContext, WeakView,
4+ actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
5+ Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
76 };
87 use picker::{Picker, PickerDelegate};
98 use std::{
109 cmp::{self, Reverse},
1110 sync::Arc,
1211 };
13−use theme::ActiveTheme;
14−use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
12+use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
1513 use util::{
1614 channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
1715 ResultExt,
@@ -69,7 +67,7 @@ impl CommandPalette { · 51 unmodified lines
6967 }
7068 }
7169
72−impl EventEmitter<Manager> for CommandPalette {}
70+impl EventEmitter<DismissEvent> for CommandPalette {}
7371
7472 impl FocusableView for CommandPalette {
7573 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
@@ -78,7 +76,7 @@ impl FocusableView for CommandPalette { · 2 unmodified lines
7876 }
7977
8078 impl Render for CommandPalette {
81− type Element = Div<Self>;
79+ type Element = Div;
8280
8381 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
8482 v_stack().w_96().child(self.picker.clone())
@@ -141,7 +139,7 @@ impl CommandPaletteDelegate { · 56 unmodified lines
141139 }
142140
143141 impl PickerDelegate for CommandPaletteDelegate {
144− type ListItem = Div<Picker<Self>>;
142+ type ListItem = ListItem;
145143
146144 fn placeholder_text(&self) -> Arc<str> {
147145 "Execute a command...".into()
@@ -269,7 +267,7 @@ impl PickerDelegate for CommandPaletteDelegate { · 121 unmodified lines
269267
270268 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
271269 self.command_palette
272− .update(cx, |_, cx| cx.emit(Manager::Dismiss))
270+ .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
273271 .log_err();
274272 }
275273
@@ -294,24 +292,16 @@ impl PickerDelegate for CommandPaletteDelegate { · 18 unmodified lines
294292 ix: usize,
295293 selected: bool,
296294 cx: &mut ViewContext<Picker<Self>>,
297− ) -> Self::ListItem {
298− let colors = cx.theme().colors();
295+ ) -> Option<Self::ListItem> {
299296 let Some(r#match) = self.matches.get(ix) else {
300− return div();
297+ return None;
301298 };
302299 let Some(command) = self.commands.get(r#match.candidate_id) else {
303− return div();
300+ return None;
304301 };
305302
306− div()
307− .px_1()
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(
303+ Some(
304+ ListItem::new(ix).selected(selected).child(
315305 h_stack()
316306 .justify_between()
317307 .child(HighlightedLabel::new(
@@ -319,7 +309,8 @@ impl PickerDelegate for CommandPaletteDelegate { · 1 unmodified line
319309 r#match.positions.clone(),
320310 ))
321311 .children(KeyBinding::for_action(&*command.action, cx)),
322− )
312+ ),
313+ )
323314 }
324315 }
325316
crates/diagnostics2/src/diagnostics.rs+19−18
@@ -13,10 +13,10 @@ use editor::{ · 12 unmodified lines
1313 };
1414 use futures::future::try_join_all;
1515 use gpui::{
16− actions, div, AnyElement, AnyView, AppContext, Component, Context, Div, EventEmitter,
17− FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, InteractiveComponent,
18− Model, ParentComponent, Render, SharedString, Styled, Subscription, Task, View, ViewContext,
19− VisualContext, WeakView,
16+ actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusEvent,
17+ FocusHandle, Focusable, FocusableElement, FocusableView, InteractiveElement, IntoElement,
18+ Model, ParentElement, Render, SharedString, Styled, Subscription, Task, View, ViewContext,
19+ VisualContext, WeakView, WindowContext,
2020 };
2121 use language::{
2222 Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -36,7 +36,7 @@ use std::{ · 13 unmodified lines
3636 };
3737 use theme::ActiveTheme;
3838 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};
4040 use util::TryFutureExt;
4141 use workspace::{
4242 item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -91,7 +91,7 @@ struct DiagnosticGroupState { · 48 unmodified lines
9191 impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
9292
9393 impl Render for ProjectDiagnosticsEditor {
94− type Element = Focusable<Self, Div<Self>>;
94+ type Element = Focusable<Div>;
9595
9696 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
9797 let child = if self.path_states.is_empty() {
@@ -109,8 +109,8 @@ impl Render for ProjectDiagnosticsEditor { · 11 unmodified lines
109109 div()
110110 .track_focus(&self.focus_handle)
111111 .size_full()
112− .on_focus_in(Self::focus_in)
113− .on_action(Self::toggle_warnings)
112+ .on_focus_in(cx.listener(Self::focus_in))
113+ .on_action(cx.listener(Self::toggle_warnings))
114114 .child(child)
115115 }
116116 }
@@ -662,7 +662,7 @@ impl Item for ProjectDiagnosticsEditor { · 545 unmodified lines
662662 Some("Project Diagnostics".into())
663663 }
664664
665− fn tab_content<T: 'static>(&self, _detail: Option<usize>, _: &AppContext) -> AnyElement<T> {
665+ fn tab_content(&self, _detail: Option<usize>, _: &WindowContext) -> AnyElement {
666666 render_summary(&self.summary)
667667 }
668668
@@ -742,7 +742,7 @@ impl Item for ProjectDiagnosticsEditor { · 73 unmodified lines
742742 }
743743
744744 fn breadcrumb_location(&self) -> ToolbarItemLocation {
745− ToolbarItemLocation::PrimaryLeft { flex: None }
745+ ToolbarItemLocation::PrimaryLeft
746746 }
747747
748748 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
778778 .bg(gpui::red())
779779 .map(|stack| {
780780 let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
781− IconElement::new(Icon::XCircle).color(TextColor::Error)
781+ IconElement::new(Icon::XCircle).color(Color::Error)
782782 } else {
783− IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning)
783+ IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)
784784 };
785785
786786 stack.child(div().pl_8().child(icon))
787787 })
788788 .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))
790790 })
791791 .child(HighlightedLabel::new(message.clone(), highlights.clone()))
792792 .when_some(diagnostic.code.as_ref(), |stack, code| {
793793 stack.child(Label::new(code.clone()))
794794 })
795− .render()
795+ .into_any_element()
796796 })
797797 }
798798
799−pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElement<T> {
799+pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement {
800800 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()
802803 } else {
803804 h_stack()
804805 .bg(gpui::red())
@@ -806,7 +807,7 @@ pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElem · 1 unmodified line
806807 .child(Label::new(summary.error_count.to_string()))
807808 .child(IconElement::new(Icon::ExclamationTriangle))
808809 .child(Label::new(summary.warning_count.to_string()))
809− .render()
810+ .into_any_element()
810811 }
811812 }
812813
@@ -1549,7 +1550,7 @@ mod tests { · 736 unmodified lines
15491550 block_id: ix,
15501551 editor_style: &editor::EditorStyle::default(),
15511552 })
1552− .element_id()?
1553+ .inner_id()?
15531554 .try_into()
15541555 .ok()?,
15551556
crates/diagnostics2/src/items.rs+14−14
@@ -1,13 +1,13 @@
11 use collections::HashSet;
22 use editor::{Editor, GoToDiagnostic};
33 use gpui::{
4− rems, Div, EventEmitter, InteractiveComponent, ParentComponent, Render, Stateful,
5− StatefulInteractiveComponent, Styled, Subscription, View, ViewContext, WeakView,
4+ rems, Div, EventEmitter, InteractiveElement, ParentElement, Render, Stateful,
5+ StatefulInteractiveElement, Styled, Subscription, View, ViewContext, WeakView,
66 };
77 use language::Diagnostic;
88 use lsp::LanguageServerId;
99 use theme::ActiveTheme;
10−use ui::{h_stack, Icon, IconElement, Label, TextColor, Tooltip};
10+use ui::{h_stack, Color, Icon, IconElement, Label, Tooltip};
1111 use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
1212
1313 use crate::ProjectDiagnosticsEditor;
@@ -22,30 +22,30 @@ pub struct DiagnosticIndicator { · 8 unmodified lines
2222 }
2323
2424 impl Render for DiagnosticIndicator {
25− type Element = Stateful<Self, Div<Self>>;
25+ type Element = Stateful<Div>;
2626
2727 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
2828 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)),
3030 (0, warning_count) => h_stack()
3131 .gap_1()
32− .child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning))
32+ .child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
3333 .child(Label::new(warning_count.to_string())),
3434 (error_count, 0) => h_stack()
3535 .gap_1()
36− .child(IconElement::new(Icon::XCircle).color(TextColor::Error))
36+ .child(IconElement::new(Icon::XCircle).color(Color::Error))
3737 .child(Label::new(error_count.to_string())),
3838 (error_count, warning_count) => h_stack()
3939 .gap_1()
40− .child(IconElement::new(Icon::XCircle).color(TextColor::Error))
40+ .child(IconElement::new(Icon::XCircle).color(Color::Error))
4141 .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))
4343 .child(Label::new(warning_count.to_string())),
4444 };
4545
4646 h_stack()
47− .id(cx.entity_id())
48− .on_action(Self::go_to_next_diagnostic)
47+ .id("diagnostic-indicator")
48+ .on_action(cx.listener(Self::go_to_next_diagnostic))
4949 .rounded_md()
5050 .flex_none()
5151 .h(rems(1.375))
@@ -54,14 +54,14 @@ impl Render for DiagnosticIndicator { · 2 unmodified lines
5454 .bg(cx.theme().colors().ghost_element_background)
5555 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
5656 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
57− .tooltip(|_, cx| Tooltip::text("Project Diagnostics", cx))
58− .on_click(|this, _, cx| {
57+ .tooltip(|cx| Tooltip::text("Project Diagnostics", cx))
58+ .on_click(cx.listener(|this, _, cx| {
5959 if let Some(workspace) = this.workspace.upgrade() {
6060 workspace.update(cx, |workspace, cx| {
6161 ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
6262 })
6363 }
64− })
64+ }))
6565 .child(diagnostic_indicator)
6666 }
6767 }
crates/diagnostics2/src/toolbar_controls.rs+6−6
@@ -1,5 +1,5 @@
11 use crate::ProjectDiagnosticsEditor;
2−use gpui::{div, Div, EventEmitter, ParentComponent, Render, ViewContext, WeakView};
2+use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
33 use ui::{Icon, IconButton, Tooltip};
44 use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
55
@@ -8,7 +8,7 @@ pub struct ToolbarControls { · 2 unmodified lines
88 }
99
1010 impl Render for ToolbarControls {
11− type Element = Div<Self>;
11+ type Element = Div;
1212
1313 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
1414 let include_warnings = self
@@ -26,14 +26,14 @@ impl Render for ToolbarControls { · 11 unmodified lines
2626
2727 div().child(
2828 IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
29− .tooltip(move |_, cx| Tooltip::text(tooltip, cx))
30− .on_click(|this: &mut Self, cx| {
29+ .tooltip(move |cx| Tooltip::text(tooltip, cx))
30+ .on_click(cx.listener(|this, _, cx| {
3131 if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
3232 editor.update(cx, |editor, cx| {
3333 editor.toggle_warnings(&Default::default(), cx);
3434 });
3535 }
36− }),
36+ })),
3737 )
3838 }
3939 }
@@ -49,7 +49,7 @@ impl ToolbarItemView for ToolbarControls { · 9 unmodified lines
4949 if let Some(pane_item) = active_pane_item.as_ref() {
5050 if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
5151 self.editor = Some(editor.downgrade());
52− ToolbarItemLocation::PrimaryRight { flex: None }
52+ ToolbarItemLocation::PrimaryRight
5353 } else {
5454 ToolbarItemLocation::Hidden
5555 }
crates/editor/src/editor.rs+45−22
@@ -1001,17 +1001,18 @@ impl CompletionsMenu { · 1000 unmodified lines
10011001
10021002 fn pre_resolve_completion_documentation(
10031003 &self,
1004− project: Option<ModelHandle<Project>>,
1004+ editor: &Editor,
10051005 cx: &mut ViewContext<Editor>,
1006− ) {
1006+ ) -> Option<Task<()>> {
10071007 let settings = settings::get::<EditorSettings>(cx);
10081008 if !settings.show_completion_documentation {
1009− return;
1009+ return None;
10101010 }
10111011
1012− let Some(project) = project else {
1013− return;
1012+ let Some(project) = editor.project.clone() else {
1013+ return None;
10141014 };
1015+
10151016 let client = project.read(cx).client();
10161017 let language_registry = project.read(cx).languages().clone();
10171018
@@ -1021,7 +1022,7 @@ impl CompletionsMenu { · 3 unmodified lines
10211022 let completions = self.completions.clone();
10221023 let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
10231024
1024− cx.spawn(move |this, mut cx| async move {
1025+ Some(cx.spawn(move |this, mut cx| async move {
10251026 if is_remote {
10261027 let Some(project_id) = project_id else {
10271028 log::error!("Remote project without remote_id");
@@ -1083,8 +1084,7 @@ impl CompletionsMenu { · 55 unmodified lines
10831084 _ = this.update(&mut cx, |_, cx| cx.notify());
10841085 }
10851086 }
1086− })
1087− .detach();
1087+ }))
10881088 }
10891089
10901090 fn attempt_resolve_selected_completion_documentation(
@@ -3423,7 +3423,7 @@ impl Editor { · 2332 unmodified lines
34233423 to_insert,
34243424 }) = self.inlay_hint_cache.spawn_hint_refresh(
34253425 reason_description,
3426− self.excerpt_visible_offsets(required_languages.as_ref(), cx),
3426+ self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
34273427 invalidate_cache,
34283428 cx,
34293429 ) {
@@ -3442,11 +3442,15 @@ impl Editor { · 12 unmodified lines
34423442 .collect()
34433443 }
34443444
3445− pub fn excerpt_visible_offsets(
3445+ pub fn excerpts_for_inlay_hints_query(
34463446 &self,
34473447 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
34483448 cx: &mut ViewContext<'_, '_, Editor>,
34493449 ) -> 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);
34503454 let multi_buffer = self.buffer().read(cx);
34513455 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
34523456 let multi_buffer_visible_start = self
@@ -3466,6 +3470,14 @@ impl Editor { · 13 unmodified lines
34663470 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
34673471 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
34683472 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+ }
34693481 let language = buffer.language()?;
34703482 if let Some(restrict_to_languages) = restrict_to_languages {
34713483 if !restrict_to_languages.contains(language) {
@@ -3580,7 +3592,8 @@ impl Editor { · 108 unmodified lines
35803592 let id = post_inc(&mut self.next_completion_id);
35813593 let task = cx.spawn(|this, mut cx| {
35823594 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 {
35843597 let mut menu = CompletionsMenu {
35853598 id,
35863599 initial_position: position,
@@ -3601,21 +3614,26 @@ impl Editor { · 14 unmodified lines
36013614 selected_item: 0,
36023615 list: Default::default(),
36033616 };
3617+
36043618 menu.filter(query.as_deref(), cx.background()).await;
3619+
36053620 if menu.matches.is_empty() {
3606− None
3621+ (None, None)
36073622 } else {
3608− _ = this.update(&mut cx, |editor, cx| {
3609− menu.pre_resolve_completion_documentation(editor.project.clone(), cx);
3610− });
3611− Some(menu)
3623+ let pre_resolve_task = this
3624+ .update(&mut cx, |editor, cx| {
3625+ menu.pre_resolve_completion_documentation(editor, cx)
3626+ })
3627+ .ok()
3628+ .flatten();
3629+ (Some(menu), pre_resolve_task)
36123630 }
36133631 } else {
3614− None
3632+ (None, None)
36153633 };
36163634
36173635 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);
36193637
36203638 let mut context_menu = this.context_menu.write();
36213639 match context_menu.as_ref() {
@@ -3636,10 +3654,10 @@ impl Editor { · 14 unmodified lines
36363654 drop(context_menu);
36373655 this.discard_copilot_suggestion(cx);
36383656 cx.notify();
3639− } else if this.completion_tasks.is_empty() {
3640− // If there are no more completion tasks and the last menu was
3641− // empty, we should hide it. If it was already hidden, we should
3642− // also show the copilot suggestion when available.
3657+ } else if this.completion_tasks.len() <= 1 {
3658+ // If there are no more completion tasks (omitting ourself) and
3659+ // the last menu was empty, we should hide it. If it was already
3660+ // hidden, we should also show the copilot suggestion when available.
36433661 drop(context_menu);
36443662 if this.hide_context_menu(cx).is_none() {
36453663 this.update_visible_copilot_suggestion(cx);
@@ -3647,10 +3665,15 @@ impl Editor { · 1 unmodified line
36473665 }
36483666 })?;
36493667
3668+ if let Some(pre_resolve_task) = pre_resolve_task {
3669+ pre_resolve_task.await;
3670+ }
3671+
36503672 Ok::<_, anyhow::Error>(())
36513673 }
36523674 .log_err()
36533675 });
3676+
36543677 self.completion_tasks.push((id, task));
36553678 }
36563679
crates/editor/src/inlay_hint_cache.rs+4−2
@@ -861,7 +861,7 @@ async fn fetch_and_update_hints( · 860 unmodified lines
861861 let inlay_hints_fetch_task = editor
862862 .update(&mut cx, |editor, cx| {
863863 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) {
865865 Some((_, _, current_visible_range)) => {
866866 let visible_offset_length = current_visible_range.len();
867867 let double_visible_range = current_visible_range
@@ -2237,7 +2237,9 @@ pub mod tests { · 1369 unmodified lines
22372237 editor: &ViewHandle<Editor>,
22382238 cx: &mut gpui::TestAppContext,
22392239 ) -> 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+ });
22412243 assert_eq!(
22422244 ranges.len(),
22432245 1,
crates/editor2/src/display_map/block_map.rs+3−3
@@ -50,7 +50,7 @@ struct BlockRow(u32); · 49 unmodified lines
5050 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
5151 struct WrapRow(u32);
5252
53−pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>;
53+pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement>;
5454
5555 pub struct Block {
5656 id: BlockId,
@@ -69,7 +69,7 @@ where · 12 unmodified lines
6969 pub position: P,
7070 pub height: u8,
7171 pub style: BlockStyle,
72− pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement<Editor>>,
72+ pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>,
7373 pub disposition: BlockDisposition,
7474 }
7575
@@ -947,7 +947,7 @@ impl DerefMut for BlockContext<'_, '_> { · 871 unmodified lines
947947 }
948948
949949 impl Block {
950− pub fn render(&self, cx: &mut BlockContext) -> AnyElement<Editor> {
950+ pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
951951 self.render.lock()(cx)
952952 }
953953
crates/editor2/src/editor.rs+530−558
@@ -40,11 +40,12 @@ use fuzzy::{StringMatch, StringMatchCandidate}; · 39 unmodified lines
4040 use git::diff_hunk_to_display;
4141 use gpui::{
4242 actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,
43− AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
44− EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
45− Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled,
46− Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext,
47− WeakView, WindowContext,
43+ AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
44+ DispatchPhase, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures,
45+ FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model,
46+ MouseButton, ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText,
47+ Subscription, Task, TextRun, TextStyle, UniformListScrollHandle, View, ViewContext,
48+ VisualContext, WeakView, WhiteSpace, WindowContext,
4849 };
4950 use highlight_matching_bracket::refresh_matching_bracket_highlights;
5051 use hover_popover::{hide_hover, HoverState};
@@ -54,13 +55,14 @@ use itertools::Itertools; · 3 unmodified lines
5455 pub use language::{char_kind, CharKind};
5556 use language::{
5657 language_settings::{self, all_language_settings, InlayHintSettings},
57− point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape,
58− Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,
59− OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
58+ markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel,
59+ Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language,
60+ LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal,
61+ TransactionId,
6062 };
6163 use lazy_static::lazy_static;
6264 use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
63−use lsp::{DiagnosticSeverity, Documentation, LanguageServerId};
65+use lsp::{DiagnosticSeverity, LanguageServerId};
6466 use movement::TextLayoutDetails;
6567 use multi_buffer::ToOffsetUtf16;
6668 pub use multi_buffer::{
@@ -97,12 +99,12 @@ use text::{OffsetUtf16, Rope}; · 30 unmodified lines
9799 use theme::{
98100 ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
99101 };
100−use ui::{v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip};
102+use ui::{h_stack, v_stack, HighlightedLabel, IconButton, Popover, StyledExt, Tooltip};
101103 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
102104 use workspace::{
103105 item::{ItemEvent, ItemHandle},
104106 searchable::SearchEvent,
105− ItemNavHistory, SplitDirection, ViewId, Workspace,
107+ ItemNavHistory, Pane, SplitDirection, ViewId, Workspace,
106108 };
107109
108110 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
115117
116118 pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
117119
118−// pub fn render_parsed_markdown<Tag: 'static>(
119−// parsed: &language::ParsedMarkdown,
120−// editor_style: &EditorStyle,
121−// workspace: Option<WeakView<Workspace>>,
122−// cx: &mut ViewContext<Editor>,
123−// ) -> Text {
124−// enum RenderedMarkdown {}
125−
126−// let parsed = parsed.clone();
127−// let view_id = cx.view_id();
128−// let code_span_background_color = editor_style.document_highlight_read_background;
129−
130−// let mut region_id = 0;
131−
132−// todo!()
133−// // Text::new(parsed.text, editor_style.text.clone())
134−// // .with_highlights(
135−// // parsed
136−// // .highlights
137−// // .iter()
138−// // .filter_map(|(range, highlight)| {
139−// // let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
140−// // Some((range.clone(), highlight))
141−// // })
142−// // .collect::<Vec<_>>(),
143−// // )
144−// // .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| {
145−// // region_id += 1;
146−// // let region = parsed.regions[ix].clone();
147−
148−// // if let Some(link) = region.link {
149−// // cx.scene().push_cursor_region(CursorRegion {
150−// // bounds,
151−// // style: CursorStyle::PointingHand,
152−// // });
153−// // cx.scene().push_mouse_region(
154−// // MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds)
155−// // .on_down::<Editor, _>(MouseButton::Left, move |_, _, cx| match &link {
156−// // markdown::Link::Web { url } => cx.platform().open_url(url),
157−// // markdown::Link::Path { path } => {
158−// // if let Some(workspace) = &workspace {
159−// // _ = workspace.update(cx, |workspace, cx| {
160−// // workspace.open_abs_path(path.clone(), false, cx).detach();
161−// // });
162−// // }
163−// // }
164−// // }),
165−// // );
166−// // }
167−
168−// // if region.code {
169−// // cx.draw_quad(Quad {
170−// // bounds,
171−// // background: Some(code_span_background_color),
172−// // corner_radii: (2.0).into(),
173−// // order: todo!(),
174−// // content_mask: todo!(),
175−// // border_color: todo!(),
176−// // border_widths: todo!(),
177−// // });
178−// // }
179−// // })
180−// // .with_soft_wrap(true)
181−// }
120+pub fn render_parsed_markdown(
121+ element_id: impl Into<ElementId>,
122+ parsed: &language::ParsedMarkdown,
123+ editor_style: &EditorStyle,
124+ workspace: Option<WeakView<Workspace>>,
125+ cx: &mut ViewContext<Editor>,
126+) -> InteractiveText {
127+ let code_span_background_color = cx
128+ .theme()
129+ .colors()
130+ .editor_document_highlight_read_background;
131+
132+ let highlights = gpui::combine_highlights(
133+ parsed.highlights.iter().filter_map(|(range, highlight)| {
134+ let highlight = highlight.to_highlight_style(&editor_style.syntax)?;
135+ Some((range.clone(), highlight))
136+ }),
137+ parsed
138+ .regions
139+ .iter()
140+ .zip(&parsed.region_ranges)
141+ .filter_map(|(region, range)| {
142+ if region.code {
143+ Some((
144+ range.clone(),
145+ HighlightStyle {
146+ background_color: Some(code_span_background_color),
147+ ..Default::default()
148+ },
149+ ))
150+ } else {
151+ None
152+ }
153+ }),
154+ );
155+ let runs = text_runs_for_highlights(&parsed.text, &editor_style.text, highlights);
156+
157+ // todo!("add the ability to change cursor style for link ranges")
158+ let mut links = Vec::new();
159+ let mut link_ranges = Vec::new();
160+ for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
161+ if let Some(link) = region.link.clone() {
162+ links.push(link);
163+ link_ranges.push(range.clone());
164+ }
165+ }
166+
167+ InteractiveText::new(
168+ element_id,
169+ StyledText::new(parsed.text.clone()).with_runs(runs),
170+ )
171+ .on_click(link_ranges, move |clicked_range_ix, cx| {
172+ match &links[clicked_range_ix] {
173+ markdown::Link::Web { url } => cx.open_url(url),
174+ markdown::Link::Path { path } => {
175+ if let Some(workspace) = &workspace {
176+ _ = workspace.update(cx, |workspace, cx| {
177+ workspace.open_abs_path(path.clone(), false, cx).detach();
178+ });
179+ }
180+ }
181+ }
182+ })
183+}
182184
183185 #[derive(PartialEq, Clone, Deserialize, Default, Action)]
184186 pub struct SelectNext {
@@ -529,8 +531,6 @@ pub fn init(cx: &mut AppContext) { · 344 unmodified lines
529531 // cx.register_action_type(Editor::context_menu_next);
530532 // cx.register_action_type(Editor::context_menu_last);
531533
532− hover_popover::init(cx);
533−
534534 workspace::register_project_item::<Editor>(cx);
535535 workspace::register_followable_item::<Editor>(cx);
536536 workspace::register_deserializable_item::<Editor>(cx);
@@ -663,6 +663,7 @@ pub struct Editor { · 126 unmodified lines
663663 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
664664 gutter_width: Pixels,
665665 style: Option<EditorStyle>,
666+ editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
666667 }
667668
668669 pub struct EditorSnapshot {
@@ -905,12 +906,16 @@ impl ContextMenu { · 236 unmodified lines
905906 &self,
906907 cursor_position: DisplayPoint,
907908 style: &EditorStyle,
909+ max_height: Pixels,
908910 workspace: Option<WeakView<Workspace>>,
909911 cx: &mut ViewContext<Editor>,
910− ) -> (DisplayPoint, AnyElement<Editor>) {
912+ ) -> (DisplayPoint, AnyElement) {
911913 match self {
912− ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)),
913− ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
914+ ContextMenu::Completions(menu) => (
915+ cursor_position,
916+ menu.render(style, max_height, workspace, cx),
917+ ),
918+ ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, max_height, cx),
914919 }
915920 }
916921 }
@@ -966,20 +971,22 @@ impl CompletionsMenu { · 49 unmodified lines
966971
967972 fn pre_resolve_completion_documentation(
968973 &self,
969− project: Option<Model<Project>>,
970− cx: &mut ViewContext<Editor>,
971− ) {
974+ _editor: &Editor,
975+ _cx: &mut ViewContext<Editor>,
976+ ) -> Option<Task<()>> {
972977 // todo!("implementation below ");
978+ None
973979 }
974− // ) {
980+ // {
975981 // let settings = EditorSettings::get_global(cx);
976982 // if !settings.show_completion_documentation {
977− // return;
983+ // return None;
978984 // }
979985
980− // let Some(project) = project else {
981− // return;
986+ // let Some(project) = editor.project.clone() else {
987+ // return None;
982988 // };
989+
983990 // let client = project.read(cx).client();
984991 // let language_registry = project.read(cx).languages().clone();
985992
@@ -989,7 +996,7 @@ impl CompletionsMenu { · 3 unmodified lines
989996 // let completions = self.completions.clone();
990997 // let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
991998
992− // cx.spawn(move |this, mut cx| async move {
999+ // Some(cx.spawn(move |this, mut cx| async move {
9931000 // if is_remote {
9941001 // let Some(project_id) = project_id else {
9951002 // log::error!("Remote project without remote_id");
@@ -1051,8 +1058,7 @@ impl CompletionsMenu { · 55 unmodified lines
10511058 // _ = this.update(&mut cx, |_, cx| cx.notify());
10521059 // }
10531060 // }
1054− // })
1055− // .detach();
1061+ // }))
10561062 // }
10571063
10581064 fn attempt_resolve_selected_completion_documentation(
@@ -1221,210 +1227,156 @@ impl CompletionsMenu { · 162 unmodified lines
12211227 fn render(
12221228 &self,
12231229 style: &EditorStyle,
1230+ max_height: Pixels,
12241231 workspace: Option<WeakView<Workspace>>,
12251232 cx: &mut ViewContext<Editor>,
1226− ) -> AnyElement<Editor> {
1227− todo!("old implementation below")
1228− }
1233+ ) -> AnyElement {
1234+ let settings = EditorSettings::get_global(cx);
1235+ let show_completion_documentation = settings.show_completion_documentation;
12291236
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+ }
12311252
1232− // let settings = EditorSettings>(cx);
1233− // let show_completion_documentation = settings.show_completion_documentation;
1253+ len
1254+ })
1255+ .map(|(ix, _)| ix);
12341256
1235− // let widest_completion_ix = self
1236− // .matches
1237− // .iter()
1238− // .enumerate()
1239− // .max_by_key(|(_, mat)| {
1240− // let completions = self.completions.read();
1241− // let completion = &completions[mat.candidate_id];
1242− // let documentation = &completion.documentation;
1257+ let completions = self.completions.clone();
1258+ let matches = self.matches.clone();
1259+ let selected_item = self.selected_item;
1260+ let style = style.clone();
12431261
1244− // let mut len = completion.label.text.chars().count();
1245− // if let Some(Documentation::SingleLine(text)) = documentation {
1246− // if show_completion_documentation {
1247− // len += text.chars().count();
1248− // }
1249− // }
1262+ let multiline_docs = {
1263+ let mat = &self.matches[selected_item];
1264+ let multiline_docs = match &self.completions.read()[mat.candidate_id].documentation {
1265+ Some(Documentation::MultiLinePlainText(text)) => {
1266+ Some(div().child(SharedString::from(text.clone())))
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();
12501296
1251− // len
1252− // })
1253− // .map(|(ix, _)| ix);
1297+ matches[range]
1298+ .iter()
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];
12541304
1255− // let completions = self.completions.clone();
1256− // let matches = self.matches.clone();
1257− // let selected_item = self.selected_item;
1258−
1259− // let list = UniformList::new(self.list.clone(), matches.len(), cx, {
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− // };
1305+ let documentation = if show_completion_documentation {
1306+ &completion.documentation
1307+ } else {
1308+ &None
1309+ };
12751310
1276− // items.push(
1277− // MouseEventHandler::new::<CompletionTag, _>(
1278− // mat.candidate_id,
1279− // cx,
1280− // |state, _| {
1281− // let item_style = if item_ix == selected_item {
1282− // style.autocomplete.selected_item
1283− // } else if state.hovered() {
1284− // style.autocomplete.hovered_item
1285− // } else {
1286− // style.autocomplete.item
1287− // };
1288−
1289− // let completion_label =
1290− // Text::new(completion.label.text.clone(), style.text.clone())
1291− // .with_soft_wrap(false)
1292− // .with_highlights(
1293− // combine_syntax_and_fuzzy_match_highlights(
1294− // &completion.label.text,
1295− // style.text.color.into(),
1296− // styled_runs_for_code_label(
1297− // &completion.label,
1298− // &style.syntax,
1299− // ),
1300− // &mat.positions,
1301− // ),
1302− // );
1303−
1304− // if let Some(Documentation::SingleLine(text)) = documentation {
1305− // Flex::row()
1306− // .with_child(completion_label)
1307− // .with_children((|| {
1308− // let text_style = TextStyle {
1309− // color: style.autocomplete.inline_docs_color,
1310− // font_size: style.text.font_size
1311− // * style.autocomplete.inline_docs_size_percent,
1312− // ..style.text.clone()
1313− // };
1314−
1315− // let label = Text::new(text.clone(), text_style)
1316− // .aligned()
1317− // .constrained()
1318− // .dynamically(move |constraint, _, _| {
1319− // gpui::SizeConstraint {
1320− // min: constraint.min,
1321− // max: vec2f(
1322− // constraint.max.x(),
1323− // constraint.min.y(),
1324− // ),
1325− // }
1326− // });
1327−
1328− // if Some(item_ix) == widest_completion_ix {
1329− // Some(
1330− // label
1331− // .contained()
1332− // .with_style(
1333− // style
1334− // .autocomplete
1335− // .inline_docs_container,
1336− // )
1337− // .into_any(),
1338− // )
1339− // } else {
1340− // Some(label.flex_float().into_any())
1341− // }
1342− // })())
1343− // .into_any()
1344− // } else {
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− // }
1311+ let highlights = gpui::combine_highlights(
1312+ mat.ranges().map(|range| (range, FontWeight::BOLD.into())),
1313+ styled_runs_for_code_label(&completion.label, &style.syntax).map(
1314+ |(range, mut highlight)| {
1315+ // Ignore font weight for syntax highlighting, as we'll use it
1316+ // for fuzzy matches.
1317+ highlight.font_weight = None;
1318+ (range, highlight)
1319+ },
1320+ ),
1321+ );
1322+ let completion_label = StyledText::new(completion.label.text.clone())
1323+ .with_runs(text_runs_for_highlights(
1324+ &completion.label.text,
1325+ &style.text,
1326+ highlights,
1327+ ));
1328+ let documentation_label =
1329+ if let Some(Documentation::SingleLine(text)) = documentation {
1330+ Some(SharedString::from(text.clone()))
1331+ } else {
1332+ None
1333+ };
1334+
1335+ div()
1336+ .id(mat.candidate_id)
1337+ .min_w(px(220.))
1338+ .max_w(px(540.))
1339+ .whitespace_nowrap()
1340+ .overflow_hidden()
1341+ .text_ui()
1342+ .px_1()
1343+ .rounded(px(4.))
1344+ .bg(cx.theme().colors().ghost_element_background)
1345+ .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1346+ .when(item_ix == selected_item, |div| {
1347+ div.bg(cx.theme().colors().ghost_element_selected)
1348+ })
1349+ .on_mouse_down(
1350+ MouseButton::Left,
1351+ cx.listener(move |editor, event, cx| {
1352+ cx.stop_propagation();
1353+ editor
1354+ .confirm_completion(
1355+ &ConfirmCompletion {
1356+ item_ix: Some(item_ix),
1357+ },
1358+ cx,
1359+ )
1360+ .map(|task| task.detach_and_log_err(cx));
1361+ }),
1362+ )
1363+ .child(completion_label)
1364+ .children(documentation_label)
1365+ })
1366+ .collect()
1367+ },
1368+ )
1369+ .max_h(max_height)
1370+ .track_scroll(self.scroll_handle.clone())
1371+ .with_width_from_item(widest_completion_ix);
1372+
1373+ Popover::new()
1374+ .child(list)
1375+ .when_some(multiline_docs, |popover, multiline_docs| {
1376+ popover.aside(multiline_docs)
1377+ })
1378+ .into_any_element()
1379+ }
14281380
14291381 pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
14301382 let mut matches = if let Some(query) = query {
@@ -1540,14 +1492,17 @@ impl CodeActionsMenu { · 109 unmodified lines
15401492 &self,
15411493 mut cursor_position: DisplayPoint,
15421494 style: &EditorStyle,
1495+ max_height: Pixels,
15431496 cx: &mut ViewContext<Editor>,
1544− ) -> (DisplayPoint, AnyElement<Editor>) {
1497+ ) -> (DisplayPoint, AnyElement) {
15451498 let actions = self.actions.clone();
15461499 let selected_item = self.selected_item;
1500+
15471501 let element = uniform_list(
1502+ cx.view().clone(),
15481503 "code_actions_menu",
15491504 self.actions.len(),
1550− move |editor, range, cx| {
1505+ move |this, range, cx| {
15511506 actions[range.clone()]
15521507 .iter()
15531508 .enumerate()
@@ -1569,18 +1524,22 @@ impl CodeActionsMenu { · 15 unmodified lines
15691524 .bg(colors.element_hover)
15701525 .text_color(colors.text_accent)
15711526 })
1572− .on_mouse_down(MouseButton::Left, move |editor: &mut Editor, _, cx| {
1573− cx.stop_propagation();
1574− editor
1575− .confirm_code_action(
1576− &ConfirmCodeAction {
1577− item_ix: Some(item_ix),
1578− },
1579− cx,
1580− )
1581− .map(|task| task.detach_and_log_err(cx));
1582− })
1583− .child(action.lsp_action.title.clone())
1527+ .on_mouse_down(
1528+ MouseButton::Left,
1529+ cx.listener(move |editor, _, cx| {
1530+ cx.stop_propagation();
1531+ editor
1532+ .confirm_code_action(
1533+ &ConfirmCodeAction {
1534+ item_ix: Some(item_ix),
1535+ },
1536+ cx,
1537+ )
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()))
15841543 })
15851544 .collect()
15861545 },
@@ -1588,6 +1547,8 @@ impl CodeActionsMenu { · 1 unmodified line
15881547 .elevation_1(cx)
15891548 .px_2()
15901549 .py_1()
1550+ .max_h(max_height)
1551+ .track_scroll(self.scroll_handle.clone())
15911552 .with_width_from_item(
15921553 self.actions
15931554 .iter()
@@ -1595,7 +1556,7 @@ impl CodeActionsMenu { · 1 unmodified line
15951556 .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
15961557 .map(|(ix, _)| ix),
15971558 )
1598− .render();
1559+ .into_any_element();
15991560
16001561 if self.deployed_from_indicator {
16011562 *cursor_position.column_mut() = 0;
@@ -1943,6 +1904,7 @@ impl Editor { · 341 unmodified lines
19431904 pixel_position_of_newest_cursor: None,
19441905 gutter_width: Default::default(),
19451906 style: None,
1907+ editor_actions: Default::default(),
19461908 _subscriptions: vec![
19471909 cx.observe(&buffer, Self::on_buffer_changed),
19481910 cx.subscribe(&buffer, Self::on_buffer_event),
@@ -2074,10 +2036,14 @@ impl Editor { · 125 unmodified lines
20742036 &self.buffer
20752037 }
20762038
2077− fn workspace(&self) -> Option<View<Workspace>> {
2039+ pub fn workspace(&self) -> Option<View<Workspace>> {
20782040 self.workspace.as_ref()?.0.upgrade()
20792041 }
20802042
2043+ pub fn pane(&self, cx: &AppContext) -> Option<View<Pane>> {
2044+ self.workspace()?.read(cx).pane_for(&self.handle.upgrade()?)
2045+ }
2046+
20812047 pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
20822048 self.buffer().read(cx).title(cx)
20832049 }
@@ -2319,6 +2285,7 @@ impl Editor { · 235 unmodified lines
23192285
23202286 self.blink_manager.update(cx, BlinkManager::pause_blinking);
23212287 cx.emit(EditorEvent::SelectionsChanged { local });
2288+ cx.emit(SearchEvent::MatchesInvalidated);
23222289
23232290 if self.selections.disjoint_anchors().len() == 1 {
23242291 cx.emit(SearchEvent::ActiveMatchChanged)
@@ -3437,7 +3404,7 @@ impl Editor { · 1112 unmodified lines
34373404 to_insert,
34383405 }) = self.inlay_hint_cache.spawn_hint_refresh(
34393406 reason_description,
3440− self.excerpt_visible_offsets(required_languages.as_ref(), cx),
3407+ self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
34413408 invalidate_cache,
34423409 cx,
34433410 ) {
@@ -3456,11 +3423,15 @@ impl Editor { · 12 unmodified lines
34563423 .collect()
34573424 }
34583425
3459− pub fn excerpt_visible_offsets(
3426+ pub fn excerpts_for_inlay_hints_query(
34603427 &self,
34613428 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
34623429 cx: &mut ViewContext<Editor>,
34633430 ) -> 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);
34643435 let multi_buffer = self.buffer().read(cx);
34653436 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
34663437 let multi_buffer_visible_start = self
@@ -3480,6 +3451,15 @@ impl Editor { · 13 unmodified lines
34803451 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
34813452 .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
34823453 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+
34833463 let language = buffer.language()?;
34843464 if let Some(restrict_to_languages) = restrict_to_languages {
34853465 if !restrict_to_languages.contains(language) {
@@ -3594,7 +3574,8 @@ impl Editor { · 108 unmodified lines
35943574 let id = post_inc(&mut self.next_completion_id);
35953575 let task = cx.spawn(|this, mut cx| {
35963576 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 {
35983579 let mut menu = CompletionsMenu {
35993580 id,
36003581 initial_position: position,
@@ -3617,20 +3598,24 @@ impl Editor { · 16 unmodified lines
36173598 };
36183599 menu.filter(query.as_deref(), cx.background_executor().clone())
36193600 .await;
3601+
36203602 if menu.matches.is_empty() {
3621− None
3603+ (None, None)
36223604 } else {
3623− _ = this.update(&mut cx, |editor, cx| {
3624− menu.pre_resolve_completion_documentation(editor.project.clone(), cx);
3625− });
3626− Some(menu)
3605+ let pre_resolve_task = this
3606+ .update(&mut cx, |editor, cx| {
3607+ menu.pre_resolve_completion_documentation(editor, cx)
3608+ })
3609+ .ok()
3610+ .flatten();
3611+ (Some(menu), pre_resolve_task)
36273612 }
36283613 } else {
3629− None
3614+ (None, None)
36303615 };
36313616
36323617 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);
36343619
36353620 let mut context_menu = this.context_menu.write();
36363621 match context_menu.as_ref() {
@@ -3662,142 +3647,147 @@ impl Editor { · 25 unmodified lines
36623647 }
36633648 })?;
36643649
3650+ if let Some(pre_resolve_task) = pre_resolve_task {
3651+ pre_resolve_task.await;
3652+ }
3653+
36653654 Ok::<_, anyhow::Error>(())
36663655 }
36673656 .log_err()
36683657 });
3658+
36693659 self.completion_tasks.push((id, task));
36703660 }
36713661
3672− // pub fn confirm_completion(
3673− // &mut self,
3674− // action: &ConfirmCompletion,
3675− // cx: &mut ViewContext<Self>,
3676− // ) -> Option<Task<Result<()>>> {
3677− // use language::ToOffset as _;
3662+ pub fn confirm_completion(
3663+ &mut self,
3664+ action: &ConfirmCompletion,
3665+ cx: &mut ViewContext<Self>,
3666+ ) -> Option<Task<Result<()>>> {
3667+ use language::ToOffset as _;
36783668
3679− // let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
3680− // menu
3681− // } else {
3682− // return None;
3683− // };
3669+ let completions_menu = if let ContextMenu::Completions(menu) = self.hide_context_menu(cx)? {
3670+ menu
3671+ } else {
3672+ return None;
3673+ };
36843674
3685− // let mat = completions_menu
3686− // .matches
3687− // .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
3688− // let buffer_handle = completions_menu.buffer;
3689− // let completions = completions_menu.completions.read();
3690− // let completion = completions.get(mat.candidate_id)?;
3691−
3692− // let snippet;
3693− // let text;
3694− // if completion.is_snippet() {
3695− // snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
3696− // text = snippet.as_ref().unwrap().text.clone();
3697− // } else {
3698− // snippet = None;
3699− // text = completion.new_text.clone();
3700− // };
3701− // let selections = self.selections.all::<usize>(cx);
3702− // let buffer = buffer_handle.read(cx);
3703− // let old_range = completion.old_range.to_offset(buffer);
3704− // 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− // }
3675+ let mat = completions_menu
3676+ .matches
3677+ .get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
3678+ let buffer_handle = completions_menu.buffer;
3679+ let completions = completions_menu.completions.read();
3680+ let completion = completions.get(mat.candidate_id)?;
3681+
3682+ let snippet;
3683+ let text;
3684+ if completion.is_snippet() {
3685+ snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
3686+ text = snippet.as_ref().unwrap().text.clone();
3687+ } else {
3688+ snippet = None;
3689+ text = completion.new_text.clone();
3690+ };
3691+ let selections = self.selections.all::<usize>(cx);
3692+ let buffer = buffer_handle.read(cx);
3693+ let old_range = completion.old_range.to_offset(buffer);
3694+ let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
37103695
3711− // let lookbehind = newest_selection
3712− // .start
3713− // .text_anchor
3714− // .to_offset(buffer)
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..];
3696+ let newest_selection = self.selections.newest_anchor();
3697+ if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
3698+ return None;
3699+ }
37593700
3760− // cx.emit(Event::InputHandled {
3761− // utf16_range_to_replace: range_to_replace,
3762− // text: text.into(),
3763− // });
3701+ let lookbehind = newest_selection
3702+ .start
3703+ .text_anchor
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();
37643714
3765− // self.transact(cx, |this, cx| {
3766− // if let Some(mut snippet) = snippet {
3767− // snippet.text = text.to_string();
3768− // for tabstop in snippet.tabstops.iter_mut().flatten() {
3769− // tabstop.start -= common_prefix_len as isize;
3770− // tabstop.end -= common_prefix_len as isize;
3771− // }
3715+ let snapshot = self.buffer.read(cx).snapshot(cx);
3716+ let mut range_to_replace: Option<Range<isize>> = None;
3717+ let mut ranges = Vec::new();
3718+ for selection in &selections {
3719+ if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
3720+ let start = selection.start.saturating_sub(lookbehind);
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..];
37723749
3773− // this.insert_snippet(&ranges, snippet, cx).log_err();
3774− // } else {
3775− // this.buffer.update(cx, |buffer, cx| {
3776− // buffer.edit(
3777− // ranges.iter().map(|range| (range.clone(), text)),
3778− // this.autoindent_mode.clone(),
3779− // cx,
3780− // );
3781− // });
3782− // }
3750+ cx.emit(EditorEvent::InputHandled {
3751+ utf16_range_to_replace: range_to_replace,
3752+ text: text.into(),
3753+ });
37833754
3784− // this.refresh_copilot_suggestions(true, cx);
3785− // });
3755+ self.transact(cx, |this, cx| {
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+ }
37863762
3787− // let project = self.project.clone()?;
3788− // let apply_edits = project.update(cx, |project, cx| {
3789− // project.apply_additional_edits_for_completion(
3790− // buffer_handle,
3791− // completion.clone(),
3792− // true,
3793− // cx,
3794− // )
3795− // });
3796− // Some(cx.foreground().spawn(async move {
3797− // apply_edits.await?;
3798− // Ok(())
3799− // }))
3800− // }
3763+ this.insert_snippet(&ranges, snippet, cx).log_err();
3764+ } else {
3765+ this.buffer.update(cx, |buffer, cx| {
3766+ buffer.edit(
3767+ ranges.iter().map(|range| (range.clone(), text)),
3768+ this.autoindent_mode.clone(),
3769+ cx,
3770+ );
3771+ });
3772+ }
3773+
3774+ this.refresh_copilot_suggestions(true, cx);
3775+ });
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+ }
38013791
38023792 pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
38033793 let mut context_menu = self.context_menu.write();
@@ -4353,19 +4343,19 @@ impl Editor { · 549 unmodified lines
43534343 style: &EditorStyle,
43544344 is_active: bool,
43554345 cx: &mut ViewContext<Self>,
4356− ) -> Option<AnyElement<Self>> {
4346+ ) -> Option<IconButton> {
43574347 if self.available_code_actions.is_some() {
43584348 Some(
4359− IconButton::new("code_actions_indicator", ui::Icon::Bolt)
4360− .on_click(|editor: &mut Editor, cx| {
4349+ IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(cx.listener(
4350+ |editor, e, cx| {
43614351 editor.toggle_code_actions(
43624352 &ToggleCodeActions {
43634353 deployed_from_indicator: true,
43644354 },
43654355 cx,
43664356 );
4367− })
4368− .render(),
4357+ },
4358+ )),
43694359 )
43704360 } else {
43714361 None
@@ -4380,7 +4370,7 @@ impl Editor { · 8 unmodified lines
43804370 line_height: Pixels,
43814371 gutter_margin: Pixels,
43824372 cx: &mut ViewContext<Self>,
4383− ) -> Vec<Option<AnyElement<Self>>> {
4373+ ) -> Vec<Option<IconButton>> {
43844374 fold_data
43854375 .iter()
43864376 .enumerate()
@@ -4393,16 +4383,15 @@ impl Editor { · 6 unmodified lines
43934383 FoldStatus::Foldable => ui::Icon::ChevronDown,
43944384 };
43954385 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 {
43974387 FoldStatus::Folded => {
43984388 editor.unfold_at(&UnfoldAt { buffer_row }, cx);
43994389 }
44004390 FoldStatus::Foldable => {
44014391 editor.fold_at(&FoldAt { buffer_row }, cx);
44024392 }
4403− })
4404− .color(ui::TextColor::Muted)
4405− .render()
4393+ }))
4394+ .color(ui::Color::Muted)
44064395 })
44074396 })
44084397 .flatten()
@@ -4421,12 +4410,14 @@ impl Editor { · 12 unmodified lines
44214410 &self,
44224411 cursor_position: DisplayPoint,
44234412 style: &EditorStyle,
4413+ max_height: Pixels,
44244414 cx: &mut ViewContext<Editor>,
4425− ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
4415+ ) -> Option<(DisplayPoint, AnyElement)> {
44264416 self.context_menu.read().as_ref().map(|menu| {
44274417 menu.render(
44284418 cursor_position,
44294419 style,
4420+ max_height,
44304421 self.workspace.as_ref().map(|(w, _)| w.clone()),
44314422 cx,
44324423 )
@@ -7781,7 +7772,7 @@ impl Editor { · 3348 unmodified lines
77817772 }
77827773 div()
77837774 .pl(cx.anchor_x)
7784− .child(rename_editor.render_with(EditorElement::new(
7775+ .child(EditorElement::new(
77857776 &rename_editor,
77867777 EditorStyle {
77877778 background: cx.theme().system().transparent,
@@ -7789,11 +7780,13 @@ impl Editor { · 1 unmodified line
77897780 text: text_style,
77907781 scrollbar_width: cx.editor_style.scrollbar_width,
77917782 syntax: cx.editor_style.syntax.clone(),
7792− diagnostic_style:
7793− cx.editor_style.diagnostic_style.clone(),
7783+ diagnostic_style: cx
7784+ .editor_style
7785+ .diagnostic_style
7786+ .clone(),
77947787 },
7795− )))
7796− .render()
7788+ ))
7789+ .into_any_element()
77977790 }
77987791 }),
77997792 disposition: BlockDisposition::Below,
@@ -9207,6 +9200,26 @@ impl Editor { · 1407 unmodified lines
92079200 cx.emit(EditorEvent::Blurred);
92089201 cx.notify();
92099202 }
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+ }
92109223 }
92119224
92129225 pub trait CollaborationHub {
@@ -9396,7 +9409,9 @@ impl Render for Editor { · 183 unmodified lines
93969409 font_weight: FontWeight::NORMAL,
93979410 font_style: FontStyle::Normal,
93989411 line_height: relative(1.).into(),
9412+ background_color: None,
93999413 underline: None,
9414+ white_space: WhiteSpace::Normal,
94009415 },
94019416
94029417 EditorMode::AutoHeight { max_lines } => todo!(),
@@ -9409,7 +9424,9 @@ impl Render for Editor { · 6 unmodified lines
94099424 font_weight: FontWeight::NORMAL,
94109425 font_style: FontStyle::Normal,
94119426 line_height: relative(settings.buffer_line_height.value()),
9427+ background_color: None,
94129428 underline: None,
9429+ white_space: WhiteSpace::Normal,
94139430 },
94149431 };
94159432
@@ -9996,11 +10013,11 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend · 580 unmodified lines
999610013 .ml(cx.anchor_x)
999710014 }))
999810015 .cursor_pointer()
9999− .on_click(move |_, _, cx| {
10016+ .on_click(cx.listener(move |_, _, cx| {
1000010017 cx.write_to_clipboard(ClipboardItem::new(message.clone()));
10001− })
10002− .tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx))
10003− .render()
10018+ }))
10019+ .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx))
10020+ .into_any_element()
1000410021 })
1000510022 }
1000610023
@@ -10047,120 +10064,75 @@ pub fn diagnostic_style( · 40 unmodified lines
1004710064 }
1004810065 }
1004910066
10050−pub fn combine_syntax_and_fuzzy_match_highlights(
10067+pub fn text_runs_for_highlights(
1005110068 text: &str,
10052− default_style: HighlightStyle,
10053− syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
10054− match_indices: &[usize],
10055−) -> Vec<(Range<usize>, HighlightStyle)> {
10056− let mut result = Vec::new();
10057− let mut match_indices = match_indices.iter().copied().peekable();
10058−
10059− for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
10060− {
10061− syntax_highlight.font_weight = None;
10062−
10063− // Add highlights for any fuzzy match characters before the next
10064− // syntax highlight range.
10065− while let Some(&match_index) = match_indices.peek() {
10066− if match_index >= range.start {
10067− break;
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− }
10069+ default_style: &TextStyle,
10070+ highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
10071+) -> Vec<TextRun> {
10072+ let mut runs = Vec::new();
10073+ let mut ix = 0;
10074+ for (range, highlight) in highlights {
10075+ if ix < range.start {
10076+ runs.push(default_style.clone().to_run(range.start - ix));
10077+ }
10078+ runs.push(
10079+ default_style
10080+ .clone()
10081+ .highlight(highlight)
10082+ .to_run(range.len()),
10083+ );
10084+ ix = range.end;
1011210085 }
10113−
10114− fn char_ix_after(ix: usize, text: &str) -> usize {
10115− ix + text[ix..].chars().next().unwrap().len_utf8()
10086+ if ix < text.len() {
10087+ runs.push(default_style.to_run(text.len() - ix));
1011610088 }
10117−
10118− result
10089+ runs
1011910090 }
1012010091
10121−// pub fn styled_runs_for_code_label<'a>(
10122−// label: &'a CodeLabel,
10123−// syntax_theme: &'a theme::SyntaxTheme,
10124−// ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
10125−// let fade_out = HighlightStyle {
10126−// fade_out: Some(0.35),
10127−// ..Default::default()
10128−// };
10092+pub fn styled_runs_for_code_label<'a>(
10093+ label: &'a CodeLabel,
10094+ syntax_theme: &'a theme::SyntaxTheme,
10095+) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
10096+ let fade_out = HighlightStyle {
10097+ fade_out: Some(0.35),
10098+ ..Default::default()
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);
1012910114
10130−// let mut prev_end = label.filter_range.end;
10131−// label
10132−// .runs
10133−// .iter()
10134−// .enumerate()
10135−// .flat_map(move |(ix, (range, highlight_id))| {
10136−// let style = if let Some(style) = highlight_id.style(syntax_theme) {
10137−// style
10138−// } else {
10139−// return Default::default();
10140−// };
10141−// let mut muted_style = style;
10142−// muted_style.highlight(fade_out);
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);
10115+ let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
10116+ if range.start >= label.filter_range.end {
10117+ if range.start > prev_end {
10118+ runs.push((prev_end..range.start, fade_out));
10119+ }
10120+ runs.push((range.clone(), muted_style));
10121+ } else if range.end <= label.filter_range.end {
10122+ runs.push((range.clone(), style));
10123+ } else {
10124+ runs.push((range.start..label.filter_range.end, style));
10125+ runs.push((label.filter_range.end..range.end, muted_style));
10126+ }
10127+ prev_end = cmp::max(prev_end, range.end);
1015710128
10158−// if ix + 1 == label.runs.len() && label.text.len() > prev_end {
10159−// runs.push((prev_end..label.text.len(), fade_out));
10160−// }
10129+ if ix + 1 == label.runs.len() && label.text.len() > prev_end {
10130+ runs.push((prev_end..label.text.len(), fade_out));
10131+ }
1016110132
10162−// runs
10163−// })
10133+ runs
10134+ })
10135+}
1016410136
1016510137 pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
1016610138 let mut index = 0;
crates/editor2/src/editor_tests.rs+1−70
@@ -3048,7 +3048,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { · 3047 unmodified lines
30483048 position: snapshot.anchor_after(Point::new(2, 0)),
30493049 disposition: BlockDisposition::Below,
30503050 height: 1,
3051− render: Arc::new(|_| div().render()),
3051+ render: Arc::new(|_| div().into_any()),
30523052 }],
30533053 Some(Autoscroll::fit()),
30543054 cx,
@@ -6740,75 +6740,6 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { · 3685 unmodified lines
67406740 // );
67416741 // }
67426742
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−
68126743 #[gpui::test]
68136744 async fn go_to_prev_overlapping_diagnostic(
68146745 executor: BackgroundExecutor,
crates/editor2/src/element.rs+844−837
@@ -5,7 +5,9 @@ use crate::{ · 4 unmodified lines
55 },
66 editor_settings::ShowScrollbar,
77 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+ },
911 link_go_to_definition::{
1012 go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
1113 update_inlay_link_and_hover_points, GoToDefinitionTrigger,
@@ -19,11 +21,11 @@ use anyhow::Result; · 7 unmodified lines
1921 use collections::{BTreeMap, HashMap};
2022 use gpui::{
2123 div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
22− BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element,
23− ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, LineLayout,
24− MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels,
25− ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveComponent, Style, Styled,
26− TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine,
24+ BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
25+ ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, IntoElement, LineLayout,
26+ MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
27+ ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
28+ TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
2729 };
2830 use itertools::Itertools;
2931 use language::language_settings::ShowWhitespaceSetting;
@@ -112,18 +114,202 @@ impl SelectionLayout { · 82 unmodified lines
112114 }
113115
114116 pub struct EditorElement {
115− editor_id: EntityId,
117+ editor: View<Editor>,
116118 style: EditorStyle,
117119 }
118120
119121 impl EditorElement {
120122 pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
121123 Self {
122− editor_id: editor.entity_id(),
124+ editor: editor.clone(),
123125 style,
124126 }
125127 }
126128
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+
127313 fn mouse_down(
128314 editor: &mut Editor,
129315 event: &MouseDownEvent,
@@ -349,7 +535,7 @@ impl EditorElement { · 219 unmodified lines
349535 gutter_bounds: Bounds<Pixels>,
350536 text_bounds: Bounds<Pixels>,
351537 layout: &LayoutState,
352− cx: &mut ViewContext<Editor>,
538+ cx: &mut WindowContext,
353539 ) {
354540 let bounds = gutter_bounds.union(&text_bounds);
355541 let scroll_top =
@@ -459,8 +645,7 @@ impl EditorElement { · 103 unmodified lines
459645 &mut self,
460646 bounds: Bounds<Pixels>,
461647 layout: &mut LayoutState,
462− editor: &mut Editor,
463− cx: &mut ViewContext<Editor>,
648+ cx: &mut WindowContext,
464649 ) {
465650 let line_height = layout.position_map.line_height;
466651
@@ -488,13 +673,14 @@ impl EditorElement { · 21 unmodified lines
488673 }
489674 }
490675
491− for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() {
492− if let Some(fold_indicator) = fold_indicator.as_mut() {
676+ for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
677+ if let Some(mut fold_indicator) = fold_indicator {
678+ let mut fold_indicator = fold_indicator.into_any_element();
493679 let available_space = size(
494680 AvailableSpace::MinContent,
495681 AvailableSpace::Definite(line_height * 0.55),
496682 );
497− let fold_indicator_size = fold_indicator.measure(available_space, editor, cx);
683+ let fold_indicator_size = fold_indicator.measure(available_space, cx);
498684
499685 let position = point(
500686 bounds.size.width - layout.gutter_padding,
@@ -505,32 +691,29 @@ impl EditorElement { · 4 unmodified lines
505691 (line_height - fold_indicator_size.height) / 2.,
506692 );
507693 let origin = bounds.origin + position + centering_offset;
508− fold_indicator.draw(origin, available_space, editor, cx);
694+ fold_indicator.draw(origin, available_space, cx);
509695 }
510696 }
511697
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();
513700 let available_space = size(
514701 AvailableSpace::MinContent,
515702 AvailableSpace::Definite(line_height),
516703 );
517− let indicator_size = indicator.element.measure(available_space, editor, cx);
704+ let indicator_size = button.measure(available_space, cx);
705+
518706 let mut x = Pixels::ZERO;
519707 let mut y = indicator.row as f32 * line_height - scroll_top;
520708 // Center indicator.
521709 x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
522710 y += (line_height - indicator_size.height) / 2.;
523− indicator
524− .element
525− .draw(bounds.origin + point(x, y), available_space, editor, cx);
711+
712+ button.draw(bounds.origin + point(x, y), available_space, cx);
526713 }
527714 }
528715
529− fn paint_diff_hunks(
530− bounds: Bounds<Pixels>,
531− layout: &LayoutState,
532− cx: &mut ViewContext<Editor>,
533− ) {
716+ fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
534717 // todo!()
535718 // let diff_style = &theme::current(cx).editor.diff.clone();
536719 // let line_height = layout.position_map.line_height;
@@ -618,14 +801,19 @@ impl EditorElement { · 81 unmodified lines
618801 &mut self,
619802 text_bounds: Bounds<Pixels>,
620803 layout: &mut LayoutState,
621− editor: &mut Editor,
622− cx: &mut ViewContext<Editor>,
804+ cx: &mut WindowContext,
623805 ) {
624806 let scroll_position = layout.position_map.snapshot.scroll_position();
625807 let start_row = layout.visible_display_row_range.start;
626808 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
627809 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;
629817
630818 cx.with_content_mask(
631819 Some(ContentMask {
@@ -674,20 +862,22 @@ impl EditorElement { · 42 unmodified lines
674862 div()
675863 .id(fold.id)
676864 .size_full()
677− .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
678− .on_click(move |editor: &mut Editor, _, cx| {
679− editor.unfold_ranges(
680− [fold_range.start..fold_range.end],
681− true,
682− false,
683− cx,
684− );
685− cx.stop_propagation();
686− })
865+ .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
866+ .on_click(cx.listener_for(
867+ &self.editor,
868+ move |editor: &mut Editor, _, cx| {
869+ editor.unfold_ranges(
870+ [fold_range.start..fold_range.end],
871+ true,
872+ false,
873+ cx,
874+ );
875+ cx.stop_propagation();
876+ },
877+ ))
687878 .draw(
688879 fold_bounds.origin,
689880 fold_bounds.size,
690− editor,
691881 cx,
692882 |fold_element_state, cx| {
693883 if fold_element_state.is_active() {
@@ -748,7 +938,7 @@ impl EditorElement { · 54 unmodified lines
748938 invisible_display_ranges.push(selection.range.clone());
749939 }
750940
751− if !selection.is_local || editor.show_local_cursors(cx) {
941+ if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) {
752942 let cursor_position = selection.head;
753943 if layout
754944 .visible_display_row_range
@@ -800,12 +990,14 @@ impl EditorElement { · 45 unmodified lines
800990 * layout.position_map.line_height
801991 - layout.position_map.scroll_position.y;
802992 if selection.is_newest {
803− editor.pixel_position_of_newest_cursor = Some(point(
804− text_bounds.origin.x + x + block_width / 2.,
805− text_bounds.origin.y
806− + y
807− + layout.position_map.line_height / 2.,
808− ));
993+ self.editor.update(cx, |editor, _| {
994+ editor.pixel_position_of_newest_cursor = Some(point(
995+ text_bounds.origin.x + x + block_width / 2.,
996+ text_bounds.origin.y
997+ + y
998+ + layout.position_map.line_height / 2.,
999+ ))
1000+ });
8091001 }
8101002 cursors.push(Cursor {
8111003 color: selection_style.cursor,
@@ -840,17 +1032,11 @@ impl EditorElement { · 28 unmodified lines
8401032 }
8411033 });
8421034
843− if let Some((position, context_menu)) = layout.context_menu.as_mut() {
844− cx.with_z_index(1, |cx| {
845− let line_height = self.style.text.line_height_in_pixels(cx.rem_size());
846− let available_space = size(
847− AvailableSpace::MinContent,
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);
1035+ cx.with_z_index(1, |cx| {
1036+ if let Some((position, mut context_menu)) = layout.context_menu.take() {
1037+ let available_space =
1038+ size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1039+ let context_menu_size = context_menu.measure(available_space, cx);
8541040
8551041 let cursor_row_layout = &layout.position_map.line_layouts
8561042 [(position.row() - start_row) as usize]
@@ -871,84 +1057,77 @@ impl EditorElement { · 14 unmodified lines
8711057 }
8721058
8731059 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;
8751061 }
8761062
877− context_menu.draw(list_origin, available_space, editor, cx);
878− })
879− }
1063+ cx.break_content_mask(|cx| {
1064+ context_menu.draw(list_origin, available_space, cx)
1065+ });
1066+ }
8801067
881− // if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
882− // cx.scene().push_stacking_context(None, None);
883−
884− // // This is safe because we check on layout whether the required row is available
885− // let hovered_row_layout =
886− // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
887−
888− // // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
889− // // height. This is the size we will use to decide whether to render popovers above or below
890− // // the hovered line.
891− // let first_size = hover_popovers[0].size();
892− // let height_to_reserve = first_size.y
893− // + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height;
894−
895− // // Compute Hovered Point
896− // let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
897− // let y = position.row() as f32 * layout.position_map.line_height - scroll_top;
898− // let hovered_point = content_origin + point(x, y);
899−
900− // if hovered_point.y - height_to_reserve > 0.0 {
901− // // There is enough space above. Render popovers above the hovered point
902− // let mut current_y = hovered_point.y;
903− // for hover_popover in hover_popovers {
904− // let size = hover_popover.size();
905− // let mut popover_origin = point(hovered_point.x, current_y - size.y);
906−
907− // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x);
908− // if x_out_of_bounds < 0.0 {
909− // popover_origin.set_x(popover_origin.x + x_out_of_bounds);
910− // }
911−
912− // hover_popover.paint(
913− // popover_origin,
914− // Bounds::<Pixels>::from_points(
915− // gpui::Point::<Pixels>::zero(),
916− // point(f32::MAX, f32::MAX),
917− // ), // Let content bleed outside of editor
918− // editor,
919− // cx,
920− // );
921−
922− // current_y = popover_origin.y - HOVER_POPOVER_GAP;
923− // }
924− // } else {
925− // // 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;
927− // for hover_popover in hover_popovers {
928− // let size = hover_popover.size();
929− // let mut popover_origin = point(hovered_point.x, current_y);
930−
931− // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x);
932− // if x_out_of_bounds < 0.0 {
933− // popover_origin.set_x(popover_origin.x + x_out_of_bounds);
934− // }
935−
936− // hover_popover.paint(
937− // popover_origin,
938− // Bounds::<Pixels>::from_points(
939− // gpui::Point::<Pixels>::zero(),
940− // point(f32::MAX, f32::MAX),
941− // ), // Let content bleed outside of editor
942− // editor,
943− // cx,
944− // );
945−
946− // current_y = popover_origin.y + size.y + HOVER_POPOVER_GAP;
947− // }
948− // }
949−
950− // cx.scene().pop_stacking_context();
951− // }
1068+ if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() {
1069+ let available_space =
1070+ size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1071+
1072+ // This is safe because we check on layout whether the required row is available
1073+ let hovered_row_layout = &layout.position_map.line_layouts
1074+ [(position.row() - start_row) as usize]
1075+ .line;
1076+
1077+ // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
1078+ // height. This is the size we will use to decide whether to render popovers above or below
1079+ // the hovered line.
1080+ let first_size = hover_popovers[0].measure(available_space, cx);
1081+ let height_to_reserve = first_size.height
1082+ + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height;
1083+
1084+ // Compute Hovered Point
1085+ let x = hovered_row_layout.x_for_index(position.column() as usize)
1086+ - layout.position_map.scroll_position.x;
1087+ let y = position.row() as f32 * layout.position_map.line_height
1088+ - layout.position_map.scroll_position.y;
1089+ let hovered_point = content_origin + point(x, y);
1090+
1091+ if hovered_point.y - height_to_reserve > Pixels::ZERO {
1092+ // There is enough space above. Render popovers above the hovered point
1093+ let mut current_y = hovered_point.y;
1094+ for mut hover_popover in hover_popovers {
1095+ let size = hover_popover.measure(available_space, cx);
1096+ let mut popover_origin =
1097+ point(hovered_point.x, current_y - size.height);
1098+
1099+ let x_out_of_bounds =
1100+ text_bounds.upper_right().x - (popover_origin.x + size.width);
1101+ if x_out_of_bounds < Pixels::ZERO {
1102+ popover_origin.x = popover_origin.x + x_out_of_bounds;
1103+ }
1104+
1105+ cx.break_content_mask(|cx| {
1106+ hover_popover.draw(popover_origin, available_space, cx)
1107+ });
1108+
1109+ current_y = popover_origin.y - HOVER_POPOVER_GAP;
1110+ }
1111+ } else {
1112+ // There is not enough space above. Render popovers below the hovered point
1113+ let mut current_y = hovered_point.y + layout.position_map.line_height;
1114+ for mut hover_popover in hover_popovers {
1115+ let size = hover_popover.measure(available_space, cx);
1116+ let mut popover_origin = point(hovered_point.x, current_y);
1117+
1118+ let x_out_of_bounds =
1119+ text_bounds.upper_right().x - (popover_origin.x + size.width);
1120+ if x_out_of_bounds < Pixels::ZERO {
1121+ popover_origin.x = popover_origin.x + x_out_of_bounds;
1122+ }
1123+
1124+ hover_popover.draw(popover_origin, available_space, cx);
1125+
1126+ current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
1127+ }
1128+ }
1129+ }
1130+ })
9521131 },
9531132 )
9541133 }
@@ -1165,7 +1344,7 @@ impl EditorElement { · 210 unmodified lines
11651344 layout: &LayoutState,
11661345 content_origin: gpui::Point<Pixels>,
11671346 bounds: Bounds<Pixels>,
1168− cx: &mut ViewContext<Editor>,
1347+ cx: &mut WindowContext,
11691348 ) {
11701349 let start_row = layout.visible_display_row_range.start;
11711350 let end_row = layout.visible_display_row_range.end;
@@ -1217,14 +1396,13 @@ impl EditorElement { · 45 unmodified lines
12171396 &mut self,
12181397 bounds: Bounds<Pixels>,
12191398 layout: &mut LayoutState,
1220− editor: &mut Editor,
1221− cx: &mut ViewContext<Editor>,
1399+ cx: &mut WindowContext,
12221400 ) {
12231401 let scroll_position = layout.position_map.snapshot.scroll_position();
12241402 let scroll_left = scroll_position.x * layout.position_map.em_width;
12251403 let scroll_top = scroll_position.y * layout.position_map.line_height;
12261404
1227− for block in &mut layout.blocks {
1405+ for block in layout.blocks.drain(..) {
12281406 let mut origin = bounds.origin
12291407 + point(
12301408 Pixels::ZERO,
@@ -1233,13 +1411,11 @@ impl EditorElement { · 2 unmodified lines
12331411 if !matches!(block.style, BlockStyle::Sticky) {
12341412 origin += point(-scroll_left, Pixels::ZERO);
12351413 }
1236− block
1237− .element
1238− .draw(origin, block.available_space, editor, cx);
1414+ block.element.draw(origin, block.available_space, cx);
12391415 }
12401416 }
12411417
1242− fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels {
1418+ fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
12431419 let style = &self.style;
12441420 let font_size = style.text.font_size.to_pixels(cx.rem_size());
12451421 let layout = cx
@@ -1260,7 +1436,7 @@ impl EditorElement { · 14 unmodified lines
12601436 layout.width
12611437 }
12621438
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 {
12641440 let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
12651441 self.column_pixels(digit_count, cx)
12661442 }
@@ -1415,7 +1591,7 @@ impl EditorElement { · 148 unmodified lines
14151591 }
14161592
14171593 fn layout_lines(
1418− &mut self,
1594+ &self,
14191595 rows: Range<u32>,
14201596 line_number_layouts: &[Option<ShapedLine>],
14211597 snapshot: &EditorSnapshot,
@@ -1471,483 +1647,457 @@ impl EditorElement { · 49 unmodified lines
14711647
14721648 fn compute_layout(
14731649 &mut self,
1474− editor: &mut Editor,
1475− cx: &mut ViewContext<'_, Editor>,
14761650 mut bounds: Bounds<Pixels>,
1651+ cx: &mut WindowContext,
14771652 ) -> LayoutState {
1478− // let mut size = constraint.max;
1479− // if size.x.is_infinite() {
1480− // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
1481− // }
1482−
1483− let snapshot = editor.snapshot(cx);
1484− let style = self.style.clone();
1653+ self.editor.update(cx, |editor, cx| {
1654+ // let mut size = constraint.max;
1655+ // if size.x.is_infinite() {
1656+ // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
1657+ // }
1658+
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+ };
14851692
1486− let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
1487− let font_size = style.text.font_size.to_pixels(cx.rem_size());
1488− let line_height = style.text.line_height_in_pixels(cx.rem_size());
1489− let em_width = cx
1490− .text_system()
1491− .typographic_bounds(font_id, font_size, 'm')
1492− .unwrap()
1493− .size
1494− .width;
1495− let em_advance = cx
1496− .text_system()
1497− .advance(font_id, font_size, 'm')
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− };
1693+ editor.gutter_width = gutter_width;
1694+ let text_width = bounds.size.width - gutter_width;
1695+ let overscroll = size(em_width, px(0.));
1696+ let snapshot = {
1697+ editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
1698+
1699+ let editor_width = text_width - gutter_margin - overscroll.width - em_width;
1700+ let wrap_width = match editor.soft_wrap_mode(cx) {
1701+ SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
1702+ SoftWrap::EditorWidth => editor_width,
1703+ SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
1704+ };
15161705
1517− editor.gutter_width = gutter_width;
1518− let text_width = bounds.size.width - gutter_width;
1519− let overscroll = size(em_width, px(0.));
1520− let snapshot = {
1521− editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
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),
1706+ if editor.set_wrap_width(Some(wrap_width), cx) {
1707+ editor.snapshot(cx)
1708+ } else {
1709+ snapshot
1710+ }
15281711 };
15291712
1530− if editor.set_wrap_width(Some(wrap_width), cx) {
1531− editor.snapshot(cx)
1713+ let wrap_guides = editor
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()
15321758 } else {
15331759 snapshot
1534− }
1535− };
1760+ .buffer_snapshot
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+ };
15361770
1537− let wrap_guides = editor
1538− .wrap_guides(cx)
1539− .iter()
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− };
1771+ let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
1772+ let mut active_rows = BTreeMap::new();
1773+ let is_singleton = editor.is_singleton(cx);
15941774
1595− let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
1596− let mut active_rows = BTreeMap::new();
1597− let is_singleton = editor.is_singleton(cx);
1775+ let highlighted_rows = editor.highlighted_rows();
1776+ let highlighted_ranges = editor.background_highlights_in_range(
1777+ start_anchor..end_anchor,
1778+ &snapshot.display_snapshot,
1779+ cx.theme().colors(),
1780+ );
15981781
1599− let highlighted_rows = editor.highlighted_rows();
1600− let highlighted_ranges = editor.background_highlights_in_range(
1601− start_anchor..end_anchor,
1602− &snapshot.display_snapshot,
1603− cx.theme().colors(),
1604− );
1782+ let mut newest_selection_head = None;
1783+
1784+ if editor.show_local_selections {
1785+ let mut local_selections: Vec<Selection<Point>> = editor
1786+ .selections
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+ }
16051806
1606− let mut newest_selection_head = None;
1607−
1608− if editor.show_local_selections {
1609− let mut local_selections: Vec<Selection<Point>> = editor
1610− .selections
1611− .disjoint_in_range(start_anchor..end_anchor, cx);
1612− local_selections.extend(editor.selections.pending(cx));
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);
1807+ for row in cmp::max(layout.active_rows.start, start_row)
1808+ ..=cmp::min(layout.active_rows.end, end_row)
1809+ {
1810+ let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
1811+ *contains_non_empty_selection |= !is_empty;
1812+ }
1813+ layouts.push(layout);
16291814 }
16301815
1631− for row in cmp::max(layout.active_rows.start, start_row)
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);
1816+ selections.push((style.local_player, layouts));
16381817 }
16391818
1640− selections.push((style.local_player, layouts));
1641− }
1642−
1643− if let Some(collaboration_hub) = &editor.collaboration_hub {
1644− // When following someone, render the local selections in their color.
1645− if let Some(leader_id) = editor.leader_peer_id {
1646− if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
1647− if let Some(participant_index) = collaboration_hub
1648− .user_participant_indices(cx)
1649− .get(&collaborator.user_id)
1650− {
1651− if let Some((local_selection_style, _)) = selections.first_mut() {
1652− *local_selection_style = cx
1653− .theme()
1654− .players()
1655− .color_for_participant(participant_index.0);
1819+ if let Some(collaboration_hub) = &editor.collaboration_hub {
1820+ // When following someone, render the local selections in their color.
1821+ if let Some(leader_id) = editor.leader_peer_id {
1822+ if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
1823+ if let Some(participant_index) = collaboration_hub
1824+ .user_participant_indices(cx)
1825+ .get(&collaborator.user_id)
1826+ {
1827+ if let Some((local_selection_style, _)) = selections.first_mut() {
1828+ *local_selection_style = cx
1829+ .theme()
1830+ .players()
1831+ .color_for_participant(participant_index.0);
1832+ }
16561833 }
16571834 }
16581835 }
1659− }
16601836
1661− let mut remote_selections = HashMap::default();
1662− for selection in snapshot.remote_selections_in_range(
1663− &(start_anchor..end_anchor),
1664− collaboration_hub.as_ref(),
1665− cx,
1666− ) {
1667− let selection_style = if let Some(participant_index) = selection.participant_index {
1668− cx.theme()
1669− .players()
1670− .color_for_participant(participant_index.0)
1671− } else {
1672− cx.theme().players().absent()
1673− };
1837+ let mut remote_selections = HashMap::default();
1838+ for selection in snapshot.remote_selections_in_range(
1839+ &(start_anchor..end_anchor),
1840+ collaboration_hub.as_ref(),
1841+ cx,
1842+ ) {
1843+ let selection_style = if let Some(participant_index) = selection.participant_index {
1844+ cx.theme()
1845+ .players()
1846+ .color_for_participant(participant_index.0)
1847+ } else {
1848+ cx.theme().players().absent()
1849+ };
16741850
1675− // Don't re-render the leader's selections, since the local selections
1676− // match theirs.
1677− if Some(selection.peer_id) == editor.leader_peer_id {
1678− continue;
1851+ // Don't re-render the leader's selections, since the local selections
1852+ // match theirs.
1853+ if Some(selection.peer_id) == editor.leader_peer_id {
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+ ));
16791869 }
16801870
1681− remote_selections
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− ));
1871+ selections.extend(remote_selections.into_values());
16931872 }
16941873
1695− selections.extend(remote_selections.into_values());
1696− }
1697−
1698− let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1699− let show_scrollbars = match scrollbar_settings.show {
1700− ShowScrollbar::Auto => {
1701− // Git
1702− (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
1703− ||
1704− // Selections
1705− (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
1706− // Scrollmanager
1707− || editor.scroll_manager.scrollbars_visible()
1708− }
1709− ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
1710− ShowScrollbar::Always => true,
1711− ShowScrollbar::Never => false,
1712− };
1874+ let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1875+ let show_scrollbars = match scrollbar_settings.show {
1876+ ShowScrollbar::Auto => {
1877+ // Git
1878+ (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
1879+ ||
1880+ // Selections
1881+ (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
1882+ // Scrollmanager
1883+ || editor.scroll_manager.scrollbars_visible()
1884+ }
1885+ ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
1886+ ShowScrollbar::Always => true,
1887+ ShowScrollbar::Never => false,
1888+ };
17131889
1714− let head_for_relative = newest_selection_head.unwrap_or_else(|| {
1715− let newest = editor.selections.newest::<Point>(cx);
1716− SelectionLayout::new(
1717− newest,
1718− editor.selections.line_mode,
1719− editor.cursor_shape,
1720− &snapshot.display_snapshot,
1721− true,
1722− true,
1723− )
1724− .head
1725− });
1890+ let head_for_relative = newest_selection_head.unwrap_or_else(|| {
1891+ let newest = editor.selections.newest::<Point>(cx);
1892+ SelectionLayout::new(
1893+ newest,
1894+ editor.selections.line_mode,
1895+ editor.cursor_shape,
1896+ &snapshot.display_snapshot,
1897+ true,
1898+ true,
1899+ )
1900+ .head
1901+ });
17261902
1727− let (line_numbers, fold_statuses) = self.shape_line_numbers(
1728− start_row..end_row,
1729− &active_rows,
1730− head_for_relative,
1731− is_singleton,
1732− &snapshot,
1733− cx,
1734− );
1903+ let (line_numbers, fold_statuses) = self.shape_line_numbers(
1904+ start_row..end_row,
1905+ &active_rows,
1906+ head_for_relative,
1907+ is_singleton,
1908+ &snapshot,
1909+ cx,
1910+ );
17351911
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);
17371913
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);
17391915
1740− let mut max_visible_line_width = Pixels::ZERO;
1741− let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
1742− for line_with_invisibles in &line_layouts {
1743− if line_with_invisibles.line.width > max_visible_line_width {
1744− max_visible_line_width = line_with_invisibles.line.width;
1916+ let mut max_visible_line_width = Pixels::ZERO;
1917+ let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
1918+ for line_with_invisibles in &line_layouts {
1919+ if line_with_invisibles.line.width > max_visible_line_width {
1920+ max_visible_line_width = line_with_invisibles.line.width;
1921+ }
17451922 }
1746− }
17471923
1748− let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
1749− .unwrap()
1750− .width;
1751− let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
1924+ let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
1925+ .unwrap()
1926+ .width;
1927+ let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
17521928
1753− let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
1754− self.layout_blocks(
1755− start_row..end_row,
1756− &snapshot,
1757− bounds.size.width,
1758− scroll_width,
1759− gutter_padding,
1760− gutter_width,
1761− em_width,
1762− gutter_width + gutter_margin,
1763− line_height,
1764− &style,
1765− &line_layouts,
1766− editor,
1767− cx,
1768− )
1769− });
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− );
1929+ let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
1930+ self.layout_blocks(
1931+ start_row..end_row,
1932+ &snapshot,
1933+ bounds.size.width,
1934+ scroll_width,
1935+ gutter_padding,
1936+ gutter_width,
1937+ em_width,
1938+ gutter_width + gutter_margin,
1939+ line_height,
1940+ &style,
1941+ &line_layouts,
1942+ editor,
1943+ cx,
1944+ )
1945+ });
17751946
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+ );
17771951
1778− let autoscrolled = if autoscroll_horizontally {
1779− editor.autoscroll_horizontally(
1780− start_row,
1781− text_size.width,
1782− scroll_width,
1783− em_width,
1784− &line_layouts,
1785− cx,
1786− )
1787− } else {
1788− false
1789− };
1952+ let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
1953+
1954+ let autoscrolled = if autoscroll_horizontally {
1955+ editor.autoscroll_horizontally(
1956+ start_row,
1957+ text_size.width,
1958+ scroll_width,
1959+ em_width,
1960+ &line_layouts,
1961+ cx,
1962+ )
1963+ } else {
1964+ false
1965+ };
17901966
1791− if clamped || autoscrolled {
1792− snapshot = editor.snapshot(cx);
1793− }
1967+ if clamped || autoscrolled {
1968+ snapshot = editor.snapshot(cx);
1969+ }
17941970
1795− let mut context_menu = None;
1796− let mut code_actions_indicator = None;
1797− if let Some(newest_selection_head) = newest_selection_head {
1798− if (start_row..end_row).contains(&newest_selection_head.row()) {
1799− if editor.context_menu_visible() {
1800− context_menu =
1801− editor.render_context_menu(newest_selection_head, &self.style, cx);
1802− }
1971+ let mut context_menu = None;
1972+ let mut code_actions_indicator = None;
1973+ if let Some(newest_selection_head) = newest_selection_head {
1974+ if (start_row..end_row).contains(&newest_selection_head.row()) {
1975+ if editor.context_menu_visible() {
1976+ let max_height = (12. * line_height).min((bounds.size.height - line_height) / 2.);
1977+ context_menu =
1978+ editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
1979+ }
18031980
1804− let active = matches!(
1805− editor.context_menu.read().as_ref(),
1806− Some(crate::ContextMenu::CodeActions(_))
1807− );
1981+ let active = matches!(
1982+ editor.context_menu.read().as_ref(),
1983+ Some(crate::ContextMenu::CodeActions(_))
1984+ );
18081985
1809− code_actions_indicator = editor
1810− .render_code_actions_indicator(&style, active, cx)
1811− .map(|element| CodeActionsIndicator {
1812− row: newest_selection_head.row(),
1813− element,
1814− });
1986+ code_actions_indicator = editor
1987+ .render_code_actions_indicator(&style, active, cx)
1988+ .map(|element| CodeActionsIndicator {
1989+ row: newest_selection_head.row(),
1990+ button: element,
1991+ });
1992+ }
18151993 }
1816− }
18171994
1818− let visible_rows = start_row..start_row + line_layouts.len() as u32;
1819− // todo!("hover")
1820− // let mut hover = editor.hover_state.render(
1821− // &snapshot,
1822− // &style,
1823− // visible_rows,
1824− // editor.workspace.as_ref().map(|(w, _)| w.clone()),
1825− // cx,
1826− // );
1827− // let mode = editor.mode;
1828−
1829− let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
1830− editor.render_fold_indicators(
1831− fold_statuses,
1995+ let visible_rows = start_row..start_row + line_layouts.len() as u32;
1996+ let max_size = size(
1997+ (120. * em_width) // Default size
1998+ .min(bounds.size.width / 2.) // Shrink to half of the editor width
1999+ .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
2000+ (16. * line_height) // Default size
2001+ .min(bounds.size.height / 2.) // Shrink to half of the editor height
2002+ .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
2003+ );
2004+
2005+ let mut hover = editor.hover_state.render(
2006+ &snapshot,
18322007 &style,
1833− editor.gutter_hovered,
1834− line_height,
1835− gutter_margin,
2008+ visible_rows,
2009+ max_size,
2010+ editor.workspace.as_ref().map(|(w, _)| w.clone()),
18362011 cx,
1837− )
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− // }
2012+ );
18752013
1876− let invisible_symbol_font_size = font_size / 2.;
1877− let tab_invisible = cx
1878− .text_system()
1879− .shape_line(
1880− "→".into(),
1881− invisible_symbol_font_size,
1882− &[TextRun {
1883− len: "→".len(),
1884− font: self.style.text.font(),
1885− color: cx.theme().colors().editor_invisible,
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();
2014+ let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
2015+ editor.render_fold_indicators(
2016+ fold_statuses,
2017+ &style,
2018+ editor.gutter_hovered,
2019+ line_height,
2020+ gutter_margin,
2021+ cx,
2022+ )
2023+ });
19052024
1906− LayoutState {
1907− mode: editor_mode,
1908− position_map: Arc::new(PositionMap {
1909− size: bounds.size,
1910− scroll_position: point(
1911− scroll_position.x * em_width,
1912− scroll_position.y * line_height,
1913− ),
1914− scroll_max,
1915− line_layouts,
1916− line_height,
1917− em_width,
1918− em_advance,
1919− snapshot,
1920− }),
1921− visible_anchor_range: start_anchor..end_anchor,
1922− visible_display_row_range: start_row..end_row,
1923− wrap_guides,
1924− gutter_size,
1925− gutter_padding,
1926− text_size,
1927− scrollbar_row_range,
1928− show_scrollbars,
1929− is_singleton,
1930− max_row,
1931− gutter_margin,
1932− active_rows,
1933− highlighted_rows,
1934− highlighted_ranges,
1935− line_numbers,
1936− display_hunks,
1937− blocks,
1938− selections,
1939− context_menu,
1940− code_actions_indicator,
1941− fold_indicators,
1942− tab_invisible,
1943− space_invisible,
1944− // hover_popovers: hover,
1945− }
2025+ let invisible_symbol_font_size = font_size / 2.;
2026+ let tab_invisible = cx
2027+ .text_system()
2028+ .shape_line(
2029+ "→".into(),
2030+ invisible_symbol_font_size,
2031+ &[TextRun {
2032+ len: "→".len(),
2033+ font: self.style.text.font(),
2034+ color: cx.theme().colors().editor_invisible,
2035+ background_color: None,
2036+ underline: None,
2037+ }],
2038+ )
2039+ .unwrap();
2040+ let space_invisible = cx
2041+ .text_system()
2042+ .shape_line(
2043+ "•".into(),
2044+ invisible_symbol_font_size,
2045+ &[TextRun {
2046+ len: "•".len(),
2047+ font: self.style.text.font(),
2048+ color: cx.theme().colors().editor_invisible,
2049+ background_color: None,
2050+ underline: None,
2051+ }],
2052+ )
2053+ .unwrap();
2054+
2055+ LayoutState {
2056+ mode: editor_mode,
2057+ position_map: Arc::new(PositionMap {
2058+ size: bounds.size,
2059+ scroll_position: point(
2060+ scroll_position.x * em_width,
2061+ scroll_position.y * line_height,
2062+ ),
2063+ scroll_max,
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+ })
19462096 }
19472097
19482098 #[allow(clippy::too_many_arguments)]
19492099 fn layout_blocks(
1950− &mut self,
2100+ &self,
19512101 rows: Range<u32>,
19522102 snapshot: &EditorSnapshot,
19532103 editor_width: Pixels,
@@ -2028,12 +2178,10 @@ impl EditorElement { · 74 unmodified lines
20282178 let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
20292179
20302180 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| {
20322182 editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
2033− })
2034− .tooltip(move |_, cx| {
2035− Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx)
2036− })
2183+ }))
2184+ .tooltip(|cx| Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx))
20372185 });
20382186
20392187 let element = if *starts_new_buffer {
@@ -2043,15 +2191,20 @@ impl EditorElement { · 3 unmodified lines
20432191 // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
20442192 if let Some(path) = path {
20452193 filename = path.file_name().map(|f| f.to_string_lossy().to_string());
2046− parent_path =
2047− path.parent().map(|p| p.to_string_lossy().to_string() + "/");
2194+ parent_path = path
2195+ .parent()
2196+ .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
20482197 }
20492198
20502199 h_stack()
20512200 .id("path header block")
20522201 .size_full()
20532202 .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+ )
20552208 .children(parent_path)
20562209 .children(jump_icon) // .p_x(gutter_padding)
20572210 } else {
@@ -2063,11 +2216,11 @@ impl EditorElement { · 5 unmodified lines
20632216 .child("⋯")
20642217 .children(jump_icon) // .p_x(gutter_padding)
20652218 };
2066− element.render()
2219+ element.into_any()
20672220 }
20682221 };
20692222
2070− let size = element.measure(available_space, editor, cx);
2223+ let size = element.measure(available_space, cx);
20712224 (element, size)
20722225 };
20732226
@@ -2126,47 +2279,61 @@ impl EditorElement { · 52 unmodified lines
21262279 gutter_bounds: Bounds<Pixels>,
21272280 text_bounds: Bounds<Pixels>,
21282281 layout: &LayoutState,
2129− cx: &mut ViewContext<Editor>,
2282+ cx: &mut WindowContext,
21302283 ) {
21312284 let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
21322285
21332286 cx.on_mouse_event({
21342287 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| {
21362291 if phase != DispatchPhase::Bubble {
21372292 return;
21382293 }
21392294
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 {
21412299 cx.stop_propagation();
21422300 }
21432301 }
21442302 });
2303+
21452304 cx.on_mouse_event({
21462305 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| {
21482309 if phase != DispatchPhase::Bubble {
21492310 return;
21502311 }
21512312
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 {
21532318 cx.stop_propagation()
21542319 }
21552320 }
21562321 });
2322+
21572323 cx.on_mouse_event({
21582324 let position_map = layout.position_map.clone();
2159− move |editor, event: &MouseUpEvent, phase, cx| {
2160− if phase != DispatchPhase::Bubble {
2161− return;
2162− }
2325+ let editor = self.editor.clone();
2326+ move |event: &MouseUpEvent, phase, cx| {
2327+ let should_cancel = editor.update(cx, |editor, cx| {
2328+ Self::mouse_up(editor, event, &position_map, text_bounds, cx)
2329+ });
21632330
2164− if Self::mouse_up(editor, event, &position_map, text_bounds, cx) {
2331+ if should_cancel {
21652332 cx.stop_propagation()
21662333 }
21672334 }
21682335 });
2169− // todo!()
2336+ //todo!()
21702337 // on_down(MouseButton::Right, {
21712338 // let position_map = layout.position_map.clone();
21722339 // move |event, editor, cx| {
@@ -2183,12 +2350,17 @@ impl EditorElement { · 10 unmodified lines
21832350 // });
21842351 cx.on_mouse_event({
21852352 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| {
21872355 if phase != DispatchPhase::Bubble {
21882356 return;
21892357 }
21902358
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 {
21922364 cx.stop_propagation()
21932365 }
21942366 }
@@ -2267,7 +2439,7 @@ impl LineWithInvisibles { · 72 unmodified lines
22672439 len: line_chunk.len(),
22682440 font: text_style.font(),
22692441 color: text_style.color,
2270− background_color: None,
2442+ background_color: text_style.background_color,
22712443 underline: text_style.underline,
22722444 });
22732445
@@ -2317,7 +2489,7 @@ impl LineWithInvisibles { · 43 unmodified lines
23172489 content_origin: gpui::Point<Pixels>,
23182490 whitespace_setting: ShowWhitespaceSetting,
23192491 selection_ranges: &[Range<DisplayPoint>],
2320− cx: &mut ViewContext<Editor>,
2492+ cx: &mut WindowContext,
23212493 ) {
23222494 let line_height = layout.position_map.line_height;
23232495 let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
@@ -2349,7 +2521,7 @@ impl LineWithInvisibles { · 25 unmodified lines
23492521 row: u32,
23502522 line_height: Pixels,
23512523 whitespace_setting: ShowWhitespaceSetting,
2352− cx: &mut ViewContext<Editor>,
2524+ cx: &mut WindowContext,
23532525 ) {
23542526 let allowed_invisibles_regions = match whitespace_setting {
23552527 ShowWhitespaceSetting::None => return,
@@ -2392,41 +2564,42 @@ enum Invisible { · 36 unmodified lines
23922564 Whitespace { line_offset: usize },
23932565 }
23942566
2395−impl Element<Editor> for EditorElement {
2396− type ElementState = ();
2397−
2398− fn element_id(&self) -> Option<gpui::ElementId> {
2399− Some(self.editor_id.into())
2400− }
2567+impl Element for EditorElement {
2568+ type State = ();
24012569
24022570 fn layout(
24032571 &mut self,
2404− editor: &mut Editor,
2405− element_state: Option<Self::ElementState>,
2406− cx: &mut gpui::ViewContext<Editor>,
2407− ) -> (gpui::LayoutId, Self::ElementState) {
2408− editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
2409−
2410− let rem_size = cx.rem_size();
2411− let mut style = Style::default();
2412− style.size.width = relative(1.).into();
2413− style.size.height = match editor.mode {
2414− EditorMode::SingleLine => self.style.text.line_height_in_pixels(cx.rem_size()).into(),
2415− EditorMode::AutoHeight { .. } => todo!(),
2416− EditorMode::Full => relative(1.).into(),
2417− };
2418− let layout_id = cx.request_layout(&style, None);
2419− (layout_id, ())
2572+ element_state: Option<Self::State>,
2573+ cx: &mut gpui::WindowContext,
2574+ ) -> (gpui::LayoutId, Self::State) {
2575+ self.editor.update(cx, |editor, cx| {
2576+ editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
2577+
2578+ let rem_size = cx.rem_size();
2579+ let mut style = Style::default();
2580+ style.size.width = relative(1.).into();
2581+ style.size.height = match editor.mode {
2582+ EditorMode::SingleLine => {
2583+ self.style.text.line_height_in_pixels(cx.rem_size()).into()
2584+ }
2585+ EditorMode::AutoHeight { .. } => todo!(),
2586+ EditorMode::Full => relative(1.).into(),
2587+ };
2588+ let layout_id = cx.request_layout(&style, None);
2589+
2590+ (layout_id, ())
2591+ })
24202592 }
24212593
24222594 fn paint(
2423− &mut self,
2595+ mut self,
24242596 bounds: Bounds<gpui::Pixels>,
2425− editor: &mut Editor,
2426− element_state: &mut Self::ElementState,
2427− cx: &mut gpui::ViewContext<Editor>,
2597+ element_state: &mut Self::State,
2598+ cx: &mut gpui::WindowContext,
24282599 ) {
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);
24302603 let gutter_bounds = Bounds {
24312604 origin: bounds.origin,
24322605 size: layout.gutter_size,
@@ -2436,43 +2609,46 @@ impl Element<Editor> for EditorElement { · 3 unmodified lines
24362609 size: layout.text_size,
24372610 };
24382611
2439− let dispatch_context = editor.dispatch_context(cx);
2440− cx.with_key_dispatch(
2441− dispatch_context,
2442− Some(editor.focus_handle.clone()),
2443− |_, cx| {
2444− register_actions(cx);
2445−
2446− // We call with_z_index to establish a new stacking context.
2447− cx.with_z_index(0, |cx| {
2448− cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
2449− // Paint mouse listeners first, so any elements we paint on top of the editor
2450− // take precedence.
2451− self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
2452− let input_handler = ElementInputHandler::new(bounds, cx);
2453− cx.handle_input(&editor.focus_handle, input_handler);
2454−
2455− self.paint_background(gutter_bounds, text_bounds, &layout, cx);
2456− if layout.gutter_size.width > Pixels::ZERO {
2457− self.paint_gutter(gutter_bounds, &mut layout, editor, cx);
2458− }
2459− self.paint_text(text_bounds, &mut layout, editor, cx);
2612+ let focus_handle = editor.focus_handle(cx);
2613+ let dispatch_context = self.editor.read(cx).dispatch_context(cx);
2614+ cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
2615+ self.register_actions(cx);
2616+
2617+ // We call with_z_index to establish a new stacking context.
2618+ cx.with_z_index(0, |cx| {
2619+ cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
2620+ // Paint mouse listeners first, so any elements we paint on top of the editor
2621+ // take precedence.
2622+ self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
2623+ let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
2624+ cx.handle_input(&focus_handle, input_handler);
2625+
2626+ self.paint_background(gutter_bounds, text_bounds, &layout, cx);
2627+ if layout.gutter_size.width > Pixels::ZERO {
2628+ self.paint_gutter(gutter_bounds, &mut layout, cx);
2629+ }
2630+ self.paint_text(text_bounds, &mut layout, cx);
24602631
2461− if !layout.blocks.is_empty() {
2462− cx.with_element_id(Some("editor_blocks"), |cx| {
2463− self.paint_blocks(bounds, &mut layout, editor, cx);
2464− })
2465− }
2466− });
2632+ if !layout.blocks.is_empty() {
2633+ cx.with_element_id(Some("editor_blocks"), |cx| {
2634+ self.paint_blocks(bounds, &mut layout, cx);
2635+ })
2636+ }
24672637 });
2468− },
2469− )
2638+ });
2639+ })
24702640 }
24712641 }
24722642
2473−impl Component<Editor> for EditorElement {
2474− fn render(self) -> AnyElement<Editor> {
2475− AnyElement::new(self)
2643+impl IntoElement for EditorElement {
2644+ type Element = 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
24762652 }
24772653 }
24782654
@@ -3097,17 +3273,17 @@ pub struct LayoutState { · 618 unmodified lines
30973273 show_scrollbars: bool,
30983274 is_singleton: bool,
30993275 max_row: u32,
3100− context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
3276+ context_menu: Option<(DisplayPoint, AnyElement)>,
31013277 code_actions_indicator: Option<CodeActionsIndicator>,
3102− // hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
3103− fold_indicators: Vec<Option<AnyElement<Editor>>>,
3278+ hover_popovers: Option<(DisplayPoint, Vec<AnyElement>)>,
3279+ fold_indicators: Vec<Option<IconButton>>,
31043280 tab_invisible: ShapedLine,
31053281 space_invisible: ShapedLine,
31063282 }
31073283
31083284 struct CodeActionsIndicator {
31093285 row: u32,
3110− element: AnyElement<Editor>,
3286+ button: IconButton,
31113287 }
31123288
31133289 struct PositionMap {
@@ -3192,7 +3368,7 @@ impl PositionMap { · 78 unmodified lines
31923368
31933369 struct BlockLayout {
31943370 row: u32,
3195− element: AnyElement<Editor>,
3371+ element: AnyElement,
31963372 available_space: Size<AvailableSpace>,
31973373 style: BlockStyle,
31983374 }
@@ -3897,187 +4073,18 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { · 698 unmodified lines
38974073 // }
38984074 // }
38994075
3900−fn register_actions(cx: &mut ViewContext<Editor>) {
3901− register_action(cx, Editor::move_left);
3902− register_action(cx, Editor::move_right);
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>,
4076+pub fn register_action<T: Action>(
4077+ view: &View<Editor>,
4078+ cx: &mut WindowContext,
40754079 listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
40764080 ) {
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| {
40784083 let action = action.downcast_ref().unwrap();
40794084 if phase == DispatchPhase::Bubble {
4080− listener(editor, action, cx);
4085+ view.update(cx, |editor, cx| {
4086+ listener(editor, action, cx);
4087+ })
40814088 }
40824089 })
40834090 }
crates/editor2/src/hover_popover.rs+929−946
@@ -1,15 +1,21 @@
11 use crate::{
2− display_map::InlayOffset,
2+ display_map::{InlayOffset, ToDisplayPoint},
33 link_go_to_definition::{InlayHighlight, RangeInEditor},
44 Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
55 ExcerptId, RangeToAnchorExt,
66 };
77 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+};
913 use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
14+use lsp::DiagnosticSeverity;
1015 use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project};
1116 use settings::Settings;
1217 use std::{ops::Range, sync::Arc, time::Duration};
18+use ui::Tooltip;
1319 use util::TryFutureExt;
1420 use workspace::Workspace;
1521
@@ -17,22 +23,17 @@ pub const HOVER_DELAY_MILLIS: u64 = 350; · 1 unmodified line
1723 pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
1824
1925 pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
20−pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.;
21−pub const HOVER_POPOVER_GAP: f32 = 10.;
26+pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
27+pub const HOVER_POPOVER_GAP: Pixels = px(10.);
2228
23−// actions!(editor, [Hover]);
29+actions!(Hover);
2430
25−pub fn init(cx: &mut AppContext) {
26− // cx.add_action(hover);
31+/// Bindable action which uses the most recent selection head to trigger a 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);
2735 }
2836
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−
3637 /// The internal hover action dispatches between `show_hover` or `hide_hover`
3738 /// depending on whether a point to hover over is provided.
3839 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
7475 }
7576
7677 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 {
79− // if editor.pending_rename.is_some() {
80− // return;
81− // }
82−
83− // let Some(project) = editor.project.clone() else {
84− // return;
85− // };
86−
87− // if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
88− // if let RangeInEditor::Inlay(range) = symbol_range {
89− // if range == &inlay_hover.range {
90− // // Hover triggered from same location as last time. Don't show again.
91− // return;
92− // }
93− // }
94− // hide_hover(editor, cx);
95− // }
96−
97− // let task = cx.spawn(|this, mut cx| {
98− // async move {
99− // cx.background_executor()
100− // .timer(Duration::from_millis(HOVER_DELAY_MILLIS))
101− // .await;
102− // this.update(&mut cx, |this, _| {
103− // this.hover_state.diagnostic_popover = None;
104− // })?;
105−
106− // let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
107− // let blocks = vec![inlay_hover.tooltip];
108− // let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
109−
110− // let hover_popover = InfoPopover {
111− // project: project.clone(),
112− // symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
113− // blocks,
114− // parsed_content,
115− // };
116−
117− // this.update(&mut cx, |this, cx| {
118− // // Highlight the selected symbol using a background highlight
119− // this.highlight_inlay_background::<HoverState>(
120− // vec![inlay_hover.range],
121− // |theme| theme.editor.hover_popover.highlight,
122− // cx,
123− // );
124− // this.hover_state.info_popover = Some(hover_popover);
125− // cx.notify();
126− // })?;
127−
128− // anyhow::Ok(())
129− // }
130− // .log_err()
131− // });
132−
133− // editor.hover_state.info_task = Some(task);
134− // }
78+ if EditorSettings::get_global(cx).hover_popover_enabled {
79+ if editor.pending_rename.is_some() {
80+ return;
81+ }
82+
83+ let Some(project) = editor.project.clone() else {
84+ return;
85+ };
86+
87+ if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover {
88+ if let RangeInEditor::Inlay(range) = symbol_range {
89+ if range == &inlay_hover.range {
90+ // Hover triggered from same location as last time. Don't show again.
91+ return;
92+ }
93+ }
94+ hide_hover(editor, cx);
95+ }
96+
97+ let task = cx.spawn(|this, mut cx| {
98+ async move {
99+ cx.background_executor()
100+ .timer(Duration::from_millis(HOVER_DELAY_MILLIS))
101+ .await;
102+ this.update(&mut cx, |this, _| {
103+ this.hover_state.diagnostic_popover = None;
104+ })?;
105+
106+ let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
107+ let blocks = vec![inlay_hover.tooltip];
108+ let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
109+
110+ let hover_popover = InfoPopover {
111+ project: project.clone(),
112+ symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
113+ blocks,
114+ parsed_content,
115+ };
116+
117+ this.update(&mut cx, |this, cx| {
118+ // Highlight the selected symbol using a background highlight
119+ this.highlight_inlay_background::<HoverState>(
120+ vec![inlay_hover.range],
121+ |theme| theme.element_hover, // todo!("use a proper background here")
122+ cx,
123+ );
124+ this.hover_state.info_popover = Some(hover_popover);
125+ cx.notify();
126+ })?;
127+
128+ anyhow::Ok(())
129+ }
130+ .log_err()
131+ });
132+
133+ editor.hover_state.info_task = Some(task);
134+ }
135135 }
136136
137137 /// Hides the type information popup.
@@ -420,43 +420,42 @@ impl HoverState { · 282 unmodified lines
420420 snapshot: &EditorSnapshot,
421421 style: &EditorStyle,
422422 visible_rows: Range<u32>,
423+ max_size: Size<Pixels>,
423424 workspace: Option<WeakView<Workspace>>,
424425 cx: &mut ViewContext<Editor>,
425− ) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
426− todo!("old version below")
426+ ) -> Option<(DisplayPoint, Vec<AnyElement>)> {
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))
427458 }
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− // }
460459 }
461460
462461 #[derive(Debug, Clone)]
@@ -467,35 +466,36 @@ pub struct InfoPopover { · 4 unmodified lines
467466 parsed_content: ParsedMarkdown,
468467 }
469468
470−// impl InfoPopover {
471−// pub fn render(
472−// &mut self,
473−// style: &EditorStyle,
474−// workspace: Option<WeakView<Workspace>>,
475−// cx: &mut ViewContext<Editor>,
476−// ) -> AnyElement<Editor> {
477−// MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
478−// Flex::column()
479−// .scrollable::<HoverBlock>(0, None, cx)
480−// .with_child(crate::render_parsed_markdown::<HoverBlock>(
481−// &self.parsed_content,
482−// style,
483−// workspace,
484−// cx,
485−// ))
486−// .contained()
487−// .with_style(style.hover_popover.container)
488−// })
489−// .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
490−// .with_cursor_style(CursorStyle::Arrow)
491−// .with_padding(Padding {
492−// bottom: HOVER_POPOVER_GAP,
493−// top: HOVER_POPOVER_GAP,
494−// ..Default::default()
495−// })
496−// .into_any()
497−// }
498−// }
469+impl InfoPopover {
470+ pub fn render(
471+ &mut self,
472+ style: &EditorStyle,
473+ max_size: Size<Pixels>,
474+ workspace: Option<WeakView<Workspace>>,
475+ cx: &mut ViewContext<Editor>,
476+ ) -> AnyElement {
477+ div()
478+ .id("info_popover")
479+ .overflow_y_scroll()
480+ .bg(gpui::red())
481+ .max_w(max_size.width)
482+ .max_h(max_size.height)
483+ // Prevent a mouse move on the popover from being propagated to the editor,
484+ // because that would dismiss the popover.
485+ .on_mouse_move(|_, cx| cx.stop_propagation())
486+ // Prevent a mouse down on the popover from being propagated to the editor,
487+ // because that would move the cursor.
488+ .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
489+ .child(crate::render_parsed_markdown(
490+ "content",
491+ &self.parsed_content,
492+ style,
493+ workspace,
494+ cx,
495+ ))
496+ .into_any_element()
497+ }
498+}
499499
500500 #[derive(Debug, Clone)]
501501 pub struct DiagnosticPopover {
@@ -504,57 +504,40 @@ pub struct DiagnosticPopover { · 2 unmodified lines
504504 }
505505
506506 impl DiagnosticPopover {
507− pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
508− todo!()
509− // enum PrimaryDiagnostic {}
510−
511− // let mut text_style = style.hover_popover.prose.clone();
512− // text_style.font_size = style.text.font_size;
513− // let diagnostic_source_style = style.hover_popover.diagnostic_source_highlight.clone();
514−
515− // let text = match &self.local_diagnostic.diagnostic.source {
516− // Some(source) => Text::new(
517− // format!("{source}: {}", self.local_diagnostic.diagnostic.message),
518− // text_style,
519− // )
520− // .with_highlights(vec![(0..source.len(), diagnostic_source_style)]),
521−
522− // None => Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style),
523− // };
524−
525− // let container_style = match self.local_diagnostic.diagnostic.severity {
526− // DiagnosticSeverity::HINT => style.hover_popover.info_container,
527− // DiagnosticSeverity::INFORMATION => style.hover_popover.info_container,
528− // DiagnosticSeverity::WARNING => style.hover_popover.warning_container,
529− // DiagnosticSeverity::ERROR => style.hover_popover.error_container,
530− // _ => style.hover_popover.container,
531− // };
532−
533− // let tooltip_style = theme::current(cx).tooltip.clone();
534−
535− // MouseEventHandler::new::<DiagnosticPopover, _>(0, cx, |_, _| {
536− // text.with_soft_wrap(true)
537− // .contained()
538− // .with_style(container_style)
539− // })
540− // .with_padding(Padding {
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()
507+ pub fn render(
508+ &self,
509+ style: &EditorStyle,
510+ max_size: Size<Pixels>,
511+ cx: &mut ViewContext<Editor>,
512+ ) -> AnyElement {
513+ let text = match &self.local_diagnostic.diagnostic.source {
514+ Some(source) => format!("{source}: {}", self.local_diagnostic.diagnostic.message),
515+ None => self.local_diagnostic.diagnostic.message.clone(),
516+ };
517+
518+ let container_bg = crate::diagnostic_style(
519+ self.local_diagnostic.diagnostic.severity,
520+ true,
521+ &style.diagnostic_style,
522+ );
523+
524+ div()
525+ .id("diagnostic")
526+ .overflow_y_scroll()
527+ .bg(container_bg)
528+ .max_w(max_size.width)
529+ .max_h(max_size.height)
530+ .cursor(CursorStyle::PointingHand)
531+ .tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx))
532+ // Prevent a mouse move on the popover from being propagated to the editor,
533+ // because that would dismiss the popover.
534+ .on_mouse_move(|_, cx| cx.stop_propagation())
535+ // Prevent a mouse down on the popover from being propagated to the editor,
536+ // because that would move the cursor.
537+ .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
538+ .on_click(cx.listener(|editor, _, cx| editor.go_to_diagnostic(&Default::default(), cx)))
539+ .child(SharedString::from(text))
540+ .into_any_element()
558541 }
559542
560543 pub fn activation_info(&self) -> (usize, Anchor) {
@@ -567,763 +550,763 @@ impl DiagnosticPopover { · 6 unmodified lines
567550 }
568551 }
569552
570−// #[cfg(test)]
571−// mod tests {
572−// use super::*;
573−// use crate::{
574−// editor_tests::init_test,
575−// element::PointForPosition,
576−// inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
577−// link_go_to_definition::update_inlay_link_and_hover_points,
578−// test::editor_lsp_test_context::EditorLspTestContext,
579−// InlayId,
580−// };
581−// use collections::BTreeSet;
582−// use gpui::fonts::{HighlightStyle, Underline, Weight};
583−// use indoc::indoc;
584−// use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
585−// use lsp::LanguageServerId;
586−// use project::{HoverBlock, HoverBlockKind};
587−// use smol::stream::StreamExt;
588−// use unindent::Unindent;
589−// use util::test::marked_text_ranges;
590−
591−// #[gpui::test]
592−// async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
593−// init_test(cx, |_| {});
594−
595−// let mut cx = EditorLspTestContext::new_rust(
596−// lsp::ServerCapabilities {
597−// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
598−// ..Default::default()
599−// },
600−// cx,
601−// )
602−// .await;
603−
604−// // Basic hover delays and then pops without moving the mouse
605−// cx.set_state(indoc! {"
606−// fn ˇtest() { println!(); }
607−// "});
608−// let hover_point = cx.display_point(indoc! {"
609−// fn test() { printˇln!(); }
610−// "});
611−
612−// cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
613−// assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
614−
615−// // After delay, hover should be visible.
616−// let symbol_range = cx.lsp_range(indoc! {"
617−// fn test() { «println!»(); }
618−// "});
619−// let mut requests =
620−// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
621−// Ok(Some(lsp::Hover {
622−// contents: lsp::HoverContents::Markup(lsp::MarkupContent {
623−// kind: lsp::MarkupKind::Markdown,
624−// value: "some basic docs".to_string(),
625−// }),
626−// range: Some(symbol_range),
627−// }))
628−// });
629−// cx.foreground()
630−// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
631−// requests.next().await;
632−
633−// cx.editor(|editor, _| {
634−// assert!(editor.hover_state.visible());
635−// assert_eq!(
636−// editor.hover_state.info_popover.clone().unwrap().blocks,
637−// vec![HoverBlock {
638−// text: "some basic docs".to_string(),
639−// kind: HoverBlockKind::Markdown,
640−// },]
641−// )
642−// });
643−
644−// // Mouse moved with no hover response dismisses
645−// let hover_point = cx.display_point(indoc! {"
646−// fn teˇst() { println!(); }
647−// "});
648−// let mut request = cx
649−// .lsp
650−// .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
651−// cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
652−// cx.foreground()
653−// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
654−// request.next().await;
655−// cx.editor(|editor, _| {
656−// assert!(!editor.hover_state.visible());
657−// });
658−// }
659−
660−// #[gpui::test]
661−// async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
662−// init_test(cx, |_| {});
663−
664−// let mut cx = EditorLspTestContext::new_rust(
665−// lsp::ServerCapabilities {
666−// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
667−// ..Default::default()
668−// },
669−// cx,
670−// )
671−// .await;
672−
673−// // Hover with keyboard has no delay
674−// cx.set_state(indoc! {"
675−// fˇn test() { println!(); }
676−// "});
677−// cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
678−// let symbol_range = cx.lsp_range(indoc! {"
679−// «fn» test() { println!(); }
680−// "});
681−// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
682−// Ok(Some(lsp::Hover {
683−// contents: lsp::HoverContents::Markup(lsp::MarkupContent {
684−// kind: lsp::MarkupKind::Markdown,
685−// value: "some other basic docs".to_string(),
686−// }),
687−// range: Some(symbol_range),
688−// }))
689−// })
690−// .next()
691−// .await;
692−
693−// cx.condition(|editor, _| editor.hover_state.visible()).await;
694−// cx.editor(|editor, _| {
695−// assert_eq!(
696−// editor.hover_state.info_popover.clone().unwrap().blocks,
697−// vec![HoverBlock {
698−// text: "some other basic docs".to_string(),
699−// kind: HoverBlockKind::Markdown,
700−// }]
701−// )
702−// });
703−// }
704−
705−// #[gpui::test]
706−// async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
707−// init_test(cx, |_| {});
708−
709−// let mut cx = EditorLspTestContext::new_rust(
710−// lsp::ServerCapabilities {
711−// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
712−// ..Default::default()
713−// },
714−// cx,
715−// )
716−// .await;
717−
718−// // Hover with keyboard has no delay
719−// cx.set_state(indoc! {"
720−// fˇn test() { println!(); }
721−// "});
722−// cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
723−// let symbol_range = cx.lsp_range(indoc! {"
724−// «fn» test() { println!(); }
725−// "});
726−// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
727−// Ok(Some(lsp::Hover {
728−// contents: lsp::HoverContents::Array(vec![
729−// lsp::MarkedString::String("regular text for hover to show".to_string()),
730−// lsp::MarkedString::String("".to_string()),
731−// lsp::MarkedString::LanguageString(lsp::LanguageString {
732−// language: "Rust".to_string(),
733−// value: "".to_string(),
734−// }),
735−// ]),
736−// range: Some(symbol_range),
737−// }))
738−// })
739−// .next()
740−// .await;
741−
742−// cx.condition(|editor, _| editor.hover_state.visible()).await;
743−// cx.editor(|editor, _| {
744−// assert_eq!(
745−// editor.hover_state.info_popover.clone().unwrap().blocks,
746−// vec![HoverBlock {
747−// text: "regular text for hover to show".to_string(),
748−// kind: HoverBlockKind::Markdown,
749−// }],
750−// "No empty string hovers should be shown"
751−// );
752−// });
753−// }
754−
755−// #[gpui::test]
756−// async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
757−// init_test(cx, |_| {});
758−
759−// let mut cx = EditorLspTestContext::new_rust(
760−// lsp::ServerCapabilities {
761−// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
762−// ..Default::default()
763−// },
764−// cx,
765−// )
766−// .await;
767−
768−// // Hover with keyboard has no delay
769−// cx.set_state(indoc! {"
770−// fˇn test() { println!(); }
771−// "});
772−// cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
773−// let symbol_range = cx.lsp_range(indoc! {"
774−// «fn» test() { println!(); }
775−// "});
776−
777−// let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
778−// let markdown_string = format!("\n```rust\n{code_str}```");
779−
780−// let closure_markdown_string = markdown_string.clone();
781−// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
782−// let future_markdown_string = closure_markdown_string.clone();
783−// async move {
784−// Ok(Some(lsp::Hover {
785−// contents: lsp::HoverContents::Markup(lsp::MarkupContent {
786−// kind: lsp::MarkupKind::Markdown,
787−// value: future_markdown_string,
788−// }),
789−// range: Some(symbol_range),
790−// }))
791−// }
792−// })
793−// .next()
794−// .await;
795−
796−// cx.condition(|editor, _| editor.hover_state.visible()).await;
797−// cx.editor(|editor, _| {
798−// let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
799−// assert_eq!(
800−// blocks,
801−// vec![HoverBlock {
802−// text: markdown_string,
803−// kind: HoverBlockKind::Markdown,
804−// }],
805−// );
806−
807−// let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
808−// assert_eq!(
809−// rendered.text,
810−// code_str.trim(),
811−// "Should not have extra line breaks at end of rendered hover"
812−// );
813−// });
814−// }
815−
816−// #[gpui::test]
817−// async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
818−// init_test(cx, |_| {});
819−
820−// let mut cx = EditorLspTestContext::new_rust(
821−// lsp::ServerCapabilities {
822−// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
823−// ..Default::default()
824−// },
825−// cx,
826−// )
827−// .await;
828−
829−// // Hover with just diagnostic, pops DiagnosticPopover immediately and then
830−// // info popover once request completes
831−// cx.set_state(indoc! {"
832−// fn teˇst() { println!(); }
833−// "});
834−
835−// // Send diagnostic to client
836−// let range = cx.text_anchor_range(indoc! {"
837−// fn «test»() { println!(); }
838−// "});
839−// cx.update_buffer(|buffer, cx| {
840−// let snapshot = buffer.text_snapshot();
841−// let set = DiagnosticSet::from_sorted_entries(
842−// vec![DiagnosticEntry {
843−// range,
844−// diagnostic: Diagnostic {
845−// message: "A test diagnostic message.".to_string(),
846−// ..Default::default()
847−// },
848−// }],
849−// &snapshot,
850−// );
851−// buffer.update_diagnostics(LanguageServerId(0), set, cx);
852−// });
853−
854−// // Hover pops diagnostic immediately
855−// cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
856−// cx.foreground().run_until_parked();
857−
858−// cx.editor(|Editor { hover_state, .. }, _| {
859−// assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none())
860−// });
861−
862−// // Info Popover shows after request responded to
863−// let range = cx.lsp_range(indoc! {"
864−// fn «test»() { println!(); }
865−// "});
866−// cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
867−// Ok(Some(lsp::Hover {
868−// contents: lsp::HoverContents::Markup(lsp::MarkupContent {
869−// kind: lsp::MarkupKind::Markdown,
870−// value: "some new docs".to_string(),
871−// }),
872−// range: Some(range),
873−// }))
874−// });
875−// cx.foreground()
876−// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
877−
878−// cx.foreground().run_until_parked();
879−// cx.editor(|Editor { hover_state, .. }, _| {
880−// hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()
881−// });
882−// }
883−
884−// #[gpui::test]
885−// fn test_render_blocks(cx: &mut gpui::TestAppContext) {
886−// init_test(cx, |_| {});
887−
888−// cx.add_window(|cx| {
889−// let editor = Editor::single_line(None, cx);
890−// let style = editor.style(cx);
891−
892−// struct Row {
893−// blocks: Vec<HoverBlock>,
894−// expected_marked_text: String,
895−// expected_styles: Vec<HighlightStyle>,
896−// }
897−
898−// let rows = &[
899−// // Strong emphasis
900−// Row {
901−// blocks: vec![HoverBlock {
902−// text: "one **two** three".to_string(),
903−// kind: HoverBlockKind::Markdown,
904−// }],
905−// expected_marked_text: "one «two» three".to_string(),
906−// expected_styles: vec![HighlightStyle {
907−// weight: Some(Weight::BOLD),
908−// ..Default::default()
909−// }],
910−// },
911−// // Links
912−// Row {
913−// blocks: vec![HoverBlock {
914−// text: "one [two](https://the-url) three".to_string(),
915−// kind: HoverBlockKind::Markdown,
916−// }],
917−// expected_marked_text: "one «two» three".to_string(),
918−// expected_styles: vec![HighlightStyle {
919−// underline: Some(Underline {
920−// thickness: 1.0.into(),
921−// ..Default::default()
922−// }),
923−// ..Default::default()
924−// }],
925−// },
926−// // Lists
927−// Row {
928−// blocks: vec![HoverBlock {
929−// text: "
930−// lists:
931−// * one
932−// - a
933−// - b
934−// * two
935−// - [c](https://the-url)
936−// - d"
937−// .unindent(),
938−// kind: HoverBlockKind::Markdown,
939−// }],
940−// expected_marked_text: "
941−// lists:
942−// - one
943−// - a
944−// - b
945−// - two
946−// - «c»
947−// - d"
948−// .unindent(),
949−// expected_styles: vec![HighlightStyle {
950−// underline: Some(Underline {
951−// thickness: 1.0.into(),
952−// ..Default::default()
953−// }),
954−// ..Default::default()
955−// }],
956−// },
957−// // Multi-paragraph list items
958−// Row {
959−// blocks: vec![HoverBlock {
960−// text: "
961−// * one two
962−// three
963−
964−// * four five
965−// * six seven
966−// eight
967−
968−// nine
969−// * ten
970−// * six"
971−// .unindent(),
972−// kind: HoverBlockKind::Markdown,
973−// }],
974−// expected_marked_text: "
975−// - one two three
976−// - four five
977−// - six seven eight
978−
979−// nine
980−// - ten
981−// - six"
982−// .unindent(),
983−// expected_styles: vec![HighlightStyle {
984−// underline: Some(Underline {
985−// thickness: 1.0.into(),
986−// ..Default::default()
987−// }),
988−// ..Default::default()
989−// }],
990−// },
991−// ];
992−
993−// for Row {
994−// blocks,
995−// expected_marked_text,
996−// expected_styles,
997−// } in &rows[0..]
998−// {
999−// let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
1000−
1001−// let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
1002−// let expected_highlights = ranges
1003−// .into_iter()
1004−// .zip(expected_styles.iter().cloned())
1005−// .collect::<Vec<_>>();
1006−// assert_eq!(
1007−// rendered.text, expected_text,
1008−// "wrong text for input {blocks:?}"
1009−// );
1010−
1011−// let rendered_highlights: Vec<_> = rendered
1012−// .highlights
1013−// .iter()
1014−// .filter_map(|(range, highlight)| {
1015−// let highlight = highlight.to_highlight_style(&style.syntax)?;
1016−// Some((range.clone(), highlight))
1017−// })
1018−// .collect();
1019−
1020−// assert_eq!(
1021−// rendered_highlights, expected_highlights,
1022−// "wrong highlights for input {blocks:?}"
1023−// );
1024−// }
1025−
1026−// editor
1027−// });
1028−// }
1029−
1030−// #[gpui::test]
1031−// async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
1032−// init_test(cx, |settings| {
1033−// settings.defaults.inlay_hints = Some(InlayHintSettings {
1034−// enabled: true,
1035−// show_type_hints: true,
1036−// show_parameter_hints: true,
1037−// show_other_hints: true,
1038−// })
1039−// });
1040−
1041−// let mut cx = EditorLspTestContext::new_rust(
1042−// lsp::ServerCapabilities {
1043−// inlay_hint_provider: Some(lsp::OneOf::Right(
1044−// lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {
1045−// resolve_provider: Some(true),
1046−// ..Default::default()
1047−// }),
1048−// )),
1049−// ..Default::default()
1050−// },
1051−// cx,
1052−// )
1053−// .await;
1054−
1055−// cx.set_state(indoc! {"
1056−// struct TestStruct;
1057−
1058−// // ==================
1059−
1060−// struct TestNewType<T>(T);
1061−
1062−// fn main() {
1063−// let variableˇ = TestNewType(TestStruct);
1064−// }
1065−// "});
1066−
1067−// let hint_start_offset = cx.ranges(indoc! {"
1068−// struct TestStruct;
1069−
1070−// // ==================
1071−
1072−// struct TestNewType<T>(T);
1073−
1074−// fn main() {
1075−// let variableˇ = TestNewType(TestStruct);
1076−// }
1077−// "})[0]
1078−// .start;
1079−// let hint_position = cx.to_lsp(hint_start_offset);
1080−// let new_type_target_range = cx.lsp_range(indoc! {"
1081−// struct TestStruct;
1082−
1083−// // ==================
1084−
1085−// struct «TestNewType»<T>(T);
1086−
1087−// fn main() {
1088−// let variable = TestNewType(TestStruct);
1089−// }
1090−// "});
1091−// let struct_target_range = cx.lsp_range(indoc! {"
1092−// struct «TestStruct»;
1093−
1094−// // ==================
1095−
1096−// struct TestNewType<T>(T);
1097−
1098−// fn main() {
1099−// let variable = TestNewType(TestStruct);
1100−// }
1101−// "});
1102−
1103−// let uri = cx.buffer_lsp_url.clone();
1104−// let new_type_label = "TestNewType";
1105−// let struct_label = "TestStruct";
1106−// let entire_hint_label = ": TestNewType<TestStruct>";
1107−// let closure_uri = uri.clone();
1108−// cx.lsp
1109−// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1110−// let task_uri = closure_uri.clone();
1111−// async move {
1112−// assert_eq!(params.text_document.uri, task_uri);
1113−// Ok(Some(vec![lsp::InlayHint {
1114−// position: hint_position,
1115−// label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
1116−// value: entire_hint_label.to_string(),
1117−// ..Default::default()
1118−// }]),
1119−// kind: Some(lsp::InlayHintKind::TYPE),
1120−// text_edits: None,
1121−// tooltip: None,
1122−// padding_left: Some(false),
1123−// padding_right: Some(false),
1124−// data: None,
1125−// }]))
1126−// }
1127−// })
1128−// .next()
1129−// .await;
1130−// cx.foreground().run_until_parked();
1131−// cx.update_editor(|editor, cx| {
1132−// let expected_layers = vec![entire_hint_label.to_string()];
1133−// assert_eq!(expected_layers, cached_hint_labels(editor));
1134−// assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1135−// });
1136−
1137−// let inlay_range = cx
1138−// .ranges(indoc! {"
1139−// struct TestStruct;
1140−
1141−// // ==================
1142−
1143−// struct TestNewType<T>(T);
1144−
1145−// fn main() {
1146−// let variable« »= TestNewType(TestStruct);
1147−// }
1148−// "})
1149−// .get(0)
1150−// .cloned()
1151−// .unwrap();
1152−// let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| {
1153−// let snapshot = editor.snapshot(cx);
1154−// let previous_valid = inlay_range.start.to_display_point(&snapshot);
1155−// let next_valid = inlay_range.end.to_display_point(&snapshot);
1156−// assert_eq!(previous_valid.row(), next_valid.row());
1157−// assert!(previous_valid.column() < next_valid.column());
1158−// let exact_unclipped = DisplayPoint::new(
1159−// previous_valid.row(),
1160−// previous_valid.column()
1161−// + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2)
1162−// as u32,
1163−// );
1164−// PointForPosition {
1165−// previous_valid,
1166−// next_valid,
1167−// exact_unclipped,
1168−// column_overshoot_after_line_end: 0,
1169−// }
1170−// });
1171−// cx.update_editor(|editor, cx| {
1172−// update_inlay_link_and_hover_points(
1173−// &editor.snapshot(cx),
1174−// new_type_hint_part_hover_position,
1175−// editor,
1176−// true,
1177−// false,
1178−// cx,
1179−// );
1180−// });
1181−
1182−// let resolve_closure_uri = uri.clone();
1183−// cx.lsp
1184−// .handle_request::<lsp::request::InlayHintResolveRequest, _, _>(
1185−// move |mut hint_to_resolve, _| {
1186−// let mut resolved_hint_positions = BTreeSet::new();
1187−// let task_uri = resolve_closure_uri.clone();
1188−// async move {
1189−// let inserted = resolved_hint_positions.insert(hint_to_resolve.position);
1190−// assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice");
1191−
1192−// // `: TestNewType<TestStruct>`
1193−// hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![
1194−// lsp::InlayHintLabelPart {
1195−// value: ": ".to_string(),
1196−// ..Default::default()
1197−// },
1198−// lsp::InlayHintLabelPart {
1199−// value: new_type_label.to_string(),
1200−// location: Some(lsp::Location {
1201−// uri: task_uri.clone(),
1202−// range: new_type_target_range,
1203−// }),
1204−// tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
1205−// "A tooltip for `{new_type_label}`"
1206−// ))),
1207−// ..Default::default()
1208−// },
1209−// lsp::InlayHintLabelPart {
1210−// value: "<".to_string(),
1211−// ..Default::default()
1212−// },
1213−// lsp::InlayHintLabelPart {
1214−// value: struct_label.to_string(),
1215−// location: Some(lsp::Location {
1216−// uri: task_uri,
1217−// range: struct_target_range,
1218−// }),
1219−// tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
1220−// lsp::MarkupContent {
1221−// kind: lsp::MarkupKind::Markdown,
1222−// value: format!("A tooltip for `{struct_label}`"),
1223−// },
1224−// )),
1225−// ..Default::default()
1226−// },
1227−// lsp::InlayHintLabelPart {
1228−// value: ">".to_string(),
1229−// ..Default::default()
1230−// },
1231−// ]);
1232−
1233−// Ok(hint_to_resolve)
1234−// }
1235−// },
1236−// )
1237−// .next()
1238−// .await;
1239−// cx.foreground().run_until_parked();
1240−
1241−// cx.update_editor(|editor, cx| {
1242−// update_inlay_link_and_hover_points(
1243−// &editor.snapshot(cx),
1244−// new_type_hint_part_hover_position,
1245−// editor,
1246−// true,
1247−// false,
1248−// cx,
1249−// );
1250−// });
1251−// cx.foreground()
1252−// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1253−// cx.foreground().run_until_parked();
1254−// cx.update_editor(|editor, cx| {
1255−// let hover_state = &editor.hover_state;
1256−// assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
1257−// let popover = hover_state.info_popover.as_ref().unwrap();
1258−// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1259−// assert_eq!(
1260−// popover.symbol_range,
1261−// RangeInEditor::Inlay(InlayHighlight {
1262−// inlay: InlayId::Hint(0),
1263−// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1264−// range: ": ".len()..": ".len() + new_type_label.len(),
1265−// }),
1266−// "Popover range should match the new type label part"
1267−// );
1268−// assert_eq!(
1269−// popover.parsed_content.text,
1270−// format!("A tooltip for `{new_type_label}`"),
1271−// "Rendered text should not anyhow alter backticks"
1272−// );
1273−// });
1274−
1275−// let struct_hint_part_hover_position = cx.update_editor(|editor, cx| {
1276−// let snapshot = editor.snapshot(cx);
1277−// let previous_valid = inlay_range.start.to_display_point(&snapshot);
1278−// let next_valid = inlay_range.end.to_display_point(&snapshot);
1279−// assert_eq!(previous_valid.row(), next_valid.row());
1280−// assert!(previous_valid.column() < next_valid.column());
1281−// let exact_unclipped = DisplayPoint::new(
1282−// previous_valid.row(),
1283−// previous_valid.column()
1284−// + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2)
1285−// as u32,
1286−// );
1287−// PointForPosition {
1288−// previous_valid,
1289−// next_valid,
1290−// exact_unclipped,
1291−// column_overshoot_after_line_end: 0,
1292−// }
1293−// });
1294−// cx.update_editor(|editor, cx| {
1295−// update_inlay_link_and_hover_points(
1296−// &editor.snapshot(cx),
1297−// struct_hint_part_hover_position,
1298−// editor,
1299−// true,
1300−// false,
1301−// cx,
1302−// );
1303−// });
1304−// cx.foreground()
1305−// .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1306−// cx.foreground().run_until_parked();
1307−// cx.update_editor(|editor, cx| {
1308−// let hover_state = &editor.hover_state;
1309−// assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
1310−// let popover = hover_state.info_popover.as_ref().unwrap();
1311−// let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1312−// assert_eq!(
1313−// popover.symbol_range,
1314−// RangeInEditor::Inlay(InlayHighlight {
1315−// inlay: InlayId::Hint(0),
1316−// inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1317−// range: ": ".len() + new_type_label.len() + "<".len()
1318−// ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(),
1319−// }),
1320−// "Popover range should match the struct label part"
1321−// );
1322−// assert_eq!(
1323−// popover.parsed_content.text,
1324−// format!("A tooltip for {struct_label}"),
1325−// "Rendered markdown element should remove backticks from text"
1326−// );
1327−// });
1328−// }
1329−// }
553+#[cfg(test)]
554+mod tests {
555+ use super::*;
556+ use crate::{
557+ editor_tests::init_test,
558+ element::PointForPosition,
559+ inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
560+ link_go_to_definition::update_inlay_link_and_hover_points,
561+ test::editor_lsp_test_context::EditorLspTestContext,
562+ InlayId,
563+ };
564+ use collections::BTreeSet;
565+ use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
566+ use indoc::indoc;
567+ use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
568+ use lsp::LanguageServerId;
569+ use project::{HoverBlock, HoverBlockKind};
570+ use smol::stream::StreamExt;
571+ use unindent::Unindent;
572+ use util::test::marked_text_ranges;
573+
574+ #[gpui::test]
575+ async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
576+ init_test(cx, |_| {});
577+
578+ let mut cx = EditorLspTestContext::new_rust(
579+ lsp::ServerCapabilities {
580+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
581+ ..Default::default()
582+ },
583+ cx,
584+ )
585+ .await;
586+
587+ // Basic hover delays and then pops without moving the mouse
588+ cx.set_state(indoc! {"
589+ fn ˇtest() { println!(); }
590+ "});
591+ let hover_point = cx.display_point(indoc! {"
592+ fn test() { printˇln!(); }
593+ "});
594+
595+ cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
596+ assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
597+
598+ // After delay, hover should be visible.
599+ let symbol_range = cx.lsp_range(indoc! {"
600+ fn test() { «println!»(); }
601+ "});
602+ let mut requests =
603+ cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
604+ Ok(Some(lsp::Hover {
605+ contents: lsp::HoverContents::Markup(lsp::MarkupContent {
606+ kind: lsp::MarkupKind::Markdown,
607+ value: "some basic docs".to_string(),
608+ }),
609+ range: Some(symbol_range),
610+ }))
611+ });
612+ cx.background_executor
613+ .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
614+ requests.next().await;
615+
616+ cx.editor(|editor, _| {
617+ assert!(editor.hover_state.visible());
618+ assert_eq!(
619+ editor.hover_state.info_popover.clone().unwrap().blocks,
620+ vec![HoverBlock {
621+ text: "some basic docs".to_string(),
622+ kind: HoverBlockKind::Markdown,
623+ },]
624+ )
625+ });
626+
627+ // Mouse moved with no hover response dismisses
628+ let hover_point = cx.display_point(indoc! {"
629+ fn teˇst() { println!(); }
630+ "});
631+ let mut request = cx
632+ .lsp
633+ .handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
634+ cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
635+ cx.background_executor
636+ .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
637+ request.next().await;
638+ cx.editor(|editor, _| {
639+ assert!(!editor.hover_state.visible());
640+ });
641+ }
642+
643+ #[gpui::test]
644+ async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
645+ init_test(cx, |_| {});
646+
647+ let mut cx = EditorLspTestContext::new_rust(
648+ lsp::ServerCapabilities {
649+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
650+ ..Default::default()
651+ },
652+ cx,
653+ )
654+ .await;
655+
656+ // Hover with keyboard has no delay
657+ cx.set_state(indoc! {"
658+ fˇn test() { println!(); }
659+ "});
660+ cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
661+ let symbol_range = cx.lsp_range(indoc! {"
662+ «fn» test() { println!(); }
663+ "});
664+ cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
665+ Ok(Some(lsp::Hover {
666+ contents: lsp::HoverContents::Markup(lsp::MarkupContent {
667+ kind: lsp::MarkupKind::Markdown,
668+ value: "some other basic docs".to_string(),
669+ }),
670+ range: Some(symbol_range),
671+ }))
672+ })
673+ .next()
674+ .await;
675+
676+ cx.condition(|editor, _| editor.hover_state.visible()).await;
677+ cx.editor(|editor, _| {
678+ assert_eq!(
679+ editor.hover_state.info_popover.clone().unwrap().blocks,
680+ vec![HoverBlock {
681+ text: "some other basic docs".to_string(),
682+ kind: HoverBlockKind::Markdown,
683+ }]
684+ )
685+ });
686+ }
687+
688+ #[gpui::test]
689+ async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
690+ init_test(cx, |_| {});
691+
692+ let mut cx = EditorLspTestContext::new_rust(
693+ lsp::ServerCapabilities {
694+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
695+ ..Default::default()
696+ },
697+ cx,
698+ )
699+ .await;
700+
701+ // Hover with keyboard has no delay
702+ cx.set_state(indoc! {"
703+ fˇn test() { println!(); }
704+ "});
705+ cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
706+ let symbol_range = cx.lsp_range(indoc! {"
707+ «fn» test() { println!(); }
708+ "});
709+ cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
710+ Ok(Some(lsp::Hover {
711+ contents: lsp::HoverContents::Array(vec![
712+ lsp::MarkedString::String("regular text for hover to show".to_string()),
713+ lsp::MarkedString::String("".to_string()),
714+ lsp::MarkedString::LanguageString(lsp::LanguageString {
715+ language: "Rust".to_string(),
716+ value: "".to_string(),
717+ }),
718+ ]),
719+ range: Some(symbol_range),
720+ }))
721+ })
722+ .next()
723+ .await;
724+
725+ cx.condition(|editor, _| editor.hover_state.visible()).await;
726+ cx.editor(|editor, _| {
727+ assert_eq!(
728+ editor.hover_state.info_popover.clone().unwrap().blocks,
729+ vec![HoverBlock {
730+ text: "regular text for hover to show".to_string(),
731+ kind: HoverBlockKind::Markdown,
732+ }],
733+ "No empty string hovers should be shown"
734+ );
735+ });
736+ }
737+
738+ #[gpui::test]
739+ async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
740+ init_test(cx, |_| {});
741+
742+ let mut cx = EditorLspTestContext::new_rust(
743+ lsp::ServerCapabilities {
744+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
745+ ..Default::default()
746+ },
747+ cx,
748+ )
749+ .await;
750+
751+ // Hover with keyboard has no delay
752+ cx.set_state(indoc! {"
753+ fˇn test() { println!(); }
754+ "});
755+ cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
756+ let symbol_range = cx.lsp_range(indoc! {"
757+ «fn» test() { println!(); }
758+ "});
759+
760+ let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
761+ let markdown_string = format!("\n```rust\n{code_str}```");
762+
763+ let closure_markdown_string = markdown_string.clone();
764+ cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
765+ let future_markdown_string = closure_markdown_string.clone();
766+ async move {
767+ Ok(Some(lsp::Hover {
768+ contents: lsp::HoverContents::Markup(lsp::MarkupContent {
769+ kind: lsp::MarkupKind::Markdown,
770+ value: future_markdown_string,
771+ }),
772+ range: Some(symbol_range),
773+ }))
774+ }
775+ })
776+ .next()
777+ .await;
778+
779+ cx.condition(|editor, _| editor.hover_state.visible()).await;
780+ cx.editor(|editor, _| {
781+ let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
782+ assert_eq!(
783+ blocks,
784+ vec![HoverBlock {
785+ text: markdown_string,
786+ kind: HoverBlockKind::Markdown,
787+ }],
788+ );
789+
790+ let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
791+ assert_eq!(
792+ rendered.text,
793+ code_str.trim(),
794+ "Should not have extra line breaks at end of rendered hover"
795+ );
796+ });
797+ }
798+
799+ #[gpui::test]
800+ async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
801+ init_test(cx, |_| {});
802+
803+ let mut cx = EditorLspTestContext::new_rust(
804+ lsp::ServerCapabilities {
805+ hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
806+ ..Default::default()
807+ },
808+ cx,
809+ )
810+ .await;
811+
812+ // Hover with just diagnostic, pops DiagnosticPopover immediately and then
813+ // info popover once request completes
814+ cx.set_state(indoc! {"
815+ fn teˇst() { println!(); }
816+ "});
817+
818+ // Send diagnostic to client
819+ let range = cx.text_anchor_range(indoc! {"
820+ fn «test»() { println!(); }
821+ "});
822+ cx.update_buffer(|buffer, cx| {
823+ let snapshot = buffer.text_snapshot();
824+ let set = DiagnosticSet::from_sorted_entries(
825+ vec![DiagnosticEntry {
826+ range,
827+ diagnostic: Diagnostic {
828+ message: "A test diagnostic message.".to_string(),
829+ ..Default::default()
830+ },
831+ }],
832+ &snapshot,
833+ );
834+ buffer.update_diagnostics(LanguageServerId(0), set, cx);
835+ });
836+
837+ // Hover pops diagnostic immediately
838+ cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
839+ cx.background_executor.run_until_parked();
840+
841+ cx.editor(|Editor { hover_state, .. }, _| {
842+ assert!(hover_state.diagnostic_popover.is_some() && hover_state.info_popover.is_none())
843+ });
844+
845+ // Info Popover shows after request responded to
846+ let range = cx.lsp_range(indoc! {"
847+ fn «test»() { println!(); }
848+ "});
849+ cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
850+ Ok(Some(lsp::Hover {
851+ contents: lsp::HoverContents::Markup(lsp::MarkupContent {
852+ kind: lsp::MarkupKind::Markdown,
853+ value: "some new docs".to_string(),
854+ }),
855+ range: Some(range),
856+ }))
857+ });
858+ cx.background_executor
859+ .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
860+
861+ cx.background_executor.run_until_parked();
862+ cx.editor(|Editor { hover_state, .. }, _| {
863+ hover_state.diagnostic_popover.is_some() && hover_state.info_task.is_some()
864+ });
865+ }
866+
867+ #[gpui::test]
868+ fn test_render_blocks(cx: &mut gpui::TestAppContext) {
869+ init_test(cx, |_| {});
870+
871+ let editor = cx.add_window(|cx| Editor::single_line(cx));
872+ editor
873+ .update(cx, |editor, cx| {
874+ let style = editor.style.clone().unwrap();
875+
876+ struct Row {
877+ blocks: Vec<HoverBlock>,
878+ expected_marked_text: String,
879+ expected_styles: Vec<HighlightStyle>,
880+ }
881+
882+ let rows = &[
883+ // Strong emphasis
884+ Row {
885+ blocks: vec![HoverBlock {
886+ text: "one **two** three".to_string(),
887+ kind: HoverBlockKind::Markdown,
888+ }],
889+ expected_marked_text: "one «two» three".to_string(),
890+ expected_styles: vec![HighlightStyle {
891+ font_weight: Some(FontWeight::BOLD),
892+ ..Default::default()
893+ }],
894+ },
895+ // Links
896+ Row {
897+ blocks: vec![HoverBlock {
898+ text: "one [two](https://the-url) three".to_string(),
899+ kind: HoverBlockKind::Markdown,
900+ }],
901+ expected_marked_text: "one «two» three".to_string(),
902+ expected_styles: vec![HighlightStyle {
903+ underline: Some(UnderlineStyle {
904+ thickness: 1.0.into(),
905+ ..Default::default()
906+ }),
907+ ..Default::default()
908+ }],
909+ },
910+ // Lists
911+ Row {
912+ blocks: vec![HoverBlock {
913+ text: "
914+ lists:
915+ * one
916+ - a
917+ - b
918+ * two
919+ - [c](https://the-url)
920+ - d"
921+ .unindent(),
922+ kind: HoverBlockKind::Markdown,
923+ }],
924+ expected_marked_text: "
925+ lists:
926+ - one
927+ - a
928+ - b
929+ - two
930+ - «c»
931+ - d"
932+ .unindent(),
933+ expected_styles: vec![HighlightStyle {
934+ underline: Some(UnderlineStyle {
935+ thickness: 1.0.into(),
936+ ..Default::default()
937+ }),
938+ ..Default::default()
939+ }],
940+ },
941+ // Multi-paragraph list items
942+ Row {
943+ blocks: vec![HoverBlock {
944+ text: "
945+ * one two
946+ three
947+
948+ * four five
949+ * six seven
950+ eight
951+
952+ nine
953+ * ten
954+ * six"
955+ .unindent(),
956+ kind: HoverBlockKind::Markdown,
957+ }],
958+ expected_marked_text: "
959+ - one two three
960+ - four five
961+ - six seven eight
962+
963+ nine
964+ - ten
965+ - six"
966+ .unindent(),
967+ expected_styles: vec![HighlightStyle {
968+ underline: Some(UnderlineStyle {
969+ thickness: 1.0.into(),
970+ ..Default::default()
971+ }),
972+ ..Default::default()
973+ }],
974+ },
975+ ];
976+
977+ for Row {
978+ blocks,
979+ expected_marked_text,
980+ expected_styles,
981+ } in &rows[0..]
982+ {
983+ let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None));
984+
985+ let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
986+ let expected_highlights = ranges
987+ .into_iter()
988+ .zip(expected_styles.iter().cloned())
989+ .collect::<Vec<_>>();
990+ assert_eq!(
991+ rendered.text, expected_text,
992+ "wrong text for input {blocks:?}"
993+ );
994+
995+ let rendered_highlights: Vec<_> = rendered
996+ .highlights
997+ .iter()
998+ .filter_map(|(range, highlight)| {
999+ let highlight = highlight.to_highlight_style(&style.syntax)?;
1000+ Some((range.clone(), highlight))
1001+ })
1002+ .collect();
1003+
1004+ assert_eq!(
1005+ rendered_highlights, expected_highlights,
1006+ "wrong highlights for input {blocks:?}"
1007+ );
1008+ }
1009+ })
1010+ .unwrap();
1011+ }
1012+
1013+ #[gpui::test]
1014+ async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
1015+ init_test(cx, |settings| {
1016+ settings.defaults.inlay_hints = Some(InlayHintSettings {
1017+ enabled: true,
1018+ show_type_hints: true,
1019+ show_parameter_hints: true,
1020+ show_other_hints: true,
1021+ })
1022+ });
1023+
1024+ let mut cx = EditorLspTestContext::new_rust(
1025+ lsp::ServerCapabilities {
1026+ inlay_hint_provider: Some(lsp::OneOf::Right(
1027+ lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {
1028+ resolve_provider: Some(true),
1029+ ..Default::default()
1030+ }),
1031+ )),
1032+ ..Default::default()
1033+ },
1034+ cx,
1035+ )
1036+ .await;
1037+
1038+ cx.set_state(indoc! {"
1039+ struct TestStruct;
1040+
1041+ // ==================
1042+
1043+ struct TestNewType<T>(T);
1044+
1045+ fn main() {
1046+ let variableˇ = TestNewType(TestStruct);
1047+ }
1048+ "});
1049+
1050+ let hint_start_offset = cx.ranges(indoc! {"
1051+ struct TestStruct;
1052+
1053+ // ==================
1054+
1055+ struct TestNewType<T>(T);
1056+
1057+ fn main() {
1058+ let variableˇ = TestNewType(TestStruct);
1059+ }
1060+ "})[0]
1061+ .start;
1062+ let hint_position = cx.to_lsp(hint_start_offset);
1063+ let new_type_target_range = cx.lsp_range(indoc! {"
1064+ struct TestStruct;
1065+
1066+ // ==================
1067+
1068+ struct «TestNewType»<T>(T);
1069+
1070+ fn main() {
1071+ let variable = TestNewType(TestStruct);
1072+ }
1073+ "});
1074+ let struct_target_range = cx.lsp_range(indoc! {"
1075+ struct «TestStruct»;
1076+
1077+ // ==================
1078+
1079+ struct TestNewType<T>(T);
1080+
1081+ fn main() {
1082+ let variable = TestNewType(TestStruct);
1083+ }
1084+ "});
1085+
1086+ let uri = cx.buffer_lsp_url.clone();
1087+ let new_type_label = "TestNewType";
1088+ let struct_label = "TestStruct";
1089+ let entire_hint_label = ": TestNewType<TestStruct>";
1090+ let closure_uri = uri.clone();
1091+ cx.lsp
1092+ .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1093+ let task_uri = closure_uri.clone();
1094+ async move {
1095+ assert_eq!(params.text_document.uri, task_uri);
1096+ Ok(Some(vec![lsp::InlayHint {
1097+ position: hint_position,
1098+ label: lsp::InlayHintLabel::LabelParts(vec![lsp::InlayHintLabelPart {
1099+ value: entire_hint_label.to_string(),
1100+ ..Default::default()
1101+ }]),
1102+ kind: Some(lsp::InlayHintKind::TYPE),
1103+ text_edits: None,
1104+ tooltip: None,
1105+ padding_left: Some(false),
1106+ padding_right: Some(false),
1107+ data: None,
1108+ }]))
1109+ }
1110+ })
1111+ .next()
1112+ .await;
1113+ cx.background_executor.run_until_parked();
1114+ cx.update_editor(|editor, cx| {
1115+ let expected_layers = vec![entire_hint_label.to_string()];
1116+ assert_eq!(expected_layers, cached_hint_labels(editor));
1117+ assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1118+ });
1119+
1120+ let inlay_range = cx
1121+ .ranges(indoc! {"
1122+ struct TestStruct;
1123+
1124+ // ==================
1125+
1126+ struct TestNewType<T>(T);
1127+
1128+ fn main() {
1129+ let variable« »= TestNewType(TestStruct);
1130+ }
1131+ "})
1132+ .get(0)
1133+ .cloned()
1134+ .unwrap();
1135+ let new_type_hint_part_hover_position = cx.update_editor(|editor, cx| {
1136+ let snapshot = editor.snapshot(cx);
1137+ let previous_valid = inlay_range.start.to_display_point(&snapshot);
1138+ let next_valid = inlay_range.end.to_display_point(&snapshot);
1139+ assert_eq!(previous_valid.row(), next_valid.row());
1140+ assert!(previous_valid.column() < next_valid.column());
1141+ let exact_unclipped = DisplayPoint::new(
1142+ previous_valid.row(),
1143+ previous_valid.column()
1144+ + (entire_hint_label.find(new_type_label).unwrap() + new_type_label.len() / 2)
1145+ as u32,
1146+ );
1147+ PointForPosition {
1148+ previous_valid,
1149+ next_valid,
1150+ exact_unclipped,
1151+ column_overshoot_after_line_end: 0,
1152+ }
1153+ });
1154+ cx.update_editor(|editor, cx| {
1155+ update_inlay_link_and_hover_points(
1156+ &editor.snapshot(cx),
1157+ new_type_hint_part_hover_position,
1158+ editor,
1159+ true,
1160+ false,
1161+ cx,
1162+ );
1163+ });
1164+
1165+ let resolve_closure_uri = uri.clone();
1166+ cx.lsp
1167+ .handle_request::<lsp::request::InlayHintResolveRequest, _, _>(
1168+ move |mut hint_to_resolve, _| {
1169+ let mut resolved_hint_positions = BTreeSet::new();
1170+ let task_uri = resolve_closure_uri.clone();
1171+ async move {
1172+ let inserted = resolved_hint_positions.insert(hint_to_resolve.position);
1173+ assert!(inserted, "Hint {hint_to_resolve:?} was resolved twice");
1174+
1175+ // `: TestNewType<TestStruct>`
1176+ hint_to_resolve.label = lsp::InlayHintLabel::LabelParts(vec![
1177+ lsp::InlayHintLabelPart {
1178+ value: ": ".to_string(),
1179+ ..Default::default()
1180+ },
1181+ lsp::InlayHintLabelPart {
1182+ value: new_type_label.to_string(),
1183+ location: Some(lsp::Location {
1184+ uri: task_uri.clone(),
1185+ range: new_type_target_range,
1186+ }),
1187+ tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
1188+ "A tooltip for `{new_type_label}`"
1189+ ))),
1190+ ..Default::default()
1191+ },
1192+ lsp::InlayHintLabelPart {
1193+ value: "<".to_string(),
1194+ ..Default::default()
1195+ },
1196+ lsp::InlayHintLabelPart {
1197+ value: struct_label.to_string(),
1198+ location: Some(lsp::Location {
1199+ uri: task_uri,
1200+ range: struct_target_range,
1201+ }),
1202+ tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
1203+ lsp::MarkupContent {
1204+ kind: lsp::MarkupKind::Markdown,
1205+ value: format!("A tooltip for `{struct_label}`"),
1206+ },
1207+ )),
1208+ ..Default::default()
1209+ },
1210+ lsp::InlayHintLabelPart {
1211+ value: ">".to_string(),
1212+ ..Default::default()
1213+ },
1214+ ]);
1215+
1216+ Ok(hint_to_resolve)
1217+ }
1218+ },
1219+ )
1220+ .next()
1221+ .await;
1222+ cx.background_executor.run_until_parked();
1223+
1224+ cx.update_editor(|editor, cx| {
1225+ update_inlay_link_and_hover_points(
1226+ &editor.snapshot(cx),
1227+ new_type_hint_part_hover_position,
1228+ editor,
1229+ true,
1230+ false,
1231+ cx,
1232+ );
1233+ });
1234+ cx.background_executor
1235+ .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1236+ cx.background_executor.run_until_parked();
1237+ cx.update_editor(|editor, cx| {
1238+ let hover_state = &editor.hover_state;
1239+ assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
1240+ let popover = hover_state.info_popover.as_ref().unwrap();
1241+ let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1242+ assert_eq!(
1243+ popover.symbol_range,
1244+ RangeInEditor::Inlay(InlayHighlight {
1245+ inlay: InlayId::Hint(0),
1246+ inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1247+ range: ": ".len()..": ".len() + new_type_label.len(),
1248+ }),
1249+ "Popover range should match the new type label part"
1250+ );
1251+ assert_eq!(
1252+ popover.parsed_content.text,
1253+ format!("A tooltip for `{new_type_label}`"),
1254+ "Rendered text should not anyhow alter backticks"
1255+ );
1256+ });
1257+
1258+ let struct_hint_part_hover_position = cx.update_editor(|editor, cx| {
1259+ let snapshot = editor.snapshot(cx);
1260+ let previous_valid = inlay_range.start.to_display_point(&snapshot);
1261+ let next_valid = inlay_range.end.to_display_point(&snapshot);
1262+ assert_eq!(previous_valid.row(), next_valid.row());
1263+ assert!(previous_valid.column() < next_valid.column());
1264+ let exact_unclipped = DisplayPoint::new(
1265+ previous_valid.row(),
1266+ previous_valid.column()
1267+ + (entire_hint_label.find(struct_label).unwrap() + struct_label.len() / 2)
1268+ as u32,
1269+ );
1270+ PointForPosition {
1271+ previous_valid,
1272+ next_valid,
1273+ exact_unclipped,
1274+ column_overshoot_after_line_end: 0,
1275+ }
1276+ });
1277+ cx.update_editor(|editor, cx| {
1278+ update_inlay_link_and_hover_points(
1279+ &editor.snapshot(cx),
1280+ struct_hint_part_hover_position,
1281+ editor,
1282+ true,
1283+ false,
1284+ cx,
1285+ );
1286+ });
1287+ cx.background_executor
1288+ .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
1289+ cx.background_executor.run_until_parked();
1290+ cx.update_editor(|editor, cx| {
1291+ let hover_state = &editor.hover_state;
1292+ assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some());
1293+ let popover = hover_state.info_popover.as_ref().unwrap();
1294+ let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
1295+ assert_eq!(
1296+ popover.symbol_range,
1297+ RangeInEditor::Inlay(InlayHighlight {
1298+ inlay: InlayId::Hint(0),
1299+ inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right),
1300+ range: ": ".len() + new_type_label.len() + "<".len()
1301+ ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(),
1302+ }),
1303+ "Popover range should match the struct label part"
1304+ );
1305+ assert_eq!(
1306+ popover.parsed_content.text,
1307+ format!("A tooltip for {struct_label}"),
1308+ "Rendered markdown element should remove backticks from text"
1309+ );
1310+ });
1311+ }
1312+}
crates/editor2/src/inlay_hint_cache.rs+4−2
@@ -861,7 +861,7 @@ async fn fetch_and_update_hints( · 860 unmodified lines
861861 let inlay_hints_fetch_task = editor
862862 .update(&mut cx, |editor, cx| {
863863 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) {
865865 Some((_, _, current_visible_range)) => {
866866 let visible_offset_length = current_visible_range.len();
867867 let double_visible_range = current_visible_range
@@ -2201,7 +2201,9 @@ pub mod tests { · 1333 unmodified lines
22012201 cx: &mut gpui::TestAppContext,
22022202 ) -> Range<Point> {
22032203 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+ })
22052207 .unwrap();
22062208 assert_eq!(
22072209 ranges.len(),
crates/editor2/src/items.rs+23−27
@@ -9,8 +9,8 @@ use collections::HashSet; · 8 unmodified lines
99 use futures::future::try_join_all;
1010 use gpui::{
1111 div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
12− FocusHandle, Model, ParentComponent, Pixels, SharedString, Styled, Subscription, Task, View,
13− ViewContext, VisualContext, WeakView,
12+ FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
13+ ViewContext, VisualContext, WeakView, WindowContext,
1414 };
1515 use language::{
1616 proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -30,7 +30,7 @@ use std::{ · 13 unmodified lines
3030 };
3131 use text::Selection;
3232 use theme::{ActiveTheme, Theme};
33−use ui::{Label, TextColor};
33+use ui::{Color, Label};
3434 use util::{paths::PathExt, ResultExt, TryFutureExt};
3535 use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
3636 use workspace::{
@@ -584,7 +584,7 @@ impl Item for Editor { · 547 unmodified lines
584584 Some(path.to_string_lossy().to_string().into())
585585 }
586586
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 {
588588 let theme = cx.theme();
589589
590590 AnyElement::new(
@@ -604,7 +604,7 @@ impl Item for Editor { · 13 unmodified lines
604604 &description,
605605 MAX_TAB_TITLE_LEN,
606606 ))
607− .color(TextColor::Muted),
607+ .color(Color::Muted),
608608 ),
609609 )
610610 })),
@@ -761,7 +761,7 @@ impl Item for Editor { · 150 unmodified lines
761761 }
762762
763763 fn breadcrumb_location(&self) -> ToolbarItemLocation {
764− ToolbarItemLocation::PrimaryLeft { flex: None }
764+ ToolbarItemLocation::PrimaryLeft
765765 }
766766
767767 fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
@@ -907,17 +907,15 @@ impl SearchableItem for Editor { · 139 unmodified lines
907907 type Match = Range<Anchor>;
908908
909909 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
910− todo!()
911− // self.clear_background_highlights::<BufferSearchHighlights>(cx);
910+ self.clear_background_highlights::<BufferSearchHighlights>(cx);
912911 }
913912
914913 fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
915− todo!()
916− // self.highlight_background::<BufferSearchHighlights>(
917− // matches,
918− // |theme| theme.search.match_background,
919− // cx,
920− // );
914+ self.highlight_background::<BufferSearchHighlights>(
915+ matches,
916+ |theme| theme.title_bar_background, // todo: update theme
917+ cx,
918+ );
921919 }
922920
923921 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
@@ -952,22 +950,20 @@ impl SearchableItem for Editor { · 28 unmodified lines
952950 matches: Vec<Range<Anchor>>,
953951 cx: &mut ViewContext<Self>,
954952 ) {
955− todo!()
956− // self.unfold_ranges([matches[index].clone()], false, true, cx);
957− // let range = self.range_for_match(&matches[index]);
958− // self.change_selections(Some(Autoscroll::fit()), cx, |s| {
959− // s.select_ranges([range]);
960− // })
953+ self.unfold_ranges([matches[index].clone()], false, true, cx);
954+ let range = self.range_for_match(&matches[index]);
955+ self.change_selections(Some(Autoscroll::fit()), cx, |s| {
956+ s.select_ranges([range]);
957+ })
961958 }
962959
963960 fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
964− todo!()
965− // self.unfold_ranges(matches.clone(), false, false, cx);
966− // let mut ranges = Vec::new();
967− // for m in &matches {
968− // ranges.push(self.range_for_match(&m))
969− // }
970− // self.change_selections(None, cx, |s| s.select_ranges(ranges));
961+ self.unfold_ranges(matches.clone(), false, false, cx);
962+ let mut ranges = Vec::new();
963+ for m in &matches {
964+ ranges.push(self.range_for_match(&m))
965+ }
966+ self.change_selections(None, cx, |s| s.select_ranges(ranges));
971967 }
972968 fn replace(
973969 &mut self,
crates/file_finder2/src/file_finder.rs+13−23
@@ -2,9 +2,8 @@ use collections::HashMap; · 1 unmodified line
22 use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
33 use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
44 use gpui::{
5− actions, div, AppContext, Component, Div, EventEmitter, FocusHandle, FocusableView,
6− InteractiveComponent, Manager, Model, ParentComponent, Render, Styled, Task, View, ViewContext,
7− VisualContext, WeakView,
5+ actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
6+ ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
87 };
98 use picker::{Picker, PickerDelegate};
109 use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -16,8 +15,7 @@ use std::{ · 5 unmodified lines
1615 },
1716 };
1817 use text::Point;
19−use theme::ActiveTheme;
20−use ui::{v_stack, HighlightedLabel, StyledExt};
18+use ui::{v_stack, HighlightedLabel, ListItem};
2119 use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
2220 use workspace::Workspace;
2321
@@ -111,14 +109,14 @@ impl FileFinder { · 87 unmodified lines
111109 }
112110 }
113111
114−impl EventEmitter<Manager> for FileFinder {}
112+impl EventEmitter<DismissEvent> for FileFinder {}
115113 impl FocusableView for FileFinder {
116114 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
117115 self.picker.focus_handle(cx)
118116 }
119117 }
120118 impl Render for FileFinder {
121− type Element = Div<Self>;
119+ type Element = Div;
122120
123121 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
124122 v_stack().w_96().child(self.picker.clone())
@@ -530,7 +528,7 @@ impl FileFinderDelegate { · 405 unmodified lines
530528 }
531529
532530 impl PickerDelegate for FileFinderDelegate {
533− type ListItem = Div<Picker<Self>>;
531+ type ListItem = ListItem;
534532
535533 fn placeholder_text(&self) -> Arc<str> {
536534 "Search project files...".into()
@@ -690,7 +688,7 @@ impl PickerDelegate for FileFinderDelegate { · 153 unmodified lines
690688 }
691689 }
692690 finder
693− .update(&mut cx, |_, cx| cx.emit(Manager::Dismiss))
691+ .update(&mut cx, |_, cx| cx.emit(DismissEvent::Dismiss))
694692 .ok()?;
695693
696694 Some(())
@@ -702,7 +700,7 @@ impl PickerDelegate for FileFinderDelegate { · 5 unmodified lines
702700
703701 fn dismissed(&mut self, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
704702 self.file_finder
705− .update(cx, |_, cx| cx.emit(Manager::Dismiss))
703+ .update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
706704 .log_err();
707705 }
708706
@@ -711,30 +709,22 @@ impl PickerDelegate for FileFinderDelegate { · 2 unmodified lines
711709 ix: usize,
712710 selected: bool,
713711 cx: &mut ViewContext<Picker<Self>>,
714− ) -> Self::ListItem {
712+ ) -> Option<Self::ListItem> {
715713 let path_match = self
716714 .matches
717715 .get(ix)
718716 .expect("Invalid matches state: no element for index {ix}");
719− let theme = cx.theme();
720− let colors = theme.colors();
721717
722718 let (file_name, file_name_positions, full_path, full_path_positions) =
723719 self.labels_for_match(path_match, cx, ix);
724720
725− div()
726− .px_1()
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(
721+ Some(
722+ ListItem::new(ix).selected(selected).child(
734723 v_stack()
735724 .child(HighlightedLabel::new(file_name, file_name_positions))
736725 .child(HighlightedLabel::new(full_path, full_path_positions)),
737− )
726+ ),
727+ )
738728 }
739729 }
740730
crates/fuzzy2/src/strings.rs+28−0
@@ -6,6 +6,8 @@ use gpui::BackgroundExecutor; · 5 unmodified lines
66 use std::{
77 borrow::Cow,
88 cmp::{self, Ordering},
9+ iter,
10+ ops::Range,
911 sync::atomic::AtomicBool,
1012 };
1113
@@ -54,6 +56,32 @@ pub struct StringMatch { · 42 unmodified lines
5456 pub string: String,
5557 }
5658
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+
5785 impl PartialEq for StringMatch {
5886 fn eq(&self, other: &Self) -> bool {
5987 self.cmp(other).is_eq()
crates/go_to_line2/src/go_to_line.rs+22−21
@@ -1,14 +1,13 @@
11 use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
22 use gpui::{
3− actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager,
4− ParentComponent, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
3+ actions, div, prelude::*, AppContext, DismissEvent, Div, EventEmitter, FocusHandle,
4+ FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
55 WindowContext,
66 };
77 use text::{Bias, Point};
88 use theme::ActiveTheme;
9−use ui::{h_stack, v_stack, Label, StyledExt, TextColor};
9+use ui::{h_stack, v_stack, Color, Label, StyledExt};
1010 use util::paths::FILE_ROW_COLUMN_DELIMITER;
11−use workspace::Workspace;
1211
1312 actions!(Toggle);
1413
@@ -26,22 +25,24 @@ pub struct GoToLine { · 11 unmodified lines
2625
2726 impl FocusableView for GoToLine {
2827 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
29− self.active_editor.focus_handle(cx)
28+ self.line_editor.focus_handle(cx)
3029 }
3130 }
32−impl EventEmitter<Manager> for GoToLine {}
31+impl EventEmitter<DismissEvent> for GoToLine {}
3332
3433 impl GoToLine {
35− fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
36− workspace.register_action(|workspace, _: &Toggle, cx| {
37− let Some(editor) = workspace
38− .active_item(cx)
39− .and_then(|active_item| active_item.downcast::<Editor>())
40− else {
34+ fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
35+ let handle = cx.view().downgrade();
36+ editor.register_action(move |_: &Toggle, cx| {
37+ let Some(editor) = handle.upgrade() else {
4138 return;
4239 };
43−
44− workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
40+ let Some(workspace) = editor.read(cx).workspace() else {
41+ return;
42+ };
43+ workspace.update(cx, |workspace, cx| {
44+ workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
45+ })
4546 });
4647 }
4748
@@ -89,7 +90,7 @@ impl GoToLine { · 41 unmodified lines
8990 ) {
9091 match event {
9192 // todo!() this isn't working...
92− editor::EditorEvent::Blurred => cx.emit(Manager::Dismiss),
93+ editor::EditorEvent::Blurred => cx.emit(DismissEvent::Dismiss),
9394 editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx),
9495 _ => {}
9596 }
@@ -124,7 +125,7 @@ impl GoToLine { · 28 unmodified lines
124125 }
125126
126127 fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
127− cx.emit(Manager::Dismiss);
128+ cx.emit(DismissEvent::Dismiss);
128129 }
129130
130131 fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
@@ -141,19 +142,19 @@ impl GoToLine { · 10 unmodified lines
141142 self.prev_scroll_position.take();
142143 }
143144
144− cx.emit(Manager::Dismiss);
145+ cx.emit(DismissEvent::Dismiss);
145146 }
146147 }
147148
148149 impl Render for GoToLine {
149− type Element = Div<Self>;
150+ type Element = Div;
150151
151152 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
152153 div()
153154 .elevation_2(cx)
154155 .key_context("GoToLine")
155− .on_action(Self::cancel)
156− .on_action(Self::confirm)
156+ .on_action(cx.listener(Self::cancel))
157+ .on_action(cx.listener(Self::confirm))
157158 .w_96()
158159 .child(
159160 v_stack()
@@ -177,7 +178,7 @@ impl Render for GoToLine { · 17 unmodified lines
177178 .justify_between()
178179 .px_2()
179180 .py_1()
180− .child(Label::new(self.current_text.clone()).color(TextColor::Muted)),
181+ .child(Label::new(self.current_text.clone()).color(Color::Muted)),
181182 ),
182183 )
183184 }
crates/gpui2/src/app.rs+27−8
@@ -10,11 +10,12 @@ pub use entity_map::*; · 9 unmodified lines
1010 pub use model_context::*;
1111 use refineable::Refineable;
1212 use smallvec::SmallVec;
13+use smol::future::FutureExt;
1314 #[cfg(any(test, feature = "test-support"))]
1415 pub use test_context::*;
1516
1617 use crate::{
17− current_platform, image_cache::ImageCache, Action, ActionRegistry, AnyBox, AnyView,
18+ current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView,
1819 AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
1920 DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
2021 ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform,
@@ -28,7 +29,7 @@ use futures::{channel::oneshot, future::LocalBoxFuture, Future}; · 7 unmodified lines
2829 use parking_lot::Mutex;
2930 use slotmap::SlotMap;
3031 use std::{
31− any::{type_name, Any, TypeId},
32+ any::{type_name, TypeId},
3233 cell::{Ref, RefCell, RefMut},
3334 marker::PhantomData,
3435 mem,
@@ -194,7 +195,7 @@ pub struct AppContext { · 159 unmodified lines
194195 asset_source: Arc<dyn AssetSource>,
195196 pub(crate) image_cache: ImageCache,
196197 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>>,
198199 pub(crate) entities: EntityMap,
199200 pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
200201 pub(crate) windows: SlotMap<WindowId, Option<Window>>,
@@ -424,7 +425,7 @@ impl AppContext { · 223 unmodified lines
424425 /// Opens a new window with the given option and the root view returned by the given function.
425426 /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
426427 /// functionality.
427− pub fn open_window<V: Render>(
428+ pub fn open_window<V: 'static + Render>(
428429 &mut self,
429430 options: crate::WindowOptions,
430431 build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
@@ -579,7 +580,7 @@ impl AppContext { · 148 unmodified lines
579580 .windows
580581 .iter()
581582 .filter_map(|(_, window)| {
582− let window = window.as_ref().unwrap();
583+ let window = window.as_ref()?;
583584 if window.dirty {
584585 Some(window.handle.clone())
585586 } else {
@@ -983,6 +984,22 @@ impl AppContext { · 397 unmodified lines
983984 pub fn all_action_names(&self) -> &[SharedString] {
984985 self.actions.all_action_names()
985986 }
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+ }
9861003 }
9871004
9881005 impl Context for AppContext {
@@ -1032,7 +1049,9 @@ impl Context for AppContext { · 43 unmodified lines
10321049 let root_view = window.root_view.clone().unwrap();
10331050 let result = update(root_view, &mut WindowContext::new(cx, &mut window));
10341051
1035− if !window.removed {
1052+ if window.removed {
1053+ cx.windows.remove(handle.id);
1054+ } else {
10361055 cx.windows
10371056 .get_mut(handle.id)
10381057 .ok_or_else(|| anyhow!("window not found"))?
@@ -1104,12 +1123,12 @@ pub(crate) enum Effect { · 65 unmodified lines
11041123
11051124 /// Wraps a global variable value during `update_global` while the value has been moved to the stack.
11061125 pub(crate) struct GlobalLease<G: 'static> {
1107− global: AnyBox,
1126+ global: Box<dyn Any>,
11081127 global_type: PhantomData<G>,
11091128 }
11101129
11111130 impl<G: 'static> GlobalLease<G> {
1112− fn new(global: AnyBox) -> Self {
1131+ fn new(global: Box<dyn Any>) -> Self {
11131132 GlobalLease {
11141133 global,
11151134 global_type: PhantomData,
crates/gpui2/src/app/async_context.rs+6−6
@@ -1,7 +1,7 @@
11 use crate::{
2− AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, FocusableView,
3− ForegroundExecutor, Manager, Model, ModelContext, Render, Result, Task, View, ViewContext,
4− VisualContext, WindowContext, WindowHandle,
2+ AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, Context, DismissEvent,
3+ FocusableView, ForegroundExecutor, Model, ModelContext, Render, Result, Task, View,
4+ ViewContext, VisualContext, WindowContext, WindowHandle,
55 };
66 use anyhow::{anyhow, Context as _};
77 use derive_more::{Deref, DerefMut};
@@ -115,7 +115,7 @@ impl AsyncAppContext { · 107 unmodified lines
115115 build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
116116 ) -> Result<WindowHandle<V>>
117117 where
118− V: Render,
118+ V: 'static + Render,
119119 {
120120 let app = self
121121 .app
@@ -306,7 +306,7 @@ impl VisualContext for AsyncWindowContext { · 184 unmodified lines
306306 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
307307 ) -> Self::Result<View<V>>
308308 where
309− V: Render,
309+ V: 'static + Render,
310310 {
311311 self.window
312312 .update(self, |_, cx| cx.replace_root_view(build_view))
@@ -326,7 +326,7 @@ impl VisualContext for AsyncWindowContext { · 13 unmodified lines
326326 V: crate::ManagedView,
327327 {
328328 self.window.update(self, |_, cx| {
329− view.update(cx, |_, cx| cx.emit(Manager::Dismiss))
329+ view.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
330330 })
331331 }
332332 }
crates/gpui2/src/app/entity_map.rs+5−5
@@ -1,10 +1,10 @@
1−use crate::{private::Sealed, AnyBox, AppContext, Context, Entity, ModelContext};
1+use crate::{private::Sealed, AppContext, Context, Entity, ModelContext};
22 use anyhow::{anyhow, Result};
33 use derive_more::{Deref, DerefMut};
44 use parking_lot::{RwLock, RwLockUpgradableReadGuard};
55 use slotmap::{SecondaryMap, SlotMap};
66 use std::{
7− any::{type_name, TypeId},
7+ any::{type_name, Any, TypeId},
88 fmt::{self, Display},
99 hash::{Hash, Hasher},
1010 marker::PhantomData,
@@ -31,7 +31,7 @@ impl Display for EntityId { · 20 unmodified lines
3131 }
3232
3333 pub(crate) struct EntityMap {
34− entities: SecondaryMap<EntityId, AnyBox>,
34+ entities: SecondaryMap<EntityId, Box<dyn Any>>,
3535 ref_counts: Arc<RwLock<EntityRefCounts>>,
3636 }
3737
@@ -102,7 +102,7 @@ impl EntityMap { · 64 unmodified lines
102102 );
103103 }
104104
105− pub fn take_dropped(&mut self) -> Vec<(EntityId, AnyBox)> {
105+ pub fn take_dropped(&mut self) -> Vec<(EntityId, Box<dyn Any>)> {
106106 let mut ref_counts = self.ref_counts.write();
107107 let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids);
108108
@@ -122,7 +122,7 @@ impl EntityMap { · 13 unmodified lines
122122 }
123123
124124 pub struct Lease<'a, T> {
125− entity: Option<AnyBox>,
125+ entity: Option<Box<dyn Any>>,
126126 pub model: &'a Model<T>,
127127 entity_type: PhantomData<T>,
128128 }
crates/gpui2/src/app/test_context.rs+14−15
@@ -1,8 +1,9 @@
11 use crate::{
22 div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
3− BackgroundExecutor, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent,
4− Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, TestWindow,
5− View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
3+ BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
4+ KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
5+ TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
6+ WindowOptions,
67 };
78 use anyhow::{anyhow, bail};
89 use futures::{Stream, StreamExt};
@@ -126,7 +127,7 @@ impl TestAppContext { · 117 unmodified lines
126127 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
127128 where
128129 F: FnOnce(&mut ViewContext<V>) -> V,
129− V: Render,
130+ V: 'static + Render,
130131 {
131132 let mut cx = self.app.borrow_mut();
132133 cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
@@ -143,7 +144,7 @@ impl TestAppContext { · 10 unmodified lines
143144 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
144145 where
145146 F: FnOnce(&mut ViewContext<V>) -> V,
146− V: Render,
147+ V: 'static + Render,
147148 {
148149 let mut cx = self.app.borrow_mut();
149150 let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
@@ -296,21 +297,19 @@ impl TestAppContext { · 146 unmodified lines
296297 .unwrap()
297298 }
298299
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 = ()> {
300301 let (tx, rx) = futures::channel::mpsc::unbounded();
301−
302− entity.update(self, move |_, cx: &mut ModelContext<T>| {
302+ self.update(|cx| {
303303 cx.observe(entity, {
304304 let tx = tx.clone();
305− move |_, _, _| {
305+ move |_, _| {
306306 let _ = tx.unbounded_send(());
307307 }
308308 })
309309 .detach();
310−
311− cx.on_release(move |_, _| tx.close_channel()).detach();
310+ cx.observe_release(entity, move |_, _| tx.close_channel())
311+ .detach()
312312 });
313−
314313 rx
315314 }
316315
@@ -591,7 +590,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { · 274 unmodified lines
591590 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
592591 ) -> Self::Result<View<V>>
593592 where
594− V: Render,
593+ V: 'static + Render,
595594 {
596595 self.window
597596 .update(self.cx, |_, cx| cx.replace_root_view(build_view))
@@ -612,7 +611,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { · 14 unmodified lines
612611 {
613612 self.window
614613 .update(self.cx, |_, cx| {
615− view.update(cx, |_, cx| cx.emit(crate::Manager::Dismiss))
614+ view.update(cx, |_, cx| cx.emit(crate::DismissEvent::Dismiss))
616615 })
617616 .unwrap()
618617 }
@@ -631,7 +630,7 @@ impl AnyWindowHandle { · 12 unmodified lines
631630 pub struct EmptyView {}
632631
633632 impl Render for EmptyView {
634− type Element = Div<Self>;
633+ type Element = Div;
635634
636635 fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
637636 div()
crates/gpui2/src/element.rs+356−215
@@ -1,236 +1,328 @@
11 use crate::{
22 AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext,
3+ WindowContext,
34 };
45 use derive_more::{Deref, DerefMut};
56 pub(crate) use smallvec::SmallVec;
6−use std::{any::Any, fmt::Debug, mem};
7+use std::{any::Any, fmt::Debug};
78
8−pub trait Element<V: 'static> {
9− type ElementState: 'static;
9+pub trait Render: 'static + Sized {
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;
1017
1118 fn element_id(&self) -> Option<ElementId>;
1219
13− fn layout(
14− &mut self,
15− view_state: &mut V,
16− element_state: Option<Self::ElementState>,
17− cx: &mut ViewContext<V>,
18− ) -> (LayoutId, Self::ElementState);
20+ fn into_element(self) -> Self::Element;
1921
20− fn paint(
21− &mut self,
22− bounds: Bounds<Pixels>,
23− view_state: &mut V,
24− element_state: &mut Self::ElementState,
25− cx: &mut ViewContext<V>,
26− );
22+ fn into_any_element(self) -> AnyElement {
23+ self.into_element().into_any()
24+ }
2725
2826 fn draw<T, R>(
2927 self,
3028 origin: Point<Pixels>,
3129 available_space: Size<T>,
32− view_state: &mut V,
33− cx: &mut ViewContext<V>,
34− f: impl FnOnce(&Self::ElementState, &mut ViewContext<V>) -> R,
30+ cx: &mut WindowContext,
31+ f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R,
3532 ) -> R
3633 where
37− Self: Sized,
3834 T: Clone + Default + Debug + Into<AvailableSpace>,
3935 {
40− let mut element = RenderedElement {
41− element: self,
42− phase: ElementRenderPhase::Start,
36+ let element = self.into_element();
37+ let element_id = element.element_id();
38+ let element = DrawableElement {
39+ element: Some(element),
40+ phase: ElementDrawPhase::Start,
4341 };
44− element.draw(origin, available_space.map(Into::into), view_state, cx);
45− if let ElementRenderPhase::Painted { frame_state } = &element.phase {
46− if let Some(frame_state) = frame_state.as_ref() {
47− f(&frame_state, cx)
42+
43+ let frame_state =
44+ DrawableElement::draw(element, origin, available_space.map(Into::into), 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)
4879 } else {
49− let element_id = element
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− })
80+ this
5881 }
59− } else {
60− unreachable!()
82+ })
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),
61121 }
62122 }
63123 }
64124
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+
65163 #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
66164 pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
67165
68−pub trait ParentComponent<V: 'static> {
69− fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
166+pub trait ParentElement {
167+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>;
70168
71− fn child(mut self, child: impl Component<V>) -> Self
169+ fn child(mut self, child: impl IntoElement) -> Self
72170 where
73171 Self: Sized,
74172 {
75− self.children_mut().push(child.render());
173+ self.children_mut().push(child.into_element().into_any());
76174 self
77175 }
78176
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
80178 where
81179 Self: Sized,
82180 {
83181 self.children_mut()
84− .extend(iter.into_iter().map(|item| item.render()));
182+ .extend(children.into_iter().map(|child| child.into_any_element()));
85183 self
86184 }
87185 }
88186
89−trait ElementObject<V> {
187+trait ElementObject {
90188 fn element_id(&self) -> Option<ElementId>;
91− fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
92− fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
189+
190+ fn layout(&mut self, cx: &mut WindowContext) -> LayoutId;
191+
192+ fn paint(&mut self, cx: &mut WindowContext);
193+
93194 fn measure(
94195 &mut self,
95196 available_space: Size<AvailableSpace>,
96− view_state: &mut V,
97− cx: &mut ViewContext<V>,
197+ cx: &mut WindowContext,
98198 ) -> Size<Pixels>;
199+
99200 fn draw(
100201 &mut self,
101202 origin: Point<Pixels>,
102203 available_space: Size<AvailableSpace>,
103− view_state: &mut V,
104− cx: &mut ViewContext<V>,
204+ cx: &mut WindowContext,
105205 );
106206 }
107207
108−struct RenderedElement<V: 'static, E: Element<V>> {
109− element: E,
110− phase: ElementRenderPhase<E::ElementState>,
208+pub struct DrawableElement<E: Element> {
209+ element: Option<E>,
210+ phase: ElementDrawPhase<E::State>,
111211 }
112212
113213 #[derive(Default)]
114−enum ElementRenderPhase<V> {
214+enum ElementDrawPhase<S> {
115215 #[default]
116216 Start,
117217 LayoutRequested {
118218 layout_id: LayoutId,
119− frame_state: Option<V>,
219+ frame_state: Option<S>,
120220 },
121221 LayoutComputed {
122222 layout_id: LayoutId,
123223 available_space: Size<AvailableSpace>,
124− frame_state: Option<V>,
125− },
126− Painted {
127− frame_state: Option<V>,
224+ frame_state: Option<S>,
128225 },
129226 }
130227
131−/// Internal struct that wraps an element to store Layout and ElementState after the element is rendered.
132−/// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for
133−/// improved usability.
134−impl<V, E: Element<V>> RenderedElement<V, E> {
228+/// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
229+impl<E: Element> DrawableElement<E> {
135230 fn new(element: E) -> Self {
136− RenderedElement {
137− element,
138− phase: ElementRenderPhase::Start,
231+ DrawableElement {
232+ element: Some(element),
233+ phase: ElementDrawPhase::Start,
139234 }
140235 }
141−}
142236
143−impl<V, E> ElementObject<V> for RenderedElement<V, E>
144−where
145− E: Element<V>,
146− E::ElementState: 'static,
147−{
148237 fn element_id(&self) -> Option<ElementId> {
149− self.element.element_id()
238+ self.element.as_ref()?.element_id()
150239 }
151240
152− fn layout(&mut self, state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
153− let (layout_id, frame_state) = match mem::take(&mut self.phase) {
154− ElementRenderPhase::Start => {
155− if let Some(id) = self.element.element_id() {
156− let layout_id = cx.with_element_state(id, |element_state, cx| {
157− self.element.layout(state, element_state, cx)
158− });
159− (layout_id, None)
160− } else {
161− let (layout_id, frame_state) = self.element.layout(state, None, cx);
162− (layout_id, Some(frame_state))
163− }
164− }
165− ElementRenderPhase::LayoutRequested { .. }
166− | ElementRenderPhase::LayoutComputed { .. }
167− | ElementRenderPhase::Painted { .. } => {
168− panic!("element rendered twice")
169− }
241+ fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
242+ let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
243+ {
244+ let layout_id = cx.with_element_state(id, |element_state, cx| {
245+ self.element.as_mut().unwrap().layout(element_state, cx)
246+ });
247+ (layout_id, None)
248+ } else {
249+ let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(None, cx);
250+ (layout_id, Some(frame_state))
170251 };
171252
172− self.phase = ElementRenderPhase::LayoutRequested {
253+ self.phase = ElementDrawPhase::LayoutRequested {
173254 layout_id,
174255 frame_state,
175256 };
176257 layout_id
177258 }
178259
179− fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
180− self.phase = match mem::take(&mut self.phase) {
181− ElementRenderPhase::LayoutRequested {
260+ fn paint(mut self, cx: &mut WindowContext) -> Option<E::State> {
261+ match self.phase {
262+ ElementDrawPhase::LayoutRequested {
182263 layout_id,
183− mut frame_state,
264+ frame_state,
184265 }
185− | ElementRenderPhase::LayoutComputed {
266+ | ElementDrawPhase::LayoutComputed {
186267 layout_id,
187− mut frame_state,
268+ frame_state,
188269 ..
189270 } => {
190271 let bounds = cx.layout_bounds(layout_id);
191− if let Some(id) = self.element.element_id() {
192− cx.with_element_state(id, |element_state, cx| {
272+
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| {
193287 let mut element_state = element_state.unwrap();
194288 self.element
195− .paint(bounds, view_state, &mut element_state, cx);
289+ .take()
290+ .unwrap()
291+ .paint(bounds, &mut element_state, cx);
196292 ((), element_state)
197293 });
198− } else {
199− self.element
200− .paint(bounds, view_state, frame_state.as_mut().unwrap(), cx);
294+ None
201295 }
202− ElementRenderPhase::Painted { frame_state }
203296 }
204297
205298 _ => panic!("must call layout before paint"),
206− };
299+ }
207300 }
208301
209302 fn measure(
210303 &mut self,
211304 available_space: Size<AvailableSpace>,
212− view_state: &mut V,
213− cx: &mut ViewContext<V>,
305+ cx: &mut WindowContext,
214306 ) -> Size<Pixels> {
215− if matches!(&self.phase, ElementRenderPhase::Start) {
216− self.layout(view_state, cx);
307+ if matches!(&self.phase, ElementDrawPhase::Start) {
308+ self.layout(cx);
217309 }
218310
219311 let layout_id = match &mut self.phase {
220− ElementRenderPhase::LayoutRequested {
312+ ElementDrawPhase::LayoutRequested {
221313 layout_id,
222314 frame_state,
223315 } => {
224316 cx.compute_layout(*layout_id, available_space);
225317 let layout_id = *layout_id;
226− self.phase = ElementRenderPhase::LayoutComputed {
318+ self.phase = ElementDrawPhase::LayoutComputed {
227319 layout_id,
228320 available_space,
229321 frame_state: frame_state.take(),
230322 };
231323 layout_id
232324 }
233− ElementRenderPhase::LayoutComputed {
325+ ElementDrawPhase::LayoutComputed {
234326 layout_id,
235327 available_space: prev_available_space,
236328 ..
@@ -248,154 +340,203 @@ where · 11 unmodified lines
248340 }
249341
250342 fn draw(
251− &mut self,
343+ mut self,
252344 origin: Point<Pixels>,
253345 available_space: Size<AvailableSpace>,
254− view_state: &mut V,
255− cx: &mut ViewContext<V>,
256− ) {
257− self.measure(available_space, view_state, cx);
258− cx.with_absolute_element_offset(origin, |cx| self.paint(view_state, cx))
346+ cx: &mut WindowContext,
347+ ) -> Option<E::State> {
348+ self.measure(available_space, cx);
349+ cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
259350 }
260351 }
261352
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;
263355
264−impl<V> AnyElement<V> {
265− pub fn new<E>(element: E) -> Self
266− where
267− V: 'static,
268− E: 'static + Element<V>,
269− E::ElementState: Any,
270− {
271− AnyElement(Box::new(RenderedElement::new(element)))
272− }
356+// fn layout(
357+// &mut self,
358+// element_state: Option<Self::State>,
359+// cx: &mut WindowContext,
360+// ) -> (LayoutId, Self::State) {
273361
274− pub fn element_id(&self) -> Option<ElementId> {
275− self.0.element_id()
362+// }
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()
276393 }
277394
278− pub fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
279− self.0.layout(view_state, cx)
395+ fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
396+ DrawableElement::layout(self.as_mut().unwrap(), cx)
280397 }
281398
282− pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
283− self.0.paint(view_state, cx)
399+ fn paint(&mut self, cx: &mut WindowContext) {
400+ DrawableElement::paint(self.take().unwrap(), cx);
284401 }
285402
286− /// Initializes this element and performs layout within the given available space to determine its size.
287− pub fn measure(
403+ fn measure(
288404 &mut self,
289405 available_space: Size<AvailableSpace>,
290− view_state: &mut V,
291− cx: &mut ViewContext<V>,
406+ cx: &mut WindowContext,
292407 ) -> Size<Pixels> {
293− self.0.measure(available_space, view_state, cx)
408+ DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
294409 }
295410
296− /// Initializes this element and performs layout in the available space, then paints it at the given origin.
297− pub fn draw(
411+ fn draw(
298412 &mut self,
299413 origin: Point<Pixels>,
300414 available_space: Size<AvailableSpace>,
301− view_state: &mut V,
302− cx: &mut ViewContext<V>,
415+ cx: &mut WindowContext,
303416 ) {
304− self.0.draw(origin, available_space, view_state, cx)
417+ DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
305418 }
306419 }
307420
308−pub trait Component<V> {
309− fn render(self) -> AnyElement<V>;
421+pub struct AnyElement(Box<dyn ElementObject>);
310422
311− fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
423+impl AnyElement {
424+ pub fn new<E>(element: E) -> Self
312425 where
313− Self: Sized,
314− U: Component<V>,
426+ E: 'static + Element,
427+ E::State: Any,
315428 {
316− f(self)
429+ AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>)
317430 }
318431
319− fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
320− where
321− Self: Sized,
322− {
323− self.map(|this| if condition { then(this) } else { this })
432+ pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
433+ self.0.layout(cx)
324434 }
325435
326− fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
327− where
328− Self: Sized,
329− {
330− self.map(|this| {
331− if let Some(value) = option {
332− then(this, value)
333− } else {
334− this
335− }
336− })
436+ pub fn paint(mut self, cx: &mut WindowContext) {
437+ self.0.paint(cx)
337438 }
338−}
339439
340−impl<V> Component<V> for AnyElement<V> {
341− fn render(self) -> AnyElement<V> {
342− self
440+ /// Initializes this element and performs layout within the given available space to determine its size.
441+ pub fn measure(
442+ &mut self,
443+ available_space: Size<AvailableSpace>,
444+ cx: &mut WindowContext,
445+ ) -> Size<Pixels> {
446+ self.0.measure(available_space, cx)
343447 }
344−}
345448
346−impl<V, E, F> Element<V> for Option<F>
347−where
348− V: 'static,
349− E: 'static + Component<V>,
350− F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
351−{
352− type ElementState = AnyElement<V>;
449+ /// Initializes this element and performs layout in the available space, then paints it at the given origin.
450+ pub fn draw(
451+ mut self,
452+ origin: Point<Pixels>,
453+ available_space: Size<AvailableSpace>,
454+ cx: &mut WindowContext,
455+ ) {
456+ self.0.draw(origin, available_space, cx)
457+ }
353458
354− fn element_id(&self) -> Option<ElementId> {
355− None
459+ /// Converts this `AnyElement` into a trait object that can be stored and manipulated.
460+ pub fn into_any(self) -> AnyElement {
461+ AnyElement::new(self)
356462 }
357463
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+
358472 fn layout(
359473 &mut self,
360− view_state: &mut V,
361− _: Option<Self::ElementState>,
362− cx: &mut ViewContext<V>,
363− ) -> (LayoutId, Self::ElementState) {
364− let render = self.take().unwrap();
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)
474+ _: Option<Self::State>,
475+ cx: &mut WindowContext,
476+ ) -> (LayoutId, Self::State) {
477+ let layout_id = self.layout(cx);
478+ (layout_id, ())
368479 }
369480
370− fn paint(
371− &mut self,
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)
481+ fn paint(self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
482+ self.paint(cx);
378483 }
379484 }
380485
381−impl<V, E, F> Component<V> for Option<F>
382−where
383− V: 'static,
384− E: 'static + Component<V>,
385− F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
386−{
387− fn render(self) -> AnyElement<V> {
388− AnyElement::new(self)
486+impl IntoElement for AnyElement {
487+ type Element = Self;
488+
489+ fn element_id(&self) -> Option<ElementId> {
490+ None
389491 }
390−}
391492
392−impl<V, E, F> Component<V> for F
393−where
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))
493+ fn into_element(self) -> Self::Element {
494+ self
400495 }
401496 }
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+// }
crates/gpui2/src/elements/div.rs+290−342
@@ -1,9 +1,9 @@
11 use crate::{
22 point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
3− BorrowWindow, Bounds, ClickEvent, Component, DispatchPhase, Element, ElementId, FocusEvent,
4− FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
5− MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, Point, Render, ScrollWheelEvent,
6− SharedString, Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility,
3+ BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
4+ IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
5+ MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
6+ SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext,
77 };
88 use collections::HashMap;
99 use refineable::Refineable;
@@ -12,7 +12,6 @@ use std::{ · 2 unmodified lines
1212 any::{Any, TypeId},
1313 cell::RefCell,
1414 fmt::Debug,
15− marker::PhantomData,
1615 mem,
1716 rc::Rc,
1817 time::Duration,
@@ -28,30 +27,24 @@ pub struct GroupStyle { · 9 unmodified lines
2827 pub style: StyleRefinement,
2928 }
3029
31−pub trait InteractiveComponent<V: 'static>: Sized + Element<V> {
32− fn interactivity(&mut self) -> &mut Interactivity<V>;
30+pub trait InteractiveElement: Sized + Element {
31+ fn interactivity(&mut self) -> &mut Interactivity;
3332
3433 fn group(mut self, group: impl Into<SharedString>) -> Self {
3534 self.interactivity().group = Some(group.into());
3635 self
3736 }
3837
39− fn id(mut self, id: impl Into<ElementId>) -> Stateful<V, Self> {
38+ fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
4039 self.interactivity().element_id = Some(id.into());
4140
42− Stateful {
43− element: self,
44− view_type: PhantomData,
45− }
41+ Stateful { element: self }
4642 }
4743
48− fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<V, Self> {
44+ fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> {
4945 self.interactivity().focusable = true;
5046 self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
51− Focusable {
52− element: self,
53− view_type: PhantomData,
54− }
47+ Focusable { element: self }
5548 }
5649
5750 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
8578 fn on_mouse_down(
8679 mut self,
8780 button: MouseButton,
88− handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
81+ listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
8982 ) -> Self {
9083 self.interactivity().mouse_down_listeners.push(Box::new(
91− move |view, event, bounds, phase, cx| {
84+ move |event, bounds, phase, cx| {
9285 if phase == DispatchPhase::Bubble
9386 && event.button == button
9487 && bounds.contains_point(&event.position)
9588 {
96− handler(view, event, cx)
89+ (listener)(event, cx)
9790 }
9891 },
9992 ));
@@ -102,12 +95,12 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines
10295
10396 fn on_any_mouse_down(
10497 mut self,
105− handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
98+ listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
10699 ) -> Self {
107100 self.interactivity().mouse_down_listeners.push(Box::new(
108− move |view, event, bounds, phase, cx| {
101+ move |event, bounds, phase, cx| {
109102 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
110− handler(view, event, cx)
103+ (listener)(event, cx)
111104 }
112105 },
113106 ));
@@ -117,43 +110,43 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 3 unmodified lines
117110 fn on_mouse_up(
118111 mut self,
119112 button: MouseButton,
120− handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
113+ listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
121114 ) -> Self {
122− self.interactivity().mouse_up_listeners.push(Box::new(
123− move |view, event, bounds, phase, cx| {
115+ self.interactivity()
116+ .mouse_up_listeners
117+ .push(Box::new(move |event, bounds, phase, cx| {
124118 if phase == DispatchPhase::Bubble
125119 && event.button == button
126120 && bounds.contains_point(&event.position)
127121 {
128− handler(view, event, cx)
122+ (listener)(event, cx)
129123 }
130− },
131− ));
124+ }));
132125 self
133126 }
134127
135128 fn on_any_mouse_up(
136129 mut self,
137− handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
130+ listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
138131 ) -> Self {
139− self.interactivity().mouse_up_listeners.push(Box::new(
140− move |view, event, bounds, phase, cx| {
132+ self.interactivity()
133+ .mouse_up_listeners
134+ .push(Box::new(move |event, bounds, phase, cx| {
141135 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
142− handler(view, event, cx)
136+ (listener)(event, cx)
143137 }
144− },
145− ));
138+ }));
146139 self
147140 }
148141
149142 fn on_mouse_down_out(
150143 mut self,
151− handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
144+ listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
152145 ) -> Self {
153146 self.interactivity().mouse_down_listeners.push(Box::new(
154− move |view, event, bounds, phase, cx| {
147+ move |event, bounds, phase, cx| {
155148 if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
156− handler(view, event, cx)
149+ (listener)(event, cx)
157150 }
158151 },
159152 ));
@@ -163,29 +156,29 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 3 unmodified lines
163156 fn on_mouse_up_out(
164157 mut self,
165158 button: MouseButton,
166− handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
159+ listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
167160 ) -> Self {
168− self.interactivity().mouse_up_listeners.push(Box::new(
169− move |view, event, bounds, phase, cx| {
161+ self.interactivity()
162+ .mouse_up_listeners
163+ .push(Box::new(move |event, bounds, phase, cx| {
170164 if phase == DispatchPhase::Capture
171165 && event.button == button
172166 && !bounds.contains_point(&event.position)
173167 {
174− handler(view, event, cx);
168+ (listener)(event, cx);
175169 }
176− },
177− ));
170+ }));
178171 self
179172 }
180173
181174 fn on_mouse_move(
182175 mut self,
183− handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,
176+ listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
184177 ) -> Self {
185178 self.interactivity().mouse_move_listeners.push(Box::new(
186− move |view, event, bounds, phase, cx| {
179+ move |event, bounds, phase, cx| {
187180 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
188− handler(view, event, cx);
181+ (listener)(event, cx);
189182 }
190183 },
191184 ));
@@ -194,29 +187,29 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines
194187
195188 fn on_scroll_wheel(
196189 mut self,
197− handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,
190+ listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
198191 ) -> Self {
199192 self.interactivity().scroll_wheel_listeners.push(Box::new(
200− move |view, event, bounds, phase, cx| {
193+ move |event, bounds, phase, cx| {
201194 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
202− handler(view, event, cx);
195+ (listener)(event, cx);
203196 }
204197 },
205198 ));
206199 self
207200 }
208201
209− /// Capture the given action, fires during the capture phase
202+ /// Capture the given action, before normal action dispatch can fire
210203 fn capture_action<A: Action>(
211204 mut self,
212− listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
205+ listener: impl Fn(&A, &mut WindowContext) + 'static,
213206 ) -> Self {
214207 self.interactivity().action_listeners.push((
215208 TypeId::of::<A>(),
216− Box::new(move |view, action, phase, cx| {
209+ Box::new(move |action, phase, cx| {
217210 let action = action.downcast_ref().unwrap();
218211 if phase == DispatchPhase::Capture {
219− listener(view, action, cx)
212+ (listener)(action, cx)
220213 }
221214 }),
222215 ));
@@ -224,10 +217,7 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 1 unmodified line
224217 }
225218
226219 /// Add a listener for the given action, fires during the bubble event phase
227− fn on_action<A: Action>(
228− mut self,
229− listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
230− ) -> Self {
220+ fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
231221 // NOTE: this debug assert has the side-effect of working around
232222 // a bug where a crate consisting only of action definitions does
233223 // not register the actions in debug builds:
@@ -244,10 +234,10 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 10 unmodified lines
244234 // );
245235 self.interactivity().action_listeners.push((
246236 TypeId::of::<A>(),
247− Box::new(move |view, action, phase, cx| {
237+ Box::new(move |action, phase, cx| {
248238 let action = action.downcast_ref().unwrap();
249239 if phase == DispatchPhase::Bubble {
250− listener(view, action, cx)
240+ (listener)(action, cx)
251241 }
252242 }),
253243 ));
@@ -256,24 +246,53 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 2 unmodified lines
256246
257247 fn on_key_down(
258248 mut self,
259− listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
249+ listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
260250 ) -> Self {
261251 self.interactivity()
262252 .key_down_listeners
263− .push(Box::new(move |view, event, phase, cx| {
264− listener(view, event, phase, cx)
253+ .push(Box::new(move |event, phase, cx| {
254+ if phase == DispatchPhase::Bubble {
255+ (listener)(event, cx)
256+ }
265257 }));
266258 self
267259 }
268260
269− fn on_key_up(
261+ fn capture_key_down(
270262 mut self,
271− listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
263+ listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
272264 ) -> 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 {
273276 self.interactivity()
274277 .key_up_listeners
275− .push(Box::new(move |view, event, phase, cx| {
276− listener(view, event, phase, cx)
278+ .push(Box::new(move |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+ }
277296 }));
278297 self
279298 }
@@ -302,25 +321,22 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> { · 22 unmodified lines
302321
303322 fn on_drop<W: 'static>(
304323 mut self,
305− listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,
324+ listener: impl Fn(&View<W>, &mut WindowContext) + 'static,
306325 ) -> Self {
307326 self.interactivity().drop_listeners.push((
308327 TypeId::of::<W>(),
309− Box::new(move |view, dragged_view, cx| {
310− listener(view, dragged_view.downcast().unwrap(), cx);
328+ Box::new(move |dragged_view, cx| {
329+ listener(&dragged_view.downcast().unwrap(), cx);
311330 }),
312331 ));
313332 self
314333 }
315334 }
316335
317−pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveComponent<V> {
318− fn focusable(mut self) -> Focusable<V, Self> {
336+pub trait StatefulInteractiveElement: InteractiveElement {
337+ fn focusable(mut self) -> Focusable<Self> {
319338 self.interactivity().focusable = true;
320− Focusable {
321− element: self,
322− view_type: PhantomData,
323− }
339+ Focusable { element: self }
324340 }
325341
326342 fn overflow_scroll(mut self) -> Self {
@@ -362,23 +378,17 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 35 unmodified lines
362378 self
363379 }
364380
365− fn on_click(
366− mut self,
367− listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
368− ) -> Self
381+ fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self
369382 where
370383 Self: Sized,
371384 {
372385 self.interactivity()
373386 .click_listeners
374− .push(Box::new(move |view, event, cx| listener(view, event, cx)));
387+ .push(Box::new(move |event, cx| listener(event, cx)));
375388 self
376389 }
377390
378− fn on_drag<W>(
379− mut self,
380− listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
381− ) -> Self
391+ fn on_drag<W>(mut self, listener: impl Fn(&mut WindowContext) -> View<W> + 'static) -> Self
382392 where
383393 Self: Sized,
384394 W: 'static + Render,
@@ -387,15 +397,14 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 2 unmodified lines
387397 self.interactivity().drag_listener.is_none(),
388398 "calling on_drag more than once on the same element is not supported"
389399 );
390− self.interactivity().drag_listener =
391− Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
392− view: listener(view_state, cx).into(),
393− cursor_offset,
394− }));
400+ self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag {
401+ view: listener(cx).into(),
402+ cursor_offset,
403+ }));
395404 self
396405 }
397406
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
399408 where
400409 Self: Sized,
401410 {
@@ -407,10 +416,7 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 5 unmodified lines
407416 self
408417 }
409418
410− fn tooltip(
411− mut self,
412− build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static,
413− ) -> Self
419+ fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self
414420 where
415421 Self: Sized,
416422 {
@@ -418,14 +424,13 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo · 1 unmodified line
418424 self.interactivity().tooltip_builder.is_none(),
419425 "calling tooltip more than once on the same element is not supported"
420426 );
421− self.interactivity().tooltip_builder =
422− Some(Rc::new(move |view_state, cx| build_tooltip(view_state, cx)));
427+ self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip));
423428
424429 self
425430 }
426431 }
427432
428−pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> {
433+pub trait FocusableElement: InteractiveElement {
429434 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
430435 where
431436 Self: Sized,
@@ -442,49 +447,41 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 10 unmodified lines
442447 self
443448 }
444449
445− fn on_focus(
446− mut self,
447− listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
448− ) -> Self
450+ fn on_focus(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
449451 where
450452 Self: Sized,
451453 {
452− self.interactivity().focus_listeners.push(Box::new(
453− move |view, focus_handle, event, cx| {
454+ self.interactivity()
455+ .focus_listeners
456+ .push(Box::new(move |focus_handle, event, cx| {
454457 if event.focused.as_ref() == Some(focus_handle) {
455− listener(view, event, cx)
458+ listener(event, cx)
456459 }
457− },
458− ));
460+ }));
459461 self
460462 }
461463
462− fn on_blur(
463− mut self,
464− listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
465− ) -> Self
464+ fn on_blur(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
466465 where
467466 Self: Sized,
468467 {
469− self.interactivity().focus_listeners.push(Box::new(
470− move |view, focus_handle, event, cx| {
468+ self.interactivity()
469+ .focus_listeners
470+ .push(Box::new(move |focus_handle, event, cx| {
471471 if event.blurred.as_ref() == Some(focus_handle) {
472− listener(view, event, cx)
472+ listener(event, cx)
473473 }
474− },
475− ));
474+ }));
476475 self
477476 }
478477
479− fn on_focus_in(
480− mut self,
481− listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
482− ) -> Self
478+ fn on_focus_in(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
483479 where
484480 Self: Sized,
485481 {
486− self.interactivity().focus_listeners.push(Box::new(
487− move |view, focus_handle, event, cx| {
482+ self.interactivity()
483+ .focus_listeners
484+ .push(Box::new(move |focus_handle, event, cx| {
488485 let descendant_blurred = event
489486 .blurred
490487 .as_ref()
@@ -495,22 +492,19 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 4 unmodified lines
495492 .map_or(false, |focused| focus_handle.contains(focused, cx));
496493
497494 if !descendant_blurred && descendant_focused {
498− listener(view, event, cx)
495+ listener(event, cx)
499496 }
500− },
501− ));
497+ }));
502498 self
503499 }
504500
505− fn on_focus_out(
506− mut self,
507− listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
508− ) -> Self
501+ fn on_focus_out(mut self, listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static) -> Self
509502 where
510503 Self: Sized,
511504 {
512− self.interactivity().focus_listeners.push(Box::new(
513− move |view, focus_handle, event, cx| {
505+ self.interactivity()
506+ .focus_listeners
507+ .push(Box::new(move |focus_handle, event, cx| {
514508 let descendant_blurred = event
515509 .blurred
516510 .as_ref()
@@ -520,98 +514,80 @@ pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> { · 3 unmodified lines
520514 .as_ref()
521515 .map_or(false, |focused| focus_handle.contains(focused, cx));
522516 if descendant_blurred && !descendant_focused {
523− listener(view, event, cx)
517+ listener(event, cx)
524518 }
525− },
526− ));
519+ }));
527520 self
528521 }
529522 }
530523
531−pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
532−
533−pub type FocusListener<V> =
534− Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
524+pub type FocusListeners = SmallVec<[FocusListener; 2]>;
535525
536−pub type MouseDownListener<V> = Box<
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−>;
526+pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
542527
543−pub type MouseMoveListener<V> = Box<
544− dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
545−>;
528+pub type MouseDownListener =
529+ Box<dyn Fn(&MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
530+pub type MouseUpListener =
531+ Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
546532
547−pub type ScrollWheelListener<V> = Box<
548− dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
549− + 'static,
550−>;
533+pub type MouseMoveListener =
534+ Box<dyn Fn(&MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
551535
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>;
553538
554−pub type DragListener<V> =
555− Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
539+pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
556540
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>;
558542
559−pub type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
543+type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static;
560544
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>;
562546
563−pub type KeyDownListener<V> =
564− Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
547+pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
565548
566−pub type KeyUpListener<V> =
567− Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
549+pub type KeyUpListener = Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
568550
569−pub type ActionListener<V> =
570− Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
551+pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
571552
572−pub fn div<V: 'static>() -> Div<V> {
553+pub fn div() -> Div {
573554 Div {
574555 interactivity: Interactivity::default(),
575556 children: SmallVec::default(),
576557 }
577558 }
578559
579−pub struct Div<V> {
580− interactivity: Interactivity<V>,
581− children: SmallVec<[AnyElement<V>; 2]>,
560+pub struct Div {
561+ interactivity: Interactivity,
562+ children: SmallVec<[AnyElement; 2]>,
582563 }
583564
584−impl<V> Styled for Div<V> {
565+impl Styled for Div {
585566 fn style(&mut self) -> &mut StyleRefinement {
586567 &mut self.interactivity.base_style
587568 }
588569 }
589570
590−impl<V: 'static> InteractiveComponent<V> for Div<V> {
591− fn interactivity(&mut self) -> &mut Interactivity<V> {
571+impl InteractiveElement for Div {
572+ fn interactivity(&mut self) -> &mut Interactivity {
592573 &mut self.interactivity
593574 }
594575 }
595576
596−impl<V: 'static> ParentComponent<V> for Div<V> {
597− fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
577+impl ParentElement for Div {
578+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
598579 &mut self.children
599580 }
600581 }
601582
602−impl<V: 'static> Element<V> for Div<V> {
603− type ElementState = DivState;
604−
605− fn element_id(&self) -> Option<ElementId> {
606− self.interactivity.element_id.clone()
607− }
583+impl Element for Div {
584+ type State = DivState;
608585
609586 fn layout(
610587 &mut self,
611− view_state: &mut V,
612− element_state: Option<Self::ElementState>,
613− cx: &mut ViewContext<V>,
614− ) -> (LayoutId, Self::ElementState) {
588+ element_state: Option<Self::State>,
589+ cx: &mut WindowContext,
590+ ) -> (LayoutId, Self::State) {
615591 let mut child_layout_ids = SmallVec::new();
616592 let mut interactivity = mem::take(&mut self.interactivity);
617593 let (layout_id, interactive_state) = interactivity.layout(
@@ -622,7 +598,7 @@ impl<V: 'static> Element<V> for Div<V> { · 4 unmodified lines
622598 child_layout_ids = self
623599 .children
624600 .iter_mut()
625− .map(|child| child.layout(view_state, cx))
601+ .map(|child| child.layout(cx))
626602 .collect::<SmallVec<_>>();
627603 cx.request_layout(&style, child_layout_ids.iter().copied())
628604 })
@@ -639,11 +615,10 @@ impl<V: 'static> Element<V> for Div<V> { · 10 unmodified lines
639615 }
640616
641617 fn paint(
642− &mut self,
618+ self,
643619 bounds: Bounds<Pixels>,
644− view_state: &mut V,
645− element_state: &mut Self::ElementState,
646− cx: &mut ViewContext<V>,
620+ element_state: &mut Self::State,
621+ cx: &mut WindowContext,
647622 ) {
648623 let mut child_min = point(Pixels::MAX, Pixels::MAX);
649624 let mut child_max = Point::default();
@@ -658,8 +633,7 @@ impl<V: 'static> Element<V> for Div<V> { · 8 unmodified lines
658633 (child_max - child_min).into()
659634 };
660635
661− let mut interactivity = mem::take(&mut self.interactivity);
662− interactivity.paint(
636+ self.interactivity.paint(
663637 bounds,
664638 content_size,
665639 &mut element_state.interactive_state,
@@ -679,8 +653,8 @@ impl<V: 'static> Element<V> for Div<V> { · 13 unmodified lines
679653 cx.with_text_style(style.text_style().cloned(), |cx| {
680654 cx.with_content_mask(style.overflow_mask(bounds), |cx| {
681655 cx.with_element_offset(scroll_offset, |cx| {
682− for child in &mut self.children {
683− child.paint(view_state, cx);
656+ for child in self.children {
657+ child.paint(cx);
684658 }
685659 })
686660 })
@@ -689,13 +663,18 @@ impl<V: 'static> Element<V> for Div<V> { · 2 unmodified lines
689663 })
690664 },
691665 );
692− self.interactivity = interactivity;
693666 }
694667 }
695668
696−impl<V: 'static> Component<V> for Div<V> {
697− fn render(self) -> AnyElement<V> {
698− AnyElement::new(self)
669+impl IntoElement for Div {
670+ type Element = 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
699678 }
700679 }
701680
@@ -710,12 +689,12 @@ impl DivState { · 8 unmodified lines
710689 }
711690 }
712691
713−pub struct Interactivity<V> {
692+pub struct Interactivity {
714693 pub element_id: Option<ElementId>,
715694 pub key_context: KeyContext,
716695 pub focusable: bool,
717696 pub tracked_focus_handle: Option<FocusHandle>,
718− pub focus_listeners: FocusListeners<V>,
697+ pub focus_listeners: FocusListeners,
719698 pub group: Option<SharedString>,
720699 pub base_style: StyleRefinement,
721700 pub focus_style: StyleRefinement,
@@ -726,29 +705,26 @@ pub struct Interactivity<V> { · 4 unmodified lines
726705 pub group_active_style: Option<GroupStyle>,
727706 pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
728707 pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
729− pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
730− pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
731− pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
732− pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
733− pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
734− pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
735− pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
736− pub drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
737− pub click_listeners: SmallVec<[ClickListener<V>; 2]>,
738− pub drag_listener: Option<DragListener<V>>,
739− pub hover_listener: Option<HoverListener<V>>,
740− pub tooltip_builder: Option<TooltipBuilder<V>>,
708+ pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>,
709+ pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>,
710+ pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>,
711+ pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>,
712+ pub key_down_listeners: SmallVec<[KeyDownListener; 2]>,
713+ pub key_up_listeners: SmallVec<[KeyUpListener; 2]>,
714+ pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>,
715+ pub drop_listeners: SmallVec<[(TypeId, Box<DropListener>); 2]>,
716+ pub click_listeners: SmallVec<[ClickListener; 2]>,
717+ pub drag_listener: Option<DragListener>,
718+ pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
719+ pub tooltip_builder: Option<TooltipBuilder>,
741720 }
742721
743−impl<V> Interactivity<V>
744−where
745− V: 'static,
746−{
722+impl Interactivity {
747723 pub fn layout(
748724 &mut self,
749725 element_state: Option<InteractiveElementState>,
750− cx: &mut ViewContext<V>,
751− f: impl FnOnce(Style, &mut ViewContext<V>) -> LayoutId,
726+ cx: &mut WindowContext,
727+ f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
752728 ) -> (LayoutId, InteractiveElementState) {
753729 let mut element_state = element_state.unwrap_or_default();
754730
@@ -770,12 +746,12 @@ where · 15 unmodified lines
770746 }
771747
772748 pub fn paint(
773− &mut self,
749+ mut self,
774750 bounds: Bounds<Pixels>,
775751 content_size: Size<Pixels>,
776752 element_state: &mut InteractiveElementState,
777− cx: &mut ViewContext<V>,
778− f: impl FnOnce(Style, Point<Pixels>, &mut ViewContext<V>),
753+ cx: &mut WindowContext,
754+ f: impl FnOnce(Style, Point<Pixels>, &mut WindowContext),
779755 ) {
780756 let style = self.compute_style(Some(bounds), element_state, cx);
781757
@@ -787,26 +763,26 @@ where · 5 unmodified lines
787763 }
788764
789765 for listener in self.mouse_down_listeners.drain(..) {
790− cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
791− listener(state, event, &bounds, phase, cx);
766+ cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
767+ listener(event, &bounds, phase, cx);
792768 })
793769 }
794770
795771 for listener in self.mouse_up_listeners.drain(..) {
796− cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
797− listener(state, event, &bounds, phase, cx);
772+ cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
773+ listener(event, &bounds, phase, cx);
798774 })
799775 }
800776
801777 for listener in self.mouse_move_listeners.drain(..) {
802− cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
803− listener(state, event, &bounds, phase, cx);
778+ cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
779+ listener(event, &bounds, phase, cx);
804780 })
805781 }
806782
807783 for listener in self.scroll_wheel_listeners.drain(..) {
808− cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
809− listener(state, event, &bounds, phase, cx);
784+ cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
785+ listener(event, &bounds, phase, cx);
810786 })
811787 }
812788
@@ -817,7 +793,7 @@ where · 4 unmodified lines
817793
818794 if let Some(group_bounds) = hover_group_bounds {
819795 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| {
821797 if phase == DispatchPhase::Capture {
822798 if group_bounds.contains_point(&event.position) != hovered {
823799 cx.notify();
@@ -830,7 +806,7 @@ where · 6 unmodified lines
830806 || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
831807 {
832808 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| {
834810 if phase == DispatchPhase::Capture {
835811 if bounds.contains_point(&event.position) != hovered {
836812 cx.notify();
@@ -841,7 +817,7 @@ where · 4 unmodified lines
841817
842818 if cx.active_drag.is_some() {
843819 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| {
845821 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
846822 if let Some(drag_state_type) =
847823 cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
@@ -852,7 +828,7 @@ where · 4 unmodified lines
852828 .active_drag
853829 .take()
854830 .expect("checked for type drag state type above");
855− listener(view, drag.view.clone(), cx);
831+ listener(drag.view.clone(), cx);
856832 cx.notify();
857833 cx.stop_propagation();
858834 }
@@ -872,7 +848,7 @@ where · 13 unmodified lines
872848 if let Some(drag_listener) = drag_listener {
873849 let active_state = element_state.clicked_state.clone();
874850
875− cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
851+ cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
876852 if cx.active_drag.is_some() {
877853 if phase == DispatchPhase::Capture {
878854 cx.notify();
@@ -883,7 +859,7 @@ where · 4 unmodified lines
883859 {
884860 *active_state.borrow_mut() = ElementClickedState::default();
885861 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);
887863 cx.active_drag = Some(drag);
888864 cx.notify();
889865 cx.stop_propagation();
@@ -891,21 +867,21 @@ where · 1 unmodified line
891867 });
892868 }
893869
894− cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| {
870+ cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
895871 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
896872 let mouse_click = ClickEvent {
897873 down: mouse_down.clone(),
898874 up: event.clone(),
899875 };
900876 for listener in &click_listeners {
901− listener(view_state, &mouse_click, cx);
877+ listener(&mouse_click, cx);
902878 }
903879 }
904880 *pending_mouse_down.borrow_mut() = None;
905881 cx.notify();
906882 });
907883 } else {
908− cx.on_mouse_event(move |_view_state, event: &MouseDownEvent, phase, cx| {
884+ cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
909885 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
910886 *pending_mouse_down.borrow_mut() = Some(event.clone());
911887 cx.notify();
@@ -918,7 +894,7 @@ where · 6 unmodified lines
918894 let was_hovered = element_state.hover_state.clone();
919895 let has_mouse_down = element_state.pending_mouse_down.clone();
920896
921− cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
897+ cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
922898 if phase != DispatchPhase::Bubble {
923899 return;
924900 }
@@ -930,7 +906,7 @@ where · 5 unmodified lines
930906 *was_hovered = is_hovered;
931907 drop(was_hovered);
932908
933− hover_listener(view_state, is_hovered, cx);
909+ hover_listener(&is_hovered, cx);
934910 }
935911 });
936912 }
@@ -939,7 +915,7 @@ where · 2 unmodified lines
939915 let active_tooltip = element_state.active_tooltip.clone();
940916 let pending_mouse_down = element_state.pending_mouse_down.clone();
941917
942− cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
918+ cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
943919 if phase != DispatchPhase::Bubble {
944920 return;
945921 }
@@ -956,12 +932,12 @@ where · 10 unmodified lines
956932 let active_tooltip = active_tooltip.clone();
957933 let tooltip_builder = tooltip_builder.clone();
958934
959− move |view, mut cx| async move {
935+ move |mut cx| async move {
960936 cx.background_executor().timer(TOOLTIP_DELAY).await;
961− view.update(&mut cx, move |view_state, cx| {
937+ cx.update(|_, cx| {
962938 active_tooltip.borrow_mut().replace(ActiveTooltip {
963939 tooltip: Some(AnyTooltip {
964− view: tooltip_builder(view_state, cx),
940+ view: tooltip_builder(cx),
965941 cursor_offset: cx.mouse_position(),
966942 }),
967943 _task: None,
@@ -979,7 +955,7 @@ where · 11 unmodified lines
979955 });
980956
981957 let active_tooltip = element_state.active_tooltip.clone();
982− cx.on_mouse_event(move |_, _: &MouseDownEvent, _, _| {
958+ cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
983959 active_tooltip.borrow_mut().take();
984960 });
985961
@@ -992,7 +968,7 @@ where · 6 unmodified lines
992968
993969 let active_state = element_state.clicked_state.clone();
994970 if !active_state.borrow().is_clicked() {
995− cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
971+ cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| {
996972 if phase == DispatchPhase::Capture {
997973 *active_state.borrow_mut() = ElementClickedState::default();
998974 cx.notify();
@@ -1003,7 +979,7 @@ where · 4 unmodified lines
1003979 .group_active_style
1004980 .as_ref()
1005981 .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| {
1007983 if phase == DispatchPhase::Bubble {
1008984 let group = active_group_bounds
1009985 .map_or(false, |bounds| bounds.contains_point(&down.position));
@@ -1025,7 +1001,7 @@ where · 15 unmodified lines
10251001 let line_height = cx.line_height();
10261002 let scroll_max = (content_size - bounds.size).max(&Size::default());
10271003
1028− cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| {
1004+ cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
10291005 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
10301006 let mut scroll_offset = scroll_offset.borrow_mut();
10311007 let old_scroll_offset = *scroll_offset;
@@ -1063,27 +1039,25 @@ where · 31 unmodified lines
10631039 element_state.focus_handle.clone(),
10641040 |_, cx| {
10651041 for listener in self.key_down_listeners.drain(..) {
1066− cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {
1067− listener(state, event, phase, cx);
1042+ cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
1043+ listener(event, phase, cx);
10681044 })
10691045 }
10701046
10711047 for listener in self.key_up_listeners.drain(..) {
1072− cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {
1073− listener(state, event, phase, cx);
1048+ cx.on_key_event(move |event: &KeyUpEvent, phase, cx| {
1049+ listener(event, phase, cx);
10741050 })
10751051 }
10761052
1077− for (action_type, listener) in self.action_listeners.drain(..) {
1053+ for (action_type, listener) in self.action_listeners {
10781054 cx.on_action(action_type, listener)
10791055 }
10801056
10811057 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 {
10831059 let focus_handle = focus_handle.clone();
1084− cx.on_focus_changed(move |view, event, cx| {
1085− listener(view, &focus_handle, event, cx)
1086− });
1060+ cx.on_focus_changed(move |event, cx| listener(&focus_handle, event, cx));
10871061 }
10881062 }
10891063
@@ -1100,7 +1074,7 @@ where · 10 unmodified lines
11001074 &self,
11011075 bounds: Option<Bounds<Pixels>>,
11021076 element_state: &mut InteractiveElementState,
1103− cx: &mut ViewContext<V>,
1077+ cx: &mut WindowContext,
11041078 ) -> Style {
11051079 let mut style = Style::default();
11061080 style.refine(&self.base_style);
@@ -1171,7 +1145,7 @@ where · 64 unmodified lines
11711145 }
11721146 }
11731147
1174−impl<V: 'static> Default for Interactivity<V> {
1148+impl Default for Interactivity {
11751149 fn default() -> Self {
11761150 Self {
11771151 element_id: None,
@@ -1259,31 +1233,25 @@ impl GroupBounds { · 81 unmodified lines
12591233 }
12601234 }
12611235
1262−pub struct Focusable<V, E> {
1236+pub struct Focusable<E> {
12631237 element: E,
1264− view_type: PhantomData<V>,
12651238 }
12661239
1267−impl<V: 'static, E: InteractiveComponent<V>> FocusableComponent<V> for Focusable<V, E> {}
1240+impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
12681241
1269−impl<V, E> InteractiveComponent<V> for Focusable<V, E>
1242+impl<E> InteractiveElement for Focusable<E>
12701243 where
1271− V: 'static,
1272− E: InteractiveComponent<V>,
1244+ E: InteractiveElement,
12731245 {
1274− fn interactivity(&mut self) -> &mut Interactivity<V> {
1246+ fn interactivity(&mut self) -> &mut Interactivity {
12751247 self.element.interactivity()
12761248 }
12771249 }
12781250
1279−impl<V: 'static, E: StatefulInteractiveComponent<V, E>> StatefulInteractiveComponent<V, E>
1280− for Focusable<V, E>
1281−{
1282−}
1251+impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
12831252
1284−impl<V, E> Styled for Focusable<V, E>
1253+impl<E> Styled for Focusable<E>
12851254 where
1286− V: 'static,
12871255 E: Styled,
12881256 {
12891257 fn style(&mut self) -> &mut StyleRefinement {
@@ -1291,65 +1259,55 @@ where · 1 unmodified line
12911259 }
12921260 }
12931261
1294−impl<V, E> Element<V> for Focusable<V, E>
1262+impl<E> Element for Focusable<E>
12951263 where
1296− V: 'static,
1297− E: Element<V>,
1264+ E: Element,
12981265 {
1299− type ElementState = E::ElementState;
1300−
1301− fn element_id(&self) -> Option<ElementId> {
1302− self.element.element_id()
1303− }
1266+ type State = E::State;
13041267
13051268 fn layout(
13061269 &mut self,
1307− view_state: &mut V,
1308− element_state: Option<Self::ElementState>,
1309− cx: &mut ViewContext<V>,
1310− ) -> (LayoutId, Self::ElementState) {
1311− self.element.layout(view_state, element_state, cx)
1270+ state: Option<Self::State>,
1271+ cx: &mut WindowContext,
1272+ ) -> (LayoutId, Self::State) {
1273+ self.element.layout(state, cx)
13121274 }
13131275
1314− fn paint(
1315− &mut self,
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);
1276+ fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1277+ self.element.paint(bounds, state, cx)
13221278 }
13231279 }
13241280
1325−impl<V, E> Component<V> for Focusable<V, E>
1281+impl<E> IntoElement for Focusable<E>
13261282 where
1327− V: 'static,
1328− E: 'static + Element<V>,
1283+ E: Element,
13291284 {
1330− fn render(self) -> AnyElement<V> {
1331− AnyElement::new(self)
1285+ type Element = E;
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
13321293 }
13331294 }
13341295
1335−impl<V, E> ParentComponent<V> for Focusable<V, E>
1296+impl<E> ParentElement for Focusable<E>
13361297 where
1337− V: 'static,
1338− E: ParentComponent<V>,
1298+ E: ParentElement,
13391299 {
1340− fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
1300+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
13411301 self.element.children_mut()
13421302 }
13431303 }
13441304
1345−pub struct Stateful<V, E> {
1305+pub struct Stateful<E> {
13461306 element: E,
1347− view_type: PhantomData<V>,
13481307 }
13491308
1350−impl<V, E> Styled for Stateful<V, E>
1309+impl<E> Styled for Stateful<E>
13511310 where
1352− V: 'static,
13531311 E: Styled,
13541312 {
13551313 fn style(&mut self) -> &mut StyleRefinement {
@@ -1357,73 +1315,63 @@ where · 1 unmodified line
13571315 }
13581316 }
13591317
1360−impl<V, E> StatefulInteractiveComponent<V, E> for Stateful<V, E>
1318+impl<E> StatefulInteractiveElement for Stateful<E>
13611319 where
1362− V: 'static,
1363− E: Element<V>,
1364− Self: InteractiveComponent<V>,
1320+ E: Element,
1321+ Self: InteractiveElement,
13651322 {
13661323 }
13671324
1368−impl<V, E> InteractiveComponent<V> for Stateful<V, E>
1325+impl<E> InteractiveElement for Stateful<E>
13691326 where
1370− V: 'static,
1371− E: InteractiveComponent<V>,
1327+ E: InteractiveElement,
13721328 {
1373− fn interactivity(&mut self) -> &mut Interactivity<V> {
1329+ fn interactivity(&mut self) -> &mut Interactivity {
13741330 self.element.interactivity()
13751331 }
13761332 }
13771333
1378−impl<V: 'static, E: FocusableComponent<V>> FocusableComponent<V> for Stateful<V, E> {}
1334+impl<E: FocusableElement> FocusableElement for Stateful<E> {}
13791335
1380−impl<V, E> Element<V> for Stateful<V, E>
1336+impl<E> Element for Stateful<E>
13811337 where
1382− V: 'static,
1383− E: Element<V>,
1338+ E: Element,
13841339 {
1385− type ElementState = E::ElementState;
1386−
1387− fn element_id(&self) -> Option<ElementId> {
1388− self.element.element_id()
1389− }
1340+ type State = E::State;
13901341
13911342 fn layout(
13921343 &mut self,
1393− view_state: &mut V,
1394− element_state: Option<Self::ElementState>,
1395− cx: &mut ViewContext<V>,
1396− ) -> (LayoutId, Self::ElementState) {
1397− self.element.layout(view_state, element_state, cx)
1344+ state: Option<Self::State>,
1345+ cx: &mut WindowContext,
1346+ ) -> (LayoutId, Self::State) {
1347+ self.element.layout(state, cx)
13981348 }
13991349
1400− fn paint(
1401− &mut self,
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)
1350+ fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
1351+ self.element.paint(bounds, state, cx)
14081352 }
14091353 }
14101354
1411−impl<V, E> Component<V> for Stateful<V, E>
1355+impl<E> IntoElement for Stateful<E>
14121356 where
1413− V: 'static,
1414− E: 'static + Element<V>,
1357+ E: Element,
14151358 {
1416− fn render(self) -> AnyElement<V> {
1417− AnyElement::new(self)
1359+ type Element = 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
14181367 }
14191368 }
14201369
1421−impl<V, E> ParentComponent<V> for Stateful<V, E>
1370+impl<E> ParentElement for Stateful<E>
14221371 where
1423− V: 'static,
1424− E: ParentComponent<V>,
1372+ E: ParentElement,
14251373 {
1426− fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
1374+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
14271375 self.element.children_mut()
14281376 }
14291377 }
crates/gpui2/src/elements/img.rs+87−57
@@ -1,70 +1,83 @@
1+use std::sync::Arc;
2+
13 use crate::{
2− AnyElement, BorrowWindow, Bounds, Component, Element, InteractiveComponent,
3− InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement,
4− Styled, ViewContext,
4+ Bounds, Element, ImageData, InteractiveElement, InteractiveElementState, Interactivity,
5+ IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
56 };
67 use futures::FutureExt;
78 use util::ResultExt;
89
9−pub struct Img<V: 'static> {
10− interactivity: Interactivity<V>,
11− uri: Option<SharedString>,
10+#[derive(Clone, Debug)]
11+pub enum ImageSource {
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>,
1232 grayscale: bool,
1333 }
1434
15−pub fn img<V: 'static>() -> Img<V> {
35+pub fn img() -> Img {
1636 Img {
1737 interactivity: Interactivity::default(),
18− uri: None,
38+ source: None,
1939 grayscale: false,
2040 }
2141 }
2242
23−impl<V> Img<V>
24−where
25− V: 'static,
26−{
43+impl Img {
2744 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));
2950 self
3051 }
3152
53+ pub fn source(mut self, source: impl Into<ImageSource>) -> Self {
54+ self.source = Some(source.into());
55+ self
56+ }
3257 pub fn grayscale(mut self, grayscale: bool) -> Self {
3358 self.grayscale = grayscale;
3459 self
3560 }
3661 }
3762
38−impl<V> Component<V> for Img<V> {
39− fn render(self) -> AnyElement<V> {
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− }
63+impl Element for Img {
64+ type State = InteractiveElementState;
5065
5166 fn layout(
5267 &mut self,
53− _view_state: &mut V,
54− element_state: Option<Self::ElementState>,
55− cx: &mut ViewContext<V>,
56− ) -> (LayoutId, Self::ElementState) {
68+ element_state: Option<Self::State>,
69+ cx: &mut WindowContext,
70+ ) -> (LayoutId, Self::State) {
5771 self.interactivity.layout(element_state, cx, |style, cx| {
5872 cx.request_layout(&style, None)
5973 })
6074 }
6175
6276 fn paint(
63− &mut self,
77+ self,
6478 bounds: Bounds<Pixels>,
65− _view_state: &mut V,
66− element_state: &mut Self::ElementState,
67− cx: &mut ViewContext<V>,
79+ element_state: &mut Self::State,
80+ cx: &mut WindowContext,
6881 ) {
6982 self.interactivity.paint(
7083 bounds,
@@ -74,42 +87,59 @@ impl<V> Element<V> for Img<V> { · 3 unmodified lines
7487 |style, _scroll_offset, cx| {
7588 let corner_radii = style.corner_radii;
7689
77− if let Some(uri) = self.uri.clone() {
78− // eprintln!(">>> image_cache.get({uri}");
79− let image_future = cx.image_cache.get(uri.clone());
80− // eprintln!("<<< image_cache.get({uri}");
81− if let Some(data) = image_future
82− .clone()
83− .now_or_never()
84− .and_then(|result| result.ok())
85− {
86− let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
87− cx.with_z_index(1, |cx| {
88− cx.paint_image(bounds, corner_radii, data, self.grayscale)
89− .log_err()
90− });
91− } else {
92− cx.spawn(|_, mut cx| async move {
93− if image_future.await.ok().is_some() {
94− cx.on_next_frame(|cx| cx.notify());
90+ if let Some(source) = self.source {
91+ let image = match source {
92+ ImageSource::Uri(uri) => {
93+ let image_future = cx.image_cache.get(uri.clone());
94+ if let Some(data) = image_future
95+ .clone()
96+ .now_or_never()
97+ .and_then(|result| result.ok())
98+ {
99+ data
100+ } else {
101+ cx.spawn(|mut cx| async move {
102+ if image_future.await.ok().is_some() {
103+ cx.on_next_frame(|cx| cx.notify());
104+ }
105+ })
106+ .detach();
107+ return;
95108 }
96− })
97− .detach()
98− }
109+ }
110+ ImageSource::Data(image) => image,
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+ });
99117 }
100118 },
101119 )
102120 }
103121 }
104122
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 {
106136 fn style(&mut self) -> &mut StyleRefinement {
107137 &mut self.interactivity.base_style
108138 }
109139 }
110140
111−impl<V> InteractiveComponent<V> for Img<V> {
112− fn interactivity(&mut self) -> &mut Interactivity<V> {
141+impl InteractiveElement for Img {
142+ fn interactivity(&mut self) -> &mut Interactivity {
113143 &mut self.interactivity
114144 }
115145 }
crates/gpui2/src/elements/overlay.rs+35−32
@@ -2,16 +2,16 @@ use smallvec::SmallVec; · 1 unmodified line
22 use taffy::style::{Display, Position};
33
44 use crate::{
5− point, AnyElement, BorrowWindow, Bounds, Component, Element, LayoutId, ParentComponent, Pixels,
6− Point, Size, Style,
5+ point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
6+ Point, Size, Style, WindowContext,
77 };
88
99 pub struct OverlayState {
1010 child_layout_ids: SmallVec<[LayoutId; 4]>,
1111 }
1212
13−pub struct Overlay<V> {
14− children: SmallVec<[AnyElement<V>; 2]>,
13+pub struct Overlay {
14+ children: SmallVec<[AnyElement; 2]>,
1515 anchor_corner: AnchorCorner,
1616 fit_mode: OverlayFitMode,
1717 // todo!();
@@ -21,7 +21,7 @@ pub struct Overlay<V> { · 3 unmodified lines
2121
2222 /// overlay gives you a floating element that will avoid overflowing the window bounds.
2323 /// Its children should have no margin to avoid measurement issues.
24−pub fn overlay<V: 'static>() -> Overlay<V> {
24+pub fn overlay() -> Overlay {
2525 Overlay {
2626 children: SmallVec::new(),
2727 anchor_corner: AnchorCorner::TopLeft,
@@ -30,7 +30,7 @@ pub fn overlay<V: 'static>() -> Overlay<V> { · 2 unmodified lines
3030 }
3131 }
3232
33−impl<V> Overlay<V> {
33+impl Overlay {
3434 /// Sets which corner of the overlay should be anchored to the current position.
3535 pub fn anchor(mut self, anchor: AnchorCorner) -> Self {
3636 self.anchor_corner = anchor;
@@ -51,35 +51,24 @@ impl<V> Overlay<V> { · 14 unmodified lines
5151 }
5252 }
5353
54−impl<V: 'static> ParentComponent<V> for Overlay<V> {
55− fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
54+impl ParentElement for Overlay {
55+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
5656 &mut self.children
5757 }
5858 }
5959
60−impl<V: 'static> Component<V> for Overlay<V> {
61− fn render(self) -> AnyElement<V> {
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− }
60+impl Element for Overlay {
61+ type State = OverlayState;
7262
7363 fn layout(
7464 &mut self,
75− view_state: &mut V,
76− _: Option<Self::ElementState>,
77− cx: &mut crate::ViewContext<V>,
78− ) -> (crate::LayoutId, Self::ElementState) {
65+ _: Option<Self::State>,
66+ cx: &mut WindowContext,
67+ ) -> (crate::LayoutId, Self::State) {
7968 let child_layout_ids = self
8069 .children
8170 .iter_mut()
82− .map(|child| child.layout(view_state, cx))
71+ .map(|child| child.layout(cx))
8372 .collect::<SmallVec<_>>();
8473
8574 let mut overlay_style = Style::default();
@@ -92,11 +81,10 @@ impl<V: 'static> Element<V> for Overlay<V> { · 6 unmodified lines
9281 }
9382
9483 fn paint(
95− &mut self,
84+ self,
9685 bounds: crate::Bounds<crate::Pixels>,
97− view_state: &mut V,
98− element_state: &mut Self::ElementState,
99− cx: &mut crate::ViewContext<V>,
86+ element_state: &mut Self::State,
87+ cx: &mut WindowContext,
10088 ) {
10189 if element_state.child_layout_ids.is_empty() {
10290 return;
@@ -117,6 +105,7 @@ impl<V: 'static> Element<V> for Overlay<V> { · 14 unmodified lines
117105 origin: Point::zero(),
118106 size: cx.viewport_size(),
119107 };
108+ dbg!(limits);
120109
121110 match self.fit_mode {
122111 OverlayFitMode::SnapToWindow => {
@@ -156,13 +145,27 @@ impl<V: 'static> Element<V> for Overlay<V> { · 33 unmodified lines
156145 }
157146
158147 cx.with_element_offset(desired.origin - bounds.origin, |cx| {
159− for child in &mut self.children {
160− child.paint(view_state, cx);
161− }
148+ cx.break_content_mask(|cx| {
149+ for child in self.children {
150+ child.paint(cx);
151+ }
152+ })
162153 })
163154 }
164155 }
165156
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+
166169 enum Axis {
167170 Horizontal,
168171 Vertical,
crates/gpui2/src/elements/svg.rs+28−33
@@ -1,60 +1,43 @@
11 use crate::{
2− AnyElement, Bounds, Component, Element, ElementId, InteractiveComponent,
3− InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement,
4− Styled, ViewContext,
2+ Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
3+ IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
54 };
65 use util::ResultExt;
76
8−pub struct Svg<V: 'static> {
9− interactivity: Interactivity<V>,
7+pub struct Svg {
8+ interactivity: Interactivity,
109 path: Option<SharedString>,
1110 }
1211
13−pub fn svg<V: 'static>() -> Svg<V> {
12+pub fn svg() -> Svg {
1413 Svg {
1514 interactivity: Interactivity::default(),
1615 path: None,
1716 }
1817 }
1918
20−impl<V> Svg<V> {
19+impl Svg {
2120 pub fn path(mut self, path: impl Into<SharedString>) -> Self {
2221 self.path = Some(path.into());
2322 self
2423 }
2524 }
2625
27−impl<V> Component<V> for Svg<V> {
28− fn render(self) -> AnyElement<V> {
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− }
26+impl Element for Svg {
27+ type State = InteractiveElementState;
3928
4029 fn layout(
4130 &mut self,
42− _view_state: &mut V,
43− element_state: Option<Self::ElementState>,
44− cx: &mut ViewContext<V>,
45− ) -> (LayoutId, Self::ElementState) {
31+ element_state: Option<Self::State>,
32+ cx: &mut WindowContext,
33+ ) -> (LayoutId, Self::State) {
4634 self.interactivity.layout(element_state, cx, |style, cx| {
4735 cx.request_layout(&style, None)
4836 })
4937 }
5038
51− fn paint(
52− &mut self,
53− bounds: Bounds<Pixels>,
54− _view_state: &mut V,
55− element_state: &mut Self::ElementState,
56− cx: &mut ViewContext<V>,
57− ) where
39+ fn paint(self, bounds: Bounds<Pixels>, element_state: &mut Self::State, cx: &mut WindowContext)
40+ where
5841 Self: Sized,
5942 {
6043 self.interactivity
@@ -66,14 +49,26 @@ impl<V> Element<V> for Svg<V> { · 5 unmodified lines
6649 }
6750 }
6851
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 {
7065 fn style(&mut self) -> &mut StyleRefinement {
7166 &mut self.interactivity.base_style
7267 }
7368 }
7469
75−impl<V> InteractiveComponent<V> for Svg<V> {
76− fn interactivity(&mut self) -> &mut Interactivity<V> {
70+impl InteractiveElement for Svg {
71+ fn interactivity(&mut self) -> &mut Interactivity {
7772 &mut self.interactivity
7873 }
7974 }
crates/gpui2/src/elements/text.rs+253−111
@@ -1,74 +1,177 @@
11 use crate::{
2− AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels,
3− SharedString, Size, TextRun, ViewContext, WrappedLine,
2+ Bounds, DispatchPhase, Element, ElementId, IntoElement, LayoutId, MouseDownEvent, MouseUpEvent,
3+ Pixels, Point, SharedString, Size, TextRun, WhiteSpace, WindowContext, WrappedLine,
44 };
5+use anyhow::anyhow;
56 use parking_lot::{Mutex, MutexGuard};
67 use smallvec::SmallVec;
7−use std::{cell::Cell, rc::Rc, sync::Arc};
8+use std::{cell::Cell, ops::Range, rc::Rc, sync::Arc};
89 use util::ResultExt;
910
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 {
1178 text: SharedString,
1279 runs: Option<Vec<TextRun>>,
1380 }
1481
15−impl Text {
16− /// Renders text with runs of different styles.
17− ///
18− /// Callers are responsible for setting the correct style for each run.
19− /// For text with a uniform style, you can usually avoid calling this constructor
20− /// and just pass text directly.
21− pub fn styled(text: SharedString, runs: Vec<TextRun>) -> Self {
22− Text {
23− text,
24− runs: Some(runs),
82+impl StyledText {
83+ pub fn new(text: impl Into<SharedString>) -> Self {
84+ StyledText {
85+ text: text.into(),
86+ runs: None,
2587 }
2688 }
89+
90+ pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
91+ self.runs = Some(runs);
92+ self
93+ }
2794 }
2895
29−impl<V: 'static> Component<V> for Text {
30− fn render(self) -> AnyElement<V> {
31− AnyElement::new(self)
96+impl Element for StyledText {
97+ type State = TextState;
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)
32111 }
33112 }
34113
35−impl<V: 'static> Element<V> for Text {
36− type ElementState = TextState;
114+impl IntoElement for StyledText {
115+ type Element = Self;
37116
38117 fn element_id(&self) -> Option<crate::ElementId> {
39118 None
40119 }
41120
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+
42141 fn layout(
43142 &mut self,
44− _view: &mut V,
45− element_state: Option<Self::ElementState>,
46− cx: &mut ViewContext<V>,
47− ) -> (LayoutId, Self::ElementState) {
48− let element_state = element_state.unwrap_or_default();
143+ text: SharedString,
144+ runs: Option<Vec<TextRun>>,
145+ cx: &mut WindowContext,
146+ ) -> LayoutId {
49147 let text_system = cx.text_system().clone();
50148 let text_style = cx.text_style();
51149 let font_size = text_style.font_size.to_pixels(cx.rem_size());
52150 let line_height = text_style
53151 .line_height
54152 .to_pixels(font_size.into(), cx.rem_size());
55− let text = self.text.clone();
153+ let text = SharedString::from(text);
56154
57155 let rem_size = cx.rem_size();
58156
59− let runs = if let Some(runs) = self.runs.take() {
157+ let runs = if let Some(runs) = runs {
60158 runs
61159 } else {
62160 vec![text_style.to_run(text.len())]
63161 };
64162
65163 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+
67166 move |known_dimensions, available_space| {
68− let wrap_width = known_dimensions.width.or(match available_space.width {
69− crate::AvailableSpace::Definite(x) => Some(x),
70− _ => None,
71− });
167+ let wrap_width = if text_style.white_space == WhiteSpace::Normal {
168+ known_dimensions.width.or(match available_space.width {
169+ crate::AvailableSpace::Definite(x) => Some(x),
170+ _ => None,
171+ })
172+ } else {
173+ None
174+ };
72175
73176 if let Some(text_state) = element_state.0.lock().as_ref() {
74177 if text_state.size.is_some()
@@ -80,10 +183,7 @@ impl<V: 'static> Element<V> for Text { · 5 unmodified lines
80183
81184 let Some(lines) = text_system
82185 .shape_text(
83− &text,
84− font_size,
85− &runs[..],
86− wrap_width, // Wrap if we know the width.
186+ &text, font_size, &runs, wrap_width, // Wrap if we know the width.
87187 )
88188 .log_err()
89189 else {
@@ -100,7 +200,7 @@ impl<V: 'static> Element<V> for Text { · 10 unmodified lines
100200 for line in &lines {
101201 let line_size = line.size(line_height);
102202 size.height += line_size.height;
103− size.width = size.width.max(line_size.width);
203+ size.width = size.width.max(line_size.width).ceil();
104204 }
105205
106206 element_state.lock().replace(TextStateInner {
@@ -114,20 +214,14 @@ impl<V: 'static> Element<V> for Text { · 7 unmodified lines
114214 }
115215 });
116216
117− (layout_id, element_state)
217+ layout_id
118218 }
119219
120− fn paint(
121− &mut self,
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();
220+ fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
221+ let element_state = self.lock();
128222 let element_state = element_state
129223 .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))
131225 .unwrap();
132226
133227 let line_height = element_state.line_height;
@@ -137,108 +231,156 @@ impl<V: 'static> Element<V> for Text { · 3 unmodified lines
137231 line_origin.y += line.size(line_height).height;
138232 }
139233 }
140−}
141234
142−#[derive(Default, Clone)]
143−pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
235+ fn index_for_position(&self, bounds: Bounds<Pixels>, position: Point<Pixels>) -> Option<usize> {
236+ if !bounds.contains_point(&position) {
237+ return None;
238+ }
144239
145−impl TextState {
146− fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
147− self.0.lock()
240+ let element_state = self.lock();
241+ let element_state = element_state
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
148262 }
149263 }
150264
151−struct TextStateInner {
152− lines: SmallVec<[WrappedLine; 1]>,
153− line_height: Pixels,
154− wrap_width: Option<Pixels>,
155− size: Option<Size<Pixels>>,
265+pub struct InteractiveText {
266+ element_id: ElementId,
267+ text: StyledText,
268+ click_listener: Option<Box<dyn Fn(InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
156269 }
157270
158−struct InteractiveText {
159− id: ElementId,
160− text: Text,
271+struct InteractiveTextClickEvent {
272+ mouse_down_index: usize,
273+ mouse_up_index: usize,
161274 }
162275
163−struct InteractiveTextState {
276+pub struct InteractiveTextState {
164277 text_state: TextState,
165− clicked_range_ixs: Rc<Cell<SmallVec<[usize; 1]>>>,
278+ mouse_down_index: Rc<Cell<Option<usize>>>,
166279 }
167280
168−impl<V: 'static> Element<V> for InteractiveText {
169− type ElementState = InteractiveTextState;
281+impl InteractiveText {
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+ }
170289
171− fn element_id(&self) -> Option<ElementId> {
172− Some(self.id.clone())
290+ pub fn on_click(
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
173304 }
305+}
306+
307+impl Element for InteractiveText {
308+ type State = InteractiveTextState;
174309
175310 fn layout(
176311 &mut self,
177− view_state: &mut V,
178− element_state: Option<Self::ElementState>,
179− cx: &mut ViewContext<V>,
180− ) -> (LayoutId, Self::ElementState) {
312+ state: Option<Self::State>,
313+ cx: &mut WindowContext,
314+ ) -> (LayoutId, Self::State) {
181315 if let Some(InteractiveTextState {
182− text_state,
183− clicked_range_ixs,
184− }) = element_state
316+ mouse_down_index, ..
317+ }) = state
185318 {
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);
187320 let element_state = InteractiveTextState {
188321 text_state,
189− clicked_range_ixs,
322+ mouse_down_index,
190323 };
191324 (layout_id, element_state)
192325 } 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);
194327 let element_state = InteractiveTextState {
195328 text_state,
196− clicked_range_ixs: Rc::default(),
329+ mouse_down_index: Rc::default(),
197330 };
198331 (layout_id, element_state)
199332 }
200333 }
201334
202− fn paint(
203− &mut self,
204− bounds: Bounds<Pixels>,
205− view_state: &mut V,
206− element_state: &mut Self::ElementState,
207− cx: &mut ViewContext<V>,
208− ) {
209− self.text
210− .paint(bounds, view_state, &mut element_state.text_state, cx)
211− }
212−}
335+ fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
336+ if let Some(click_listener) = self.click_listener {
337+ let text_state = state.text_state.clone();
338+ let mouse_down = state.mouse_down_index.clone();
339+ if let Some(mouse_down_index) = mouse_down.get() {
340+ cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
341+ if phase == DispatchPhase::Bubble {
342+ if let Some(mouse_up_index) =
343+ text_state.index_for_position(bounds, event.position)
344+ {
345+ click_listener(
346+ InteractiveTextClickEvent {
347+ mouse_down_index,
348+ mouse_up_index,
349+ },
350+ cx,
351+ )
352+ }
213353
214−impl<V: 'static> Component<V> for SharedString {
215− fn render(self) -> AnyElement<V> {
216− Text {
217− text: self,
218− runs: None,
354+ mouse_down.take();
355+ cx.notify();
356+ }
357+ });
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+ }
219370 }
220− .render()
371+
372+ self.text.paint(bounds, &mut state.text_state, cx)
221373 }
222374 }
223375
224−impl<V: 'static> Component<V> for &'static str {
225− fn render(self) -> AnyElement<V> {
226− Text {
227− text: self.into(),
228− runs: None,
229− }
230− .render()
376+impl IntoElement for InteractiveText {
377+ type Element = Self;
378+
379+ fn element_id(&self) -> Option<ElementId> {
380+ Some(self.element_id.clone())
231381 }
232−}
233382
234−// TODO: Figure out how to pass `String` to `child` without this.
235−// This impl doesn't exist in the `gpui2` crate.
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()
383+ fn into_element(self) -> Self::Element {
384+ self
243385 }
244386 }
crates/gpui2/src/elements/uniform_list.rs+81−90
@@ -1,61 +1,60 @@
11 use crate::{
2− point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element,
3− ElementId, InteractiveComponent, InteractiveElementState, Interactivity, LayoutId, Pixels,
4− Point, Size, StyleRefinement, Styled, ViewContext,
2+ point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
3+ ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
4+ Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
55 };
66 use smallvec::SmallVec;
7−use std::{cell::RefCell, cmp, mem, ops::Range, rc::Rc};
7+use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
88 use taffy::style::Overflow;
99
1010 /// uniform_list provides lazy rendering for a set of items that are of uniform height.
1111 /// 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.
13−pub fn uniform_list<I, V, C>(
12+/// uniform_list will only render the visible subset of items.
13+pub fn uniform_list<I, R, V>(
14+ view: View<V>,
1415 id: I,
1516 item_count: usize,
16− f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<C>,
17−) -> UniformList<V>
17+ f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<R>,
18+) -> UniformList
1819 where
1920 I: Into<ElementId>,
20− V: 'static,
21− C: Component<V>,
21+ R: IntoElement,
22+ V: Render,
2223 {
2324 let id = id.into();
24− let mut style = StyleRefinement::default();
25− style.overflow.y = Some(Overflow::Hidden);
25+ let mut base_style = StyleRefinement::default();
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+ };
2636
2737 UniformList {
2838 id: id.clone(),
29− style,
3039 item_count,
3140 item_to_measure_index: 0,
32− render_items: Box::new(move |view, visible_range, cx| {
33− f(view, visible_range, cx)
34− .into_iter()
35− .map(|component| component.render())
36− .collect()
37− }),
41+ render_items: Box::new(render_range),
3842 interactivity: Interactivity {
3943 element_id: Some(id.into()),
44+ base_style,
4045 ..Default::default()
4146 },
4247 scroll_handle: None,
4348 }
4449 }
4550
46−pub struct UniformList<V: 'static> {
51+pub struct UniformList {
4752 id: ElementId,
48− style: StyleRefinement,
4953 item_count: usize,
5054 item_to_measure_index: usize,
51− render_items: Box<
52− dyn for<'a> Fn(
53− &'a mut V,
54− Range<usize>,
55− &'a mut ViewContext<V>,
56− ) -> SmallVec<[AnyElement<V>; 64]>,
57− >,
58− interactivity: Interactivity<V>,
55+ render_items:
56+ Box<dyn for<'a> Fn(Range<usize>, &'a mut WindowContext) -> SmallVec<[AnyElement; 64]>>,
57+ interactivity: Interactivity,
5958 scroll_handle: Option<UniformListScrollHandle>,
6059 }
6160
@@ -89,9 +88,9 @@ impl UniformListScrollHandle { · 27 unmodified lines
8988 }
9089 }
9190
92−impl<V: 'static> Styled for UniformList<V> {
91+impl Styled for UniformList {
9392 fn style(&mut self) -> &mut StyleRefinement {
94− &mut self.style
93+ &mut self.interactivity.base_style
9594 }
9695 }
9796
@@ -101,29 +100,24 @@ pub struct UniformListState { · 3 unmodified lines
101100 item_size: Size<Pixels>,
102101 }
103102
104−impl<V: 'static> Element<V> for UniformList<V> {
105− type ElementState = UniformListState;
106−
107− fn element_id(&self) -> Option<crate::ElementId> {
108− Some(self.id.clone())
109− }
103+impl Element for UniformList {
104+ type State = UniformListState;
110105
111106 fn layout(
112107 &mut self,
113− view_state: &mut V,
114− element_state: Option<Self::ElementState>,
115− cx: &mut ViewContext<V>,
116− ) -> (LayoutId, Self::ElementState) {
108+ state: Option<Self::State>,
109+ cx: &mut WindowContext,
110+ ) -> (LayoutId, Self::State) {
117111 let max_items = self.item_count;
118112 let rem_size = cx.rem_size();
119− let item_size = element_state
113+ let item_size = state
120114 .as_ref()
121115 .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));
123117
124118 let (layout_id, interactive) =
125119 self.interactivity
126− .layout(element_state.map(|s| s.interactive), cx, |style, cx| {
120+ .layout(state.map(|s| s.interactive), cx, |style, cx| {
127121 cx.request_measured_layout(
128122 style,
129123 rem_size,
@@ -159,11 +153,10 @@ impl<V: 'static> Element<V> for UniformList<V> { · 29 unmodified lines
159153 }
160154
161155 fn paint(
162− &mut self,
156+ self,
163157 bounds: Bounds<crate::Pixels>,
164− view_state: &mut V,
165− element_state: &mut Self::ElementState,
166− cx: &mut ViewContext<V>,
158+ element_state: &mut Self::State,
159+ cx: &mut WindowContext,
167160 ) {
168161 let style =
169162 self.interactivity
@@ -183,14 +176,15 @@ impl<V: 'static> Element<V> for UniformList<V> { · 13 unmodified lines
183176 height: item_size.height * self.item_count,
184177 };
185178
186− let mut interactivity = mem::take(&mut self.interactivity);
187179 let shared_scroll_offset = element_state
188180 .interactive
189181 .scroll_offset
190182 .get_or_insert_with(Rc::default)
191183 .clone();
192184
193− interactivity.paint(
185+ let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
186+
187+ self.interactivity.paint(
194188 bounds,
195189 content_size,
196190 &mut element_state.interactive,
@@ -209,9 +203,6 @@ impl<V: 'static> Element<V> for UniformList<V> { · 12 unmodified lines
209203 style.paint(bounds, cx);
210204
211205 if self.item_count > 0 {
212− let item_height = self
213− .measure_item(view_state, Some(padded_bounds.size.width), cx)
214− .height;
215206 if let Some(scroll_handle) = self.scroll_handle.clone() {
216207 scroll_handle.0.borrow_mut().replace(ScrollHandleState {
217208 item_height,
@@ -219,58 +210,64 @@ impl<V: 'static> Element<V> for UniformList<V> { · 1 unmodified line
219210 scroll_offset: shared_scroll_offset,
220211 });
221212 }
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− };
227213
228214 let first_visible_element_ix =
229215 (-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;
230219 let visible_range = first_visible_element_ix
231− ..cmp::min(
232− first_visible_element_ix + visible_item_count,
233− self.item_count,
234− );
220+ ..cmp::min(last_visible_element_ix, self.item_count);
235221
236− let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
222+ let items = (self.render_items)(visible_range.clone(), cx);
237223 cx.with_z_index(1, |cx| {
238− for (item, ix) in items.iter_mut().zip(visible_range) {
239− let item_origin = padded_bounds.origin
240− + point(px(0.), item_height * ix + scroll_offset.y);
241− let available_space = size(
242− AvailableSpace::Definite(padded_bounds.size.width),
243− AvailableSpace::Definite(item_height),
244− );
245− item.draw(item_origin, available_space, view_state, cx);
246− }
224+ let content_mask = ContentMask {
225+ bounds: padded_bounds,
226+ };
227+ cx.with_content_mask(Some(content_mask), |cx| {
228+ for (item, ix) in items.into_iter().zip(visible_range) {
229+ let item_origin = padded_bounds.origin
230+ + point(px(0.), item_height * ix + scroll_offset.y);
231+ let available_space = size(
232+ AvailableSpace::Definite(padded_bounds.size.width),
233+ AvailableSpace::Definite(item_height),
234+ );
235+ item.draw(item_origin, available_space, cx);
236+ }
237+ });
247238 });
248239 }
249240 })
250241 },
251242 );
252− self.interactivity = interactivity;
253243 }
254244 }
255245
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 {
257259 pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
258260 self.item_to_measure_index = item_index.unwrap_or(0);
259261 self
260262 }
261263
262− fn measure_item(
263− &self,
264− view_state: &mut V,
265− list_width: Option<Pixels>,
266− cx: &mut ViewContext<V>,
267− ) -> Size<Pixels> {
264+ fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
268265 if self.item_count == 0 {
269266 return Size::default();
270267 }
271268
272269 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);
274271 let mut item_to_measure = items.pop().unwrap();
275272 let available_space = size(
276273 list_width.map_or(AvailableSpace::MinContent, |width| {
@@ -278,7 +275,7 @@ impl<V> UniformList<V> { · 1 unmodified line
278275 }),
279276 AvailableSpace::MinContent,
280277 );
281− item_to_measure.measure(available_space, view_state, cx)
278+ item_to_measure.measure(available_space, cx)
282279 }
283280
284281 pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
@@ -287,14 +284,8 @@ impl<V> UniformList<V> { · 2 unmodified lines
287284 }
288285 }
289286
290−impl<V> InteractiveComponent<V> for UniformList<V> {
291− fn interactivity(&mut self) -> &mut crate::Interactivity<V> {
287+impl InteractiveElement for UniformList {
288+ fn interactivity(&mut self) -> &mut crate::Interactivity {
292289 &mut self.interactivity
293290 }
294291 }
295−
296−impl<V: 'static> Component<V> for UniformList<V> {
297− fn render(self) -> AnyElement<V> {
298− AnyElement::new(self)
299− }
300−}
crates/gpui2/src/gpui2.rs+1−3
@@ -78,8 +78,6 @@ use std::{ · 77 unmodified lines
7878 };
7979 use taffy::TaffyLayoutEngine;
8080
81−type AnyBox = Box<dyn Any>;
82−
8381 pub trait Context {
8482 type Result<T>;
8583
@@ -136,7 +134,7 @@ pub trait VisualContext: Context { · 50 unmodified lines
136134 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
137135 ) -> Self::Result<View<V>>
138136 where
139− V: Render;
137+ V: 'static + Render;
140138
141139 fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
142140 where
crates/gpui2/src/input.rs+5−3
@@ -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+};
24 use std::ops::Range;
35
46 /// 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
4345 impl<V: 'static> ElementInputHandler<V> {
4446 /// Used in [Element::paint] with the element's bounds and a view context for its
4547 /// 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 {
4749 ElementInputHandler {
48− view: cx.view().clone(),
50+ view,
4951 element_bounds,
5052 cx: cx.to_async(),
5153 }
crates/gpui2/src/interactive.rs+21−17
@@ -1,6 +1,6 @@
11 use crate::{
2− div, point, Component, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render,
3− ViewContext,
2+ div, point, Div, Element, FocusHandle, IntoElement, Keystroke, Modifiers, Pixels, Point,
3+ Render, ViewContext,
44 };
55 use smallvec::SmallVec;
66 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
6464 where
6565 R: Fn(&mut V, &mut ViewContext<V>) -> E,
6666 V: 'static,
67− E: Component<()>,
67+ E: IntoElement,
6868 {
6969 pub state: S,
7070 pub render_drag_handle: R,
71− view_type: PhantomData<V>,
71+ view_element_types: PhantomData<(V, E)>,
7272 }
7373
7474 impl<S, R, V, E> Drag<S, R, V, E>
7575 where
7676 R: Fn(&mut V, &mut ViewContext<V>) -> E,
7777 V: 'static,
78− E: Component<()>,
78+ E: Element,
7979 {
8080 pub fn new(state: S, render_drag_handle: R) -> Self {
8181 Drag {
8282 state,
8383 render_drag_handle,
84− view_type: PhantomData,
84+ view_element_types: Default::default(),
8585 }
8686 }
8787 }
@@ -194,7 +194,7 @@ impl Deref for MouseExitEvent { · 106 unmodified lines
194194 pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
195195
196196 impl Render for ExternalPaths {
197− type Element = Div<Self>;
197+ type Element = Div;
198198
199199 fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
200200 div() // Intentionally left empty because the platform will render icons for the dragged files
@@ -286,8 +286,8 @@ pub struct FocusEvent { · 85 unmodified lines
286286 #[cfg(test)]
287287 mod test {
288288 use crate::{
289− self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding,
290− Keystroke, ParentComponent, Render, Stateful, TestAppContext, ViewContext, VisualContext,
289+ self as gpui, div, Div, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
290+ Keystroke, ParentElement, Render, Stateful, TestAppContext, VisualContext,
291291 };
292292
293293 struct TestView {
@@ -299,20 +299,24 @@ mod test { · 5 unmodified lines
299299 actions!(TestAction);
300300
301301 impl Render for TestView {
302− type Element = Stateful<Self, Div<Self>>;
302+ type Element = Stateful<Div>;
303303
304− fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
304+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
305305 div().id("testview").child(
306306 div()
307307 .key_context("parent")
308− .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
309− .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
310− .child(|this: &mut Self, _cx: &mut ViewContext<Self>| {
308+ .on_key_down(cx.listener(|this, _, _| this.saw_key_down = true))
309+ .on_action(
310+ cx.listener(|this: &mut TestView, _: &TestAction, _| {
311+ this.saw_action = true
312+ }),
313+ )
314+ .child(
311315 div()
312316 .key_context("nested")
313− .track_focus(&this.focus_handle)
314− .render()
315− }),
317+ .track_focus(&self.focus_handle)
318+ .into_element(),
319+ ),
316320 )
317321 }
318322 }
crates/gpui2/src/platform/mac/window.rs+3−0
@@ -683,6 +683,9 @@ impl Drop for MacWindow { · 682 unmodified lines
683683 this.executor
684684 .spawn(async move {
685685 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)
686689 window.close();
687690 }
688691 })
crates/gpui2/src/prelude.rs+3−2
@@ -1,4 +1,5 @@
11 pub use crate::{
2− BorrowAppContext, BorrowWindow, Component, Context, FocusableComponent, InteractiveComponent,
3− ParentComponent, Refineable, Render, StatefulInteractiveComponent, Styled, VisualContext,
2+ BorrowAppContext, BorrowWindow, Context, Element, FocusableElement, InteractiveElement,
3+ IntoElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement, Styled,
4+ VisualContext,
45 };
crates/gpui2/src/style.rs+184−4
@@ -1,9 +1,12 @@
1+use std::{iter, mem, ops::Range};
2+
13 use crate::{
24 black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
35 Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
46 FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
5− SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext,
7+ SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
68 };
9+use collections::HashSet;
710 use refineable::{Cascade, Refineable};
811 use smallvec::SmallVec;
912 pub use taffy::style::{
@@ -128,6 +131,13 @@ pub struct BoxShadow { · 118 unmodified lines
128131 pub spread_radius: Pixels,
129132 }
130133
134+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
135+pub enum WhiteSpace {
136+ #[default]
137+ Normal,
138+ Nowrap,
139+}
140+
131141 #[derive(Refineable, Clone, Debug)]
132142 #[refineable(Debug)]
133143 pub struct TextStyle {
@@ -138,7 +148,9 @@ pub struct TextStyle { · 4 unmodified lines
138148 pub line_height: DefiniteLength,
139149 pub font_weight: FontWeight,
140150 pub font_style: FontStyle,
151+ pub background_color: Option<Hsla>,
141152 pub underline: Option<UnderlineStyle>,
153+ pub white_space: WhiteSpace,
142154 }
143155
144156 impl Default for TextStyle {
@@ -151,13 +163,16 @@ impl Default for TextStyle { · 6 unmodified lines
151163 line_height: phi(),
152164 font_weight: FontWeight::default(),
153165 font_style: FontStyle::default(),
166+ background_color: None,
154167 underline: None,
168+ white_space: WhiteSpace::Normal,
155169 }
156170 }
157171 }
158172
159173 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();
161176 if let Some(weight) = style.font_weight {
162177 self.font_weight = weight;
163178 }
@@ -173,6 +188,10 @@ impl TextStyle { · 9 unmodified lines
173188 self.color.fade_out(factor);
174189 }
175190
191+ if let Some(background_color) = style.background_color {
192+ self.background_color = Some(background_color);
193+ }
194+
176195 if let Some(underline) = style.underline {
177196 self.underline = Some(underline);
178197 }
@@ -203,7 +222,7 @@ impl TextStyle { · 24 unmodified lines
203222 style: self.font_style,
204223 },
205224 color: self.color,
206− background_color: None,
225+ background_color: self.background_color,
207226 underline: self.underline.clone(),
208227 }
209228 }
@@ -214,6 +233,7 @@ pub struct HighlightStyle { · 4 unmodified lines
214233 pub color: Option<Hsla>,
215234 pub font_weight: Option<FontWeight>,
216235 pub font_style: Option<FontStyle>,
236+ pub background_color: Option<Hsla>,
217237 pub underline: Option<UnderlineStyle>,
218238 pub fade_out: Option<f32>,
219239 }
@@ -313,7 +333,7 @@ impl Style { · 93 unmodified lines
313333 }
314334
315335 /// 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) {
317337 let rem_size = cx.rem_size();
318338
319339 cx.with_z_index(0, |cx| {
@@ -432,6 +452,7 @@ impl From<&TextStyle> for HighlightStyle { · 112 unmodified lines
432452 color: Some(other.color),
433453 font_weight: Some(other.font_weight),
434454 font_style: Some(other.font_style),
455+ background_color: other.background_color,
435456 underline: other.underline.clone(),
436457 fade_out: None,
437458 }
@@ -458,6 +479,10 @@ impl HighlightStyle { · 20 unmodified lines
458479 self.font_style = other.font_style;
459480 }
460481
482+ if other.background_color.is_some() {
483+ self.background_color = other.background_color;
484+ }
485+
461486 if other.underline.is_some() {
462487 self.underline = other.underline;
463488 }
@@ -481,6 +506,24 @@ impl From<Hsla> for HighlightStyle { · 17 unmodified lines
481506 }
482507 }
483508
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+
484527 impl From<Rgba> for HighlightStyle {
485528 fn from(color: Rgba) -> Self {
486529 Self {
@@ -489,3 +532,140 @@ impl From<Rgba> for HighlightStyle { · 2 unmodified lines
489532 }
490533 }
491534 }
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+}
crates/gpui2/src/styled.rs+26−1
@@ -1,7 +1,7 @@
11 use crate::{
22 self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
33 DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
4− SharedString, StyleRefinement, Visibility,
4+ SharedString, StyleRefinement, Visibility, WhiteSpace,
55 };
66 use crate::{BoxShadow, TextStyleRefinement};
77 use smallvec::{smallvec, SmallVec};
@@ -101,6 +101,24 @@ pub trait Styled: Sized { · 93 unmodified lines
101101 self
102102 }
103103
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+
104122 /// Sets the flex direction of the element to `column`.
105123 /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
106124 fn flex_col(mut self) -> Self {
@@ -343,6 +361,13 @@ pub trait Styled: Sized { · 236 unmodified lines
343361 self
344362 }
345363
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+
346371 fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self {
347372 self.text_style()
348373 .get_or_insert_with(Default::default)
crates/gpui2/src/text_system.rs+18−3
@@ -196,7 +196,10 @@ impl TextSystem { · 195 unmodified lines
196196 let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
197197 for run in runs {
198198 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+ {
200203 last_run.len += run.len as u32;
201204 continue;
202205 }
@@ -204,6 +207,7 @@ impl TextSystem { · 1 unmodified line
204207 decoration_runs.push(DecorationRun {
205208 len: run.len as u32,
206209 color: run.color,
210+ background_color: run.background_color,
207211 underline: run.underline.clone(),
208212 });
209213 }
@@ -254,13 +258,16 @@ impl TextSystem { · 44 unmodified lines
254258 }
255259
256260 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
258264 }) {
259265 decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;
260266 } else {
261267 decoration_runs.push(DecorationRun {
262268 len: run_len_within_line as u32,
263269 color: run.color,
270+ background_color: run.background_color,
264271 underline: run.underline.clone(),
265272 });
266273 }
@@ -283,7 +290,15 @@ impl TextSystem { · 16 unmodified lines
283290 text: SharedString::from(line_text),
284291 });
285292
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+
287302 font_runs.clear();
288303 }
289304
crates/gpui2/src/text_system/line.rs+74−14
@@ -1,6 +1,7 @@
11 use crate::{
2− black, point, px, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString,
3− UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
2+ black, point, px, size, transparent_black, BorrowWindow, Bounds, Corners, Edges, Hsla,
3+ LineLayout, Pixels, Point, Result, SharedString, UnderlineStyle, WindowContext, WrapBoundary,
4+ WrappedLineLayout,
45 };
56 use derive_more::{Deref, DerefMut};
67 use smallvec::SmallVec;
@@ -10,6 +11,7 @@ use std::sync::Arc; · 3 unmodified lines
1011 pub struct DecorationRun {
1112 pub len: u32,
1213 pub color: Hsla,
14+ pub background_color: Option<Hsla>,
1315 pub underline: Option<UnderlineStyle>,
1416 }
1517
@@ -38,7 +40,6 @@ impl ShapedLine { · 22 unmodified lines
3840 &self.layout,
3941 line_height,
4042 &self.decoration_runs,
41− None,
4243 &[],
4344 cx,
4445 )?;
@@ -72,7 +73,6 @@ impl WrappedLine { · 27 unmodified lines
7273 &self.layout.unwrapped_layout,
7374 line_height,
7475 &self.decoration_runs,
75− self.wrap_width,
7676 &self.wrap_boundaries,
7777 cx,
7878 )?;
@@ -86,7 +86,6 @@ fn paint_line( · 7 unmodified lines
8686 layout: &LineLayout,
8787 line_height: Pixels,
8888 decoration_runs: &[DecorationRun],
89− wrap_width: Option<Pixels>,
9089 wrap_boundaries: &[WrapBoundary],
9190 cx: &mut WindowContext<'_>,
9291 ) -> Result<()> {
@@ -97,6 +96,7 @@ fn paint_line( · 4 unmodified lines
9796 let mut run_end = 0;
9897 let mut color = black();
9998 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
99+ let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
100100 let text_system = cx.text_system().clone();
101101 let mut glyph_origin = origin;
102102 let mut prev_glyph_position = Point::default();
@@ -110,12 +110,28 @@ fn paint_line( · 7 unmodified lines
110110
111111 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
112112 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() {
114128 cx.paint_underline(
115− underline_origin,
129+ *underline_origin,
116130 glyph_origin.x - underline_origin.x,
117− &underline_style,
118− )?;
131+ underline_style,
132+ );
133+ underline_origin.x = origin.x;
134+ underline_origin.y += line_height;
119135 }
120136
121137 glyph_origin.x = origin.x;
@@ -123,9 +139,20 @@ fn paint_line( · 1 unmodified line
123139 }
124140 prev_glyph_position = glyph.position;
125141
142+ let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
126143 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
127144 if glyph.index >= run_end {
128145 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+
129156 if let Some((_, underline_style)) = &mut current_underline {
130157 if style_run.underline.as_ref() != Some(underline_style) {
131158 finished_underline = current_underline.take();
@@ -135,7 +162,7 @@ fn paint_line( · 3 unmodified lines
135162 current_underline.get_or_insert((
136163 point(
137164 glyph_origin.x,
138− origin.y + baseline_offset.y + (layout.descent * 0.618),
165+ glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
139166 ),
140167 UnderlineStyle {
141168 color: Some(run_underline.color.unwrap_or(style_run.color)),
@@ -149,16 +176,30 @@ fn paint_line( · 7 unmodified lines
149176 color = style_run.color;
150177 } else {
151178 run_end = layout.len;
179+ finished_background = current_background.take();
152180 finished_underline = current_underline.take();
153181 }
154182 }
155183
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+
156197 if let Some((underline_origin, underline_style)) = finished_underline {
157198 cx.paint_underline(
158199 underline_origin,
159200 glyph_origin.x - underline_origin.x,
160201 &underline_style,
161− )?;
202+ );
162203 }
163204
164205 let max_glyph_bounds = Bounds {
@@ -188,13 +229,32 @@ fn paint_line( · 23 unmodified lines
188229 }
189230 }
190231
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+
191252 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);
193253 cx.paint_underline(
194254 underline_start,
195− line_end_x - underline_start.x,
255+ last_line_end_x - underline_start.x,
196256 &underline_style,
197− )?;
257+ );
198258 }
199259
200260 Ok(())
crates/gpui2/src/text_system/line_layout.rs+35−0
@@ -198,6 +198,41 @@ impl WrappedLineLayout { · 197 unmodified lines
198198 pub fn runs(&self) -> &[ShapedRun] {
199199 &self.unwrapped_layout.runs
200200 }
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+ }
201236 }
202237
203238 pub(crate) struct LineLayoutCache {
crates/gpui2/src/view.rs+88−135
@@ -1,23 +1,17 @@
11 use crate::{
2− private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace,
3− BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle,
4− FocusableView, LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel,
2+ private::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
3+ Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement,
4+ LayoutId, Model, Pixels, Point, Render, Size, ViewContext, VisualContext, WeakModel,
55 WindowContext,
66 };
77 use anyhow::{Context, Result};
88 use std::{
9− any::{Any, TypeId},
9+ any::TypeId,
1010 hash::{Hash, Hasher},
1111 };
1212
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−
1913 pub struct View<V> {
20− pub(crate) model: Model<V>,
14+ pub model: Model<V>,
2115 }
2216
2317 impl<V> Sealed for View<V> {}
@@ -65,15 +59,15 @@ impl<V: 'static> View<V> { · 41 unmodified lines
6559 self.model.read(cx)
6660 }
6761
68− pub fn render_with<C>(&self, component: C) -> RenderViewWith<C, V>
69− where
70− C: 'static + Component<V>,
71− {
72− RenderViewWith {
73− view: self.clone(),
74− component: Some(component),
75− }
76− }
62+ // pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V>
63+ // where
64+ // E: 'static + Element,
65+ // {
66+ // RenderViewWith {
67+ // view: self.clone(),
68+ // element: Some(component),
69+ // }
70+ // }
7771
7872 pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle
7973 where
@@ -83,6 +77,24 @@ impl<V: 'static> View<V> { · 3 unmodified lines
8377 }
8478 }
8579
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+
8698 impl<V> Clone for View<V> {
8799 fn clone(&self) -> Self {
88100 Self {
@@ -105,12 +117,6 @@ impl<V> PartialEq for View<V> { · 16 unmodified lines
105117
106118 impl<V> Eq for View<V> {}
107119
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−
114120 pub struct WeakView<V> {
115121 pub(crate) model: WeakModel<V>,
116122 }
@@ -163,8 +169,8 @@ impl<V> Eq for WeakView<V> {} · 46 unmodified lines
163169 #[derive(Clone, Debug)]
164170 pub struct AnyView {
165171 model: AnyModel,
166− layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
167− paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
172+ layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
173+ paint: fn(&AnyView, AnyElement, &mut WindowContext),
168174 }
169175
170176 impl AnyView {
@@ -202,21 +208,15 @@ impl AnyView { · 31 unmodified lines
202208 cx: &mut WindowContext,
203209 ) {
204210 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);
206212 cx.window
207213 .layout_engine
208214 .compute_layout(layout_id, available_space);
209− (self.paint)(self, &mut rendered_element, cx);
215+ (self.paint)(self, rendered_element, cx);
210216 })
211217 }
212218 }
213219
214−impl<V: 'static> Component<V> for AnyView {
215− fn render(self) -> AnyElement<V> {
216− AnyElement::new(self)
217− }
218−}
219−
220220 impl<V: Render> From<View<V>> for AnyView {
221221 fn from(value: View<V>) -> Self {
222222 AnyView {
@@ -227,37 +227,51 @@ impl<V: Render> From<View<V>> for AnyView { · 4 unmodified lines
227227 }
228228 }
229229
230−impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
231− type ElementState = Box<dyn Any>;
230+impl Element for AnyView {
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>;
232249
233250 fn element_id(&self) -> Option<ElementId> {
234− Some(self.model.entity_id.into())
251+ Some(ElementId::from_entity_id(self.model.entity_id))
235252 }
236253
237− fn layout(
238− &mut 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)
254+ fn into_element(self) -> Self::Element {
255+ self
244256 }
257+}
245258
246− fn paint(
247− &mut self,
248− _bounds: Bounds<Pixels>,
249− _view_state: &mut ParentViewState,
250− rendered_element: &mut Self::ElementState,
251− cx: &mut ViewContext<ParentViewState>,
252− ) {
253− (self.paint)(self, rendered_element, cx)
259+impl IntoElement for AnyView {
260+ type Element = Self;
261+
262+ fn element_id(&self) -> Option<ElementId> {
263+ Some(ElementId::from_entity_id(self.model.entity_id))
264+ }
265+
266+ fn into_element(self) -> Self::Element {
267+ self
254268 }
255269 }
256270
257271 pub struct AnyWeakView {
258272 model: AnyWeakModel,
259− layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
260− paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
273+ layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
274+ paint: fn(&AnyView, AnyElement, &mut WindowContext),
261275 }
262276
263277 impl AnyWeakView {
@@ -271,7 +285,7 @@ impl AnyWeakView { · 7 unmodified lines
271285 }
272286 }
273287
274−impl<V: Render> From<WeakView<V>> for AnyWeakView {
288+impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
275289 fn from(view: WeakView<V>) -> Self {
276290 Self {
277291 model: view.model.into(),
@@ -281,97 +295,36 @@ impl<V: Render> From<WeakView<V>> for AnyWeakView { · 3 unmodified lines
281295 }
282296 }
283297
284−// 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>
298+impl<T, E> Render for T
313299 where
314− C: 'static + Component<ViewState>,
315− ParentViewState: 'static,
316− ViewState: 'static,
300+ T: 'static + FnMut(&mut WindowContext) -> E,
301+ E: 'static + Send + Element,
317302 {
318− type ElementState = AnyElement<ViewState>;
319−
320− fn element_id(&self) -> Option<ElementId> {
321− Some(self.view.entity_id().into())
322− }
303+ type Element = E;
323304
324− fn layout(
325− &mut self,
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))
305+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
306+ (self)(cx)
345307 }
346308 }
347309
348310 mod any_view {
349− use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext};
350− use std::any::Any;
311+ use crate::{AnyElement, AnyView, Element, LayoutId, Render, WindowContext};
351312
352− pub(crate) fn layout<V: Render>(
313+ pub(crate) fn layout<V: 'static + Render>(
353314 view: &AnyView,
354315 cx: &mut WindowContext,
355− ) -> (LayoutId, Box<dyn Any>) {
356− cx.with_element_id(Some(view.model.entity_id), |cx| {
357− let view = view.clone().downcast::<V>().unwrap();
358− view.update(cx, |view, cx| {
359− let mut element = AnyElement::new(view.render(cx));
360− let layout_id = element.layout(view, cx);
361− (layout_id, Box::new(element) as Box<dyn Any>)
362− })
363− })
316+ ) -> (LayoutId, AnyElement) {
317+ let view = view.clone().downcast::<V>().unwrap();
318+ let mut element = view.update(cx, |view, cx| view.render(cx).into_any());
319+ let layout_id = element.layout(cx);
320+ (layout_id, element)
364321 }
365322
366− pub(crate) fn paint<V: Render>(
367− view: &AnyView,
368− element: &mut Box<dyn Any>,
323+ pub(crate) fn paint<V: 'static + Render>(
324+ _view: &AnyView,
325+ element: AnyElement,
369326 cx: &mut WindowContext,
370327 ) {
371− cx.with_element_id(Some(view.model.entity_id), |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− })
328+ element.paint(cx);
376329 }
377330 }
crates/gpui2/src/window.rs+182−80
@@ -1,5 +1,5 @@
11 use crate::{
2− key_dispatch::DispatchActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
2+ key_dispatch::DispatchActionListener, px, size, Action, AnyDrag, AnyView, AppContext,
33 AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
44 DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
55 EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
@@ -187,17 +187,17 @@ impl Drop for FocusHandle { · 181 unmodified lines
187187
188188 /// FocusableView allows users of your view to easily
189189 /// focus it (using cx.focus_view(view))
190−pub trait FocusableView: Render {
190+pub trait FocusableView: 'static + Render {
191191 fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
192192 }
193193
194194 /// ManagedView is a view (like a Modal, Popover, Menu, etc.)
195195 /// 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> {}
197197
198−impl<M: FocusableView + EventEmitter<Manager>> ManagedView for M {}
198+impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
199199
200−pub enum Manager {
200+pub enum DismissEvent {
201201 Dismiss,
202202 }
203203
@@ -230,9 +230,15 @@ pub struct Window { · 26 unmodified lines
230230 pub(crate) focus: Option<FocusId>,
231231 }
232232
233+pub(crate) struct ElementStateBox {
234+ inner: Box<dyn Any>,
235+ #[cfg(debug_assertions)]
236+ type_name: &'static str,
237+}
238+
233239 // #[derive(Default)]
234240 pub(crate) struct Frame {
235− pub(crate) element_states: HashMap<GlobalElementId, AnyBox>,
241+ pub(crate) element_states: HashMap<GlobalElementId, ElementStateBox>,
236242 mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
237243 pub(crate) dispatch_tree: DispatchTree,
238244 pub(crate) focus_listeners: Vec<AnyFocusListener>,
@@ -875,7 +881,7 @@ impl<'a> WindowContext<'a> { · 636 unmodified lines
875881 origin: Point<Pixels>,
876882 width: Pixels,
877883 style: &UnderlineStyle,
878− ) -> Result<()> {
884+ ) {
879885 let scale_factor = self.scale_factor();
880886 let height = if style.wavy {
881887 style.thickness * 3.
@@ -899,7 +905,6 @@ impl<'a> WindowContext<'a> { · 17 unmodified lines
899905 wavy: style.wavy,
900906 },
901907 );
902− Ok(())
903908 }
904909
905910 /// 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
14361441 .dispatch_tree
14371442 .bindings_for_action(action)
14381443 }
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+ }
14391527 }
14401528
14411529 impl Context for WindowContext<'_> {
@@ -1559,7 +1647,7 @@ impl VisualContext for WindowContext<'_> { · 117 unmodified lines
15591647 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
15601648 ) -> Self::Result<View<V>>
15611649 where
1562− V: Render,
1650+ V: 'static + Render,
15631651 {
15641652 let slot = self.app.entities.reserve();
15651653 let view = View {
@@ -1582,7 +1670,7 @@ impl VisualContext for WindowContext<'_> { · 16 unmodified lines
15821670 where
15831671 V: ManagedView,
15841672 {
1585− self.update_view(view, |_, cx| cx.emit(Manager::Dismiss))
1673+ self.update_view(view, |_, cx| cx.emit(DismissEvent::Dismiss))
15861674 }
15871675 }
15881676
@@ -1617,6 +1705,10 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 28 unmodified lines
16171705 self.borrow_mut()
16181706 }
16191707
1708+ fn app(&self) -> &AppContext {
1709+ self.borrow()
1710+ }
1711+
16201712 fn window(&self) -> &Window {
16211713 self.borrow()
16221714 }
@@ -1667,6 +1759,24 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 44 unmodified lines
16671759 }
16681760 }
16691761
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+
16701780 /// Update the global element offset relative to the current offset. This is used to implement
16711781 /// scrolling.
16721782 fn with_element_offset<R>(
@@ -1735,10 +1845,37 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 62 unmodified lines
17351845 .remove(&global_id)
17361846 })
17371847 {
1848+ let ElementStateBox {
1849+ inner,
1850+
1851+ #[cfg(debug_assertions)]
1852+ type_name
1853+ } = any;
17381854 // 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
17401856 .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
17421879 let state = state_box
17431880 .take()
17441881 .expect("element state is already on the stack");
@@ -1747,14 +1884,27 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> { · 2 unmodified lines
17471884 cx.window_mut()
17481885 .current_frame
17491886 .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+ });
17511893 result
17521894 } else {
17531895 let (result, state) = f(None, cx);
17541896 cx.window_mut()
17551897 .current_frame
17561898 .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+ );
17581908 result
17591909 }
17601910 })
@@ -2124,49 +2274,6 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 363 unmodified lines
21242274 )
21252275 }
21262276
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−
21702277 pub fn spawn<Fut, R>(
21712278 &mut self,
21722279 f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
@@ -2243,21 +2350,6 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 70 unmodified lines
22432350 });
22442351 }
22452352
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−
22612353 pub fn emit<Evt>(&mut self, event: Evt)
22622354 where
22632355 Evt: 'static,
@@ -2282,7 +2374,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { · 18 unmodified lines
22822374 where
22832375 V: ManagedView,
22842376 {
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+ }
22862388 }
22872389 }
22882390
@@ -2355,7 +2457,7 @@ impl<V: 'static> VisualContext for ViewContext<'_, V> { · 66 unmodified lines
23552457 build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W,
23562458 ) -> Self::Result<View<W>>
23572459 where
2358− W: Render,
2460+ W: 'static + Render,
23592461 {
23602462 self.window_cx.replace_root_view(build_view)
23612463 }
@@ -2567,6 +2669,12 @@ pub enum ElementId { · 205 unmodified lines
25672669 FocusHandle(FocusId),
25682670 }
25692671
2672+impl ElementId {
2673+ pub(crate) fn from_entity_id(entity_id: EntityId) -> Self {
2674+ ElementId::View(entity_id)
2675+ }
2676+}
2677+
25702678 impl TryInto<SharedString> for ElementId {
25712679 type Error = anyhow::Error;
25722680
@@ -2579,12 +2687,6 @@ impl TryInto<SharedString> for ElementId { · 6 unmodified lines
25792687 }
25802688 }
25812689
2582−impl From<EntityId> for ElementId {
2583− fn from(id: EntityId) -> Self {
2584− ElementId::View(id)
2585− }
2586−}
2587−
25882690 impl From<usize> for ElementId {
25892691 fn from(id: usize) -> Self {
25902692 ElementId::Integer(id)
crates/gpui2_macros/src/derive_component.rs+0−66
@@ -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−}
crates/gpui2_macros/src/derive_into_element.rs+27−0
@@ -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+}
crates/gpui2_macros/src/gpui2_macros.rs+9−9
@@ -1,16 +1,11 @@
11 mod action;
2−mod derive_component;
2+mod derive_into_element;
33 mod register_action;
44 mod style_helpers;
55 mod test;
66
77 use proc_macro::TokenStream;
88
9−#[proc_macro]
10−pub fn style_helpers(args: TokenStream) -> TokenStream {
11− style_helpers::style_helpers(args)
12−}
13−
149 #[proc_macro_derive(Action)]
1510 pub fn action(input: TokenStream) -> TokenStream {
1611 action::action(input)
@@ -21,9 +16,14 @@ pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream { · 4 unmodified lines
2116 register_action::register_action_macro(attr, item)
2217 }
2318
24−#[proc_macro_derive(Component, attributes(component))]
25−pub fn derive_component(input: TokenStream) -> TokenStream {
26− derive_component::derive_component(input)
19+#[proc_macro_derive(IntoElement)]
20+pub fn derive_into_element(input: TokenStream) -> TokenStream {
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)
2727 }
2828
2929 #[proc_macro_attribute]
crates/language/src/highlight_map.rs+4−4
@@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); · 10 unmodified lines
1111 const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
1212
1313 impl HighlightMap {
14− pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self {
14+ pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self {
1515 // For each capture name in the highlight query, find the longest
1616 // key in the theme's syntax styles that matches all of the
1717 // dot-separated components of the capture name.
@@ -98,9 +98,9 @@ mod tests { · 80 unmodified lines
9898 );
9999
100100 let capture_names = &[
101− "function.special".to_string(),
102− "function.async.rust".to_string(),
103− "variable.builtin.self".to_string(),
101+ "function.special",
102+ "function.async.rust",
103+ "variable.builtin.self",
104104 ];
105105
106106 let map = HighlightMap::new(capture_names, &theme);
crates/language/src/language.rs+2−2
@@ -1383,7 +1383,7 @@ impl Language { · 1382 unmodified lines
13831383 let query = Query::new(self.grammar_mut().ts_language, source)?;
13841384
13851385 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() {
13871387 if !name.starts_with('_') {
13881388 let value = self.config.overrides.remove(name).unwrap_or_default();
13891389 for server_name in &value.opt_into_language_servers {
@@ -1396,7 +1396,7 @@ impl Language { · 6 unmodified lines
13961396 }
13971397 }
13981398
1399− override_configs_by_id.insert(ix as u32, (name.clone(), value));
1399+ override_configs_by_id.insert(ix as u32, (name.into(), value));
14001400 }
14011401 }
14021402
crates/language/src/syntax_map/syntax_map_tests.rs+1−1
@@ -1300,7 +1300,7 @@ fn assert_capture_ranges( · 1299 unmodified lines
13001300 .collect::<Vec<_>>();
13011301 for capture in captures {
13021302 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) {
13041304 actual_ranges.push(capture.node.byte_range());
13051305 }
13061306 }
crates/language2/src/buffer.rs+41−0
@@ -7,6 +7,7 @@ pub use crate::{ · 6 unmodified lines
77 use crate::{
88 diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
99 language_settings::{language_settings, LanguageSettings},
10+ markdown::parse_markdown,
1011 outline::OutlineItem,
1112 syntax_map::{
1213 SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
@@ -155,12 +156,52 @@ pub struct Diagnostic { · 142 unmodified lines
155156 pub is_unnecessary: bool,
156157 }
157158
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+
158198 #[derive(Clone, Debug)]
159199 pub struct Completion {
160200 pub old_range: Range<Anchor>,
161201 pub new_text: String,
162202 pub label: CodeLabel,
163203 pub server_id: LanguageServerId,
204+ pub documentation: Option<Documentation>,
164205 pub lsp_completion: lsp::CompletionItem,
165206 }
166207
crates/language2/src/highlight_map.rs+4−4
@@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); · 10 unmodified lines
1111 const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
1212
1313 impl HighlightMap {
14− pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self {
14+ pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self {
1515 // For each capture name in the highlight query, find the longest
1616 // key in the theme's syntax styles that matches all of the
1717 // dot-separated components of the capture name.
@@ -100,9 +100,9 @@ mod tests { · 82 unmodified lines
100100 };
101101
102102 let capture_names = &[
103− "function.special".to_string(),
104− "function.async.rust".to_string(),
105− "variable.builtin.self".to_string(),
103+ "function.special",
104+ "function.async.rust",
105+ "variable.builtin.self",
106106 ];
107107
108108 let map = HighlightMap::new(capture_names, &theme);
crates/language2/src/language2.rs+2−2
@@ -1391,7 +1391,7 @@ impl Language { · 1390 unmodified lines
13911391 let mut override_configs_by_id = HashMap::default();
13921392 for (ix, name) in query.capture_names().iter().enumerate() {
13931393 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();
13951395 for server_name in &value.opt_into_language_servers {
13961396 if !self
13971397 .config
@@ -1402,7 +1402,7 @@ impl Language { · 4 unmodified lines
14021402 }
14031403 }
14041404
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));
14061406 }
14071407 }
14081408
crates/language2/src/proto.rs+1−0
@@ -482,6 +482,7 @@ pub async fn deserialize_completion( · 481 unmodified lines
482482 lsp_completion.filter_text.as_deref(),
483483 )
484484 }),
485+ documentation: None,
485486 server_id: LanguageServerId(completion.server_id as usize),
486487 lsp_completion,
487488 })
crates/language2/src/syntax_map/syntax_map_tests.rs+1−1
@@ -1300,7 +1300,7 @@ fn assert_capture_ranges( · 1299 unmodified lines
13001300 .collect::<Vec<_>>();
13011301 for capture in captures {
13021302 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) {
13041304 actual_ranges.push(capture.node.byte_range());
13051305 }
13061306 }
crates/live_kit_client2/build.rs+11−1
@@ -61,12 +61,14 @@ fn build_bridge(swift_target: &SwiftTarget) { · 60 unmodified lines
6161
6262 let swift_package_root = swift_package_root();
6363 let swift_target_folder = swift_target_folder();
64+ let swift_cache_folder = swift_cache_folder();
6465 if !Command::new("swift")
6566 .arg("build")
6667 .arg("--disable-automatic-resolution")
6768 .args(["--configuration", &env::var("PROFILE").unwrap()])
6869 .args(["--triple", &swift_target.target.triple])
6970 .args(["--build-path".into(), swift_target_folder])
71+ .args(["--cache-path".into(), swift_cache_folder])
7072 .current_dir(&swift_package_root)
7173 .status()
7274 .unwrap()
@@ -133,9 +135,17 @@ fn swift_package_root() -> PathBuf { · 60 unmodified lines
133135 }
134136
135137 fn swift_target_folder() -> PathBuf {
138+ let target = env::var("TARGET").unwrap();
136139 env::current_dir()
137140 .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"))
139149 }
140150
141151 fn copy_dir(source: &Path, destination: &Path) {
crates/node_runtime/src/node_runtime.rs+8−6
@@ -73,6 +73,7 @@ impl RealNodeRuntime { · 72 unmodified lines
7373 let npm_file = node_dir.join("bin/npm");
7474
7575 let result = Command::new(&node_binary)
76+ .env_clear()
7677 .arg(npm_file)
7778 .arg("--version")
7879 .stdin(Stdio::null())
@@ -149,6 +150,7 @@ impl NodeRuntime for RealNodeRuntime { · 70 unmodified lines
149150 }
150151
151152 let mut command = Command::new(node_binary);
153+ command.env_clear();
152154 command.env("PATH", env_path);
153155 command.arg(npm_file).arg(subcommand);
154156 command.args(["--cache".into(), installation_path.join("cache")]);
@@ -200,11 +202,11 @@ impl NodeRuntime for RealNodeRuntime { · 45 unmodified lines
200202 &[
201203 name,
202204 "--json",
203− "-fetch-retry-mintimeout",
205+ "--fetch-retry-mintimeout",
204206 "2000",
205− "-fetch-retry-maxtimeout",
207+ "--fetch-retry-maxtimeout",
206208 "5000",
207− "-fetch-timeout",
209+ "--fetch-timeout",
208210 "5000",
209211 ],
210212 )
@@ -229,11 +231,11 @@ impl NodeRuntime for RealNodeRuntime { · 18 unmodified lines
229231
230232 let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
231233 arguments.extend_from_slice(&[
232− "-fetch-retry-mintimeout",
234+ "--fetch-retry-mintimeout",
233235 "2000",
234− "-fetch-retry-maxtimeout",
236+ "--fetch-retry-maxtimeout",
235237 "5000",
236− "-fetch-timeout",
238+ "--fetch-timeout",
237239 "5000",
238240 ]);
239241
crates/picker2/src/picker2.rs+46−39
@@ -1,10 +1,10 @@
11 use editor::Editor;
22 use gpui::{
3− div, prelude::*, uniform_list, AppContext, Component, Div, FocusHandle, FocusableView,
4− MouseButton, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
3+ div, prelude::*, uniform_list, AppContext, Div, FocusHandle, FocusableView, MouseButton,
4+ MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
55 };
66 use std::{cmp, sync::Arc};
7−use ui::{prelude::*, v_stack, Divider, Label, TextColor};
7+use ui::{prelude::*, v_stack, Color, Divider, Label};
88
99 pub struct Picker<D: PickerDelegate> {
1010 pub delegate: D,
@@ -15,7 +15,7 @@ pub struct Picker<D: PickerDelegate> { · 4 unmodified lines
1515 }
1616
1717 pub trait PickerDelegate: Sized + 'static {
18− type ListItem: Component<Picker<Self>>;
18+ type ListItem: IntoElement;
1919
2020 fn match_count(&self) -> usize;
2121 fn selected_index(&self) -> usize;
@@ -32,7 +32,7 @@ pub trait PickerDelegate: Sized + 'static { · 10 unmodified lines
3232 ix: usize,
3333 selected: bool,
3434 cx: &mut ViewContext<Picker<Self>>,
35− ) -> Self::ListItem;
35+ ) -> Option<Self::ListItem>;
3636 }
3737
3838 impl<D: PickerDelegate> FocusableView for Picker<D> {
@@ -114,6 +114,7 @@ impl<D: PickerDelegate> Picker<D> { · 75 unmodified lines
114114 }
115115
116116 fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
117+ dbg!("canceling!");
117118 self.delegate.dismissed(cx);
118119 }
119120
@@ -181,20 +182,20 @@ impl<D: PickerDelegate> Picker<D> { · 61 unmodified lines
181182 }
182183
183184 impl<D: PickerDelegate> Render for Picker<D> {
184− type Element = Div<Self>;
185+ type Element = Div;
185186
186187 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
187188 div()
188189 .key_context("picker")
189190 .size_full()
190191 .elevation_2(cx)
191− .on_action(Self::select_next)
192− .on_action(Self::select_prev)
193− .on_action(Self::select_first)
194− .on_action(Self::select_last)
195− .on_action(Self::cancel)
196− .on_action(Self::confirm)
197− .on_action(Self::secondary_confirm)
192+ .on_action(cx.listener(Self::select_next))
193+ .on_action(cx.listener(Self::select_prev))
194+ .on_action(cx.listener(Self::select_first))
195+ .on_action(cx.listener(Self::select_last))
196+ .on_action(cx.listener(Self::cancel))
197+ .on_action(cx.listener(Self::confirm))
198+ .on_action(cx.listener(Self::secondary_confirm))
198199 .child(
199200 v_stack()
200201 .py_0p5()
@@ -208,31 +209,37 @@ impl<D: PickerDelegate> Render for Picker<D> { · 7 unmodified lines
208209 .p_1()
209210 .grow()
210211 .child(
211− uniform_list("candidates", self.delegate.match_count(), {
212− move |this: &mut Self, visible_range, cx| {
213− let selected_ix = this.delegate.selected_index();
214− visible_range
215− .map(|ix| {
216− div()
217− .on_mouse_down(
218− MouseButton::Left,
219− move |this: &mut Self, event, cx| {
220− this.handle_click(
221− ix,
222− event.modifiers.command,
223− cx,
224− )
225− },
226− )
227− .child(this.delegate.render_match(
228− ix,
229− ix == selected_ix,
230− cx,
231− ))
232− })
233− .collect()
234− }
235− })
212+ uniform_list(
213+ cx.view().clone(),
214+ "candidates",
215+ self.delegate.match_count(),
216+ {
217+ let selected_index = self.delegate.selected_index();
218+
219+ move |picker, visible_range, cx| {
220+ visible_range
221+ .map(|ix| {
222+ div()
223+ .on_mouse_down(
224+ MouseButton::Left,
225+ cx.listener(move |this, event: &MouseDownEvent, cx| {
226+ this.handle_click(
227+ ix,
228+ event.modifiers.command,
229+ cx,
230+ )
231+ }),
232+ )
233+ .children(picker.delegate.render_match(
234+ ix,
235+ ix == selected_index,
236+ cx,
237+ ))
238+ })
239+ .collect()
240+ }
241+ },
242+ )
236243 .track_scroll(self.scroll_handle.clone()),
237244 )
238245 .max_h_72()
@@ -244,7 +251,7 @@ impl<D: PickerDelegate> Render for Picker<D> { · 5 unmodified lines
244251 v_stack().p_1().grow().child(
245252 div()
246253 .px_1()
247− .child(Label::new("No matches").color(TextColor::Muted)),
254+ .child(Label::new("No matches").color(Color::Muted)),
248255 ),
249256 )
250257 })
crates/project/src/project.rs+118−5
@@ -13,7 +13,7 @@ mod worktree_tests; · 12 unmodified lines
1313 use anyhow::{anyhow, Context, Result};
1414 use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
1515 use clock::ReplicaId;
16−use collections::{hash_map, BTreeMap, HashMap, HashSet};
16+use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
1717 use copilot::Copilot;
1818 use futures::{
1919 channel::{
@@ -62,7 +62,10 @@ use serde::Serialize; · 42 unmodified lines
6262 use settings::SettingsStore;
6363 use sha2::{Digest, Sha256};
6464 use similar::{ChangeTag, TextDiff};
65−use smol::channel::{Receiver, Sender};
65+use smol::{
66+ channel::{Receiver, Sender},
67+ lock::Semaphore,
68+};
6669 use std::{
6770 cmp::{self, Ordering},
6871 convert::TryInto,
@@ -557,6 +560,7 @@ enum SearchMatchCandidate { · 488 unmodified lines
557560 },
558561 Path {
559562 worktree_id: WorktreeId,
563+ is_ignored: bool,
560564 path: Arc<Path>,
561565 },
562566 }
@@ -5742,13 +5746,18 @@ impl Project { · 5179 unmodified lines
57425746 .await
57435747 .log_err();
57445748 }
5749+
57455750 background
57465751 .scoped(|scope| {
5752+ let max_concurrent_workers = Arc::new(Semaphore::new(workers));
5753+
57475754 for worker_ix in 0..workers {
57485755 let worker_start_ix = worker_ix * paths_per_worker;
57495756 let worker_end_ix = worker_start_ix + paths_per_worker;
57505757 let unnamed_buffers = opened_buffers.clone();
5758+ let limiter = Arc::clone(&max_concurrent_workers);
57515759 scope.spawn(async move {
5760+ let _guard = limiter.acquire().await;
57525761 let mut snapshot_start_ix = 0;
57535762 let mut abs_path = PathBuf::new();
57545763 for snapshot in snapshots {
@@ -5797,6 +5806,7 @@ impl Project { · 42 unmodified lines
57975806 let project_path = SearchMatchCandidate::Path {
57985807 worktree_id: snapshot.id(),
57995808 path: entry.path.clone(),
5809+ is_ignored: entry.is_ignored,
58005810 };
58015811 if matching_paths_tx.send(project_path).await.is_err() {
58025812 break;
@@ -5809,6 +5819,94 @@ impl Project { · 6 unmodified lines
58095819 }
58105820 });
58115821 }
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+ }
58125910 })
58135911 .await;
58145912 }
@@ -5917,11 +6015,24 @@ impl Project { · 102 unmodified lines
59176015 let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);
59186016 let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel();
59196017 cx.spawn(|this, cx| async move {
5920− let mut buffers = vec![];
6018+ let mut buffers = Vec::new();
6019+ let mut ignored_buffers = Vec::new();
59216020 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+ }
59236032 }
59246033 buffers.sort_by_key(|candidate| candidate.path());
6034+ ignored_buffers.sort_by_key(|candidate| candidate.path());
6035+ buffers.extend(ignored_buffers);
59256036 let matching_paths = buffers.clone();
59266037 let _ = sorted_buffers_tx.send(buffers);
59276038 for (index, candidate) in matching_paths.into_iter().enumerate() {
@@ -5933,7 +6044,9 @@ impl Project { · 5 unmodified lines
59336044 cx.spawn(|mut cx| async move {
59346045 let buffer = match candidate {
59356046 SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer),
5936− SearchMatchCandidate::Path { worktree_id, path } => this
6047+ SearchMatchCandidate::Path {
6048+ worktree_id, path, ..
6049+ } => this
59376050 .update(&mut cx, |this, cx| {
59386051 this.open_buffer((worktree_id, path), cx)
59396052 })
crates/project/src/worktree.rs+71−62
@@ -2226,7 +2226,7 @@ impl LocalSnapshot { · 2225 unmodified lines
22262226 paths
22272227 }
22282228
2229− fn is_abs_path_excluded(&self, abs_path: &Path) -> bool {
2229+ pub fn is_path_excluded(&self, abs_path: &Path) -> bool {
22302230 self.file_scan_exclusions
22312231 .iter()
22322232 .any(|exclude_matcher| exclude_matcher.is_match(abs_path))
@@ -2399,26 +2399,9 @@ impl BackgroundScannerState { · 166 unmodified lines
23992399 self.snapshot.check_invariants(false);
24002400 }
24012401
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) {
24032403 let scan_id = self.snapshot.scan_id;
2404−
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−
2404+ for dot_git_dir in dot_git_dirs_to_reload {
24222405 // If there is already a repository for this .git directory, reload
24232406 // the status for all of its files.
24242407 let repository = self
@@ -2430,7 +2413,7 @@ impl BackgroundScannerState { · 5 unmodified lines
24302413 });
24312414 match repository {
24322415 None => {
2433− self.build_git_repository(dot_git_dir.into(), fs);
2416+ self.build_git_repository(Arc::from(dot_git_dir.as_path()), fs);
24342417 }
24352418 Some((entry_id, repository)) => {
24362419 if repository.git_dir_scan_id == scan_id {
@@ -2444,7 +2427,7 @@ impl BackgroundScannerState { · 7 unmodified lines
24442427 continue;
24452428 };
24462429
2447− log::info!("reload git repository {:?}", dot_git_dir);
2430+ log::info!("reload git repository {dot_git_dir:?}");
24482431 let repository = repository.repo_ptr.lock();
24492432 let branch = repository.branch_name();
24502433 repository.reload_index();
@@ -2475,7 +2458,9 @@ impl BackgroundScannerState { · 24 unmodified lines
24752458 ids_to_preserve.insert(work_directory_id);
24762459 } else {
24772460 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
24792464 && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
24802465 {
24812466 ids_to_preserve.insert(work_directory_id);
@@ -3314,11 +3299,26 @@ impl BackgroundScanner { · 832 unmodified lines
33143299 };
33153300
33163301 let mut relative_paths = Vec::with_capacity(abs_paths.len());
3302+ let mut dot_git_paths_to_reload = HashSet::default();
33173303 abs_paths.sort_unstable();
33183304 abs_paths.dedup_by(|a, b| a.starts_with(&b));
33193305 abs_paths.retain(|abs_path| {
33203306 let snapshot = &self.state.lock().snapshot;
33213307 {
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+
33223322 let relative_path: Arc<Path> =
33233323 if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
33243324 path.into()
@@ -3328,23 +3328,30 @@ impl BackgroundScanner { · 3 unmodified lines
33283328 );
33293329 return false;
33303330 };
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+ }
33313340
3332− if !is_git_related(&abs_path) {
3333− let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
3334− snapshot
3335− .entry_for_path(parent)
3336− .map_or(false, |entry| entry.kind == EntryKind::Dir)
3337− });
3338− if !parent_dir_is_loaded {
3339− log::debug!("ignoring event {relative_path:?} within unloaded directory");
3340− return false;
3341+ // FS events may come for files which parent directory is excluded, need to check ignore those.
3342+ let mut path_to_test = abs_path.clone();
3343+ let mut excluded_file_event = snapshot.is_path_excluded(abs_path)
3344+ || snapshot.is_path_excluded(&relative_path);
3345+ while !excluded_file_event && path_to_test.pop() {
3346+ if snapshot.is_path_excluded(&path_to_test) {
3347+ excluded_file_event = true;
33413348 }
3342− if snapshot.is_abs_path_excluded(abs_path) {
3343− log::debug!(
3344− "ignoring FS event for path {relative_path:?} within excluded directory"
3345− );
3346− return false;
3349+ }
3350+ if excluded_file_event {
3351+ if !is_git_related {
3352+ log::debug!("ignoring FS event for excluded path {relative_path:?}");
33473353 }
3354+ return false;
33483355 }
33493356
33503357 relative_paths.push(relative_path);
@@ -3352,31 +3359,39 @@ impl BackgroundScanner { · 1 unmodified line
33523359 }
33533360 });
33543361
3355− if relative_paths.is_empty() {
3362+ if dot_git_paths_to_reload.is_empty() && relative_paths.is_empty() {
33563363 return;
33573364 }
33583365
3359− log::debug!("received fs events {:?}", relative_paths);
3366+ if !relative_paths.is_empty() {
3367+ log::debug!("received fs events {:?}", relative_paths);
33603368
3361− let (scan_job_tx, scan_job_rx) = channel::unbounded();
3362− self.reload_entries_for_paths(
3363− root_path,
3364− root_canonical_path,
3365− &relative_paths,
3366− abs_paths,
3367− Some(scan_job_tx.clone()),
3368− )
3369− .await;
3370− drop(scan_job_tx);
3371− self.scan_dirs(false, scan_job_rx).await;
3369+ let (scan_job_tx, scan_job_rx) = channel::unbounded();
3370+ self.reload_entries_for_paths(
3371+ root_path,
3372+ root_canonical_path,
3373+ &relative_paths,
3374+ abs_paths,
3375+ Some(scan_job_tx.clone()),
3376+ )
3377+ .await;
3378+ drop(scan_job_tx);
3379+ self.scan_dirs(false, scan_job_rx).await;
33723380
3373− let (scan_job_tx, scan_job_rx) = channel::unbounded();
3374− self.update_ignore_statuses(scan_job_tx).await;
3375− self.scan_dirs(false, scan_job_rx).await;
3381+ let (scan_job_tx, scan_job_rx) = channel::unbounded();
3382+ self.update_ignore_statuses(scan_job_tx).await;
3383+ self.scan_dirs(false, scan_job_rx).await;
3384+ }
33763385
33773386 {
33783387 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+ }
33803395 state.snapshot.completed_scan_id = state.snapshot.scan_id;
33813396 for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
33823397 state.scanned_dirs.remove(&entry_id);
@@ -3516,7 +3531,7 @@ impl BackgroundScanner { · 133 unmodified lines
35163531 let state = self.state.lock();
35173532 let snapshot = &state.snapshot;
35183533 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) {
35203535 log::error!("skipping excluded directory {:?}", job.path);
35213536 return Ok(());
35223537 }
@@ -3588,7 +3603,7 @@ impl BackgroundScanner { · 65 unmodified lines
35883603
35893604 {
35903605 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) {
35923607 let relative_path = job.path.join(child_name);
35933608 log::debug!("skipping excluded child entry {relative_path:?}");
35943609 state.remove_path(&relative_path);
@@ -4130,12 +4145,6 @@ impl BackgroundScanner { · 535 unmodified lines
41304145 }
41314146 }
41324147
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−
41394148 fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
41404149 let mut result = root_char_bag;
41414150 result.extend(
crates/project/src/worktree_tests.rs+139−0
@@ -990,6 +990,145 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { · 989 unmodified lines
990990 });
991991 }
992992
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+
9931132 #[gpui::test(iterations = 30)]
9941133 async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
9951134 init_test(cx);
crates/project2/src/lsp_command.rs+21−4
@@ -10,7 +10,7 @@ use futures::future; · 9 unmodified lines
1010 use gpui::{AppContext, AsyncAppContext, Model};
1111 use language::{
1212 language_settings::{language_settings, InlayHintKind},
13− point_from_lsp, point_to_lsp,
13+ point_from_lsp, point_to_lsp, prepare_completion_documentation,
1414 proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
1515 range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
1616 CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
@@ -1339,7 +1339,7 @@ impl LspCommand for GetCompletions { · 1322 unmodified lines
13391339 async fn response_from_lsp(
13401340 self,
13411341 completions: Option<lsp::CompletionResponse>,
1342− _: Model<Project>,
1342+ project: Model<Project>,
13431343 buffer: Model<Buffer>,
13441344 server_id: LanguageServerId,
13451345 mut cx: AsyncAppContext,
@@ -1359,7 +1359,8 @@ impl LspCommand for GetCompletions { · 13 unmodified lines
13591359 Default::default()
13601360 };
13611361
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();
13631364 let language = buffer.language().cloned();
13641365 let snapshot = buffer.snapshot();
13651366 let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
@@ -1443,14 +1444,29 @@ impl LspCommand for GetCompletions { · 77 unmodified lines
14431444 }
14441445 };
14451446
1447+ let language_registry = language_registry.clone();
14461448 let language = language.clone();
14471449 LineEnding::normalize(&mut new_text);
14481450 Some(async move {
14491451 let mut label = None;
1450− if let Some(language) = language {
1452+ if let Some(language) = language.as_ref() {
14511453 language.process_completion(&mut lsp_completion).await;
14521454 label = language.label_for_completion(&lsp_completion).await;
14531455 }
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+
14541470 Completion {
14551471 old_range,
14561472 new_text,
@@ -1460,6 +1476,7 @@ impl LspCommand for GetCompletions { · 3 unmodified lines
14601476 lsp_completion.filter_text.as_deref(),
14611477 )
14621478 }),
1479+ documentation,
14631480 server_id,
14641481 lsp_completion,
14651482 }
crates/project2/src/project2.rs+114−4
@@ -13,7 +13,7 @@ mod worktree_tests; · 12 unmodified lines
1313 use anyhow::{anyhow, Context as _, Result};
1414 use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
1515 use clock::ReplicaId;
16−use collections::{hash_map, BTreeMap, HashMap, HashSet};
16+use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
1717 use copilot::Copilot;
1818 use futures::{
1919 channel::{
@@ -63,6 +63,7 @@ use settings::{Settings, SettingsStore}; · 43 unmodified lines
6363 use sha2::{Digest, Sha256};
6464 use similar::{ChangeTag, TextDiff};
6565 use smol::channel::{Receiver, Sender};
66+use smol::lock::Semaphore;
6667 use std::{
6768 cmp::{self, Ordering},
6869 convert::TryInto,
@@ -557,6 +558,7 @@ enum SearchMatchCandidate { · 488 unmodified lines
557558 },
558559 Path {
559560 worktree_id: WorktreeId,
561+ is_ignored: bool,
560562 path: Arc<Path>,
561563 },
562564 }
@@ -5815,11 +5817,15 @@ impl Project { · 5252 unmodified lines
58155817 }
58165818 executor
58175819 .scoped(|scope| {
5820+ let max_concurrent_workers = Arc::new(Semaphore::new(workers));
5821+
58185822 for worker_ix in 0..workers {
58195823 let worker_start_ix = worker_ix * paths_per_worker;
58205824 let worker_end_ix = worker_start_ix + paths_per_worker;
58215825 let unnamed_buffers = opened_buffers.clone();
5826+ let limiter = Arc::clone(&max_concurrent_workers);
58225827 scope.spawn(async move {
5828+ let _guard = limiter.acquire().await;
58235829 let mut snapshot_start_ix = 0;
58245830 let mut abs_path = PathBuf::new();
58255831 for snapshot in snapshots {
@@ -5868,6 +5874,7 @@ impl Project { · 42 unmodified lines
58685874 let project_path = SearchMatchCandidate::Path {
58695875 worktree_id: snapshot.id(),
58705876 path: entry.path.clone(),
5877+ is_ignored: entry.is_ignored,
58715878 };
58725879 if matching_paths_tx.send(project_path).await.is_err() {
58735880 break;
@@ -5880,6 +5887,94 @@ impl Project { · 6 unmodified lines
58805887 }
58815888 });
58825889 }
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+ }
58835978 })
58845979 .await;
58855980 }
@@ -5986,11 +6081,24 @@ impl Project { · 100 unmodified lines
59866081 let (buffers_tx, buffers_rx) = smol::channel::bounded(1024);
59876082 let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel();
59886083 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();
59906086 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+ }
59926098 }
59936099 buffers.sort_by_key(|candidate| candidate.path());
6100+ ignored_buffers.sort_by_key(|candidate| candidate.path());
6101+ buffers.extend(ignored_buffers);
59946102 let matching_paths = buffers.clone();
59956103 let _ = sorted_buffers_tx.send(buffers);
59966104 for (index, candidate) in matching_paths.into_iter().enumerate() {
@@ -6002,7 +6110,9 @@ impl Project { · 5 unmodified lines
60026110 cx.spawn(move |mut cx| async move {
60036111 let buffer = match candidate {
60046112 SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer),
6005− SearchMatchCandidate::Path { worktree_id, path } => this
6113+ SearchMatchCandidate::Path {
6114+ worktree_id, path, ..
6115+ } => this
60066116 .update(&mut cx, |this, cx| {
60076117 this.open_buffer((worktree_id, path), cx)
60086118 })?
crates/project2/src/worktree.rs+72−61
@@ -2222,7 +2222,7 @@ impl LocalSnapshot { · 2221 unmodified lines
22222222 paths
22232223 }
22242224
2225− fn is_abs_path_excluded(&self, abs_path: &Path) -> bool {
2225+ pub fn is_path_excluded(&self, abs_path: &Path) -> bool {
22262226 self.file_scan_exclusions
22272227 .iter()
22282228 .any(|exclude_matcher| exclude_matcher.is_match(abs_path))
@@ -2395,26 +2395,10 @@ impl BackgroundScannerState { · 166 unmodified lines
23952395 self.snapshot.check_invariants(false);
23962396 }
23972397
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) {
23992399 let scan_id = self.snapshot.scan_id;
24002400
2401− // Find each of the .git directories that contain any of the given paths.
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−
2401+ for dot_git_dir in dot_git_dirs_to_reload {
24182402 // If there is already a repository for this .git directory, reload
24192403 // the status for all of its files.
24202404 let repository = self
@@ -2426,7 +2410,7 @@ impl BackgroundScannerState { · 5 unmodified lines
24262410 });
24272411 match repository {
24282412 None => {
2429− self.build_git_repository(dot_git_dir.into(), fs);
2413+ self.build_git_repository(Arc::from(dot_git_dir.as_path()), fs);
24302414 }
24312415 Some((entry_id, repository)) => {
24322416 if repository.git_dir_scan_id == scan_id {
@@ -2440,7 +2424,7 @@ impl BackgroundScannerState { · 7 unmodified lines
24402424 continue;
24412425 };
24422426
2443− log::info!("reload git repository {:?}", dot_git_dir);
2427+ log::info!("reload git repository {dot_git_dir:?}");
24442428 let repository = repository.repo_ptr.lock();
24452429 let branch = repository.branch_name();
24462430 repository.reload_index();
@@ -2471,7 +2455,9 @@ impl BackgroundScannerState { · 24 unmodified lines
24712455 ids_to_preserve.insert(work_directory_id);
24722456 } else {
24732457 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
24752461 && !matches!(smol::block_on(fs.metadata(&git_dir_abs_path)), Ok(None))
24762462 {
24772463 ids_to_preserve.insert(work_directory_id);
@@ -3303,11 +3289,26 @@ impl BackgroundScanner { · 825 unmodified lines
33033289 };
33043290
33053291 let mut relative_paths = Vec::with_capacity(abs_paths.len());
3292+ let mut dot_git_paths_to_reload = HashSet::default();
33063293 abs_paths.sort_unstable();
33073294 abs_paths.dedup_by(|a, b| a.starts_with(&b));
33083295 abs_paths.retain(|abs_path| {
33093296 let snapshot = &self.state.lock().snapshot;
33103297 {
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+
33113312 let relative_path: Arc<Path> =
33123313 if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
33133314 path.into()
@@ -3318,22 +3319,30 @@ impl BackgroundScanner { · 4 unmodified lines
33183319 return false;
33193320 };
33203321
3321− if !is_git_related(&abs_path) {
3322− let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
3323− snapshot
3324− .entry_for_path(parent)
3325− .map_or(false, |entry| entry.kind == EntryKind::Dir)
3326− });
3327− if !parent_dir_is_loaded {
3328− log::debug!("ignoring event {relative_path:?} within unloaded directory");
3329− return false;
3322+ let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
3323+ snapshot
3324+ .entry_for_path(parent)
3325+ .map_or(false, |entry| entry.kind == EntryKind::Dir)
3326+ });
3327+ if !parent_dir_is_loaded {
3328+ log::debug!("ignoring event {relative_path:?} within unloaded directory");
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;
33303339 }
3331− if snapshot.is_abs_path_excluded(abs_path) {
3332− log::debug!(
3333− "ignoring FS event for path {relative_path:?} within excluded directory"
3334− );
3335− return false;
3340+ }
3341+ if excluded_file_event {
3342+ if !is_git_related {
3343+ log::debug!("ignoring FS event for excluded path {relative_path:?}");
33363344 }
3345+ return false;
33373346 }
33383347
33393348 relative_paths.push(relative_path);
@@ -3341,31 +3350,39 @@ impl BackgroundScanner { · 1 unmodified line
33413350 }
33423351 });
33433352
3344− if relative_paths.is_empty() {
3353+ if dot_git_paths_to_reload.is_empty() && relative_paths.is_empty() {
33453354 return;
33463355 }
33473356
3348− log::debug!("received fs events {:?}", relative_paths);
3357+ if !relative_paths.is_empty() {
3358+ log::debug!("received fs events {:?}", relative_paths);
33493359
3350− let (scan_job_tx, scan_job_rx) = channel::unbounded();
3351− self.reload_entries_for_paths(
3352− root_path,
3353− root_canonical_path,
3354− &relative_paths,
3355− abs_paths,
3356− Some(scan_job_tx.clone()),
3357− )
3358− .await;
3359− drop(scan_job_tx);
3360− self.scan_dirs(false, scan_job_rx).await;
3360+ let (scan_job_tx, scan_job_rx) = channel::unbounded();
3361+ self.reload_entries_for_paths(
3362+ root_path,
3363+ root_canonical_path,
3364+ &relative_paths,
3365+ abs_paths,
3366+ Some(scan_job_tx.clone()),
3367+ )
3368+ .await;
3369+ drop(scan_job_tx);
3370+ self.scan_dirs(false, scan_job_rx).await;
33613371
3362− let (scan_job_tx, scan_job_rx) = channel::unbounded();
3363− self.update_ignore_statuses(scan_job_tx).await;
3364− self.scan_dirs(false, scan_job_rx).await;
3372+ let (scan_job_tx, scan_job_rx) = channel::unbounded();
3373+ self.update_ignore_statuses(scan_job_tx).await;
3374+ self.scan_dirs(false, scan_job_rx).await;
3375+ }
33653376
33663377 {
33673378 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+ }
33693386 state.snapshot.completed_scan_id = state.snapshot.scan_id;
33703387 for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
33713388 state.scanned_dirs.remove(&entry_id);
@@ -3505,7 +3522,7 @@ impl BackgroundScanner { · 133 unmodified lines
35053522 let state = self.state.lock();
35063523 let snapshot = &state.snapshot;
35073524 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) {
35093526 log::error!("skipping excluded directory {:?}", job.path);
35103527 return Ok(());
35113528 }
@@ -3577,7 +3594,7 @@ impl BackgroundScanner { · 65 unmodified lines
35773594
35783595 {
35793596 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) {
35813598 let relative_path = job.path.join(child_name);
35823599 log::debug!("skipping excluded child entry {relative_path:?}");
35833600 state.remove_path(&relative_path);
@@ -4119,12 +4136,6 @@ impl BackgroundScanner { · 535 unmodified lines
41194136 }
41204137 }
41214138
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−
41284139 fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
41294140 let mut result = root_char_bag;
41304141 result.extend(
crates/project2/src/worktree_tests.rs+143−3
@@ -992,6 +992,146 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { · 991 unmodified lines
992992 });
993993 }
994994
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+
9951135 #[gpui::test(iterations = 30)]
9961136 async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
9971137 init_test(cx);
@@ -1056,7 +1196,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { · 58 unmodified lines
10561196 async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
10571197 init_test(cx);
10581198 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));
10601200
10611201 let fs_fake = FakeFs::new(cx.background_executor.clone());
10621202 fs_fake
@@ -1096,7 +1236,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) { · 33 unmodified lines
10961236 assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
10971237 });
10981238
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));
11001240
11011241 let fs_real = Arc::new(RealFs);
11021242 let temp_root = temp_tree(json!({
@@ -2181,7 +2321,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) { · 1078 unmodified lines
21812321
21822322 fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
21832323 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))
21852325 }
21862326
21872327 #[track_caller]
crates/project_panel2/Cargo.toml+0−1
@@ -9,7 +9,6 @@ path = "src/project_panel.rs" · 8 unmodified lines
99 doctest = false
1010
1111 [dependencies]
12−context_menu = { path = "../context_menu" }
1312 collections = { path = "../collections" }
1413 db = { path = "../db2", package = "db2" }
1514 editor = { path = "../editor2", package = "editor2" }
crates/project_panel2/src/project_panel.rs+57−73
@@ -9,10 +9,9 @@ use file_associations::FileAssociations; · 8 unmodified lines
99 use anyhow::{anyhow, Result};
1010 use gpui::{
1111 actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
12− ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
13− InteractiveComponent, Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render,
14− Stateful, StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View,
15− ViewContext, VisualContext as _, WeakView, WindowContext,
12+ ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
13+ Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled,
14+ Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext,
1615 };
1716 use menu::{Confirm, SelectNext, SelectPrev};
1817 use project::{
@@ -30,7 +29,7 @@ use std::{ · 11 unmodified lines
3029 sync::Arc,
3130 };
3231 use theme::ActiveTheme as _;
33−use ui::{h_stack, v_stack, IconElement, Label};
32+use ui::{v_stack, IconElement, Label, ListItem};
3433 use unicase::UniCase;
3534 use util::{maybe, ResultExt, TryFutureExt};
3635 use workspace::{
@@ -247,7 +246,6 @@ impl ProjectPanel { · 210 unmodified lines
247246 let mut old_dock_position = this.position(cx);
248247 ProjectPanelSettings::register(cx);
249248 cx.observe_global::<SettingsStore>(move |this, cx| {
250− dbg!("OLA!");
251249 let new_dock_position = this.position(cx);
252250 if new_dock_position != old_dock_position {
253251 old_dock_position = new_dock_position;
@@ -372,7 +370,7 @@ impl ProjectPanel { · 118 unmodified lines
372370 _entry_id: ProjectEntryId,
373371 _cx: &mut ViewContext<Self>,
374372 ) {
375− todo!()
373+ // todo!()
376374 // let project = self.project.read(cx);
377375
378376 // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
@@ -645,6 +643,7 @@ impl ProjectPanel { · 266 unmodified lines
645643 }
646644
647645 fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
646+ dbg!("odd");
648647 self.edit_state = None;
649648 self.update_visible_entries(None, cx);
650649 cx.focus(&self.focus_handle);
@@ -1335,13 +1334,19 @@ impl ProjectPanel { · 684 unmodified lines
13351334 }
13361335 }
13371336
1338− fn render_entry_visual_element(
1339− details: &EntryDetails,
1340− editor: Option<&View<Editor>>,
1341− padding: Pixels,
1337+ fn render_entry(
1338+ &self,
1339+ entry_id: ProjectEntryId,
1340+ details: EntryDetails,
1341+ // dragged_entry_destination: &mut Option<Arc<Path>>,
13421342 cx: &mut ViewContext<Self>,
1343− ) -> Div<Self> {
1343+ ) -> ListItem {
1344+ let kind = details.kind;
1345+ let settings = ProjectPanelSettings::get_global(cx);
13441346 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);
13451350
13461351 let theme = cx.theme();
13471352 let filename_text_color = details
@@ -1354,14 +1359,17 @@ impl ProjectPanel { · 6 unmodified lines
13541359 })
13551360 .unwrap_or(theme.status().info);
13561361
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)
13581366 .child(if let Some(icon) = &details.icon {
13591367 div().child(IconElement::from_path(icon.to_string()))
13601368 } else {
13611369 div()
13621370 })
13631371 .child(
1364− if let (Some(editor), true) = (editor, show_editor) {
1372+ if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
13651373 div().w_full().child(editor.clone())
13661374 } else {
13671375 div()
@@ -1370,34 +1378,7 @@ impl ProjectPanel { · 2 unmodified lines
13701378 }
13711379 .ml_1(),
13721380 )
1373− .pl(padding)
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| {
1381+ .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
14011382 if !show_editor {
14021383 if kind.is_dir() {
14031384 this.toggle_expanded(entry_id, cx);
@@ -1409,10 +1390,10 @@ impl ProjectPanel { · 5 unmodified lines
14091390 }
14101391 }
14111392 }
1412− })
1413− .on_mouse_down(MouseButton::Right, move |this, event, cx| {
1393+ }))
1394+ .on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| {
14141395 this.deploy_context_menu(event.position, entry_id, cx);
1415− })
1396+ }))
14161397 // .on_drop::<ProjectEntryId>(|this, event, cx| {
14171398 // this.move_entry(
14181399 // *dragged_entry,
@@ -1425,9 +1406,9 @@ impl ProjectPanel { · 6 unmodified lines
14251406 }
14261407
14271408 impl Render for ProjectPanel {
1428− type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
1409+ type Element = Focusable<Stateful<Div>>;
14291410
1430− fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
1411+ fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
14311412 let has_worktree = self.visible_entries.len() != 0;
14321413
14331414 if has_worktree {
@@ -1435,40 +1416,43 @@ impl Render for ProjectPanel { · 1 unmodified line
14351416 .id("project-panel")
14361417 .size_full()
14371418 .key_context("ProjectPanel")
1438− .on_action(Self::select_next)
1439− .on_action(Self::select_prev)
1440− .on_action(Self::expand_selected_entry)
1441− .on_action(Self::collapse_selected_entry)
1442− .on_action(Self::collapse_all_entries)
1443− .on_action(Self::new_file)
1444− .on_action(Self::new_directory)
1445− .on_action(Self::rename)
1446− .on_action(Self::delete)
1447− .on_action(Self::confirm)
1448− .on_action(Self::open_file)
1449− .on_action(Self::cancel)
1450− .on_action(Self::cut)
1451− .on_action(Self::copy)
1452− .on_action(Self::copy_path)
1453− .on_action(Self::copy_relative_path)
1454− .on_action(Self::paste)
1455− .on_action(Self::reveal_in_finder)
1456− .on_action(Self::open_in_terminal)
1457− .on_action(Self::new_search_in_directory)
1419+ .on_action(cx.listener(Self::select_next))
1420+ .on_action(cx.listener(Self::select_prev))
1421+ .on_action(cx.listener(Self::expand_selected_entry))
1422+ .on_action(cx.listener(Self::collapse_selected_entry))
1423+ .on_action(cx.listener(Self::collapse_all_entries))
1424+ .on_action(cx.listener(Self::new_file))
1425+ .on_action(cx.listener(Self::new_directory))
1426+ .on_action(cx.listener(Self::rename))
1427+ .on_action(cx.listener(Self::delete))
1428+ .on_action(cx.listener(Self::confirm))
1429+ .on_action(cx.listener(Self::open_file))
1430+ .on_action(cx.listener(Self::cancel))
1431+ .on_action(cx.listener(Self::cut))
1432+ .on_action(cx.listener(Self::copy))
1433+ .on_action(cx.listener(Self::copy_path))
1434+ .on_action(cx.listener(Self::copy_relative_path))
1435+ .on_action(cx.listener(Self::paste))
1436+ .on_action(cx.listener(Self::reveal_in_finder))
1437+ .on_action(cx.listener(Self::open_in_terminal))
1438+ .on_action(cx.listener(Self::new_search_in_directory))
14581439 .track_focus(&self.focus_handle)
14591440 .child(
14601441 uniform_list(
1442+ cx.view().clone(),
14611443 "entries",
14621444 self.visible_entries
14631445 .iter()
14641446 .map(|(_, worktree_entries)| worktree_entries.len())
14651447 .sum(),
1466− |this: &mut Self, range, cx| {
1467− let mut items = Vec::new();
1468− this.for_each_visible_entry(range, cx, |id, details, cx| {
1469− items.push(this.render_entry(id, details, cx));
1470− });
1471− items
1448+ {
1449+ |this, range, cx| {
1450+ let mut items = Vec::new();
1451+ this.for_each_visible_entry(range, cx, |id, details, cx| {
1452+ items.push(this.render_entry(id, details, cx));
1453+ });
1454+ items
1455+ }
14721456 },
14731457 )
14741458 .size_full()
crates/rich_text2/src/rich_text.rs+2−2
@@ -56,12 +56,12 @@ pub struct Mention { · 55 unmodified lines
5656 }
5757
5858 impl RichText {
59− pub fn element<V: 'static>(
59+ pub fn element(
6060 &self,
6161 // syntax: Arc<SyntaxTheme>,
6262 // style: RichTextStyle,
6363 // cx: &mut ViewContext<V>,
64− ) -> AnyElement<V> {
64+ ) -> AnyElement {
6565 todo!();
6666
6767 // let mut region_id = 0;
crates/search/src/project_search.rs+2−5
@@ -1767,16 +1767,13 @@ impl View for ProjectSearchBar { · 1766 unmodified lines
17671767 render_option_button_icon("icons/word_search.svg", SearchOptions::WHOLE_WORD, cx)
17681768 });
17691769
1770− let mut include_ignored = is_semantic_disabled.then(|| {
1770+ let include_ignored = is_semantic_disabled.then(|| {
17711771 render_option_button_icon(
1772− // TODO proper icon
1773− "icons/case_insensitive.svg",
1772+ "icons/file_icons/git.svg",
17741773 SearchOptions::INCLUDE_IGNORED,
17751774 cx,
17761775 )
17771776 });
1778− // TODO not implemented yet
1779− let _ = include_ignored.take();
17801777
17811778 let search_button_for_mode = |mode, side, cx: &mut ViewContext<ProjectSearchBar>| {
17821779 let is_active = if let Some(search) = self.active_project_search.as_ref() {
crates/search2/Cargo.toml+40−0
@@ -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
crates/search2/src/buffer_search.rs+1747−0
@@ -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.

crates/search2/src/history.rs+184−0

This diff is large: its 185 lines aren’t shown here.

crates/search2/src/mode.rs+32−0

This diff is large: its 33 lines aren’t shown here.

crates/search2/src/project_search.rs+2680−0

This diff is large: its 2681 lines aren’t shown here.

crates/search2/src/search.rs+117−0

This diff is large: its 118 lines aren’t shown here.

crates/search2/src/search_bar.rs+35−0

This diff is large: its 36 lines aren’t shown here.

crates/semantic_index/src/semantic_index_tests.rs+2−2

This diff is large: its 16 lines aren’t shown here.

crates/story/Cargo.toml+10−0

This diff is large: its 11 lines aren’t shown here.

crates/story/src/lib.rs+3−0

This diff is large: its 4 lines aren’t shown here.

crates/story/src/story.rs+35−0

This diff is large: its 36 lines aren’t shown here.

crates/storybook2/Cargo.toml+1−0

This diff is large: its 8 lines aren’t shown here.

crates/storybook2/src/stories.rs+0−2

This diff is large: its 13 lines aren’t shown here.

crates/storybook2/src/stories/colors.rs+0−44

This diff is large: its 45 lines aren’t shown here.

crates/storybook2/src/stories/focus.rs+31−27

This diff is large: its 97 lines aren’t shown here.

crates/storybook2/src/stories/kitchen_sink.rs+7−5

This diff is large: its 35 lines aren’t shown here.

crates/storybook2/src/stories/picker.rs+16−19

This diff is large: its 66 lines aren’t shown here.

crates/storybook2/src/stories/scroll.rs+2−2

This diff is large: its 18 lines aren’t shown here.

crates/storybook2/src/stories/text.rs+19−3

This diff is large: its 40 lines aren’t shown here.

crates/storybook2/src/stories/z_index.rs+50−49

This diff is large: its 142 lines aren’t shown here.

crates/storybook2/src/story.rs+0−1

This diff is large: its 2 lines aren’t shown here.

crates/storybook2/src/story_selector.rs+7−60

This diff is large: its 102 lines aren’t shown here.

crates/storybook2/src/storybook2.rs+2−6

This diff is large: its 42 lines aren’t shown here.

crates/storybook3/Cargo.toml+0−17

This diff is large: its 18 lines aren’t shown here.

crates/storybook3/src/storybook3.rs+0−87

This diff is large: its 88 lines aren’t shown here.

crates/terminal_view2/src/terminal_element.rs+289−273

This diff is large: its 764 lines aren’t shown here.

crates/terminal_view2/src/terminal_panel.rs+2−2

This diff is large: its 18 lines aren’t shown here.

crates/terminal_view2/src/terminal_view.rs+32−42

This diff is large: its 161 lines aren’t shown here.

crates/theme2/Cargo.toml+3−2

This diff is large: its 20 lines aren’t shown here.

crates/theme2/src/one_themes.rs+2−2

This diff is large: its 11 lines aren’t shown here.

crates/theme2/src/settings.rs+1−1

This diff is large: its 9 lines aren’t shown here.

crates/theme2/src/story.rs+0−38

This diff is large: its 39 lines aren’t shown here.

crates/theme2/src/styles.rs+6−0

This diff is large: its 15 lines aren’t shown here.

crates/theme2/src/styles/players.rs+2−138

This diff is large: its 150 lines aren’t shown here.

crates/theme2/src/styles/stories/color.rs+41−0

This diff is large: its 42 lines aren’t shown here.

crates/theme2/src/styles/stories/mod.rs+5−0

This diff is large: its 6 lines aren’t shown here.

crates/theme2/src/styles/stories/players.rs+137−0

This diff is large: its 138 lines aren’t shown here.

crates/theme2/src/theme2.rs+12−5

This diff is large: its 35 lines aren’t shown here.

crates/ui2/Cargo.toml+6−1

This diff is large: its 24 lines aren’t shown here.

crates/ui2/docs/hello-world.md+5−5

This diff is large: its 43 lines aren’t shown here.

crates/ui2/src/components.rs+10−26

This diff is large: its 67 lines aren’t shown here.

crates/ui2/src/components/avatar.rs+36−40

This diff is large: its 102 lines aren’t shown here.

crates/ui2/src/components/button.rs+93−262

This diff is large: its 432 lines aren’t shown here.

crates/ui2/src/components/checkbox.rs+138−78

This diff is large: its 268 lines aren’t shown here.

crates/ui2/src/components/context_menu.rs+96−224

This diff is large: its 483 lines aren’t shown here.

crates/ui2/src/components/details.rs+0−78

This diff is large: its 79 lines aren’t shown here.

crates/ui2/src/components/disclosure.rs+19−0

This diff is large: its 20 lines aren’t shown here.

crates/ui2/src/components/divider.rs+21−2

This diff is large: its 46 lines aren’t shown here.

crates/ui2/src/components/elevated_surface.rs+0−28

This diff is large: its 29 lines aren’t shown here.

crates/ui2/src/components/facepile.rs+0−59

This diff is large: its 60 lines aren’t shown here.

crates/ui2/src/components/icon.rs+36−37

This diff is large: its 158 lines aren’t shown here.

crates/ui2/src/components/icon_button.rs+75−82

This diff is large: its 193 lines aren’t shown here.

crates/ui2/src/components/indicator.rs+0−23

This diff is large: its 24 lines aren’t shown here.

crates/ui2/src/components/input.rs+41−60

This diff is large: its 150 lines aren’t shown here.

crates/ui2/src/components/keybinding.rs+35−158

This diff is large: its 225 lines aren’t shown here.

crates/ui2/src/components/label.rs+62−129

This diff is large: its 270 lines aren’t shown here.

crates/ui2/src/components/list.rs+167−199

This diff is large: its 557 lines aren’t shown here.

crates/ui2/src/components/modal.rs+0−81

This diff is large: its 82 lines aren’t shown here.

crates/ui2/src/components/notification_toast.rs+0−40

This diff is large: its 41 lines aren’t shown here.

crates/ui2/src/components/palette.rs+0−204

This diff is large: its 205 lines aren’t shown here.

crates/ui2/src/components/panel.rs+0−150

This diff is large: its 151 lines aren’t shown here.

crates/ui2/src/components/player.rs+0−174

This diff is large: its 175 lines aren’t shown here.

crates/ui2/src/components/player_stack.rs+0−61

This diff is large: its 62 lines aren’t shown here.

crates/ui2/src/components/popover.rs+84−0

This diff is large: its 85 lines aren’t shown here.

crates/ui2/src/components/slot.rs+2−2

This diff is large: its 14 lines aren’t shown here.

crates/ui2/src/components/stack.rs+2−2

This diff is large: its 16 lines aren’t shown here.

crates/ui2/src/components/stories.rs+19−0

This diff is large: its 20 lines aren’t shown here.

crates/ui2/src/components/stories/avatar.rs+23−0

This diff is large: its 24 lines aren’t shown here.

crates/ui2/src/components/stories/button.rs+145−0

This diff is large: its 146 lines aren’t shown here.

crates/ui2/src/components/stories/checkbox.rs+49−0

This diff is large: its 50 lines aren’t shown here.

crates/ui2/src/components/stories/context_menu.rs+104−0

This diff is large: its 105 lines aren’t shown here.

crates/ui2/src/components/stories/icon.rs+21−0

This diff is large: its 22 lines aren’t shown here.

crates/ui2/src/components/stories/input.rs+18−0

This diff is large: its 19 lines aren’t shown here.

crates/ui2/src/components/stories/keybinding.rs+61−0

This diff is large: its 62 lines aren’t shown here.

crates/ui2/src/components/stories/label.rs+27−0

This diff is large: its 28 lines aren’t shown here.

crates/ui2/src/components/stories/list_item.rs+34−0

This diff is large: its 35 lines aren’t shown here.

crates/ui2/src/components/tab.rs+0−272

This diff is large: its 273 lines aren’t shown here.

crates/ui2/src/components/toast.rs+0−90

This diff is large: its 91 lines aren’t shown here.

crates/ui2/src/components/toggle.rs+0−20

This diff is large: its 28 lines aren’t shown here.

crates/ui2/src/components/tool_divider.rs+0−14

This diff is large: its 15 lines aren’t shown here.

crates/ui2/src/components/tooltip.rs+4−8

This diff is large: its 34 lines aren’t shown here.

crates/ui2/src/prelude.rs+13−144

This diff is large: its 189 lines aren’t shown here.

crates/ui2/src/settings.rs+0−74

This diff is large: its 75 lines aren’t shown here.

crates/ui2/src/static_data.rs+0−1111

This diff is large: its 1112 lines aren’t shown here.

crates/ui2/src/story.rs+0−37

This diff is large: its 38 lines aren’t shown here.

crates/ui2/src/styled_ext.rs+6−8

This diff is large: its 48 lines aren’t shown here.

crates/ui2/src/styles.rs+7−0

This diff is large: its 8 lines aren’t shown here.

crates/ui2/src/styles/color.rs+44−0

This diff is large: its 45 lines aren’t shown here.

crates/ui2/src/styles/docs/elevation.mdrenamed from crates/ui2/src/elevation.md+1−42

This diff is large: its 57 lines aren’t shown here.

crates/ui2/src/styles/elevation.rsrenamed from crates/ui2/src/elevation.rs+4−4

This diff is large: its 29 lines aren’t shown here.

crates/ui2/src/styles/typography.rs+27−0

This diff is large: its 28 lines aren’t shown here.

crates/ui2/src/to_extract.rs+0−47

This diff is large: its 48 lines aren’t shown here.

crates/ui2/src/to_extract/assistant_panel.rs+0−93

This diff is large: its 94 lines aren’t shown here.

crates/ui2/src/to_extract/breadcrumb.rs+0−113

This diff is large: its 114 lines aren’t shown here.

crates/ui2/src/to_extract/buffer.rs+0−266

This diff is large: its 267 lines aren’t shown here.

crates/ui2/src/to_extract/buffer_search.rs+0−46

This diff is large: its 47 lines aren’t shown here.

crates/ui2/src/to_extract/chat_panel.rs+0−150

This diff is large: its 151 lines aren’t shown here.

crates/ui2/src/to_extract/collab_panel.rs+0−110

This diff is large: its 111 lines aren’t shown here.

crates/ui2/src/to_extract/command_palette.rs+0−48

This diff is large: its 49 lines aren’t shown here.

crates/ui2/src/to_extract/copilot.rs+0−46

This diff is large: its 47 lines aren’t shown here.

crates/ui2/src/to_extract/editor_pane.rs+0−77

This diff is large: its 78 lines aren’t shown here.

crates/ui2/src/to_extract/language_selector.rs+0−57

This diff is large: its 58 lines aren’t shown here.

crates/ui2/src/to_extract/multi_buffer.rs+0−63

This diff is large: its 64 lines aren’t shown here.

crates/ui2/src/to_extract/notifications_panel.rs+0−371

This diff is large: its 372 lines aren’t shown here.

crates/ui2/src/to_extract/panes.rs+0−128

This diff is large: its 129 lines aren’t shown here.

crates/ui2/src/to_extract/project_panel.rs+0−75

This diff is large: its 76 lines aren’t shown here.

crates/ui2/src/to_extract/recent_projects.rs+0−53

This diff is large: its 54 lines aren’t shown here.

crates/ui2/src/to_extract/status_bar.rs+0−203

This diff is large: its 204 lines aren’t shown here.

crates/ui2/src/to_extract/tab_bar.rs+0−150

This diff is large: its 151 lines aren’t shown here.

crates/ui2/src/to_extract/terminal.rs+0−99

This diff is large: its 100 lines aren’t shown here.

crates/ui2/src/to_extract/theme_selector.rs+0−60

This diff is large: its 61 lines aren’t shown here.

crates/ui2/src/to_extract/title_bar.rs+0−218

This diff is large: its 219 lines aren’t shown here.

crates/ui2/src/to_extract/toolbar.rs+0−126

This diff is large: its 127 lines aren’t shown here.

crates/ui2/src/to_extract/traffic_lights.rs+0−100

This diff is large: its 101 lines aren’t shown here.

crates/ui2/src/to_extract/workspace.rs+0−398

This diff is large: its 399 lines aren’t shown here.

crates/ui2/src/ui2.rsrenamed from crates/ui2/src/lib.rs+2−26

This diff is large: its 39 lines aren’t shown here.

crates/util/src/channel.rs+1−2

This diff is large: its 17 lines aren’t shown here.

crates/workspace/src/workspace.rs+7−3

This diff is large: its 30 lines aren’t shown here.

crates/workspace2/Cargo.toml+1−1

This diff is large: its 16 lines aren’t shown here.

crates/workspace2/src/dock.rs+15−23

This diff is large: its 103 lines aren’t shown here.

crates/workspace2/src/item.rs+5−5

This diff is large: its 34 lines aren’t shown here.

crates/workspace2/src/modal_layer.rs+6−5

This diff is large: its 33 lines aren’t shown here.

crates/workspace2/src/notifications.rs+92−45

This diff is large: its 235 lines aren’t shown here.

crates/workspace2/src/pane.rs+96−47

This diff is large: its 248 lines aren’t shown here.

crates/workspace2/src/pane_group.rs+20−24

This diff is large: its 120 lines aren’t shown here.

crates/workspace2/src/searchable.rs+11−12

This diff is large: its 45 lines aren’t shown here.

crates/workspace2/src/shared_screen.rs+0−151

This diff is large: its 152 lines aren’t shown here.

crates/workspace2/src/status_bar.rs+88−7

This diff is large: its 129 lines aren’t shown here.

crates/workspace2/src/toolbar.rs+66−5

This diff is large: its 95 lines aren’t shown here.

crates/workspace2/src/workspace2.rs+290−198

This diff is large: its 798 lines aren’t shown here.

crates/zed/Cargo.toml+2−2

This diff is large: its 25 lines aren’t shown here.

crates/zed/src/languages.rs+12−1

This diff is large: its 39 lines aren’t shown here.

crates/zed/src/languages/elixir/embedding.scm+2−2

This diff is large: its 13 lines aren’t shown here.

crates/zed/src/languages/nu.rs+81−0

This diff is large: its 82 lines aren’t shown here.

crates/zed/src/languages/uiua.rs+55−0

This diff is large: its 56 lines aren’t shown here.

crates/zed/src/languages/uiua/config.toml+10−0

This diff is large: its 11 lines aren’t shown here.

crates/zed/src/languages/uiua/highlights.scm+50−0

This diff is large: its 51 lines aren’t shown here.

crates/zed/src/languages/uiua/indents.scm+3−0

This diff is large: its 4 lines aren’t shown here.

crates/zed/src/main.rs+13−4

This diff is large: its 48 lines aren’t shown here.

crates/zed2/Cargo.toml+4−4

This diff is large: its 35 lines aren’t shown here.

crates/zed2/src/languages.rs+12−1

This diff is large: its 39 lines aren’t shown here.

crates/zed2/src/languages/nu.rs+55−0

This diff is large: its 56 lines aren’t shown here.

crates/zed2/src/languages/uiua.rs+55−0

This diff is large: its 56 lines aren’t shown here.

crates/zed2/src/languages/uiua/config.toml+10−0

This diff is large: its 11 lines aren’t shown here.

crates/zed2/src/languages/uiua/highlights.scm+50−0

This diff is large: its 51 lines aren’t shown here.

crates/zed2/src/languages/uiua/indents.scm+3−0

This diff is large: its 4 lines aren’t shown here.

crates/zed2/src/main.rs+37−25

This diff is large: its 138 lines aren’t shown here.

crates/zed2/src/zed2.rs+13−8

This diff is large: its 35 lines aren’t shown here.

script/bump-nightly+2−0

This diff is large: its 8 lines aren’t shown here.

script/bump-zed-minor-versions+0−1

This diff is large: its 8 lines aren’t shown here.

script/bundle+6−3

This diff is large: its 37 lines aren’t shown here.

script/deploy-docs+159−0

This diff is large: its 160 lines aren’t shown here.

script/zed-local+6−3

This diff is large: its 32 lines aren’t shown here.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t